From 1624848c6c8ebdd31b68f2985f73f7e42f229064 Mon Sep 17 00:00:00 2001 From: Stanislav Jakuschevskij Date: Sat, 5 Apr 2025 15:57:28 +0200 Subject: [PATCH] cleanup: move step and sidecar validation Move step and sidecar validation functions with their call chain to `container_validation.go` and fix small typos. Create `validateSidecar` and move to `container_validation.go` to be consistent with the `validateStep` function location. Inline `validateSidecarName` function into `validateSidecar` to keep consistency because the fields `sc.Image` and `sc.Script` are validated in the caller. Keep `validationSteps` and `validateSidecars` in `task_validation.go`. Issue #7442. Signed-off-by: Stanislav Jakuschevskij --- pkg/apis/pipeline/v1/container_validation.go | 312 ++++++++++++++++- .../pipeline/v1/container_validation_test.go | 2 +- pkg/apis/pipeline/v1/task_validation.go | 325 +----------------- 3 files changed, 319 insertions(+), 320 deletions(-) diff --git a/pkg/apis/pipeline/v1/container_validation.go b/pkg/apis/pipeline/v1/container_validation.go index ec55189bc32..0a48183967d 100644 --- a/pkg/apis/pipeline/v1/container_validation.go +++ b/pkg/apis/pipeline/v1/container_validation.go @@ -21,13 +21,28 @@ import ( "errors" "fmt" "regexp" + "slices" "strings" + "time" + "github.com/tektoncd/pipeline/internal/artifactref" "github.com/tektoncd/pipeline/pkg/apis/config" + "github.com/tektoncd/pipeline/pkg/apis/pipeline" + "github.com/tektoncd/pipeline/pkg/internal/resultref" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" "knative.dev/pkg/apis" ) +// Validate ensures that a supplied Ref field is populated +// correctly. No errors are returned for a nil Ref. +func (ref *Ref) Validate(ctx context.Context) (errs *apis.FieldError) { + if ref == nil { + return errs + } + return validateRef(ctx, ref.Name, ref.Resolver, ref.Params) +} + func validateRef(ctx context.Context, refName string, refResolver ResolverName, refParams Params) (errs *apis.FieldError) { switch { case refResolver != "" || refParams != nil: @@ -80,15 +95,6 @@ func validateRef(ctx context.Context, refName string, refResolver ResolverName, return errs } -// Validate ensures that a supplied Ref field is populated -// correctly. No errors are returned for a nil Ref. -func (ref *Ref) Validate(ctx context.Context) (errs *apis.FieldError) { - if ref == nil { - return errs - } - return validateRef(ctx, ref.Name, ref.Resolver, ref.Params) -} - // RefNameLikeUrl checks if the name is url parsable and returns an error if it isn't. func RefNameLikeUrl(name string) error { schemeRegex := regexp.MustCompile(`[\w-]+:\/\/*`) @@ -97,3 +103,291 @@ func RefNameLikeUrl(name string) error { } return nil } + +func validateStep(ctx context.Context, s Step, names sets.String) (errs *apis.FieldError) { + if err := validateArtifactsReferencesInStep(ctx, s); err != nil { + return err + } + + if s.Ref != nil { + errs = errs.Also(s.Ref.Validate(ctx)) + if s.Image != "" { + errs = errs.Also(&apis.FieldError{ + Message: "image cannot be used with Ref", + Paths: []string{"image"}, + }) + } + if len(s.Command) > 0 { + errs = errs.Also(&apis.FieldError{ + Message: "command cannot be used with Ref", + Paths: []string{"command"}, + }) + } + if len(s.Args) > 0 { + errs = errs.Also(&apis.FieldError{ + Message: "args cannot be used with Ref", + Paths: []string{"args"}, + }) + } + if s.Script != "" { + errs = errs.Also(&apis.FieldError{ + Message: "script cannot be used with Ref", + Paths: []string{"script"}, + }) + } + if s.WorkingDir != "" { + errs = errs.Also(&apis.FieldError{ + Message: "working dir cannot be used with Ref", + Paths: []string{"workingDir"}, + }) + } + if s.Env != nil { + errs = errs.Also(&apis.FieldError{ + Message: "env cannot be used with Ref", + Paths: []string{"env"}, + }) + } + if len(s.VolumeMounts) > 0 { + errs = errs.Also(&apis.FieldError{ + Message: "volumeMounts cannot be used with Ref", + Paths: []string{"volumeMounts"}, + }) + } + if len(s.Results) > 0 { + errs = errs.Also(&apis.FieldError{ + Message: "results cannot be used with Ref", + Paths: []string{"results"}, + }) + } + } else { + if len(s.Params) > 0 { + errs = errs.Also(&apis.FieldError{ + Message: "params cannot be used without Ref", + Paths: []string{"params"}, + }) + } + if s.Image == "" { + errs = errs.Also(apis.ErrMissingField("Image")) + } + + if s.Script != "" { + if len(s.Command) > 0 { + errs = errs.Also(&apis.FieldError{ + Message: "script cannot be used with command", + Paths: []string{"script"}, + }) + } + } + } + + if s.Name != "" { + if names.Has(s.Name) { + errs = errs.Also(apis.ErrInvalidValue(s.Name, "name")) + } + if e := validation.IsDNS1123Label(s.Name); len(e) > 0 { + errs = errs.Also(&apis.FieldError{ + Message: fmt.Sprintf("invalid value %q", s.Name), + Paths: []string{"name"}, + 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", + }) + } + names.Insert(s.Name) + } + + if s.Timeout != nil { + if s.Timeout.Duration < time.Duration(0) { + return apis.ErrInvalidValue(s.Timeout.Duration, "negative timeout") + } + } + + for j, vm := range s.VolumeMounts { + if strings.HasPrefix(vm.MountPath, "/tekton/") && + !strings.HasPrefix(vm.MountPath, "/tekton/home") { + 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)) + } + if strings.HasPrefix(vm.Name, "tekton-internal-") { + errs = errs.Also(apis.ErrGeneric(fmt.Sprintf(`volumeMount name %q cannot start with "tekton-internal-"`, vm.Name), "name").ViaFieldIndex("volumeMounts", j)) + } + } + + if s.OnError != "" { + if !isParamRefs(string(s.OnError)) && s.OnError != Continue && s.OnError != StopAndFail { + errs = errs.Also(&apis.FieldError{ + Message: fmt.Sprintf("invalid value: \"%v\"", s.OnError), + Paths: []string{"onError"}, + Details: "Task step onError must be either \"continue\" or \"stopAndFail\"", + }) + } + } + + if s.Script != "" { + cleaned := strings.TrimSpace(s.Script) + if strings.HasPrefix(cleaned, "#!win") { + errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "windows script support", config.AlphaAPIFields).ViaField("script")) + } + } + + // StdoutConfig is an alpha feature and will fail validation if it's used in a task spec + // when the enable-api-fields feature gate is not "alpha". + if s.StdoutConfig != nil { + errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "step stdout stream support", config.AlphaAPIFields).ViaField("stdoutconfig")) + } + // StderrConfig is an alpha feature and will fail validation if it's used in a task spec + // when the enable-api-fields feature gate is not "alpha". + if s.StderrConfig != nil { + errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "step stderr stream support", config.AlphaAPIFields).ViaField("stderrconfig")) + } + + // Validate usage of step result reference. + // Referencing previous step's results are only allowed in `env`, `command` and `args`. + errs = errs.Also(validateStepResultReference(s)) + + // Validate usage of step artifacts output reference + // Referencing previous step's results are only allowed in `env`, `command` and `args`, `script`. + errs = errs.Also(validateStepArtifactsReference(s)) + return errs +} + +// isParamRefs attempts to check if a specified string looks like it contains any parameter reference +// This is useful to make sure the specified value looks like a Parameter Reference before performing any strict validation +func isParamRefs(s string) bool { + return strings.HasPrefix(s, "$("+ParamsPrefix) +} + +func validateArtifactsReferencesInStep(ctx context.Context, s Step) *apis.FieldError { + cfg := config.FromContextOrDefaults(ctx) + if cfg == nil || cfg.FeatureFlags == nil { + cfg = &config.Config{ + FeatureFlags: &config.FeatureFlags{}, + } + } + + if !cfg.FeatureFlags.EnableArtifacts { + var t []string + if s.Script != "" { + t = append(t, s.Script) + } + if len(s.Command) > 0 { + t = append(t, s.Command...) + } + if len(s.Args) > 0 { + t = append(t, s.Args...) + } + if s.Env != nil { + for _, e := range s.Env { + if e.Value != "" { + t = append(t, e.Value) + } + } + } + if slices.ContainsFunc(t, stepArtifactReferenceExists) || slices.ContainsFunc(t, taskArtifactReferenceExists) { + return apis.ErrGeneric(fmt.Sprintf("feature flag %s should be set to true to use artifacts feature.", config.EnableArtifacts), "") + } + } + return nil +} + +func stepArtifactReferenceExists(src string) bool { + return len(artifactref.StepArtifactRegex.FindAllStringSubmatch(src, -1)) > 0 || strings.Contains(src, "$("+artifactref.StepArtifactPathPattern+")") +} + +func taskArtifactReferenceExists(src string) bool { + return len(artifactref.TaskArtifactRegex.FindAllStringSubmatch(src, -1)) > 0 || strings.Contains(src, "$("+artifactref.TaskArtifactPathPattern+")") +} + +func validateStepResultReference(s Step) (errs *apis.FieldError) { + errs = errs.Also(errorIfStepResultReferenceinField(s.Name, "name")) + errs = errs.Also(errorIfStepResultReferenceinField(s.Image, "image")) + errs = errs.Also(errorIfStepResultReferenceinField(s.Script, "script")) + errs = errs.Also(errorIfStepResultReferenceinField(string(s.ImagePullPolicy), "imagePullPoliicy")) + errs = errs.Also(errorIfStepResultReferenceinField(s.WorkingDir, "workingDir")) + for _, e := range s.EnvFrom { + errs = errs.Also(errorIfStepResultReferenceinField(e.Prefix, "envFrom.prefix")) + if e.ConfigMapRef != nil { + errs = errs.Also(errorIfStepResultReferenceinField(e.ConfigMapRef.LocalObjectReference.Name, "envFrom.configMapRef")) + } + if e.SecretRef != nil { + errs = errs.Also(errorIfStepResultReferenceinField(e.SecretRef.LocalObjectReference.Name, "envFrom.secretRef")) + } + } + for _, v := range s.VolumeMounts { + errs = errs.Also(errorIfStepResultReferenceinField(v.Name, "volumeMounts.name")) + errs = errs.Also(errorIfStepResultReferenceinField(v.MountPath, "volumeMounts.mountPath")) + errs = errs.Also(errorIfStepResultReferenceinField(v.SubPath, "volumeMounts.subPath")) + } + for _, v := range s.VolumeDevices { + errs = errs.Also(errorIfStepResultReferenceinField(v.Name, "volumeDevices.name")) + errs = errs.Also(errorIfStepResultReferenceinField(v.DevicePath, "volumeDevices.devicePath")) + } + return errs +} + +func errorIfStepResultReferenceinField(value, fieldName string) (errs *apis.FieldError) { + matches := resultref.StepResultRegex.FindAllStringSubmatch(value, -1) + if len(matches) > 0 { + errs = errs.Also(&apis.FieldError{ + Message: "stepResult substitutions are only allowed in env, command and args. Found usage in", + Paths: []string{fieldName}, + }) + } + return errs +} + +func validateStepArtifactsReference(s Step) (errs *apis.FieldError) { + errs = errs.Also(errorIfStepArtifactReferencedInField(s.Name, "name")) + errs = errs.Also(errorIfStepArtifactReferencedInField(s.Image, "image")) + errs = errs.Also(errorIfStepArtifactReferencedInField(string(s.ImagePullPolicy), "imagePullPolicy")) + errs = errs.Also(errorIfStepArtifactReferencedInField(s.WorkingDir, "workingDir")) + for _, e := range s.EnvFrom { + errs = errs.Also(errorIfStepArtifactReferencedInField(e.Prefix, "envFrom.prefix")) + if e.ConfigMapRef != nil { + errs = errs.Also(errorIfStepArtifactReferencedInField(e.ConfigMapRef.LocalObjectReference.Name, "envFrom.configMapRef")) + } + if e.SecretRef != nil { + errs = errs.Also(errorIfStepArtifactReferencedInField(e.SecretRef.LocalObjectReference.Name, "envFrom.secretRef")) + } + } + for _, v := range s.VolumeMounts { + errs = errs.Also(errorIfStepArtifactReferencedInField(v.Name, "volumeMounts.name")) + errs = errs.Also(errorIfStepArtifactReferencedInField(v.MountPath, "volumeMounts.mountPath")) + errs = errs.Also(errorIfStepArtifactReferencedInField(v.SubPath, "volumeMounts.subPath")) + } + for _, v := range s.VolumeDevices { + errs = errs.Also(errorIfStepArtifactReferencedInField(v.Name, "volumeDevices.name")) + errs = errs.Also(errorIfStepArtifactReferencedInField(v.DevicePath, "volumeDevices.devicePath")) + } + return errs +} + +func errorIfStepArtifactReferencedInField(value, fieldName string) (errs *apis.FieldError) { + if stepArtifactReferenceExists(value) { + errs = errs.Also(&apis.FieldError{ + Message: "stepArtifact substitutions are only allowed in env, command, args and script. Found usage in", + Paths: []string{fieldName}, + }) + } + return errs +} + +func validateSidecar(errs *apis.FieldError, sc Sidecar) *apis.FieldError { + if sc.Name == pipeline.ReservedResultsSidecarName { + errs = errs.Also(&apis.FieldError{ + Message: fmt.Sprintf("Invalid: cannot use reserved sidecar name %v ", sc.Name), + Paths: []string{"name"}, + }) + } + + if sc.Image == "" { + errs = errs.Also(apis.ErrMissingField("image")) + } + + if sc.Script != "" { + if len(sc.Command) > 0 { + errs = errs.Also(&apis.FieldError{ + Message: "script cannot be used with command", + Paths: []string{"script"}, + }) + } + } + return errs +} diff --git a/pkg/apis/pipeline/v1/container_validation_test.go b/pkg/apis/pipeline/v1/container_validation_test.go index 74fbec19190..9e6387f8028 100644 --- a/pkg/apis/pipeline/v1/container_validation_test.go +++ b/pkg/apis/pipeline/v1/container_validation_test.go @@ -48,7 +48,7 @@ func TestRef_Valid(t *testing.T) { name: "simple ref", ref: &v1.Ref{Name: "refname"}, }, { - name: "ref name - consice syntax", + name: "ref name - concise syntax", ref: &v1.Ref{Name: "foo://baz:ver", ResolverRef: v1.ResolverRef{Resolver: "git"}}, wc: enableConciseResolverSyntax, }, { diff --git a/pkg/apis/pipeline/v1/task_validation.go b/pkg/apis/pipeline/v1/task_validation.go index c0e337a4b26..d6699216f9e 100644 --- a/pkg/apis/pipeline/v1/task_validation.go +++ b/pkg/apis/pipeline/v1/task_validation.go @@ -21,21 +21,14 @@ import ( "fmt" "path/filepath" "regexp" - "slices" - "strings" - "time" - "github.com/tektoncd/pipeline/internal/artifactref" "github.com/tektoncd/pipeline/pkg/apis/config" - "github.com/tektoncd/pipeline/pkg/apis/pipeline" "github.com/tektoncd/pipeline/pkg/apis/validate" - "github.com/tektoncd/pipeline/pkg/internal/resultref" "github.com/tektoncd/pipeline/pkg/substitution" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/validation" "knative.dev/pkg/apis" "knative.dev/pkg/webhook/resourcesemantics" ) @@ -126,36 +119,6 @@ func ValidateObjectParamsHaveProperties(ctx context.Context, params ParamSpecs) return errs } -func validateSidecars(sidecars []Sidecar) (errs *apis.FieldError) { - for _, sc := range sidecars { - errs = errs.Also(validateSidecarName(sc)) - - if sc.Image == "" { - errs = errs.Also(apis.ErrMissingField("image")) - } - - if sc.Script != "" { - if len(sc.Command) > 0 { - errs = errs.Also(&apis.FieldError{ - Message: "script cannot be used with command", - Paths: []string{"script"}, - }) - } - } - } - return errs -} - -func validateSidecarName(sc Sidecar) (errs *apis.FieldError) { - if sc.Name == pipeline.ReservedResultsSidecarName { - errs = errs.Also(&apis.FieldError{ - Message: fmt.Sprintf("Invalid: cannot use reserved sidecar name %v ", sc.Name), - Paths: []string{"name"}, - }) - } - return errs -} - func validateResults(ctx context.Context, results []TaskResult) (errs *apis.FieldError) { for index, result := range results { errs = errs.Also(result.Validate(ctx).ViaIndex(index)) @@ -236,7 +199,7 @@ func validateWorkspaceUsages(ctx context.Context, ts *TaskSpec) (errs *apis.Fiel return errs } -// ValidateVolumes validates a slice of volumes to make sure there are no dupilcate names +// ValidateVolumes validates a slice of volumes to make sure there are no duplicate names func ValidateVolumes(volumes []corev1.Volume) (errs *apis.FieldError) { // Task must not have duplicate volume names. vols := sets.NewString() @@ -266,265 +229,32 @@ func validateSteps(ctx context.Context, steps []Step) (errs *apis.FieldError) { return errs } -func errorIfStepResultReferenceinField(value, fieldName string) (errs *apis.FieldError) { - matches := resultref.StepResultRegex.FindAllStringSubmatch(value, -1) - if len(matches) > 0 { - errs = errs.Also(&apis.FieldError{ - Message: "stepResult substitutions are only allowed in env, command and args. Found usage in", - Paths: []string{fieldName}, - }) - } - return errs -} - -func stepArtifactReferenceExists(src string) bool { - return len(artifactref.StepArtifactRegex.FindAllStringSubmatch(src, -1)) > 0 || strings.Contains(src, "$("+artifactref.StepArtifactPathPattern+")") -} - -func taskArtifactReferenceExists(src string) bool { - return len(artifactref.TaskArtifactRegex.FindAllStringSubmatch(src, -1)) > 0 || strings.Contains(src, "$("+artifactref.TaskArtifactPathPattern+")") -} - -func errorIfStepArtifactReferencedInField(value, fieldName string) (errs *apis.FieldError) { - if stepArtifactReferenceExists(value) { - errs = errs.Also(&apis.FieldError{ - Message: "stepArtifact substitutions are only allowed in env, command, args and script. Found usage in", - Paths: []string{fieldName}, - }) - } - return errs -} - -func validateStepArtifactsReference(s Step) (errs *apis.FieldError) { - errs = errs.Also(errorIfStepArtifactReferencedInField(s.Name, "name")) - errs = errs.Also(errorIfStepArtifactReferencedInField(s.Image, "image")) - errs = errs.Also(errorIfStepArtifactReferencedInField(string(s.ImagePullPolicy), "imagePullPoliicy")) - errs = errs.Also(errorIfStepArtifactReferencedInField(s.WorkingDir, "workingDir")) - for _, e := range s.EnvFrom { - errs = errs.Also(errorIfStepArtifactReferencedInField(e.Prefix, "envFrom.prefix")) - if e.ConfigMapRef != nil { - errs = errs.Also(errorIfStepArtifactReferencedInField(e.ConfigMapRef.LocalObjectReference.Name, "envFrom.configMapRef")) - } - if e.SecretRef != nil { - errs = errs.Also(errorIfStepArtifactReferencedInField(e.SecretRef.LocalObjectReference.Name, "envFrom.secretRef")) - } - } - for _, v := range s.VolumeMounts { - errs = errs.Also(errorIfStepArtifactReferencedInField(v.Name, "volumeMounts.name")) - errs = errs.Also(errorIfStepArtifactReferencedInField(v.MountPath, "volumeMounts.mountPath")) - errs = errs.Also(errorIfStepArtifactReferencedInField(v.SubPath, "volumeMounts.subPath")) - } - for _, v := range s.VolumeDevices { - errs = errs.Also(errorIfStepArtifactReferencedInField(v.Name, "volumeDevices.name")) - errs = errs.Also(errorIfStepArtifactReferencedInField(v.DevicePath, "volumeDevices.devicePath")) +// ValidateStepResults validates that all of the declared StepResults are valid. +func ValidateStepResults(ctx context.Context, results []StepResult) (errs *apis.FieldError) { + for index, result := range results { + errs = errs.Also(result.Validate(ctx).ViaIndex(index)) } return errs } -func validateStepResultReference(s Step) (errs *apis.FieldError) { - errs = errs.Also(errorIfStepResultReferenceinField(s.Name, "name")) - errs = errs.Also(errorIfStepResultReferenceinField(s.Image, "image")) - errs = errs.Also(errorIfStepResultReferenceinField(s.Script, "script")) - errs = errs.Also(errorIfStepResultReferenceinField(string(s.ImagePullPolicy), "imagePullPoliicy")) - errs = errs.Also(errorIfStepResultReferenceinField(s.WorkingDir, "workingDir")) - for _, e := range s.EnvFrom { - errs = errs.Also(errorIfStepResultReferenceinField(e.Prefix, "envFrom.prefix")) - if e.ConfigMapRef != nil { - errs = errs.Also(errorIfStepResultReferenceinField(e.ConfigMapRef.LocalObjectReference.Name, "envFrom.configMapRef")) - } - if e.SecretRef != nil { - errs = errs.Also(errorIfStepResultReferenceinField(e.SecretRef.LocalObjectReference.Name, "envFrom.secretRef")) - } - } - for _, v := range s.VolumeMounts { - errs = errs.Also(errorIfStepResultReferenceinField(v.Name, "volumeMounts.name")) - errs = errs.Also(errorIfStepResultReferenceinField(v.MountPath, "volumeMounts.mountPath")) - errs = errs.Also(errorIfStepResultReferenceinField(v.SubPath, "volumeMounts.subPath")) - } - for _, v := range s.VolumeDevices { - errs = errs.Also(errorIfStepResultReferenceinField(v.Name, "volumeDevices.name")) - errs = errs.Also(errorIfStepResultReferenceinField(v.DevicePath, "volumeDevices.devicePath")) +// ValidateStepResultsVariables validates if the StepResults referenced in step script are defined in step's results. +func ValidateStepResultsVariables(ctx context.Context, results []StepResult, script string) (errs *apis.FieldError) { + resultsNames := sets.NewString() + for _, r := range results { + resultsNames.Insert(r.Name) } + errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(script, "step.results", resultsNames).ViaField("script")) + errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(script, "results", resultsNames).ViaField("script")) return errs } -func validateStep(ctx context.Context, s Step, names sets.String) (errs *apis.FieldError) { - if err := validateArtifactsReferencesInStep(ctx, s); err != nil { - return err - } - - if s.Ref != nil { - errs = errs.Also(s.Ref.Validate(ctx)) - if s.Image != "" { - errs = errs.Also(&apis.FieldError{ - Message: "image cannot be used with Ref", - Paths: []string{"image"}, - }) - } - if len(s.Command) > 0 { - errs = errs.Also(&apis.FieldError{ - Message: "command cannot be used with Ref", - Paths: []string{"command"}, - }) - } - if len(s.Args) > 0 { - errs = errs.Also(&apis.FieldError{ - Message: "args cannot be used with Ref", - Paths: []string{"args"}, - }) - } - if s.Script != "" { - errs = errs.Also(&apis.FieldError{ - Message: "script cannot be used with Ref", - Paths: []string{"script"}, - }) - } - if s.WorkingDir != "" { - errs = errs.Also(&apis.FieldError{ - Message: "working dir cannot be used with Ref", - Paths: []string{"workingDir"}, - }) - } - if s.Env != nil { - errs = errs.Also(&apis.FieldError{ - Message: "env cannot be used with Ref", - Paths: []string{"env"}, - }) - } - if len(s.VolumeMounts) > 0 { - errs = errs.Also(&apis.FieldError{ - Message: "volumeMounts cannot be used with Ref", - Paths: []string{"volumeMounts"}, - }) - } - if len(s.Results) > 0 { - errs = errs.Also(&apis.FieldError{ - Message: "results cannot be used with Ref", - Paths: []string{"results"}, - }) - } - } else { - if len(s.Params) > 0 { - errs = errs.Also(&apis.FieldError{ - Message: "params cannot be used without Ref", - Paths: []string{"params"}, - }) - } - if s.Image == "" { - errs = errs.Also(apis.ErrMissingField("Image")) - } - - if s.Script != "" { - if len(s.Command) > 0 { - errs = errs.Also(&apis.FieldError{ - Message: "script cannot be used with command", - Paths: []string{"script"}, - }) - } - } - } - - if s.Name != "" { - if names.Has(s.Name) { - errs = errs.Also(apis.ErrInvalidValue(s.Name, "name")) - } - if e := validation.IsDNS1123Label(s.Name); len(e) > 0 { - errs = errs.Also(&apis.FieldError{ - Message: fmt.Sprintf("invalid value %q", s.Name), - Paths: []string{"name"}, - 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", - }) - } - names.Insert(s.Name) - } - - if s.Timeout != nil { - if s.Timeout.Duration < time.Duration(0) { - return apis.ErrInvalidValue(s.Timeout.Duration, "negative timeout") - } - } - - for j, vm := range s.VolumeMounts { - if strings.HasPrefix(vm.MountPath, "/tekton/") && - !strings.HasPrefix(vm.MountPath, "/tekton/home") { - 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)) - } - if strings.HasPrefix(vm.Name, "tekton-internal-") { - errs = errs.Also(apis.ErrGeneric(fmt.Sprintf(`volumeMount name %q cannot start with "tekton-internal-"`, vm.Name), "name").ViaFieldIndex("volumeMounts", j)) - } - } - - if s.OnError != "" { - if !isParamRefs(string(s.OnError)) && s.OnError != Continue && s.OnError != StopAndFail { - errs = errs.Also(&apis.FieldError{ - Message: fmt.Sprintf("invalid value: \"%v\"", s.OnError), - Paths: []string{"onError"}, - Details: "Task step onError must be either \"continue\" or \"stopAndFail\"", - }) - } - } - - if s.Script != "" { - cleaned := strings.TrimSpace(s.Script) - if strings.HasPrefix(cleaned, "#!win") { - errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "windows script support", config.AlphaAPIFields).ViaField("script")) - } - } - - // StdoutConfig is an alpha feature and will fail validation if it's used in a task spec - // when the enable-api-fields feature gate is not "alpha". - if s.StdoutConfig != nil { - errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "step stdout stream support", config.AlphaAPIFields).ViaField("stdoutconfig")) - } - // StderrConfig is an alpha feature and will fail validation if it's used in a task spec - // when the enable-api-fields feature gate is not "alpha". - if s.StderrConfig != nil { - errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "step stderr stream support", config.AlphaAPIFields).ViaField("stderrconfig")) +func validateSidecars(sidecars []Sidecar) (errs *apis.FieldError) { + for _, sc := range sidecars { + errs = validateSidecar(errs, sc) } - - // Validate usage of step result reference. - // Referencing previous step's results are only allowed in `env`, `command` and `args`. - errs = errs.Also(validateStepResultReference(s)) - - // Validate usage of step artifacts output reference - // Referencing previous step's results are only allowed in `env`, `command` and `args`, `script`. - errs = errs.Also(validateStepArtifactsReference(s)) return errs } -func validateArtifactsReferencesInStep(ctx context.Context, s Step) *apis.FieldError { - cfg := config.FromContextOrDefaults(ctx) - if cfg == nil || cfg.FeatureFlags == nil { - cfg = &config.Config{ - FeatureFlags: &config.FeatureFlags{}, - } - } - - if !cfg.FeatureFlags.EnableArtifacts { - var t []string - if s.Script != "" { - t = append(t, s.Script) - } - if len(s.Command) > 0 { - t = append(t, s.Command...) - } - if len(s.Args) > 0 { - t = append(t, s.Args...) - } - if s.Env != nil { - for _, e := range s.Env { - if e.Value != "" { - t = append(t, e.Value) - } - } - } - if slices.ContainsFunc(t, stepArtifactReferenceExists) || slices.ContainsFunc(t, taskArtifactReferenceExists) { - return apis.ErrGeneric(fmt.Sprintf("feature flag %s should be set to true to use artifacts feature.", config.EnableArtifacts), "") - } - } - return nil -} - // ValidateParameterTypes validates all the types within a slice of ParamSpecs func ValidateParameterTypes(ctx context.Context, params []ParamSpec) (errs *apis.FieldError) { for _, p := range params { @@ -785,12 +515,6 @@ func validateStepVariables(ctx context.Context, step Step, prefix string, vars s return errs } -// isParamRefs attempts to check if a specified string looks like it contains any parameter reference -// This is useful to make sure the specified value looks like a Parameter Reference before performing any strict validation -func isParamRefs(s string) bool { - return strings.HasPrefix(s, "$("+ParamsPrefix) -} - // GetIndexingReferencesToArrayParams returns all strings referencing indices of TaskRun array parameters // from parameters, workspaces, and when expressions defined in the Task. // For example, if a Task has a parameter with a value "$(params.array-param-name[1])", @@ -812,22 +536,3 @@ func (ts *TaskSpec) GetIndexingReferencesToArrayParams() sets.String { } return sets.NewString(arrayIndexParamRefs...) } - -// ValidateStepResults validates that all of the declared StepResults are valid. -func ValidateStepResults(ctx context.Context, results []StepResult) (errs *apis.FieldError) { - for index, result := range results { - errs = errs.Also(result.Validate(ctx).ViaIndex(index)) - } - return errs -} - -// ValidateStepResultsVariables validates if the StepResults referenced in step script are defined in step's results. -func ValidateStepResultsVariables(ctx context.Context, results []StepResult, script string) (errs *apis.FieldError) { - resultsNames := sets.NewString() - for _, r := range results { - resultsNames.Insert(r.Name) - } - errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(script, "step.results", resultsNames).ViaField("script")) - errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(script, "results", resultsNames).ViaField("script")) - return errs -}