Skip to content

Commit da8bd5f

Browse files
committed
cleanup: move step and sidecar validation
Move step and sidecar validation functions with their call chain to container_validation.go and fix small typos. Issue tektoncd#7442. Signed-off-by: Stanislav Jakuschevskij <[email protected]>
1 parent 6c1020d commit da8bd5f

File tree

2 files changed

+336
-335
lines changed

2 files changed

+336
-335
lines changed

pkg/apis/pipeline/v1/container_validation.go

Lines changed: 336 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,29 @@ import (
2121
"errors"
2222
"fmt"
2323
"regexp"
24+
"slices"
2425
"strings"
26+
"time"
2527

28+
"github.com/tektoncd/pipeline/internal/artifactref"
2629
"github.com/tektoncd/pipeline/pkg/apis/config"
30+
"github.com/tektoncd/pipeline/pkg/apis/pipeline"
31+
"github.com/tektoncd/pipeline/pkg/internal/resultref"
32+
"github.com/tektoncd/pipeline/pkg/substitution"
33+
"k8s.io/apimachinery/pkg/util/sets"
2734
"k8s.io/apimachinery/pkg/util/validation"
2835
"knative.dev/pkg/apis"
2936
)
3037

38+
// Validate ensures that a supplied Ref field is populated
39+
// correctly. No errors are returned for a nil Ref.
40+
func (ref *Ref) Validate(ctx context.Context) (errs *apis.FieldError) {
41+
if ref == nil {
42+
return errs
43+
}
44+
return validateRef(ctx, ref.Name, ref.Resolver, ref.Params)
45+
}
46+
3147
func validateRef(ctx context.Context, refName string, refResolver ResolverName, refParams Params) (errs *apis.FieldError) {
3248
switch {
3349
case refResolver != "" || refParams != nil:
@@ -80,15 +96,6 @@ func validateRef(ctx context.Context, refName string, refResolver ResolverName,
8096
return errs
8197
}
8298

83-
// Validate ensures that a supplied Ref field is populated
84-
// correctly. No errors are returned for a nil Ref.
85-
func (ref *Ref) Validate(ctx context.Context) (errs *apis.FieldError) {
86-
if ref == nil {
87-
return errs
88-
}
89-
return validateRef(ctx, ref.Name, ref.Resolver, ref.Params)
90-
}
91-
9299
// RefNameLikeUrl checks if the name is url parsable and returns an error if it isn't.
93100
func RefNameLikeUrl(name string) error {
94101
schemeRegex := regexp.MustCompile(`[\w-]+:\/\/*`)
@@ -97,3 +104,323 @@ func RefNameLikeUrl(name string) error {
97104
}
98105
return nil
99106
}
107+
108+
func validateSidecars(sidecars []Sidecar) (errs *apis.FieldError) {
109+
for _, sc := range sidecars {
110+
errs = errs.Also(validateSidecarName(sc))
111+
112+
if sc.Image == "" {
113+
errs = errs.Also(apis.ErrMissingField("image"))
114+
}
115+
116+
if sc.Script != "" {
117+
if len(sc.Command) > 0 {
118+
errs = errs.Also(&apis.FieldError{
119+
Message: "script cannot be used with command",
120+
Paths: []string{"script"},
121+
})
122+
}
123+
}
124+
}
125+
return errs
126+
}
127+
128+
func validateSidecarName(sc Sidecar) (errs *apis.FieldError) {
129+
if sc.Name == pipeline.ReservedResultsSidecarName {
130+
errs = errs.Also(&apis.FieldError{
131+
Message: fmt.Sprintf("Invalid: cannot use reserved sidecar name %v ", sc.Name),
132+
Paths: []string{"name"},
133+
})
134+
}
135+
return errs
136+
}
137+
138+
func validateSteps(ctx context.Context, steps []Step) (errs *apis.FieldError) {
139+
// Task must not have duplicate step names.
140+
names := sets.NewString()
141+
for idx, s := range steps {
142+
errs = errs.Also(validateStep(ctx, s, names).ViaIndex(idx))
143+
if s.Results != nil {
144+
errs = errs.Also(ValidateStepResultsVariables(ctx, s.Results, s.Script).ViaIndex(idx))
145+
errs = errs.Also(ValidateStepResults(ctx, s.Results).ViaIndex(idx).ViaField("results"))
146+
}
147+
if len(s.When) > 0 {
148+
errs = errs.Also(s.When.validate(ctx).ViaIndex(idx))
149+
}
150+
}
151+
return errs
152+
}
153+
154+
func validateStep(ctx context.Context, s Step, names sets.String) (errs *apis.FieldError) {
155+
if err := validateArtifactsReferencesInStep(ctx, s); err != nil {
156+
return err
157+
}
158+
159+
if s.Ref != nil {
160+
if !config.FromContextOrDefaults(ctx).FeatureFlags.EnableStepActions && isCreateOrUpdateAndDiverged(ctx, s) {
161+
return apis.ErrGeneric(fmt.Sprintf("feature flag %s should be set to true to reference StepActions in Steps.", config.EnableStepActions), "")
162+
}
163+
errs = errs.Also(s.Ref.Validate(ctx))
164+
if s.Image != "" {
165+
errs = errs.Also(&apis.FieldError{
166+
Message: "image cannot be used with Ref",
167+
Paths: []string{"image"},
168+
})
169+
}
170+
if len(s.Command) > 0 {
171+
errs = errs.Also(&apis.FieldError{
172+
Message: "command cannot be used with Ref",
173+
Paths: []string{"command"},
174+
})
175+
}
176+
if len(s.Args) > 0 {
177+
errs = errs.Also(&apis.FieldError{
178+
Message: "args cannot be used with Ref",
179+
Paths: []string{"args"},
180+
})
181+
}
182+
if s.Script != "" {
183+
errs = errs.Also(&apis.FieldError{
184+
Message: "script cannot be used with Ref",
185+
Paths: []string{"script"},
186+
})
187+
}
188+
if s.WorkingDir != "" {
189+
errs = errs.Also(&apis.FieldError{
190+
Message: "working dir cannot be used with Ref",
191+
Paths: []string{"workingDir"},
192+
})
193+
}
194+
if s.Env != nil {
195+
errs = errs.Also(&apis.FieldError{
196+
Message: "env cannot be used with Ref",
197+
Paths: []string{"env"},
198+
})
199+
}
200+
if len(s.VolumeMounts) > 0 {
201+
errs = errs.Also(&apis.FieldError{
202+
Message: "volumeMounts cannot be used with Ref",
203+
Paths: []string{"volumeMounts"},
204+
})
205+
}
206+
if len(s.Results) > 0 {
207+
errs = errs.Also(&apis.FieldError{
208+
Message: "results cannot be used with Ref",
209+
Paths: []string{"results"},
210+
})
211+
}
212+
} else {
213+
if len(s.Params) > 0 {
214+
errs = errs.Also(&apis.FieldError{
215+
Message: "params cannot be used without Ref",
216+
Paths: []string{"params"},
217+
})
218+
}
219+
if len(s.Results) > 0 {
220+
if !config.FromContextOrDefaults(ctx).FeatureFlags.EnableStepActions && isCreateOrUpdateAndDiverged(ctx, s) {
221+
return apis.ErrGeneric(fmt.Sprintf("feature flag %s should be set to true in order to use Results in Steps.", config.EnableStepActions), "")
222+
}
223+
}
224+
if len(s.When) > 0 {
225+
if !config.FromContextOrDefaults(ctx).FeatureFlags.EnableStepActions && isCreateOrUpdateAndDiverged(ctx, s) {
226+
return apis.ErrGeneric(fmt.Sprintf("feature flag %s should be set to true in order to use When in Steps.", config.EnableStepActions), "")
227+
}
228+
}
229+
if s.Image == "" {
230+
errs = errs.Also(apis.ErrMissingField("Image"))
231+
}
232+
233+
if s.Script != "" {
234+
if len(s.Command) > 0 {
235+
errs = errs.Also(&apis.FieldError{
236+
Message: "script cannot be used with command",
237+
Paths: []string{"script"},
238+
})
239+
}
240+
}
241+
}
242+
243+
if s.Name != "" {
244+
if names.Has(s.Name) {
245+
errs = errs.Also(apis.ErrInvalidValue(s.Name, "name"))
246+
}
247+
if e := validation.IsDNS1123Label(s.Name); len(e) > 0 {
248+
errs = errs.Also(&apis.FieldError{
249+
Message: fmt.Sprintf("invalid value %q", s.Name),
250+
Paths: []string{"name"},
251+
Details: "Task step name must be a valid DNS Label, For more info refer to https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
252+
})
253+
}
254+
names.Insert(s.Name)
255+
}
256+
257+
if s.Timeout != nil {
258+
if s.Timeout.Duration < time.Duration(0) {
259+
return apis.ErrInvalidValue(s.Timeout.Duration, "negative timeout")
260+
}
261+
}
262+
263+
for j, vm := range s.VolumeMounts {
264+
if strings.HasPrefix(vm.MountPath, "/tekton/") &&
265+
!strings.HasPrefix(vm.MountPath, "/tekton/home") {
266+
errs = errs.Also(apis.ErrGeneric(fmt.Sprintf("volumeMount cannot be mounted under /tekton/ (volumeMount %q mounted at %q)", vm.Name, vm.MountPath), "mountPath").ViaFieldIndex("volumeMounts", j))
267+
}
268+
if strings.HasPrefix(vm.Name, "tekton-internal-") {
269+
errs = errs.Also(apis.ErrGeneric(fmt.Sprintf(`volumeMount name %q cannot start with "tekton-internal-"`, vm.Name), "name").ViaFieldIndex("volumeMounts", j))
270+
}
271+
}
272+
273+
if s.OnError != "" {
274+
if !isParamRefs(string(s.OnError)) && s.OnError != Continue && s.OnError != StopAndFail {
275+
errs = errs.Also(&apis.FieldError{
276+
Message: fmt.Sprintf("invalid value: \"%v\"", s.OnError),
277+
Paths: []string{"onError"},
278+
Details: "Task step onError must be either \"continue\" or \"stopAndFail\"",
279+
})
280+
}
281+
}
282+
283+
if s.Script != "" {
284+
cleaned := strings.TrimSpace(s.Script)
285+
if strings.HasPrefix(cleaned, "#!win") {
286+
errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "windows script support", config.AlphaAPIFields).ViaField("script"))
287+
}
288+
}
289+
290+
// StdoutConfig is an alpha feature and will fail validation if it's used in a task spec
291+
// when the enable-api-fields feature gate is not "alpha".
292+
if s.StdoutConfig != nil {
293+
errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "step stdout stream support", config.AlphaAPIFields).ViaField("stdoutconfig"))
294+
}
295+
// StderrConfig is an alpha feature and will fail validation if it's used in a task spec
296+
// when the enable-api-fields feature gate is not "alpha".
297+
if s.StderrConfig != nil {
298+
errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "step stderr stream support", config.AlphaAPIFields).ViaField("stderrconfig"))
299+
}
300+
301+
// Validate usage of step result reference.
302+
// Referencing previous step's results are only allowed in `env`, `command` and `args`.
303+
errs = errs.Also(validateStepResultReference(s))
304+
305+
// Validate usage of step artifacts output reference
306+
// Referencing previous step's results are only allowed in `env`, `command` and `args`, `script`.
307+
errs = errs.Also(validateStepArtifactsReference(s))
308+
return errs
309+
}
310+
311+
func validateArtifactsReferencesInStep(ctx context.Context, s Step) *apis.FieldError {
312+
if !config.FromContextOrDefaults(ctx).FeatureFlags.EnableArtifacts {
313+
var t []string
314+
t = append(t, s.Script)
315+
t = append(t, s.Command...)
316+
t = append(t, s.Args...)
317+
for _, e := range s.Env {
318+
t = append(t, e.Value)
319+
}
320+
if slices.ContainsFunc(t, stepArtifactReferenceExists) || slices.ContainsFunc(t, taskArtifactReferenceExists) {
321+
return apis.ErrGeneric(fmt.Sprintf("feature flag %s should be set to true to use artifacts feature.", config.EnableArtifacts), "")
322+
}
323+
}
324+
return nil
325+
}
326+
327+
func stepArtifactReferenceExists(src string) bool {
328+
return len(artifactref.StepArtifactRegex.FindAllStringSubmatch(src, -1)) > 0 || strings.Contains(src, "$("+artifactref.StepArtifactPathPattern+")")
329+
}
330+
331+
func taskArtifactReferenceExists(src string) bool {
332+
return len(artifactref.TaskArtifactRegex.FindAllStringSubmatch(src, -1)) > 0 || strings.Contains(src, "$("+artifactref.TaskArtifactPathPattern+")")
333+
}
334+
335+
func validateStepResultReference(s Step) (errs *apis.FieldError) {
336+
errs = errs.Also(errorIfStepResultReferencedInField(s.Name, "name"))
337+
errs = errs.Also(errorIfStepResultReferencedInField(s.Image, "image"))
338+
errs = errs.Also(errorIfStepResultReferencedInField(s.Script, "script"))
339+
errs = errs.Also(errorIfStepResultReferencedInField(string(s.ImagePullPolicy), "imagePullPolicy"))
340+
errs = errs.Also(errorIfStepResultReferencedInField(s.WorkingDir, "workingDir"))
341+
for _, e := range s.EnvFrom {
342+
errs = errs.Also(errorIfStepResultReferencedInField(e.Prefix, "envFrom.prefix"))
343+
if e.ConfigMapRef != nil {
344+
errs = errs.Also(errorIfStepResultReferencedInField(e.ConfigMapRef.LocalObjectReference.Name, "envFrom.configMapRef"))
345+
}
346+
if e.SecretRef != nil {
347+
errs = errs.Also(errorIfStepResultReferencedInField(e.SecretRef.LocalObjectReference.Name, "envFrom.secretRef"))
348+
}
349+
}
350+
for _, v := range s.VolumeMounts {
351+
errs = errs.Also(errorIfStepResultReferencedInField(v.Name, "volumeMounts.name"))
352+
errs = errs.Also(errorIfStepResultReferencedInField(v.MountPath, "volumeMounts.mountPath"))
353+
errs = errs.Also(errorIfStepResultReferencedInField(v.SubPath, "volumeMounts.subPath"))
354+
}
355+
for _, v := range s.VolumeDevices {
356+
errs = errs.Also(errorIfStepResultReferencedInField(v.Name, "volumeDevices.name"))
357+
errs = errs.Also(errorIfStepResultReferencedInField(v.DevicePath, "volumeDevices.devicePath"))
358+
}
359+
return errs
360+
}
361+
362+
func errorIfStepResultReferencedInField(value, fieldName string) (errs *apis.FieldError) {
363+
matches := resultref.StepResultRegex.FindAllStringSubmatch(value, -1)
364+
if len(matches) > 0 {
365+
errs = errs.Also(&apis.FieldError{
366+
Message: "stepResult substitutions are only allowed in env, command and args. Found usage in",
367+
Paths: []string{fieldName},
368+
})
369+
}
370+
return errs
371+
}
372+
373+
func validateStepArtifactsReference(s Step) (errs *apis.FieldError) {
374+
errs = errs.Also(errorIfStepArtifactReferencedInField(s.Name, "name"))
375+
errs = errs.Also(errorIfStepArtifactReferencedInField(s.Image, "image"))
376+
errs = errs.Also(errorIfStepArtifactReferencedInField(string(s.ImagePullPolicy), "imagePullPolicy"))
377+
errs = errs.Also(errorIfStepArtifactReferencedInField(s.WorkingDir, "workingDir"))
378+
for _, e := range s.EnvFrom {
379+
errs = errs.Also(errorIfStepArtifactReferencedInField(e.Prefix, "envFrom.prefix"))
380+
if e.ConfigMapRef != nil {
381+
errs = errs.Also(errorIfStepArtifactReferencedInField(e.ConfigMapRef.LocalObjectReference.Name, "envFrom.configMapRef"))
382+
}
383+
if e.SecretRef != nil {
384+
errs = errs.Also(errorIfStepArtifactReferencedInField(e.SecretRef.LocalObjectReference.Name, "envFrom.secretRef"))
385+
}
386+
}
387+
for _, v := range s.VolumeMounts {
388+
errs = errs.Also(errorIfStepArtifactReferencedInField(v.Name, "volumeMounts.name"))
389+
errs = errs.Also(errorIfStepArtifactReferencedInField(v.MountPath, "volumeMounts.mountPath"))
390+
errs = errs.Also(errorIfStepArtifactReferencedInField(v.SubPath, "volumeMounts.subPath"))
391+
}
392+
for _, v := range s.VolumeDevices {
393+
errs = errs.Also(errorIfStepArtifactReferencedInField(v.Name, "volumeDevices.name"))
394+
errs = errs.Also(errorIfStepArtifactReferencedInField(v.DevicePath, "volumeDevices.devicePath"))
395+
}
396+
return errs
397+
}
398+
399+
func errorIfStepArtifactReferencedInField(value, fieldName string) (errs *apis.FieldError) {
400+
if stepArtifactReferenceExists(value) {
401+
errs = errs.Also(&apis.FieldError{
402+
Message: "stepArtifact substitutions are only allowed in env, command, args and script. Found usage in",
403+
Paths: []string{fieldName},
404+
})
405+
}
406+
return errs
407+
}
408+
409+
// ValidateStepResultsVariables validates if the StepResults referenced in step script are defined in step's results.
410+
func ValidateStepResultsVariables(ctx context.Context, results []StepResult, script string) (errs *apis.FieldError) {
411+
resultsNames := sets.NewString()
412+
for _, r := range results {
413+
resultsNames.Insert(r.Name)
414+
}
415+
errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(script, "step.results", resultsNames).ViaField("script"))
416+
errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(script, "results", resultsNames).ViaField("script"))
417+
return errs
418+
}
419+
420+
// ValidateStepResults validates that all of the declared StepResults are valid.
421+
func ValidateStepResults(ctx context.Context, results []StepResult) (errs *apis.FieldError) {
422+
for index, result := range results {
423+
errs = errs.Also(result.Validate(ctx).ViaIndex(index))
424+
}
425+
return errs
426+
}

0 commit comments

Comments
 (0)