Skip to content

Commit 9882a30

Browse files
committed
Refactor Sidecar Containers Construction If Script Exists
Prior to this commit, in order to reuse the code, we convert v1beta1 Sidecar object to v1beta1.Step and then construct containers out of those steps when the script field is specified. As we are switching the storage version to v1, some fields are deprecated in v1.Step (but not Sidecar), thus it does not make sense to convert sidercar to step. While reusing as much code as possible, this commit refactor the code to seperate the container construction process for steps and sidecars.
1 parent 09d422c commit 9882a30

File tree

2 files changed

+277
-106
lines changed

2 files changed

+277
-106
lines changed

pkg/pod/script.go

+112-99
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
2626
"github.com/tektoncd/pipeline/pkg/names"
2727
corev1 "k8s.io/api/core/v1"
28-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2928
)
3029

3130
const (
@@ -71,7 +70,6 @@ var (
7170

7271
// convertScripts converts any steps and sidecars that specify a Script field into a normal Container.
7372
func convertScripts(shellImageLinux string, shellImageWin string, steps []v1beta1.Step, sidecars []v1beta1.Sidecar, debugConfig *v1beta1.TaskRunDebug) (*corev1.Container, []corev1.Container, []corev1.Container) {
74-
placeScripts := false
7573
// Place scripts is an init container used for creating scripts in the
7674
// /tekton/scripts directory which would be later used by the step containers
7775
// as a Command
@@ -96,144 +94,159 @@ func convertScripts(shellImageLinux string, shellImageWin string, steps []v1beta
9694
}
9795

9896
breakpoints := []string{}
99-
sideCarSteps := []v1beta1.Step{}
100-
for _, sidecar := range sidecars {
101-
c := sidecar.ToK8sContainer()
102-
sidecarStep := v1beta1.Step{
103-
Script: sidecar.Script,
104-
Timeout: &metav1.Duration{},
105-
}
106-
sidecarStep.SetContainerFields(*c)
107-
sideCarSteps = append(sideCarSteps, sidecarStep)
108-
}
10997

11098
// Add mounts for debug
11199
if debugConfig != nil && len(debugConfig.Breakpoint) > 0 {
112100
breakpoints = debugConfig.Breakpoint
113101
placeScriptsInit.VolumeMounts = append(placeScriptsInit.VolumeMounts, debugScriptsVolumeMount)
114102
}
115103

116-
convertedStepContainers := convertListOfSteps(steps, &placeScriptsInit, &placeScripts, breakpoints, "script")
104+
convertedStepContainers := convertListOfSteps(steps, &placeScriptsInit, breakpoints, "script")
105+
sidecarContainers := convertListOfSidecars(sidecars, &placeScriptsInit, "sidecar-script")
117106

118-
// Pass empty breakpoint list in "sidecar step to container" converter to not rewrite the scripts and add breakpoints to sidecar
119-
sidecarContainers := convertListOfSteps(sideCarSteps, &placeScriptsInit, &placeScripts, []string{}, "sidecar-script")
120-
if placeScripts {
107+
if hasScripts(steps, sidecars, breakpoints) {
121108
return &placeScriptsInit, convertedStepContainers, sidecarContainers
122109
}
123110
return nil, convertedStepContainers, sidecarContainers
124111
}
125112

126-
// convertListOfSteps does the heavy lifting for convertScripts.
127-
//
128-
// It iterates through the list of steps (or sidecars), generates the script file name and heredoc termination string,
113+
// convertListOfSidecars iterates through the list of sidecars, generates the script file name and heredoc termination string,
129114
// adds an entry to the init container args, sets up the step container to run the script, and sets the volume mounts.
130-
func convertListOfSteps(steps []v1beta1.Step, initContainer *corev1.Container, placeScripts *bool, breakpoints []string, namePrefix string) []corev1.Container {
115+
func convertListOfSidecars(sidecars []v1beta1.Sidecar, initContainer *corev1.Container, namePrefix string) []corev1.Container {
131116
containers := []corev1.Container{}
132-
for i, s := range steps {
133-
// Add debug mounts if breakpoints are present
134-
if len(breakpoints) > 0 {
135-
debugInfoVolumeMount := corev1.VolumeMount{
136-
Name: debugInfoVolumeName,
137-
MountPath: filepath.Join(debugInfoDir, fmt.Sprintf("%d", i)),
138-
}
139-
steps[i].VolumeMounts = append(steps[i].VolumeMounts, debugScriptsVolumeMount, debugInfoVolumeMount)
117+
for i, s := range sidecars {
118+
c := s.ToK8sContainer()
119+
if s.Script != "" {
120+
placeScriptInContainer(s.Script, getScriptFile(scriptsDir, fmt.Sprintf("%s-%d", namePrefix, i)), c, initContainer)
140121
}
122+
containers = append(containers, *c)
123+
}
124+
return containers
125+
}
141126

142-
if s.Script == "" {
143-
// Nothing to convert.
144-
containers = append(containers, *steps[i].ToK8sContainer())
145-
continue
127+
// convertListOfSteps iterates through the list of steps, generates the script file name and heredoc termination string,
128+
// adds an entry to the init container args, sets up the step container to run the script, and sets the volume mounts.
129+
func convertListOfSteps(steps []v1beta1.Step, initContainer *corev1.Container, breakpoints []string, namePrefix string) []corev1.Container {
130+
containers := []corev1.Container{}
131+
for i, s := range steps {
132+
c := steps[i].ToK8sContainer()
133+
if s.Script != "" {
134+
placeScriptInContainer(s.Script, getScriptFile(scriptsDir, fmt.Sprintf("%s-%d", namePrefix, i)), c, initContainer)
146135
}
136+
containers = append(containers, *c)
137+
}
138+
if len(breakpoints) > 0 {
139+
placeDebugScriptInContainers(containers, initContainer)
140+
}
141+
return containers
142+
}
147143

148-
// Check for a shebang, and add a default if it's not set.
149-
// The shebang must be the first non-empty line.
150-
cleaned := strings.TrimSpace(s.Script)
151-
hasShebang := strings.HasPrefix(cleaned, "#!")
152-
requiresWindows := strings.HasPrefix(cleaned, "#!win")
144+
func getScriptFile(scriptsDir, scriptName string) string {
145+
return filepath.Join(scriptsDir, names.SimpleNameGenerator.RestrictLengthWithRandomSuffix(scriptName))
146+
}
153147

154-
script := s.Script
155-
if !hasShebang {
156-
script = defaultScriptPreamble + s.Script
157-
}
148+
// placeScriptInContainer given a piece of script to be executed, placeScriptInContainer firstly modifies initContainer
149+
// so that it capsules the target script into scriptFile, then it modifies the container so that it can execute the scriptFile
150+
// in runtime.
151+
func placeScriptInContainer(script, scriptFile string, c *corev1.Container, initContainer *corev1.Container) {
152+
if script == "" {
153+
return
154+
}
155+
cleaned := strings.TrimSpace(script)
156+
hasShebang := strings.HasPrefix(cleaned, "#!")
157+
requiresWindows := strings.HasPrefix(cleaned, "#!win")
158158

159-
// At least one step uses a script, so we should return a
160-
// non-nil init container.
161-
*placeScripts = true
159+
if !hasShebang {
160+
script = defaultScriptPreamble + script
161+
}
162162

163-
// Append to the place-scripts script to place the
164-
// script file in a known location in the scripts volume.
165-
scriptFile := filepath.Join(scriptsDir, names.SimpleNameGenerator.RestrictLengthWithRandomSuffix(fmt.Sprintf("%s-%d", namePrefix, i)))
166-
if requiresWindows {
167-
command, args, script, scriptFile := extractWindowsScriptComponents(script, scriptFile)
168-
initContainer.Args[1] += fmt.Sprintf(`@"
163+
// Append to the place-scripts script to place the
164+
// script file in a known location in the scripts volume.
165+
if requiresWindows {
166+
command, args, script, scriptFile := extractWindowsScriptComponents(script, scriptFile)
167+
initContainer.Args[1] += fmt.Sprintf(`@"
169168
%s
170169
"@ | Out-File -FilePath %s
171170
`, script, scriptFile)
172171

173-
steps[i].Command = command
174-
// Append existing args field to end of derived args
175-
args = append(args, steps[i].Args...)
176-
steps[i].Args = args
177-
} else {
178-
// Only encode the script for linux scripts
179-
// The decode-script subcommand of the entrypoint does not work under windows
180-
script = encodeScript(script)
181-
heredoc := "_EOF_" // underscores because base64 doesnt include them in its alphabet
182-
initContainer.Args[1] += fmt.Sprintf(`scriptfile="%s"
172+
c.Command = command
173+
// Append existing args field to end of derived args
174+
args = append(args, c.Args...)
175+
c.Args = args
176+
} else {
177+
// Only encode the script for linux scripts
178+
// The decode-script subcommand of the entrypoint does not work under windows
179+
script = encodeScript(script)
180+
heredoc := "_EOF_" // underscores because base64 doesn't include them in its alphabet
181+
initContainer.Args[1] += fmt.Sprintf(`scriptfile="%s"
183182
touch ${scriptfile} && chmod +x ${scriptfile}
184183
cat > ${scriptfile} << '%s'
185184
%s
186185
%s
187186
/tekton/bin/entrypoint decode-script "${scriptfile}"
188187
`, scriptFile, heredoc, script, heredoc)
189188

190-
// Set the command to execute the correct script in the mounted
191-
// volume.
192-
// A previous merge with stepTemplate may have populated
193-
// Command and Args, even though this is not normally valid, so
194-
// we'll clear out the Args and overwrite Command.
195-
steps[i].Command = []string{scriptFile}
196-
}
197-
steps[i].VolumeMounts = append(steps[i].VolumeMounts, scriptsVolumeMount)
198-
199-
containers = append(containers, *steps[i].ToK8sContainer())
189+
// Set the command to execute the correct script in the mounted volume.
190+
// A previous merge with stepTemplate may have populated
191+
// Command and Args, even though this is not normally valid, so
192+
// we'll clear out the Args and overwrite Command.
193+
c.Command = []string{scriptFile}
200194
}
195+
c.VolumeMounts = append(c.VolumeMounts, scriptsVolumeMount)
196+
}
201197

202-
// Place debug scripts if breakpoints are enabled
203-
if len(breakpoints) > 0 {
204-
// If breakpoint is not nil then should add the init container
205-
// to write debug script files
206-
*placeScripts = true
198+
// encodeScript encodes a script field into a format that avoids kubernetes' built-in processing of container args,
199+
// which can mangle dollar signs and unexpectedly replace variable references in the user's script.
200+
func encodeScript(script string) string {
201+
return base64.StdEncoding.EncodeToString([]byte(script))
202+
}
207203

208-
type script struct {
209-
name string
210-
content string
211-
}
212-
debugScripts := []script{{
213-
name: "continue",
214-
content: defaultScriptPreamble + fmt.Sprintf(debugContinueScriptTemplate, len(steps), debugInfoDir, RunDir),
215-
}, {
216-
name: "fail-continue",
217-
content: defaultScriptPreamble + fmt.Sprintf(debugFailScriptTemplate, len(steps), debugInfoDir, RunDir),
218-
}}
219-
220-
// Add debug or breakpoint related scripts to /tekton/debug/scripts
221-
// Iterate through the debugScripts and add routine for each of them in the initContainer for their creation
222-
for _, debugScript := range debugScripts {
223-
tmpFile := filepath.Join(debugScriptsDir, fmt.Sprintf("%s-%s", "debug", debugScript.name))
224-
heredoc := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix(fmt.Sprintf("%s-%s-heredoc-randomly-generated", "debug", debugScript.name))
225-
226-
initContainer.Args[1] += fmt.Sprintf(initScriptDirective, tmpFile, heredoc, debugScript.content, heredoc)
204+
// placeDebugScriptInContainers inserts debug scripts into containers. It capsules those scripts to files in initContainer,
205+
// then executes those scripts in target containers.
206+
func placeDebugScriptInContainers(containers []corev1.Container, initContainer *corev1.Container) {
207+
for i := 0; i < len(containers); i++ {
208+
debugInfoVolumeMount := corev1.VolumeMount{
209+
Name: debugInfoVolumeName,
210+
MountPath: filepath.Join(debugInfoDir, fmt.Sprintf("%d", i)),
227211
}
212+
(&containers[i]).VolumeMounts = append((&containers[i]).VolumeMounts, debugScriptsVolumeMount, debugInfoVolumeMount)
228213
}
229214

230-
return containers
215+
type script struct {
216+
name string
217+
content string
218+
}
219+
debugScripts := []script{{
220+
name: "continue",
221+
content: defaultScriptPreamble + fmt.Sprintf(debugContinueScriptTemplate, len(containers), debugInfoDir, RunDir),
222+
}, {
223+
name: "fail-continue",
224+
content: defaultScriptPreamble + fmt.Sprintf(debugFailScriptTemplate, len(containers), debugInfoDir, RunDir),
225+
}}
226+
227+
// Add debug or breakpoint related scripts to /tekton/debug/scripts
228+
// Iterate through the debugScripts and add routine for each of them in the initContainer for their creation
229+
for _, debugScript := range debugScripts {
230+
tmpFile := filepath.Join(debugScriptsDir, fmt.Sprintf("%s-%s", "debug", debugScript.name))
231+
heredoc := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix(fmt.Sprintf("%s-%s-heredoc-randomly-generated", "debug", debugScript.name))
232+
233+
initContainer.Args[1] += fmt.Sprintf(initScriptDirective, tmpFile, heredoc, debugScript.content, heredoc)
234+
}
231235
}
232236

233-
// encodeScript encodes a script field into a format that avoids kubernetes' built-in processing of container args,
234-
// which can mangle dollar signs and unexpectedly replace variable references in the user's script.
235-
func encodeScript(script string) string {
236-
return base64.StdEncoding.EncodeToString([]byte(script))
237+
// hasScripts determines if we need to generate scripts in InitContainer given steps, sidecars and breakpoints.
238+
func hasScripts(steps []v1beta1.Step, sidecars []v1beta1.Sidecar, breakpoints []string) bool {
239+
for _, s := range steps {
240+
if s.Script != "" {
241+
return true
242+
}
243+
}
244+
for _, s := range sidecars {
245+
if s.Script != "" {
246+
return true
247+
}
248+
}
249+
return len(breakpoints) > 0
237250
}
238251

239252
func checkWindowsRequirement(steps []v1beta1.Step, sidecars []v1beta1.Sidecar) bool {

0 commit comments

Comments
 (0)