-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add script mode support to windows tasks #4128
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -136,9 +136,9 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1beta1.TaskRun, taskSpec | |
// Convert any steps with Script to command+args. | ||
// If any are found, append an init container to initialize scripts. | ||
if alphaAPIEnabled { | ||
scriptsInit, stepContainers, sidecarContainers = convertScripts(b.Images.ShellImage, steps, taskSpec.Sidecars, taskRun.Spec.Debug) | ||
scriptsInit, stepContainers, sidecarContainers = convertScripts(b.Images.ShellImage, b.Images.ShellImageWin, steps, taskSpec.Sidecars, taskRun.Spec.Debug) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should windows support go into the alpha feature gate, alongside debug support? 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Passing nil or an empty string for the windows shell image when |
||
} else { | ||
scriptsInit, stepContainers, sidecarContainers = convertScripts(b.Images.ShellImage, steps, taskSpec.Sidecars, nil) | ||
scriptsInit, stepContainers, sidecarContainers = convertScripts(b.Images.ShellImage, "", steps, taskSpec.Sidecars, nil) | ||
} | ||
if scriptsInit != nil { | ||
initContainers = append(initContainers, *scriptsInit) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,20 +70,28 @@ var ( | |
) | ||
|
||
// convertScripts converts any steps and sidecars that specify a Script field into a normal Container. | ||
// | ||
// It does this by prepending a container that writes specified Script bodies | ||
// to executable files in a shared volumeMount, then produces Containers that | ||
// simply run those executable files. | ||
func convertScripts(shellImage string, steps []v1beta1.Step, sidecars []v1beta1.Sidecar, debugConfig *v1beta1.TaskRunDebug) (*corev1.Container, []corev1.Container, []corev1.Container) { | ||
func convertScripts(shellImageLinux string, shellImageWin string, steps []v1beta1.Step, sidecars []v1beta1.Sidecar, debugConfig *v1beta1.TaskRunDebug) (*corev1.Container, []corev1.Container, []corev1.Container) { | ||
placeScripts := false | ||
// Place scripts is an init container used for creating scripts in the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment goes with the |
||
// /tekton/scripts directory which would be later used by the step containers | ||
// as a Command | ||
requiresWindows := checkWindowsRequirement(steps, sidecars) | ||
|
||
shellImage := shellImageLinux | ||
shellCommand := "sh" | ||
shellArg := "-c" | ||
// Set windows variants for Image, Command and Args | ||
if requiresWindows { | ||
shellImage = shellImageWin | ||
shellCommand = "pwsh" | ||
shellArg = "-Command" | ||
} | ||
|
||
placeScriptsInit := corev1.Container{ | ||
Name: "place-scripts", | ||
Image: shellImage, | ||
Command: []string{"sh"}, | ||
Args: []string{"-c", ""}, | ||
Command: []string{shellCommand}, | ||
Args: []string{shellArg, ""}, | ||
VolumeMounts: []corev1.VolumeMount{writeScriptsVolumeMount, toolsMount}, | ||
} | ||
|
||
|
@@ -131,6 +139,7 @@ func convertListOfSteps(steps []v1beta1.Step, initContainer *corev1.Container, p | |
// The shebang must be the first non-empty line. | ||
cleaned := strings.TrimSpace(s.Script) | ||
hasShebang := strings.HasPrefix(cleaned, "#!") | ||
requiresWindows := strings.HasPrefix(cleaned, "#!win") | ||
|
||
script := s.Script | ||
if !hasShebang { | ||
|
@@ -141,26 +150,40 @@ func convertListOfSteps(steps []v1beta1.Step, initContainer *corev1.Container, p | |
// non-nil init container. | ||
*placeScripts = true | ||
|
||
script = encodeScript(script) | ||
|
||
// Append to the place-scripts script to place the | ||
// script file in a known location in the scripts volume. | ||
scriptFile := filepath.Join(scriptsDir, names.SimpleNameGenerator.RestrictLengthWithRandomSuffix(fmt.Sprintf("%s-%d", namePrefix, i))) | ||
heredoc := "_EOF_" // underscores because base64 doesnt include them in its alphabet | ||
initContainer.Args[1] += fmt.Sprintf(`scriptfile="%s" | ||
if requiresWindows { | ||
command, args, script, scriptFile := extractWindowsScriptComponents(script, scriptFile) | ||
initContainer.Args[1] += fmt.Sprintf(`@" | ||
%s | ||
"@ | Out-File -FilePath %s | ||
`, script, scriptFile) | ||
|
||
steps[i].Command = command | ||
// Append existing args field to end of derived args | ||
args = append(args, steps[i].Args...) | ||
steps[i].Args = args | ||
} else { | ||
// Only encode the script for linux scripts | ||
// The decode-script subcommand of the entrypoint does not work under windows | ||
script = encodeScript(script) | ||
heredoc := "_EOF_" // underscores because base64 doesnt include them in its alphabet | ||
initContainer.Args[1] += fmt.Sprintf(`scriptfile="%s" | ||
touch ${scriptfile} && chmod +x ${scriptfile} | ||
cat > ${scriptfile} << '%s' | ||
%s | ||
%s | ||
/tekton/tools/entrypoint decode-script "${scriptfile}" | ||
`, scriptFile, heredoc, script, heredoc) | ||
|
||
// Set the command to execute the correct script in the mounted | ||
// volume. | ||
// A previous merge with stepTemplate may have populated | ||
// Command and Args, even though this is not normally valid, so | ||
// we'll clear out the Args and overwrite Command. | ||
steps[i].Command = []string{scriptFile} | ||
// Set the command to execute the correct script in the mounted | ||
// volume. | ||
// A previous merge with stepTemplate may have populated | ||
// Command and Args, even though this is not normally valid, so | ||
// we'll clear out the Args and overwrite Command. | ||
steps[i].Command = []string{scriptFile} | ||
} | ||
steps[i].VolumeMounts = append(steps[i].VolumeMounts, scriptsVolumeMount) | ||
|
||
// Add debug mounts if breakpoints are present | ||
|
@@ -207,3 +230,52 @@ cat > ${scriptfile} << '%s' | |
func encodeScript(script string) string { | ||
return base64.StdEncoding.EncodeToString([]byte(script)) | ||
} | ||
|
||
func checkWindowsRequirement(steps []v1beta1.Step, sidecars []v1beta1.Sidecar) bool { | ||
// Detect windows shebangs | ||
for _, step := range steps { | ||
cleaned := strings.TrimSpace(step.Script) | ||
if strings.HasPrefix(cleaned, "#!win") { | ||
return true | ||
} | ||
} | ||
// If no step needs windows, then check sidecars to be sure | ||
for _, sidecar := range sidecars { | ||
cleaned := strings.TrimSpace(sidecar.Script) | ||
if strings.HasPrefix(cleaned, "#!win") { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func extractWindowsScriptComponents(script string, fileName string) ([]string, []string, string, string) { | ||
// Set the command to execute the correct script in the mounted volume. | ||
shebangLine := strings.Split(script, "\n")[0] | ||
splitLine := strings.Split(shebangLine, " ") | ||
var command, args []string | ||
if len(splitLine) > 1 { | ||
strippedCommand := splitLine[1:] | ||
command = strippedCommand[0:1] | ||
// Handle legacy powershell limitation | ||
if strings.HasPrefix(command[0], "powershell") { | ||
fileName += ".ps1" | ||
} | ||
if len(strippedCommand) > 1 { | ||
args = strippedCommand[1:] | ||
args = append(args, fileName) | ||
} else { | ||
args = []string{fileName} | ||
} | ||
} else { | ||
// If no interpreter is specified then strip the shebang and | ||
// create a .cmd file | ||
fileName += ".cmd" | ||
commandLines := strings.Split(script, "\n")[1:] | ||
script = strings.Join(commandLines, "\n") | ||
command = []string{fileName} | ||
args = []string{} | ||
} | ||
|
||
return command, args, script, fileName | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't totally understand this bit on first read, since linux also uses shebang lines to specify how to interpret script files. Is it referring to the use of arguments (
-File
)? Or just that we have to be completely explicit otherwise tekton will assume it's for linux?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to be explicit that this script will run under windows, and we also have to tell tekton how this script is to be run (which is what a linux shebang does for us). Windows needs to know which executable (e.g. powershell.exe, or pwsh.exe) it needs to use in order to run the script, and also specify any args (e.g. both of those .exe's require the -File argument in order to interpret commands in a file).