-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
script.go
137 lines (120 loc) · 4.89 KB
/
script.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/*
Copyright 2019 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pod
import (
"fmt"
"path/filepath"
"strings"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"github.com/tektoncd/pipeline/pkg/names"
corev1 "k8s.io/api/core/v1"
)
const (
scriptsVolumeName = "tekton-internal-scripts"
scriptsDir = "/tekton/scripts"
defaultScriptPreamble = "#!/bin/sh\nset -xe\n"
)
var (
// Volume definition attached to Pods generated from TaskRuns that have
// steps that specify a Script.
scriptsVolume = corev1.Volume{
Name: scriptsVolumeName,
VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
}
scriptsVolumeMount = corev1.VolumeMount{
Name: scriptsVolumeName,
MountPath: scriptsDir,
}
)
// 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) (*corev1.Container, []corev1.Container, []corev1.Container) {
placeScripts := false
placeScriptsInit := corev1.Container{
Name: "place-scripts",
Image: shellImage,
Command: []string{"sh"},
Args: []string{"-c", ""},
VolumeMounts: []corev1.VolumeMount{scriptsVolumeMount},
}
convertedStepContainers := convertListOfSteps(steps, &placeScriptsInit, &placeScripts, "script")
// convertListOfSteps operates on overlapping fields across Step and Sidecar, hence a conversion
// from Sidecar into Step
sideCarSteps := []v1beta1.Step{}
for _, step := range sidecars {
sidecarStep := v1beta1.Step{
step.Container,
step.Script,
}
sideCarSteps = append(sideCarSteps, sidecarStep)
}
sidecarContainers := convertListOfSteps(sideCarSteps, &placeScriptsInit, &placeScripts, "sidecar-script")
if placeScripts {
return &placeScriptsInit, convertedStepContainers, sidecarContainers
}
return nil, convertedStepContainers, sidecarContainers
}
// convertListOfSteps does the heavy lifting for convertScripts.
//
// It iterates through the list of steps (or sidecars), generates the script file name and heredoc termination string,
// adds an entry to the init container args, sets up the step container to run the script, and sets the volume mounts.
func convertListOfSteps(steps []v1beta1.Step, initContainer *corev1.Container, placeScripts *bool, namePrefix string) []corev1.Container {
containers := []corev1.Container{}
for i, s := range steps {
if s.Script == "" {
// Nothing to convert.
containers = append(containers, s.Container)
continue
}
// Check for a shebang, and add a default if it's not set.
// The shebang must be the first non-empty line.
cleaned := strings.TrimSpace(s.Script)
hasShebang := strings.HasPrefix(cleaned, "#!")
script := s.Script
if !hasShebang {
script = defaultScriptPreamble + s.Script
}
// At least one step uses a script, so we should return a
// non-nil init container.
*placeScripts = true
// Append to the place-scripts script to place the
// script file in a known location in the scripts volume.
tmpFile := filepath.Join(scriptsDir, names.SimpleNameGenerator.RestrictLengthWithRandomSuffix(fmt.Sprintf("%s-%d", namePrefix, i)))
// heredoc is the "here document" placeholder string
// used to cat script contents into the file. Typically
// this is the string "EOF" but if this value were
// "EOF" it would prevent users from including the
// string "EOF" in their own scripts. Instead we
// randomly generate a string to (hopefully) prevent
// collisions.
heredoc := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix(fmt.Sprintf("%s-heredoc-randomly-generated", namePrefix))
initContainer.Args[1] += fmt.Sprintf(`tmpfile="%s"
touch ${tmpfile} && chmod +x ${tmpfile}
cat > ${tmpfile} << '%s'
%s
%s
`, tmpFile, 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{tmpFile}
steps[i].VolumeMounts = append(steps[i].VolumeMounts, scriptsVolumeMount)
containers = append(containers, steps[i].Container)
}
return containers
}