From 10922cc06642e49737c781761df55eb40d85b50a Mon Sep 17 00:00:00 2001 From: Chitrang Patel Date: Wed, 25 Oct 2023 15:39:04 -0400 Subject: [PATCH] Introduce StepAction referencing syntax in Steps This PR adds the referencing syntax for `StepActions` in `Stpes`. It also adds the necessary conversion between `v1beta1` and `v1` and the necessary validation. --- docs/stepactions.md | 48 +++++ pkg/apis/pipeline/v1/container_types.go | 9 + pkg/apis/pipeline/v1/task_validation.go | 48 ++++- pkg/apis/pipeline/v1/task_validation_test.go | 200 ++++++++++++++++++ .../pipeline/v1beta1/container_conversion.go | 17 ++ pkg/apis/pipeline/v1beta1/container_types.go | 10 + .../pipeline/v1beta1/task_conversion_test.go | 16 ++ pkg/apis/pipeline/v1beta1/task_validation.go | 48 ++++- .../pipeline/v1beta1/task_validation_test.go | 200 ++++++++++++++++++ test/parse/yaml.go | 11 + 10 files changed, 595 insertions(+), 12 deletions(-) diff --git a/docs/stepactions.md b/docs/stepactions.md index 292304efe24..838fc110036 100644 --- a/docs/stepactions.md +++ b/docs/stepactions.md @@ -54,3 +54,51 @@ spec: command: ["ls"] args:: ["-lh"] ``` + +## Referencing a StepAction + +`StepActions` can be referenced from the `Step` using the `ref` field, as follows: + +```yaml +apiVersion: tekton.dev/v1 +kind: TaskRun +metadata: + name: step-action-run +spec: + TaskSpec: + steps: + - name: action-runner + ref: + name: step-action +``` + +If a `Step` is referencing a `StepAction`, it cannot contain the fields supported by `StepActions`. This includes: +- `image` +- `command` +- `args` +- `script` +- `env` + +Using any of the above fields and referencing a `StepAction` in the same `Step` is not allowed and will cause an validation error. + +```yaml +# This is not allowed and will result in a validation error. +# Because the image is expected to be provided by the StepAction +# and not inlined. +apiVersion: tekton.dev/v1 +kind: TaskRun +metadata: + name: step-action-run +spec: + TaskSpec: + steps: + - name: action-runner + ref: + name: step-action + image: ubuntu +``` +Executing the above `TaskRun` will result in an error that looks like: + +``` +Error from server (BadRequest): error when creating "STDIN": admission webhook "validation.webhook.pipeline.tekton.dev" denied the request: validation failed: image cannot be used with Ref: spec.taskSpec.steps[0].image +``` diff --git a/pkg/apis/pipeline/v1/container_types.go b/pkg/apis/pipeline/v1/container_types.go index ccef95cf767..ff3148cdbdd 100644 --- a/pkg/apis/pipeline/v1/container_types.go +++ b/pkg/apis/pipeline/v1/container_types.go @@ -135,6 +135,15 @@ type Step struct { // Stores configuration for the stderr stream of the step. // +optional StderrConfig *StepOutputConfig `json:"stderrConfig,omitempty"` + // Contains the reference to an existing stepaction + //+optional + Ref *Ref `json:"ref,omitempty"` +} + +// Ref can be used to refer to a specific instance of a StepAction. +type Ref struct { + // Name of the referenced step + Name string `json:"name,omitempty"` } // OnErrorType defines a list of supported exiting behavior of a container on error diff --git a/pkg/apis/pipeline/v1/task_validation.go b/pkg/apis/pipeline/v1/task_validation.go index e85179d60d1..cdad33d7a80 100644 --- a/pkg/apis/pipeline/v1/task_validation.go +++ b/pkg/apis/pipeline/v1/task_validation.go @@ -264,17 +264,53 @@ func validateSteps(ctx context.Context, steps []Step) (errs *apis.FieldError) { } func validateStep(ctx context.Context, s Step, names sets.String) (errs *apis.FieldError) { - if s.Image == "" { - errs = errs.Also(apis.ErrMissingField("Image")) - } - - if s.Script != "" { + if s.Ref != nil { + if !config.FromContextOrDefaults(ctx).FeatureFlags.EnableStepActions { + return apis.ErrGeneric("feature flag %s should be set to true to reference StepActions in Steps.", config.EnableStepActions) + } + 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: "script cannot be used with command", + 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.Env != nil { + errs = errs.Also(&apis.FieldError{ + Message: "env cannot be used with Ref", + Paths: []string{"env"}, + }) + } + } else { + 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 != "" { diff --git a/pkg/apis/pipeline/v1/task_validation_test.go b/pkg/apis/pipeline/v1/task_validation_test.go index b04d43f190e..47a5de46ace 100644 --- a/pkg/apis/pipeline/v1/task_validation_test.go +++ b/pkg/apis/pipeline/v1/task_validation_test.go @@ -21,6 +21,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/tektoncd/pipeline/pkg/apis/config" cfgtesting "github.com/tektoncd/pipeline/pkg/apis/config/testing" "github.com/tektoncd/pipeline/pkg/apis/pipeline" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" @@ -520,6 +521,38 @@ func TestTaskSpecValidate(t *testing.T) { } } +func TestTaskSpecStepActionReferenceValidate(t *testing.T) { + tests := []struct { + name string + Steps []v1.Step + }{{ + name: "valid stepaction ref", + Steps: []v1.Step{{ + Name: "mystep", + WorkingDir: "/foo", + Ref: &v1.Ref{ + Name: "stepAction", + }, + }}, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ts := &v1.TaskSpec{ + Steps: tt.Steps, + } + ctx := config.ToContext(context.Background(), &config.Config{ + FeatureFlags: &config.FeatureFlags{ + EnableStepActions: true, + }, + }) + ts.SetDefaults(ctx) + if err := ts.Validate(ctx); err != nil { + t.Errorf("TaskSpec.Validate() = %v", err) + } + }) + } +} + func TestTaskValidateError(t *testing.T) { type fields struct { Params []v1.ParamSpec @@ -727,6 +760,63 @@ func TestTaskValidateError(t *testing.T) { } } +func TestTaskValidateErrorWithStepActionRef(t *testing.T) { + tests := []struct { + name string + Steps []v1.Step + enableStepActions bool + expectedError apis.FieldError + }{{ + name: "invalid step - invalid step action", + Steps: []v1.Step{{ + Image: "image", + Ref: &v1.Ref{ + Name: "stepAction", + }, + }}, + enableStepActions: true, + expectedError: apis.FieldError{ + Message: "image cannot be used with Ref", + Paths: []string{"spec.steps[0].image"}, + }, + }, { + name: "invalid step - enableStepActions not on", + Steps: []v1.Step{{ + Image: "image", + Ref: &v1.Ref{ + Name: "stepAction", + }, + }}, + enableStepActions: false, + expectedError: apis.FieldError{ + Message: "feature flag %s should be set to true to reference StepActions in Steps.", + Paths: []string{"spec.steps[0].enable-step-actions"}, + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + task := &v1.Task{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: v1.TaskSpec{ + Steps: tt.Steps, + }} + ctx := config.ToContext(context.Background(), &config.Config{ + FeatureFlags: &config.FeatureFlags{ + EnableStepActions: tt.enableStepActions, + }, + }) + task.SetDefaults(ctx) + err := task.Validate(ctx) + if err == nil { + t.Fatalf("Expected an error, got nothing for %v", task) + } + if d := cmp.Diff(tt.expectedError.Error(), err.Error(), cmpopts.IgnoreUnexported(apis.FieldError{})); d != "" { + t.Errorf("TaskSpec.Validate() errors diff %s", diff.PrintWantGot(d)) + } + }) + } +} + func TestTaskSpecValidateError(t *testing.T) { type fields struct { Params []v1.ParamSpec @@ -1370,6 +1460,116 @@ func TestTaskSpecValidateError(t *testing.T) { } } +func TestTaskiSpecValidateErrorWithStepActionRef(t *testing.T) { + tests := []struct { + name string + Steps []v1.Step + enableStepActions bool + expectedError apis.FieldError + }{{ + name: "invalid Task Spec - enableStepActions not on", + Steps: []v1.Step{{ + Image: "image", + Ref: &v1.Ref{ + Name: "stepAction", + }, + }}, + enableStepActions: false, + expectedError: apis.FieldError{ + Message: "feature flag %s should be set to true to reference StepActions in Steps.", + Paths: []string{"steps[0].enable-step-actions"}, + }, + }, { + name: "Cannot use image with Ref", + Steps: []v1.Step{{ + Ref: &v1.Ref{ + Name: "stepAction", + }, + Image: "foo", + }}, + enableStepActions: true, + expectedError: apis.FieldError{ + Message: "image cannot be used with Ref", + Paths: []string{"steps[0].image"}, + }, + }, { + name: "Cannot use command with Ref", + Steps: []v1.Step{{ + Ref: &v1.Ref{ + Name: "stepAction", + }, + Command: []string{"foo"}, + }}, + enableStepActions: true, + expectedError: apis.FieldError{ + Message: "command cannot be used with Ref", + Paths: []string{"steps[0].command"}, + }, + }, { + name: "Cannot use args with Ref", + Steps: []v1.Step{{ + Ref: &v1.Ref{ + Name: "stepAction", + }, + Args: []string{"foo"}, + }}, + enableStepActions: true, + expectedError: apis.FieldError{ + Message: "args cannot be used with Ref", + Paths: []string{"steps[0].args"}, + }, + }, { + name: "Cannot use script with Ref", + Steps: []v1.Step{{ + Ref: &v1.Ref{ + Name: "stepAction", + }, + Script: "echo hi", + }}, + enableStepActions: true, + expectedError: apis.FieldError{ + Message: "script cannot be used with Ref", + Paths: []string{"steps[0].script"}, + }, + }, { + name: "Cannot use env with Ref", + Steps: []v1.Step{{ + Ref: &v1.Ref{ + Name: "stepAction", + }, + Env: []corev1.EnvVar{{ + Name: "env1", + Value: "value1", + }}, + }}, + enableStepActions: true, + expectedError: apis.FieldError{ + Message: "env cannot be used with Ref", + Paths: []string{"steps[0].env"}, + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ts := v1.TaskSpec{ + Steps: tt.Steps, + } + ctx := config.ToContext(context.Background(), &config.Config{ + FeatureFlags: &config.FeatureFlags{ + EnableStepActions: tt.enableStepActions, + }, + }) + ts.SetDefaults(ctx) + err := ts.Validate(ctx) + if err == nil { + t.Fatalf("Expected an error, got nothing for %v", ts) + } + if d := cmp.Diff(tt.expectedError.Error(), err.Error(), cmpopts.IgnoreUnexported(apis.FieldError{})); d != "" { + t.Errorf("TaskSpec.Validate() errors diff %s", diff.PrintWantGot(d)) + } + }) + } +} + func TestTaskSpecValidateErrorSidecarName(t *testing.T) { tests := []struct { name string diff --git a/pkg/apis/pipeline/v1beta1/container_conversion.go b/pkg/apis/pipeline/v1beta1/container_conversion.go index 5bf1365fc7c..8e7171efa5e 100644 --- a/pkg/apis/pipeline/v1beta1/container_conversion.go +++ b/pkg/apis/pipeline/v1beta1/container_conversion.go @@ -22,6 +22,14 @@ import ( v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" ) +func (r Ref) convertTo(ctx context.Context, sink *v1.Ref) { + sink.Name = r.Name +} + +func (r *Ref) convertFrom(ctx context.Context, source v1.Ref) { + r.Name = source.Name +} + func (s Step) convertTo(ctx context.Context, sink *v1.Step) { sink.Name = s.Name sink.Image = s.Image @@ -47,6 +55,10 @@ func (s Step) convertTo(ctx context.Context, sink *v1.Step) { sink.OnError = (v1.OnErrorType)(s.OnError) sink.StdoutConfig = (*v1.StepOutputConfig)(s.StdoutConfig) sink.StderrConfig = (*v1.StepOutputConfig)(s.StderrConfig) + if s.Ref != nil { + sink.Ref = &v1.Ref{} + s.Ref.convertTo(ctx, sink.Ref) + } } func (s *Step) convertFrom(ctx context.Context, source v1.Step) { @@ -74,6 +86,11 @@ func (s *Step) convertFrom(ctx context.Context, source v1.Step) { s.OnError = (OnErrorType)(source.OnError) s.StdoutConfig = (*StepOutputConfig)(source.StdoutConfig) s.StderrConfig = (*StepOutputConfig)(source.StderrConfig) + if source.Ref != nil { + newRef := Ref{} + newRef.convertFrom(ctx, *source.Ref) + s.Ref = &newRef + } } func (s StepTemplate) convertTo(ctx context.Context, sink *v1.StepTemplate) { diff --git a/pkg/apis/pipeline/v1beta1/container_types.go b/pkg/apis/pipeline/v1beta1/container_types.go index 980ad392c8d..7b1a08b00c4 100644 --- a/pkg/apis/pipeline/v1beta1/container_types.go +++ b/pkg/apis/pipeline/v1beta1/container_types.go @@ -228,6 +228,16 @@ type Step struct { // Stores configuration for the stderr stream of the step. // +optional StderrConfig *StepOutputConfig `json:"stderrConfig,omitempty"` + + // Contains the reference to an existing stepaction + //+optional + Ref *Ref `json:"ref,omitempty"` +} + +// Ref can be used to refer to a specific instance of a StepAction. +type Ref struct { + // Name of the referenced step + Name string `json:"name,omitempty"` } // OnErrorType defines a list of supported exiting behavior of a container on error diff --git a/pkg/apis/pipeline/v1beta1/task_conversion_test.go b/pkg/apis/pipeline/v1beta1/task_conversion_test.go index 629ed816c7a..1136be3d78b 100644 --- a/pkg/apis/pipeline/v1beta1/task_conversion_test.go +++ b/pkg/apis/pipeline/v1beta1/task_conversion_test.go @@ -73,6 +73,15 @@ spec: steps: - image: foo - image: bar +` + stepActionTaskYAML := ` +metadata: + name: foo + namespace: bar +spec: + steps: + - ref: + name: "step-action" ` taskWithAllNoDeprecatedFieldsYAML := ` metadata: @@ -261,6 +270,9 @@ spec: multiStepTaskV1beta1 := parse.MustParseV1beta1Task(t, multiStepTaskYAML) multiStepTaskV1 := parse.MustParseV1Task(t, multiStepTaskYAML) + stepActionTaskV1beta1 := parse.MustParseV1beta1Task(t, stepActionTaskYAML) + stepActionTaskV1 := parse.MustParseV1Task(t, stepActionTaskYAML) + taskWithAllNoDeprecatedFieldsV1beta1 := parse.MustParseV1beta1Task(t, taskWithAllNoDeprecatedFieldsYAML) taskWithAllNoDeprecatedFieldsV1 := parse.MustParseV1Task(t, taskWithAllNoDeprecatedFieldsYAML) @@ -293,6 +305,10 @@ spec: name: "task conversion all non deprecated fields", v1beta1Task: taskWithAllNoDeprecatedFieldsV1beta1, v1Task: taskWithAllNoDeprecatedFieldsV1, + }, { + name: "step action in task", + v1beta1Task: stepActionTaskV1beta1, + v1Task: stepActionTaskV1, }, { name: "task conversion deprecated fields", v1beta1Task: taskWithDeprecatedFieldsV1beta1, diff --git a/pkg/apis/pipeline/v1beta1/task_validation.go b/pkg/apis/pipeline/v1beta1/task_validation.go index 36646a4d1d2..a4a38800fe7 100644 --- a/pkg/apis/pipeline/v1beta1/task_validation.go +++ b/pkg/apis/pipeline/v1beta1/task_validation.go @@ -270,17 +270,53 @@ func validateSteps(ctx context.Context, steps []Step) (errs *apis.FieldError) { } func validateStep(ctx context.Context, s Step, names sets.String) (errs *apis.FieldError) { - if s.Image == "" { - errs = errs.Also(apis.ErrMissingField("Image")) - } - - if s.Script != "" { + if s.Ref != nil { + if !config.FromContextOrDefaults(ctx).FeatureFlags.EnableStepActions { + return apis.ErrGeneric("feature flag %s should be set to true to reference StepActions in Steps.", config.EnableStepActions) + } + 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: "script cannot be used with command", + 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.Env != nil { + errs = errs.Also(&apis.FieldError{ + Message: "env cannot be used with Ref", + Paths: []string{"env"}, + }) + } + } else { + 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 != "" { diff --git a/pkg/apis/pipeline/v1beta1/task_validation_test.go b/pkg/apis/pipeline/v1beta1/task_validation_test.go index 484c90cb748..1de4ae713b5 100644 --- a/pkg/apis/pipeline/v1beta1/task_validation_test.go +++ b/pkg/apis/pipeline/v1beta1/task_validation_test.go @@ -24,6 +24,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/tektoncd/pipeline/pkg/apis/config" cfgtesting "github.com/tektoncd/pipeline/pkg/apis/config/testing" "github.com/tektoncd/pipeline/pkg/apis/pipeline" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" @@ -523,6 +524,38 @@ func TestTaskSpecValidate(t *testing.T) { } } +func TestTaskSpecStepActionReferenceValidate(t *testing.T) { + tests := []struct { + name string + Steps []v1beta1.Step + }{{ + name: "valid stepaction ref", + Steps: []v1beta1.Step{{ + Name: "mystep", + WorkingDir: "/foo", + Ref: &v1beta1.Ref{ + Name: "stepAction", + }, + }}, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ts := &v1beta1.TaskSpec{ + Steps: tt.Steps, + } + ctx := config.ToContext(context.Background(), &config.Config{ + FeatureFlags: &config.FeatureFlags{ + EnableStepActions: true, + }, + }) + ts.SetDefaults(ctx) + if err := ts.Validate(ctx); err != nil { + t.Errorf("TaskSpec.Validate() = %v", err) + } + }) + } +} + func TestTaskValidateError(t *testing.T) { type fields struct { Params []v1beta1.ParamSpec @@ -730,6 +763,63 @@ func TestTaskValidateError(t *testing.T) { } } +func TestTaskValidateErrorWithStepActionRef(t *testing.T) { + tests := []struct { + name string + Steps []v1beta1.Step + enableStepActions bool + expectedError apis.FieldError + }{{ + name: "invalid step - invalid step action", + Steps: []v1beta1.Step{{ + Image: "image", + Ref: &v1beta1.Ref{ + Name: "stepAction", + }, + }}, + enableStepActions: true, + expectedError: apis.FieldError{ + Message: "image cannot be used with Ref", + Paths: []string{"spec.steps[0].image"}, + }, + }, { + name: "invalid step - enableStepActions not on", + Steps: []v1beta1.Step{{ + Image: "image", + Ref: &v1beta1.Ref{ + Name: "stepAction", + }, + }}, + enableStepActions: false, + expectedError: apis.FieldError{ + Message: "feature flag %s should be set to true to reference StepActions in Steps.", + Paths: []string{"spec.steps[0].enable-step-actions"}, + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + task := &v1beta1.Task{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: v1beta1.TaskSpec{ + Steps: tt.Steps, + }} + ctx := config.ToContext(context.Background(), &config.Config{ + FeatureFlags: &config.FeatureFlags{ + EnableStepActions: tt.enableStepActions, + }, + }) + task.SetDefaults(ctx) + err := task.Validate(ctx) + if err == nil { + t.Fatalf("Expected an error, got nothing for %v", task) + } + if d := cmp.Diff(tt.expectedError.Error(), err.Error(), cmpopts.IgnoreUnexported(apis.FieldError{})); d != "" { + t.Errorf("TaskSpec.Validate() errors diff %s", diff.PrintWantGot(d)) + } + }) + } +} + func TestTaskSpecValidateError(t *testing.T) { type fields struct { Params []v1beta1.ParamSpec @@ -1382,6 +1472,116 @@ func TestTaskSpecValidateError(t *testing.T) { } } +func TestTaskSpecValidateErrorWithStepActionRef(t *testing.T) { + tests := []struct { + name string + Steps []v1beta1.Step + enableStepActions bool + expectedError apis.FieldError + }{{ + name: "invalid Task Spec - enableStepActions not on", + Steps: []v1beta1.Step{{ + Image: "image", + Ref: &v1beta1.Ref{ + Name: "stepAction", + }, + }}, + enableStepActions: false, + expectedError: apis.FieldError{ + Message: "feature flag %s should be set to true to reference StepActions in Steps.", + Paths: []string{"steps[0].enable-step-actions"}, + }, + }, { + name: "Cannot use image with Ref", + Steps: []v1beta1.Step{{ + Ref: &v1beta1.Ref{ + Name: "stepAction", + }, + Image: "foo", + }}, + enableStepActions: true, + expectedError: apis.FieldError{ + Message: "image cannot be used with Ref", + Paths: []string{"steps[0].image"}, + }, + }, { + name: "Cannot use command with Ref", + Steps: []v1beta1.Step{{ + Ref: &v1beta1.Ref{ + Name: "stepAction", + }, + Command: []string{"foo"}, + }}, + enableStepActions: true, + expectedError: apis.FieldError{ + Message: "command cannot be used with Ref", + Paths: []string{"steps[0].command"}, + }, + }, { + name: "Cannot use args with Ref", + Steps: []v1beta1.Step{{ + Ref: &v1beta1.Ref{ + Name: "stepAction", + }, + Args: []string{"foo"}, + }}, + enableStepActions: true, + expectedError: apis.FieldError{ + Message: "args cannot be used with Ref", + Paths: []string{"steps[0].args"}, + }, + }, { + name: "Cannot use script with Ref", + Steps: []v1beta1.Step{{ + Ref: &v1beta1.Ref{ + Name: "stepAction", + }, + Script: "echo hi", + }}, + enableStepActions: true, + expectedError: apis.FieldError{ + Message: "script cannot be used with Ref", + Paths: []string{"steps[0].script"}, + }, + }, { + name: "Cannot use env with Ref", + Steps: []v1beta1.Step{{ + Ref: &v1beta1.Ref{ + Name: "stepAction", + }, + Env: []corev1.EnvVar{{ + Name: "env1", + Value: "value1", + }}, + }}, + enableStepActions: true, + expectedError: apis.FieldError{ + Message: "env cannot be used with Ref", + Paths: []string{"steps[0].env"}, + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ts := v1beta1.TaskSpec{ + Steps: tt.Steps, + } + ctx := config.ToContext(context.Background(), &config.Config{ + FeatureFlags: &config.FeatureFlags{ + EnableStepActions: tt.enableStepActions, + }, + }) + ts.SetDefaults(ctx) + err := ts.Validate(ctx) + if err == nil { + t.Fatalf("Expected an error, got nothing for %v", ts) + } + if d := cmp.Diff(tt.expectedError.Error(), err.Error(), cmpopts.IgnoreUnexported(apis.FieldError{})); d != "" { + t.Errorf("TaskSpec.Validate() errors diff %s", diff.PrintWantGot(d)) + } + }) + } +} + func TestTaskSpecValidateErrorSidecarName(t *testing.T) { tests := []struct { name string diff --git a/test/parse/yaml.go b/test/parse/yaml.go index 3ee9f8349a6..8b0a30e11c7 100644 --- a/test/parse/yaml.go +++ b/test/parse/yaml.go @@ -23,6 +23,17 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +// MustParseV1alpha1StepAction takes YAML and parses it into a *v1alpha1.StepAction +func MustParseV1alpha1StepAction(t *testing.T, yaml string) *v1alpha1.StepAction { + t.Helper() + var sa v1alpha1.StepAction + yaml = `apiVersion: tekton.dev/v1alpha1 +kind: StepAction +` + yaml + mustParseYAML(t, yaml, &sa) + return &sa +} + // MustParseV1beta1TaskRun takes YAML and parses it into a *v1beta1.TaskRun func MustParseV1beta1TaskRun(t *testing.T, yaml string) *v1beta1.TaskRun { t.Helper()