From 2bd16b804785ba6f1f54387163d6d0fbf98a02ef Mon Sep 17 00:00:00 2001
From: Eric Zhang
[]github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.ParamSpec
alias)-(Appears on:PipelineSpec, TaskSpec, StepActionSpec) +(Appears on:PipelineSpec, TaskSpec, StepActionSpec, StepActionSpec)
ParamSpecs is a list of ParamSpec
@@ -4650,11 +4650,11 @@ string-(Appears on:Step, StepActionSpec, Step) +(Appears on:Step, StepActionSpec, Step, StepActionSpec)
StepResult used to describe the Results of a Step.
-This is field is at an ALPHA stability level and gated by “enable-step-actions” feature flag.
+This is field is at an BETA stability level and gated by “enable-step-actions” feature flag.
StepAction represents the actionable components of Step. +The Step can only reference it from the cluster or using remote resolution.
+Field | +Description | +||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
+apiVersion +string |
+
+
+tekton.dev/v1beta1
+
+ |
+||||||||||||||||||||||
+kind +string + |
+StepAction |
+||||||||||||||||||||||
+metadata + + +Kubernetes meta/v1.ObjectMeta + + + |
+
+(Optional)
+Refer to the Kubernetes API documentation for the fields of the
+metadata field.
+ |
+||||||||||||||||||||||
+spec + + +StepActionSpec + + + |
+
+(Optional)
+ Spec holds the desired state of the Step from the client ++ +
|
+
StepActionObject is implemented by StepAction
++(Appears on:StepAction) +
+StepActionSpec contains the actionable components of a step.
+Field | +Description | +
---|---|
+description + +string + + |
+
+(Optional)
+ Description is a user-facing description of the stepaction that may be +used to populate a UI. + |
+
+image + +string + + |
+
+(Optional)
+ Image reference name to run for this StepAction. +More info: https://kubernetes.io/docs/concepts/containers/images + |
+
+command + +[]string + + |
+
+(Optional)
+ Entrypoint array. Not executed within a shell. +The image’s ENTRYPOINT is used if this is not provided. +Variable references $(VAR_NAME) are expanded using the container’s environment. If a variable +cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced +to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. “$$(VAR_NAME)” will +produce the string literal “$(VAR_NAME)”. Escaped references will never be expanded, regardless +of whether the variable exists or not. Cannot be updated. +More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + |
+
+args + +[]string + + |
+
+(Optional)
+ Arguments to the entrypoint. +The image’s CMD is used if this is not provided. +Variable references $(VAR_NAME) are expanded using the container’s environment. If a variable +cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced +to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. “$$(VAR_NAME)” will +produce the string literal “$(VAR_NAME)”. Escaped references will never be expanded, regardless +of whether the variable exists or not. Cannot be updated. +More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + |
+
+env + + +[]Kubernetes core/v1.EnvVar + + + |
+
+(Optional)
+ List of environment variables to set in the container. +Cannot be updated. + |
+
+script + +string + + |
+
+(Optional)
+ Script is the contents of an executable file to execute. +If Script is not empty, the Step cannot have an Command and the Args will be passed to the Script. + |
+
+workingDir + +string + + |
+
+(Optional)
+ Step’s working directory. +If not specified, the container runtime’s default will be used, which +might be configured in the container image. +Cannot be updated. + |
+
+params + + +ParamSpecs + + + |
+
+(Optional)
+ Params is a list of input parameters required to run the stepAction. +Params must be supplied as inputs in Steps unless they declare a defaultvalue. + |
+
+results + + +[]StepResult + + + |
+
+(Optional)
+ Results are values that this StepAction can output + |
+
+securityContext + + +Kubernetes core/v1.SecurityContext + + + |
+
+(Optional)
+ SecurityContext defines the security options the Step should be run with. +If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. +More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ +The value set in StepAction will take precedence over the value from Task. + |
+
+volumeMounts + + +[]Kubernetes core/v1.VolumeMount + + + |
+
+(Optional)
+ Volumes to mount into the Step’s filesystem. +Cannot be updated. + |
+
diff --git a/docs/stepactions.md b/docs/stepactions.md index afaa35ce003..c7d5a334035 100644 --- a/docs/stepactions.md +++ b/docs/stepactions.md @@ -22,7 +22,7 @@ weight: 201 - [Cannot pass Step Results between Steps](#cannot-pass-step-results-between-steps) ## Overview -> :seedling: **`StepActions` is an [alpha](additional-configs.md#alpha-features) feature.** +> :seedling: **`StepActions` is an [beta](additional-configs.md#beta-features) feature.** > The `enable-step-actions` feature flag must be set to `"true"` to specify a `StepAction` in a `Step`. A `StepAction` is the reusable and scriptable unit of work that is performed by a `Step`. @@ -62,7 +62,7 @@ A `StepAction` definition supports the following fields: The example below demonstrates the use of most of the above-mentioned fields: ```yaml -apiVersion: tekton.dev/v1alpha1 +apiVersion: tekton.dev/v1beta1 kind: StepAction metadata: name: example-stepaction-name @@ -82,7 +82,7 @@ Like with `Tasks`, a `StepAction` must declare all the parameters that it uses. `Parameters` are passed to the `StepAction` from its corresponding `Step` referencing it. ```yaml -apiVersion: tekton.dev/v1alpha1 +apiVersion: tekton.dev/v1beta1 kind: StepAction metadata: name: stepaction-using-params diff --git a/examples/v1/taskruns/alpha/stepaction-git-resolver.yaml b/examples/v1/taskruns/beta/stepaction-git-resolver.yaml similarity index 100% rename from examples/v1/taskruns/alpha/stepaction-git-resolver.yaml rename to examples/v1/taskruns/beta/stepaction-git-resolver.yaml diff --git a/examples/v1/taskruns/alpha/stepaction-params.yaml b/examples/v1/taskruns/beta/stepaction-params.yaml similarity index 98% rename from examples/v1/taskruns/alpha/stepaction-params.yaml rename to examples/v1/taskruns/beta/stepaction-params.yaml index 52402bbdfe6..1ae7a70c70f 100644 --- a/examples/v1/taskruns/alpha/stepaction-params.yaml +++ b/examples/v1/taskruns/beta/stepaction-params.yaml @@ -1,4 +1,4 @@ -apiVersion: tekton.dev/v1alpha1 +apiVersion: tekton.dev/v1beta1 kind: StepAction metadata: name: step-action diff --git a/examples/v1/taskruns/alpha/stepaction-passing-results.yaml b/examples/v1/taskruns/beta/stepaction-passing-results.yaml similarity index 98% rename from examples/v1/taskruns/alpha/stepaction-passing-results.yaml rename to examples/v1/taskruns/beta/stepaction-passing-results.yaml index e02a771c746..f8b1b011465 100644 --- a/examples/v1/taskruns/alpha/stepaction-passing-results.yaml +++ b/examples/v1/taskruns/beta/stepaction-passing-results.yaml @@ -1,4 +1,4 @@ -apiVersion: tekton.dev/v1alpha1 +apiVersion: tekton.dev/v1beta1 kind: StepAction metadata: name: step-action diff --git a/examples/v1/taskruns/alpha/stepaction-results.yaml b/examples/v1/taskruns/beta/stepaction-results.yaml similarity index 94% rename from examples/v1/taskruns/alpha/stepaction-results.yaml rename to examples/v1/taskruns/beta/stepaction-results.yaml index 7ea048830a7..e922b1d7718 100644 --- a/examples/v1/taskruns/alpha/stepaction-results.yaml +++ b/examples/v1/taskruns/beta/stepaction-results.yaml @@ -1,4 +1,4 @@ -apiVersion: tekton.dev/v1alpha1 +apiVersion: tekton.dev/v1beta1 kind: StepAction metadata: name: step-action diff --git a/examples/v1/taskruns/alpha/stepaction.yaml b/examples/v1/taskruns/beta/stepaction.yaml similarity index 90% rename from examples/v1/taskruns/alpha/stepaction.yaml rename to examples/v1/taskruns/beta/stepaction.yaml index 3acc1e263f4..93feb955705 100644 --- a/examples/v1/taskruns/alpha/stepaction.yaml +++ b/examples/v1/taskruns/beta/stepaction.yaml @@ -1,4 +1,4 @@ -apiVersion: tekton.dev/v1alpha1 +apiVersion: tekton.dev/v1beta1 kind: StepAction metadata: name: step-action diff --git a/pkg/apis/config/feature_flags.go b/pkg/apis/config/feature_flags.go index 66fdeb5c7ad..326a33aebcf 100644 --- a/pkg/apis/config/feature_flags.go +++ b/pkg/apis/config/feature_flags.go @@ -151,8 +151,8 @@ var ( // DefaultEnableStepActions is the default PerFeatureFlag value for EnableStepActions DefaultEnableStepActions = PerFeatureFlag{ Name: EnableStepActions, - Stability: AlphaAPIFields, - Enabled: DefaultAlphaFeatureEnabled, + Stability: BetaAPIFields, + Enabled: DefaultBetaFeatureEnabled, } // DefaultEnableArtifacts is the default PerFeatureFlag value for EnableArtifacts diff --git a/pkg/apis/pipeline/v1/openapi_generated.go b/pkg/apis/pipeline/v1/openapi_generated.go index f9380013f7d..a5b1eff4048 100644 --- a/pkg/apis/pipeline/v1/openapi_generated.go +++ b/pkg/apis/pipeline/v1/openapi_generated.go @@ -3159,7 +3159,7 @@ func schema_pkg_apis_pipeline_v1_StepResult(ref common.ReferenceCallback) common return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "StepResult used to describe the Results of a Step.\n\nThis is field is at an ALPHA stability level and gated by \"enable-step-actions\" feature flag.", + Description: "StepResult used to describe the Results of a Step.\n\nThis is field is at an BETA stability level and gated by \"enable-step-actions\" feature flag.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "name": { diff --git a/pkg/apis/pipeline/v1/result_types.go b/pkg/apis/pipeline/v1/result_types.go index 6361d7a362d..b36bf6fc658 100644 --- a/pkg/apis/pipeline/v1/result_types.go +++ b/pkg/apis/pipeline/v1/result_types.go @@ -40,7 +40,7 @@ type TaskResult struct { // StepResult used to describe the Results of a Step. // -// This is field is at an ALPHA stability level and gated by "enable-step-actions" feature flag. +// This is field is at an BETA stability level and gated by "enable-step-actions" feature flag. type StepResult struct { // Name the given name Name string `json:"name"` diff --git a/pkg/apis/pipeline/v1/swagger.json b/pkg/apis/pipeline/v1/swagger.json index 8993af2ffff..b44c5d35645 100644 --- a/pkg/apis/pipeline/v1/swagger.json +++ b/pkg/apis/pipeline/v1/swagger.json @@ -1611,7 +1611,7 @@ } }, "v1.StepResult": { - "description": "StepResult used to describe the Results of a Step.\n\nThis is field is at an ALPHA stability level and gated by \"enable-step-actions\" feature flag.", + "description": "StepResult used to describe the Results of a Step.\n\nThis is field is at an BETA stability level and gated by \"enable-step-actions\" feature flag.", "type": "object", "required": [ "name" diff --git a/pkg/apis/pipeline/v1alpha1/stepaction_conversion.go b/pkg/apis/pipeline/v1alpha1/stepaction_conversion.go index a02bf76096a..b2896d0de8f 100644 --- a/pkg/apis/pipeline/v1alpha1/stepaction_conversion.go +++ b/pkg/apis/pipeline/v1alpha1/stepaction_conversion.go @@ -15,7 +15,9 @@ package v1alpha1 import ( "context" + "fmt" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "knative.dev/pkg/apis" ) @@ -23,20 +25,63 @@ var _ apis.Convertible = (*StepAction)(nil) // ConvertTo implements apis.Convertible func (s *StepAction) ConvertTo(ctx context.Context, to apis.Convertible) error { - return nil + if apis.IsInDelete(ctx) { + return nil + } + switch sink := to.(type) { + case *v1beta1.StepAction: + sink.ObjectMeta = s.ObjectMeta + return s.Spec.ConvertTo(ctx, &sink.Spec) + default: + return fmt.Errorf("unknown version, got: %T", sink) + } } // ConvertTo implements apis.Convertible -func (ss *StepActionSpec) ConvertTo(ctx context.Context, sink *StepActionSpec) error { +func (ss *StepActionSpec) ConvertTo(ctx context.Context, sink *v1beta1.StepActionSpec) error { + sink.Description = ss.Description + sink.Image = ss.Image + sink.Command = ss.Command + sink.Args = ss.Args + sink.Env = ss.Env + sink.Script = ss.Script + sink.WorkingDir = ss.WorkingDir + sink.Params = ss.Params + sink.Results = ss.Results + sink.SecurityContext = ss.SecurityContext + sink.VolumeMounts = ss.VolumeMounts + return nil } // ConvertFrom implements apis.Convertible func (s *StepAction) ConvertFrom(ctx context.Context, from apis.Convertible) error { - return nil + if apis.IsInDelete(ctx) { + return nil + } + switch source := from.(type) { + case *v1beta1.StepAction: + s.ObjectMeta = source.ObjectMeta + return s.Spec.ConvertFrom(ctx, &source.Spec) + default: + return fmt.Errorf("unknown version, got: %T", source) + } } // ConvertFrom implements apis.Convertible -func (ss *StepActionSpec) ConvertFrom(ctx context.Context, source *StepActionSpec) error { +func (ss *StepActionSpec) ConvertFrom(ctx context.Context, source *v1beta1.StepActionSpec) error { + ss.Description = source.Description + ss.Image = source.Image + ss.Command = source.Command + ss.Args = source.Args + ss.Env = source.Env + ss.Script = source.Script + ss.WorkingDir = source.WorkingDir + + ss.Params = source.Params + ss.Results = source.Results + ss.SecurityContext = source.SecurityContext + ss.VolumeMounts = source.VolumeMounts + return nil } diff --git a/pkg/apis/pipeline/v1alpha1/stepaction_conversion_test.go b/pkg/apis/pipeline/v1alpha1/stepaction_conversion_test.go new file mode 100644 index 00000000000..9193b6e7b32 --- /dev/null +++ b/pkg/apis/pipeline/v1alpha1/stepaction_conversion_test.go @@ -0,0 +1,113 @@ +package v1alpha1_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + "github.com/tektoncd/pipeline/test/diff" + "github.com/tektoncd/pipeline/test/parse" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestPipelineConversionBadType(t *testing.T) { + good, bad := &v1alpha1.StepAction{}, &v1beta1.Task{} + + if err := good.ConvertTo(context.Background(), bad); err == nil { + t.Errorf("ConvertTo() = %#v, wanted error", bad) + } + + if err := good.ConvertFrom(context.Background(), bad); err == nil { + t.Errorf("ConvertFrom() = %#v, wanted error", good) + } +} + +func TestStepActionConversion(t *testing.T) { + stepActionWithAllFieldsYaml := ` +metadata: + name: foo + namespace: bar +spec: + name: all-fields + image: foo + command: ["hello"] + args: ["world"] + results: + - name: result1 + - name: result2 + script: | + echo "I am a Step Action!!!" >> $(step.results.result1.path) + echo "I am a hidden step action!!!" >> $(step.results.result2.path) + workingDir: "/dir" + envFrom: + - prefix: prefix + params: + - name: string-param + type: string + default: "a string param" + - name: array-param + type: array + default: + - an + - array + - param + - name: object-param + type: object + properties: + key1: + type: string + key2: + type: string + key3: + type: string + default: + key1: "step-action default key1" + key2: "step-action default key2" + key3: "step-action default key3" + volumeMounts: + - name: data + mountPath: /data + securityContext: + privileged: true +` + + stepActionV1alpha1 := parse.MustParseV1alpha1StepAction(t, stepActionWithAllFieldsYaml) + stepActionV1beta1 := parse.MustParseV1beta1StepAction(t, stepActionWithAllFieldsYaml) + + var ignoreTypeMeta = cmpopts.IgnoreFields(metav1.TypeMeta{}, "Kind", "APIVersion") + + tests := []struct { + name string + v1AlphaStepAction *v1alpha1.StepAction + v1Beta1StepAction *v1beta1.StepAction + }{{ + name: "stepAction conversion with all fields", + v1AlphaStepAction: stepActionV1alpha1, + v1Beta1StepAction: stepActionV1beta1, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + v1Beta1StepAction := &v1beta1.StepAction{} + if err := test.v1AlphaStepAction.ConvertTo(context.Background(), v1Beta1StepAction); err != nil { + t.Errorf("ConvertTo() = %v", err) + return + } + t.Logf("ConvertTo() = %#v", v1Beta1StepAction) + if d := cmp.Diff(test.v1Beta1StepAction, v1Beta1StepAction, ignoreTypeMeta); d != "" { + t.Errorf("expected v1Task is different from what's converted: %s", d) + } + gotV1alpha1 := &v1alpha1.StepAction{} + if err := gotV1alpha1.ConvertFrom(context.Background(), v1Beta1StepAction); err != nil { + t.Errorf("ConvertFrom() = %v", err) + } + t.Logf("ConvertFrom() = %#v", gotV1alpha1) + if d := cmp.Diff(test.v1AlphaStepAction, gotV1alpha1, ignoreTypeMeta); d != "" { + t.Errorf("roundtrip %s", diff.PrintWantGot(d)) + } + }) + } +} diff --git a/pkg/apis/pipeline/v1beta1/openapi_generated.go b/pkg/apis/pipeline/v1beta1/openapi_generated.go index d938565b86d..a5199123b74 100644 --- a/pkg/apis/pipeline/v1beta1/openapi_generated.go +++ b/pkg/apis/pipeline/v1beta1/openapi_generated.go @@ -87,6 +87,9 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.SidecarState": schema_pkg_apis_pipeline_v1beta1_SidecarState(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.SkippedTask": schema_pkg_apis_pipeline_v1beta1_SkippedTask(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.Step": schema_pkg_apis_pipeline_v1beta1_Step(ref), + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.StepAction": schema_pkg_apis_pipeline_v1beta1_StepAction(ref), + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.StepActionList": schema_pkg_apis_pipeline_v1beta1_StepActionList(ref), + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.StepActionSpec": schema_pkg_apis_pipeline_v1beta1_StepActionSpec(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.StepOutputConfig": schema_pkg_apis_pipeline_v1beta1_StepOutputConfig(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.StepState": schema_pkg_apis_pipeline_v1beta1_StepState(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.StepTemplate": schema_pkg_apis_pipeline_v1beta1_StepTemplate(ref), @@ -4088,6 +4091,266 @@ func schema_pkg_apis_pipeline_v1beta1_Step(ref common.ReferenceCallback) common. } } +func schema_pkg_apis_pipeline_v1beta1_StepAction(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "StepAction represents the actionable components of Step. The Step can only reference it from the cluster or using remote resolution.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec holds the desired state of the Step from the client", + Default: map[string]interface{}{}, + Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.StepActionSpec"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.StepActionSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_pipeline_v1beta1_StepActionList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "StepActionList contains a list of StepActions", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.StepAction"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.StepAction", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_pipeline_v1beta1_StepActionSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "StepActionSpec contains the actionable components of a step.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "description": { + SchemaProps: spec.SchemaProps{ + Description: "Description is a user-facing description of the stepaction that may be used to populate a UI.", + Type: []string{"string"}, + Format: "", + }, + }, + "image": { + SchemaProps: spec.SchemaProps{ + Description: "Image reference name to run for this StepAction. More info: https://kubernetes.io/docs/concepts/containers/images", + Type: []string{"string"}, + Format: "", + }, + }, + "command": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Entrypoint array. Not executed within a shell. The image's ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "args": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Arguments to the entrypoint. The image's CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "env": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of environment variables to set in the container. Cannot be updated.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.EnvVar"), + }, + }, + }, + }, + }, + "script": { + SchemaProps: spec.SchemaProps{ + Description: "Script is the contents of an executable file to execute.\n\nIf Script is not empty, the Step cannot have an Command and the Args will be passed to the Script.", + Type: []string{"string"}, + Format: "", + }, + }, + "workingDir": { + SchemaProps: spec.SchemaProps{ + Description: "Step's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated.", + Type: []string{"string"}, + Format: "", + }, + }, + "params": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Params is a list of input parameters required to run the stepAction. Params must be supplied as inputs in Steps unless they declare a defaultvalue.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.ParamSpec"), + }, + }, + }, + }, + }, + "results": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Results are values that this StepAction can output", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.StepResult"), + }, + }, + }, + }, + }, + "securityContext": { + SchemaProps: spec.SchemaProps{ + Description: "SecurityContext defines the security options the Step should be run with. If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ The value set in StepAction will take precedence over the value from Task.", + Ref: ref("k8s.io/api/core/v1.SecurityContext"), + }, + }, + "volumeMounts": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + "x-kubernetes-patch-merge-key": "mountPath", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Volumes to mount into the Step's filesystem. Cannot be updated.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.VolumeMount"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.ParamSpec", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.StepResult", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.SecurityContext", "k8s.io/api/core/v1.VolumeMount"}, + } +} + func schema_pkg_apis_pipeline_v1beta1_StepOutputConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/apis/pipeline/v1beta1/register.go b/pkg/apis/pipeline/v1beta1/register.go index 6154ae5dad5..c33fa8a848b 100644 --- a/pkg/apis/pipeline/v1beta1/register.go +++ b/pkg/apis/pipeline/v1beta1/register.go @@ -58,6 +58,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &PipelineRunList{}, &CustomRun{}, &CustomRunList{}, + &StepAction{}, + &StepActionList{}, ) // &Condition{}, // &ConditionList{}, diff --git a/pkg/apis/pipeline/v1beta1/stepaction_conversion.go b/pkg/apis/pipeline/v1beta1/stepaction_conversion.go new file mode 100644 index 00000000000..6d8afd26f33 --- /dev/null +++ b/pkg/apis/pipeline/v1beta1/stepaction_conversion.go @@ -0,0 +1,42 @@ +/* +Copyright 2023 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 v1beta1 + +import ( + "context" + + "knative.dev/pkg/apis" +) + +var _ apis.Convertible = (*StepAction)(nil) + +// ConvertTo implements apis.Convertible +func (s *StepAction) ConvertTo(ctx context.Context, to apis.Convertible) error { + return nil +} + +// ConvertTo implements apis.Convertible +func (ss *StepActionSpec) ConvertTo(ctx context.Context, sink *StepActionSpec) error { + return nil +} + +// ConvertFrom implements apis.Convertible +func (s *StepAction) ConvertFrom(ctx context.Context, from apis.Convertible) error { + return nil +} + +// ConvertFrom implements apis.Convertible +func (ss *StepActionSpec) ConvertFrom(ctx context.Context, source *StepActionSpec) error { + return nil +} diff --git a/pkg/apis/pipeline/v1beta1/stepaction_defaults.go b/pkg/apis/pipeline/v1beta1/stepaction_defaults.go new file mode 100644 index 00000000000..0274a66bc2e --- /dev/null +++ b/pkg/apis/pipeline/v1beta1/stepaction_defaults.go @@ -0,0 +1,37 @@ +/* +Copyright 2023 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 v1beta1 + +import ( + "context" + + "knative.dev/pkg/apis" +) + +var _ apis.Defaultable = (*StepAction)(nil) + +// SetDefaults implements apis.Defaultable +func (s *StepAction) SetDefaults(ctx context.Context) { + s.Spec.SetDefaults(ctx) +} + +// SetDefaults set any defaults for the StepAction spec +func (ss *StepActionSpec) SetDefaults(ctx context.Context) { + for i := range ss.Params { + ss.Params[i].SetDefaults(ctx) + } + for i := range ss.Results { + ss.Results[i].SetDefaults(ctx) + } +} diff --git a/pkg/apis/pipeline/v1beta1/stepaction_types.go b/pkg/apis/pipeline/v1beta1/stepaction_types.go new file mode 100644 index 00000000000..495c9ee010d --- /dev/null +++ b/pkg/apis/pipeline/v1beta1/stepaction_types.go @@ -0,0 +1,170 @@ +/* +Copyright 2023 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 v1beta1 + +import ( + v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/pkg/apis" + "knative.dev/pkg/kmeta" +) + +// +genclient +// +genclient:noStatus +// +genreconciler:krshapedlogic=false +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// StepAction represents the actionable components of Step. +// The Step can only reference it from the cluster or using remote resolution. +// +// +k8s:openapi-gen=true +type StepAction struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata"` + + // Spec holds the desired state of the Step from the client + // +optional + Spec StepActionSpec `json:"spec"` +} + +var _ kmeta.OwnerRefable = (*StepAction)(nil) + +// StepAction returns the step action's spec +func (s *StepAction) StepActionSpec() StepActionSpec { + return s.Spec +} + +// StepActionMetadata returns the step action's ObjectMeta +func (s *StepAction) StepActionMetadata() metav1.ObjectMeta { + return s.ObjectMeta +} + +// Copy returns a deep copy of the stepaction +func (s *StepAction) Copy() StepActionObject { + return s.DeepCopy() +} + +// GetGroupVersionKind implements kmeta.OwnerRefable. +func (*StepAction) GetGroupVersionKind() schema.GroupVersionKind { + return SchemeGroupVersion.WithKind("StepAction") +} + +// StepActionList contains a list of StepActions +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type StepActionList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + Items []StepAction `json:"items"` +} + +// StepActionSpec contains the actionable components of a step. +type StepActionSpec struct { + // Description is a user-facing description of the stepaction that may be + // used to populate a UI. + // +optional + Description string `json:"description,omitempty"` + // Image reference name to run for this StepAction. + // More info: https://kubernetes.io/docs/concepts/containers/images + // +optional + Image string `json:"image,omitempty" protobuf:"bytes,2,opt,name=image"` + // Entrypoint array. Not executed within a shell. + // The image's ENTRYPOINT is used if this is not provided. + // Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + // cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + // to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + // produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + // of whether the variable exists or not. Cannot be updated. + // More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + // +optional + // +listType=atomic + Command []string `json:"command,omitempty" protobuf:"bytes,3,rep,name=command"` + // Arguments to the entrypoint. + // The image's CMD is used if this is not provided. + // Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + // cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + // to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + // produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + // of whether the variable exists or not. Cannot be updated. + // More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + // +optional + // +listType=atomic + Args []string `json:"args,omitempty" protobuf:"bytes,4,rep,name=args"` + // List of environment variables to set in the container. + // Cannot be updated. + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=atomic + Env []corev1.EnvVar `json:"env,omitempty" patchMergeKey:"name" patchStrategy:"merge" protobuf:"bytes,7,rep,name=env"` + // Script is the contents of an executable file to execute. + // + // If Script is not empty, the Step cannot have an Command and the Args will be passed to the Script. + // +optional + Script string `json:"script,omitempty"` + // Step's working directory. + // If not specified, the container runtime's default will be used, which + // might be configured in the container image. + // Cannot be updated. + // +optional + WorkingDir string `json:"workingDir,omitempty" protobuf:"bytes,5,opt,name=workingDir"` + // Params is a list of input parameters required to run the stepAction. + // Params must be supplied as inputs in Steps unless they declare a defaultvalue. + // +optional + // +listType=atomic + Params v1.ParamSpecs `json:"params,omitempty"` + // Results are values that this StepAction can output + // +optional + // +listType=atomic + Results []v1.StepResult `json:"results,omitempty"` + // SecurityContext defines the security options the Step should be run with. + // If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. + // More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + // The value set in StepAction will take precedence over the value from Task. + // +optional + SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty" protobuf:"bytes,15,opt,name=securityContext"` + // Volumes to mount into the Step's filesystem. + // Cannot be updated. + // +optional + // +patchMergeKey=mountPath + // +patchStrategy=merge + // +listType=atomic + VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty" patchMergeKey:"mountPath" patchStrategy:"merge" protobuf:"bytes,9,rep,name=volumeMounts"` +} + +// ToStep converts the StepActionSpec to a Step struct +func (ss *StepActionSpec) ToStep() *v1.Step { + return &v1.Step{ + Image: ss.Image, + Command: ss.Command, + Args: ss.Args, + WorkingDir: ss.WorkingDir, + Script: ss.Script, + Env: ss.Env, + VolumeMounts: ss.VolumeMounts, + SecurityContext: ss.SecurityContext, + Results: ss.Results, + } +} + +// StepActionObject is implemented by StepAction +type StepActionObject interface { + apis.Defaultable + StepActionMetadata() metav1.ObjectMeta + StepActionSpec() StepActionSpec + Copy() StepActionObject +} diff --git a/pkg/apis/pipeline/v1beta1/stepaction_validation.go b/pkg/apis/pipeline/v1beta1/stepaction_validation.go new file mode 100644 index 00000000000..0955c7e4f70 --- /dev/null +++ b/pkg/apis/pipeline/v1beta1/stepaction_validation.go @@ -0,0 +1,209 @@ +/* +Copyright 2023 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 v1beta1 + +import ( + "context" + "strings" + + "github.com/tektoncd/pipeline/pkg/apis/config" + v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + "github.com/tektoncd/pipeline/pkg/apis/validate" + "github.com/tektoncd/pipeline/pkg/substitution" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/sets" + "knative.dev/pkg/apis" + "knative.dev/pkg/webhook/resourcesemantics" +) + +var ( + _ apis.Validatable = (*StepAction)(nil) + _ resourcesemantics.VerbLimited = (*StepAction)(nil) +) + +// SupportedVerbs returns the operations that validation should be called for +func (s *StepAction) SupportedVerbs() []admissionregistrationv1.OperationType { + return []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update} +} + +// Validate implements apis.Validatable +func (s *StepAction) Validate(ctx context.Context) (errs *apis.FieldError) { + errs = validate.ObjectMetadata(s.GetObjectMeta()).ViaField("metadata") + errs = errs.Also(s.Spec.Validate(apis.WithinSpec(ctx)).ViaField("spec")) + return errs +} + +// Validate implements apis.Validatable +func (ss *StepActionSpec) Validate(ctx context.Context) (errs *apis.FieldError) { + if ss.Image == "" { + errs = errs.Also(apis.ErrMissingField("Image")) + } + + if ss.Script != "" { + if len(ss.Command) > 0 { + errs = errs.Also(&apis.FieldError{ + Message: "script cannot be used with command", + Paths: []string{"script"}, + }) + } + + cleaned := strings.TrimSpace(ss.Script) + if strings.HasPrefix(cleaned, "#!win") { + errs = errs.Also(config.ValidateEnabledAPIFields(ctx, "windows script support", config.AlphaAPIFields).ViaField("script")) + } + errs = errs.Also(validateNoParamSubstitutionsInScript(ss.Script)) + } + errs = errs.Also(validateUsageOfDeclaredParameters(ctx, *ss)) + errs = errs.Also(v1.ValidateParameterTypes(ctx, ss.Params).ViaField("params")) + errs = errs.Also(validateParameterVariables(ctx, *ss, ss.Params)) + errs = errs.Also(v1.ValidateStepResultsVariables(ctx, ss.Results, ss.Script)) + errs = errs.Also(v1.ValidateStepResults(ctx, ss.Results).ViaField("results")) + errs = errs.Also(validateVolumeMounts(ss.VolumeMounts, ss.Params).ViaField("volumeMounts")) + return errs +} + +// validateNoParamSubstitutionsInScript validates that param substitutions are not invoked in the script +func validateNoParamSubstitutionsInScript(script string) *apis.FieldError { + _, present, errString := substitution.ExtractVariablesFromString(script, "params") + if errString != "" || present { + return &apis.FieldError{ + Message: "param substitution in scripts is not allowed.", + Paths: []string{"script"}, + } + } + return nil +} + +// validateUsageOfDeclaredParameters validates that all parameters referenced in the Task are declared by the Task. +func validateUsageOfDeclaredParameters(ctx context.Context, sas StepActionSpec) *apis.FieldError { + params := sas.Params + var errs *apis.FieldError + _, _, objectParams := params.SortByType() + allParameterNames := sets.NewString(params.GetNames()...) + errs = errs.Also(validateStepActionVariables(ctx, sas, "params", allParameterNames)) + errs = errs.Also(ValidateObjectUsage(ctx, sas, objectParams)) + errs = errs.Also(v1.ValidateObjectParamsHaveProperties(ctx, params)) + return errs +} + +func validateVolumeMounts(volumeMounts []corev1.VolumeMount, params v1.ParamSpecs) (errs *apis.FieldError) { + if len(volumeMounts) == 0 { + return + } + paramNames := sets.String{} + for _, p := range params { + paramNames.Insert(p.Name) + } + for idx, v := range volumeMounts { + matches, _ := substitution.ExtractVariableExpressions(v.Name, "params") + if len(matches) != 1 { + errs = errs.Also(apis.ErrInvalidValue(v.Name, "name", "expect the Name to be a single param reference").ViaIndex(idx)) + return errs + } else if matches[0] != v.Name { + errs = errs.Also(apis.ErrInvalidValue(v.Name, "name", "expect the Name to be a single param reference").ViaIndex(idx)) + return errs + } + errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(v.Name, "params", paramNames).ViaIndex(idx)) + } + return errs +} + +// validateParameterVariables validates all variables within a slice of ParamSpecs against a StepAction +func validateParameterVariables(ctx context.Context, sas StepActionSpec, params v1.ParamSpecs) *apis.FieldError { + var errs *apis.FieldError + errs = errs.Also(params.ValidateNoDuplicateNames()) + stringParams, arrayParams, objectParams := params.SortByType() + stringParameterNames := sets.NewString(stringParams.GetNames()...) + arrayParameterNames := sets.NewString(arrayParams.GetNames()...) + errs = errs.Also(v1.ValidateNameFormat(stringParameterNames.Insert(arrayParameterNames.List()...), objectParams)) + return errs.Also(validateStepActionArrayUsage(sas, "params", arrayParameterNames)) +} + +// ValidateObjectUsage validates the usage of individual attributes of an object param and the usage of the entire object +func ValidateObjectUsage(ctx context.Context, sas StepActionSpec, params v1.ParamSpecs) (errs *apis.FieldError) { + objectParameterNames := sets.NewString() + for _, p := range params { + // collect all names of object type params + objectParameterNames.Insert(p.Name) + + // collect all keys for this object param + objectKeys := sets.NewString() + for key := range p.Properties { + objectKeys.Insert(key) + } + + // check if the object's key names are referenced correctly i.e. param.objectParam.key1 + errs = errs.Also(validateStepActionVariables(ctx, sas, "params\\."+p.Name, objectKeys)) + } + + return errs.Also(validateStepActionObjectUsageAsWhole(sas, "params", objectParameterNames)) +} + +// validateStepActionObjectUsageAsWhole returns an error if the StepAction contains references to the entire input object params in fields where these references are prohibited +func validateStepActionObjectUsageAsWhole(sas StepActionSpec, prefix string, vars sets.String) *apis.FieldError { + errs := substitution.ValidateNoReferencesToEntireProhibitedVariables(sas.Image, prefix, vars).ViaField("image") + errs = errs.Also(substitution.ValidateNoReferencesToEntireProhibitedVariables(sas.Script, prefix, vars).ViaField("script")) + for i, cmd := range sas.Command { + errs = errs.Also(substitution.ValidateNoReferencesToEntireProhibitedVariables(cmd, prefix, vars).ViaFieldIndex("command", i)) + } + for i, arg := range sas.Args { + errs = errs.Also(substitution.ValidateNoReferencesToEntireProhibitedVariables(arg, prefix, vars).ViaFieldIndex("args", i)) + } + for _, env := range sas.Env { + errs = errs.Also(substitution.ValidateNoReferencesToEntireProhibitedVariables(env.Value, prefix, vars).ViaFieldKey("env", env.Name)) + } + for i, vm := range sas.VolumeMounts { + errs = errs.Also(substitution.ValidateNoReferencesToEntireProhibitedVariables(vm.Name, prefix, vars).ViaFieldIndex("volumeMounts", i)) + } + return errs +} + +// validateStepActionArrayUsage returns an error if the Step contains references to the input array params in fields where these references are prohibited +func validateStepActionArrayUsage(sas StepActionSpec, prefix string, arrayParamNames sets.String) *apis.FieldError { + errs := substitution.ValidateNoReferencesToProhibitedVariables(sas.Image, prefix, arrayParamNames).ViaField("image") + errs = errs.Also(substitution.ValidateNoReferencesToProhibitedVariables(sas.Script, prefix, arrayParamNames).ViaField("script")) + for i, cmd := range sas.Command { + errs = errs.Also(substitution.ValidateVariableReferenceIsIsolated(cmd, prefix, arrayParamNames).ViaFieldIndex("command", i)) + } + for i, arg := range sas.Args { + errs = errs.Also(substitution.ValidateVariableReferenceIsIsolated(arg, prefix, arrayParamNames).ViaFieldIndex("args", i)) + } + for _, env := range sas.Env { + errs = errs.Also(substitution.ValidateNoReferencesToProhibitedVariables(env.Value, prefix, arrayParamNames).ViaFieldKey("env", env.Name)) + } + for i, vm := range sas.VolumeMounts { + errs = errs.Also(substitution.ValidateNoReferencesToProhibitedVariables(vm.Name, prefix, arrayParamNames).ViaFieldIndex("volumeMounts", i)) + } + return errs +} + +// validateStepActionVariables returns an error if the StepAction contains references to any unknown variables +func validateStepActionVariables(ctx context.Context, sas StepActionSpec, prefix string, vars sets.String) *apis.FieldError { + errs := substitution.ValidateNoReferencesToUnknownVariables(sas.Image, prefix, vars).ViaField("image") + errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(sas.Script, prefix, vars).ViaField("script")) + for i, cmd := range sas.Command { + errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(cmd, prefix, vars).ViaFieldIndex("command", i)) + } + for i, arg := range sas.Args { + errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(arg, prefix, vars).ViaFieldIndex("args", i)) + } + for _, env := range sas.Env { + errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(env.Value, prefix, vars).ViaFieldKey("env", env.Name)) + } + for i, vm := range sas.VolumeMounts { + errs = errs.Also(substitution.ValidateNoReferencesToUnknownVariables(vm.Name, prefix, vars).ViaFieldIndex("volumeMounts", i)) + } + return errs +} diff --git a/pkg/apis/pipeline/v1beta1/stepaction_validation_test.go b/pkg/apis/pipeline/v1beta1/stepaction_validation_test.go new file mode 100644 index 00000000000..49efacdf91a --- /dev/null +++ b/pkg/apis/pipeline/v1beta1/stepaction_validation_test.go @@ -0,0 +1,975 @@ +/* +Copyright 2023 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 v1beta1_test + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + "github.com/tektoncd/pipeline/test/diff" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" +) + +func TestStepActionValidate(t *testing.T) { + tests := []struct { + name string + sa *v1beta1.StepAction + wc func(context.Context) context.Context + }{{ + name: "valid step action", + sa: &v1beta1.StepAction{ + ObjectMeta: metav1.ObjectMeta{Name: "stepaction"}, + Spec: v1beta1.StepActionSpec{ + Image: "my-image", + Script: ` + #!/usr/bin/env bash + echo hello`, + }, + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + if tt.wc != nil { + ctx = tt.wc(ctx) + } + err := tt.sa.Validate(ctx) + if err != nil { + t.Errorf("StepAction.Validate() returned error for valid StepAction: %v", err) + } + }) + } +} + +func TestStepActionSpecValidate(t *testing.T) { + type fields struct { + Image string + Command []string + Args []string + Script string + Env []corev1.EnvVar + Params []v1.ParamSpec + Results []v1.StepResult + VolumeMounts []corev1.VolumeMount + } + tests := []struct { + name string + fields fields + }{{ + name: "step action with command", + fields: fields{ + Image: "myimage", + Command: []string{"ls"}, + Args: []string{"-lh"}, + }, + }, { + name: "step action with script", + fields: fields{ + Image: "myimage", + Script: "echo hi", + }, + }, { + name: "step action with env", + fields: fields{ + Image: "myimage", + Script: "echo hi", + Env: []corev1.EnvVar{{ + Name: "HOME", + Value: "/tekton/home", + }}, + }, + }, { + name: "valid params type explicit", + fields: fields{ + Image: "myimage", + Params: []v1.ParamSpec{{ + Name: "stringParam", + Type: v1.ParamTypeString, + Description: "param", + Default: v1.NewStructuredValues("default"), + }, { + Name: "objectParam", + Type: v1.ParamTypeObject, + Description: "param", + Properties: map[string]v1.PropertySpec{ + "key1": {}, + "key2": {}, + }, + Default: v1.NewObject(map[string]string{ + "key1": "var1", + "key2": "var2", + }), + }, { + Name: "objectParamWithoutDefault", + Type: v1.ParamTypeObject, + Description: "param", + Properties: map[string]v1.PropertySpec{ + "key1": {}, + "key2": {}, + }, + }, { + Name: "objectParamWithDefaultPartialKeys", + Type: v1.ParamTypeObject, + Description: "param", + Properties: map[string]v1.PropertySpec{ + "key1": {}, + "key2": {}, + }, + Default: v1.NewObject(map[string]string{ + "key1": "default", + }), + }}, + }, + }, { + name: "valid string param usage", + fields: fields{ + Image: "url", + Params: []v1.ParamSpec{{ + Name: "baz", + }, { + Name: "foo-is-baz", + }}, + Args: []string{"--flag=$(params.baz) && $(params.foo-is-baz)"}, + }, + }, { + name: "valid array param usage", + fields: fields{ + Image: "url", + Params: []v1.ParamSpec{{ + Name: "baz", + Type: v1.ParamTypeArray, + }, { + Name: "foo-is-baz", + Type: v1.ParamTypeArray, + }}, + Command: []string{"$(params.foo-is-baz)"}, + Args: []string{"$(params.baz)", "middle string", "$(params.foo-is-baz)"}, + }, + }, { + name: "valid object param usage", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "gitrepo", + Type: v1.ParamTypeObject, + Properties: map[string]v1.PropertySpec{ + "url": {}, + "commit": {}, + }, + }}, + Image: "some-git-image", + Args: []string{"-url=$(params.gitrepo.url)", "-commit=$(params.gitrepo.commit)"}, + }, + }, { + name: "valid star array usage", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "baz", + Type: v1.ParamTypeArray, + }, { + Name: "foo-is-baz", + Type: v1.ParamTypeArray, + }}, + Image: "myimage", + Command: []string{"$(params.foo-is-baz)"}, + Args: []string{"$(params.baz[*])", "middle string", "$(params.foo-is-baz[*])"}, + }, + }, { + name: "valid result", + fields: fields{ + Image: "my-image", + Args: []string{"arg"}, + Results: []v1.StepResult{{ + Name: "MY-RESULT", + Description: "my great result", + }}, + }, + }, { + name: "valid result type string", + fields: fields{ + Image: "my-image", + Args: []string{"arg"}, + Results: []v1.StepResult{{ + Name: "MY-RESULT", + Type: "string", + Description: "my great result", + }}, + }, + }, { + name: "valid result type array", + fields: fields{ + Image: "my-image", + Args: []string{"arg"}, + Results: []v1.StepResult{{ + Name: "MY-RESULT", + Type: v1.ResultsTypeArray, + Description: "my great result", + }}, + }, + }, { + name: "valid result type object", + fields: fields{ + Image: "my-image", + Args: []string{"arg"}, + Results: []v1.StepResult{{ + Name: "MY-RESULT", + Type: v1.ResultsTypeObject, + Description: "my great result", + Properties: map[string]v1.PropertySpec{ + "url": {Type: "string"}, + "commit": {Type: "string"}, + }, + }}, + }, + }, { + name: "valid volumeMounts", + fields: fields{ + Image: "my-image", + Args: []string{"arg"}, + Params: []v1.ParamSpec{{ + Name: "foo", + }, { + Name: "array-params", + Type: v1.ParamTypeArray, + }, { + Name: "object-params", + Type: v1.ParamTypeObject, + Properties: map[string]v1.PropertySpec{ + "key": {Type: "string"}, + }, + }, + }, + VolumeMounts: []corev1.VolumeMount{{ + Name: "$(params.foo)", + MountPath: "/config", + }, { + Name: "$(params.array-params[0])", + MountPath: "/config", + }, { + Name: "$(params.object-params.key)", + MountPath: "/config", + }}, + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sa := &v1beta1.StepActionSpec{ + Image: tt.fields.Image, + Command: tt.fields.Command, + Args: tt.fields.Args, + Script: tt.fields.Script, + Env: tt.fields.Env, + Params: tt.fields.Params, + Results: tt.fields.Results, + VolumeMounts: tt.fields.VolumeMounts, + } + ctx := context.Background() + sa.SetDefaults(ctx) + if err := sa.Validate(ctx); err != nil { + t.Errorf("StepActionSpec.Validate() = %v", err) + } + }) + } +} + +func TestStepActionValidateError(t *testing.T) { + type fields struct { + Image string + Command []string + Args []string + Script string + Env []corev1.EnvVar + Params []v1.ParamSpec + Results []v1.StepResult + VolumeMounts []corev1.VolumeMount + } + tests := []struct { + name string + fields fields + expectedError apis.FieldError + }{{ + name: "inexistent image field", + fields: fields{ + Args: []string{"flag"}, + }, + expectedError: apis.FieldError{ + Message: `missing field(s)`, + Paths: []string{"spec.Image"}, + }, + }, { + name: "object used in a string field", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "gitrepo", + Type: v1.ParamTypeObject, + Properties: map[string]v1.PropertySpec{ + "url": {}, + "commit": {}, + }, + }}, + Image: "$(params.gitrepo)", + Args: []string{"echo"}, + }, + expectedError: apis.FieldError{ + Message: `variable type invalid in "$(params.gitrepo)"`, + Paths: []string{"spec.image"}, + }, + }, { + name: "object star used in a string field", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "gitrepo", + Type: v1.ParamTypeObject, + Properties: map[string]v1.PropertySpec{ + "url": {}, + "commit": {}, + }, + }}, + Image: "$(params.gitrepo[*])", + Args: []string{"echo"}, + }, + expectedError: apis.FieldError{ + Message: `variable type invalid in "$(params.gitrepo[*])"`, + Paths: []string{"spec.image"}, + }, + }, { + name: "object used in a field that can accept array type", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "gitrepo", + Type: v1.ParamTypeObject, + Properties: map[string]v1.PropertySpec{ + "url": {}, + "commit": {}, + }, + }}, + Image: "myimage", + Args: []string{"$(params.gitrepo)"}, + }, + expectedError: apis.FieldError{ + Message: `variable type invalid in "$(params.gitrepo)"`, + Paths: []string{"spec.args[0]"}, + }, + }, { + name: "object star used in a field that can accept array type", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "gitrepo", + Type: v1.ParamTypeObject, + Properties: map[string]v1.PropertySpec{ + "url": {}, + "commit": {}, + }, + }}, + Image: "some-git-image", + Args: []string{"$(params.gitrepo[*])"}, + }, + expectedError: apis.FieldError{ + Message: `variable type invalid in "$(params.gitrepo[*])"`, + Paths: []string{"spec.args[0]"}, + }, + }, { + name: "non-existent individual key of an object param is used in task step", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "gitrepo", + Type: v1.ParamTypeObject, + Properties: map[string]v1.PropertySpec{ + "url": {}, + "commit": {}, + }, + }}, + Image: "some-git-image", + Args: []string{"$(params.gitrepo.non-exist-key)"}, + }, + expectedError: apis.FieldError{ + Message: `non-existent variable in "$(params.gitrepo.non-exist-key)"`, + Paths: []string{"spec.args[0]"}, + }, + }, { + name: "Inexistent param variable with existing", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "foo", + Description: "param", + Default: v1.NewStructuredValues("default"), + }}, + Image: "myimage", + Args: []string{"$(params.foo) && $(params.inexistent)"}, + }, + expectedError: apis.FieldError{ + Message: `non-existent variable in "$(params.foo) && $(params.inexistent)"`, + Paths: []string{"spec.args[0]"}, + }, + }, { + name: "invalid param reference in volumeMount.Name - not a param reference", + fields: fields{ + Image: "myimage", + Params: []v1.ParamSpec{{ + Name: "foo", + }}, + VolumeMounts: []corev1.VolumeMount{{ + Name: "params.foo", + MountPath: "/path", + }}, + }, + expectedError: apis.FieldError{ + Message: `invalid value: params.foo`, + Paths: []string{"spec.volumeMounts[0].name"}, + Details: `expect the Name to be a single param reference`, + }, + }, { + name: "invalid param reference in volumeMount.Name - nested reference", + fields: fields{ + Image: "myimage", + Params: []v1.ParamSpec{{ + Name: "foo", + }}, + VolumeMounts: []corev1.VolumeMount{{ + Name: "$(params.foo)-foo", + MountPath: "/path", + }}, + }, + expectedError: apis.FieldError{ + Message: `invalid value: $(params.foo)-foo`, + Paths: []string{"spec.volumeMounts[0].name"}, + Details: `expect the Name to be a single param reference`, + }, + }, { + name: "invalid param reference in volumeMount.Name - multiple params references", + fields: fields{ + Image: "myimage", + Params: []v1.ParamSpec{{ + Name: "foo", + }, { + Name: "bar", + }}, + VolumeMounts: []corev1.VolumeMount{{ + Name: "$(params.foo)$(params.bar)", + MountPath: "/path", + }}, + }, + expectedError: apis.FieldError{ + Message: `invalid value: $(params.foo)$(params.bar)`, + Paths: []string{"spec.volumeMounts[0].name"}, + Details: `expect the Name to be a single param reference`, + }, + }, { + name: "invalid param reference in volumeMount.Name - not defined in params", + fields: fields{ + Image: "myimage", + VolumeMounts: []corev1.VolumeMount{{ + Name: "$(params.foo)", + MountPath: "/path", + }}, + }, + expectedError: apis.FieldError{ + Message: `non-existent variable in "$(params.foo)"`, + Paths: []string{"spec.volumeMounts[0]"}, + }, + }, { + name: "invalid param reference in volumeMount.Name - array used in a volumeMounts name field", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "gitrepo", + Type: v1.ParamTypeArray, + }}, + Image: "image", + VolumeMounts: []corev1.VolumeMount{{ + Name: "$(params.gitrepo)", + }}, + }, + expectedError: apis.FieldError{ + Message: `variable type invalid in "$(params.gitrepo)"`, + Paths: []string{"spec.volumeMounts[0]"}, + }, + }, { + name: "invalid param reference in volumeMount.Name - object used in a volumeMounts name field", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "gitrepo", + Type: v1.ParamTypeObject, + Properties: map[string]v1.PropertySpec{ + "url": {}, + "commit": {}, + }, + }}, + Image: "image", + VolumeMounts: []corev1.VolumeMount{{ + Name: "$(params.gitrepo)", + }}, + }, + expectedError: apis.FieldError{ + Message: `variable type invalid in "$(params.gitrepo)"`, + Paths: []string{"spec.volumeMounts[0]"}, + }, + }, { + name: "invalid param reference in volumeMount.Name - object key not existent in params", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "gitrepo", + Type: v1.ParamTypeObject, + Properties: map[string]v1.PropertySpec{ + "url": {}, + "commit": {}, + }, + }}, + Image: "image", + VolumeMounts: []corev1.VolumeMount{{ + Name: "$(params.gitrepo.foo)", + }}, + }, + expectedError: apis.FieldError{ + Message: `non-existent variable in "$(params.gitrepo.foo)"`, + Paths: []string{"spec.volumeMounts[0]"}, + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sa := &v1beta1.StepAction{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Spec: v1beta1.StepActionSpec{ + Image: tt.fields.Image, + Command: tt.fields.Command, + Args: tt.fields.Args, + Script: tt.fields.Script, + Env: tt.fields.Env, + Params: tt.fields.Params, + Results: tt.fields.Results, + VolumeMounts: tt.fields.VolumeMounts, + }, + } + ctx := context.Background() + sa.SetDefaults(ctx) + err := sa.Validate(ctx) + if err == nil { + t.Fatalf("Expected an error, got nothing for %v", sa) + } + if d := cmp.Diff(tt.expectedError.Error(), err.Error(), cmpopts.IgnoreUnexported(apis.FieldError{})); d != "" { + t.Errorf("StepActionSpec.Validate() errors diff %s", diff.PrintWantGot(d)) + } + }) + } +} + +func TestStepActionSpecValidateError(t *testing.T) { + type fields struct { + Image string + Command []string + Args []string + Script string + Env []corev1.EnvVar + Params []v1.ParamSpec + Results []v1.StepResult + } + tests := []struct { + name string + fields fields + expectedError apis.FieldError + }{{ + name: "inexistent image field", + fields: fields{ + Args: []string{"flag"}, + }, + expectedError: apis.FieldError{ + Message: `missing field(s)`, + Paths: []string{"Image"}, + }, + }, { + name: "command and script both used.", + fields: fields{ + Image: "my-image", + Command: []string{"ls"}, + Script: "echo hi", + }, + expectedError: apis.FieldError{ + Message: `script cannot be used with command`, + Paths: []string{"script"}, + }, + }, { + name: "windows script without alpha.", + fields: fields{ + Image: "my-image", + Script: "#!win", + }, + expectedError: apis.FieldError{ + Message: `windows script support requires "enable-api-fields" feature gate to be "alpha" but it is "beta"`, + Paths: []string{}, + }, + }, { + name: "step script refers to nonexistent result", + fields: fields{ + Image: "my-image", + Script: ` + #!/usr/bin/env bash + date | tee $(results.non-exist.path)`, + Results: []v1.StepResult{{Name: "a-result"}}, + }, + expectedError: apis.FieldError{ + Message: `non-existent variable in "\n\t\t\t#!/usr/bin/env bash\n\t\t\tdate | tee $(results.non-exist.path)"`, + Paths: []string{"script"}, + }, + }, { + name: "step script refers to nonexistent stepresult", + fields: fields{ + Image: "my-image", + Script: ` + #!/usr/bin/env bash + date | tee $(step.results.non-exist.path)`, + Results: []v1.StepResult{{Name: "a-result"}}, + }, + expectedError: apis.FieldError{ + Message: `non-existent variable in "\n\t\t\t#!/usr/bin/env bash\n\t\t\tdate | tee $(step.results.non-exist.path)"`, + Paths: []string{"script"}, + }, + }, { + name: "invalid param name format", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "_validparam1", + Description: "valid param name format", + }, { + Name: "valid_param2", + Description: "valid param name format", + }, { + Name: "", + Description: "invalid param name format", + }, { + Name: "a^b", + Description: "invalid param name format", + }, { + Name: "0ab", + Description: "invalid param name format", + }, { + Name: "f oo", + Description: "invalid param name format", + }}, + Image: "myImage", + }, + expectedError: apis.FieldError{ + Message: fmt.Sprintf("The format of following array and string variable names is invalid: %s", []string{"", "0ab", "a^b", "f oo"}), + Paths: []string{"params"}, + Details: "String/Array Names: \nMust only contain alphanumeric characters, hyphens (-), underscores (_), and dots (.)\nMust begin with a letter or an underscore (_)", + }, + }, { + name: "invalid object param format - object param name and key name shouldn't contain dots.", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "invalid.name1", + Description: "object param name contains dots", + Properties: map[string]v1.PropertySpec{ + "invalid.key1": {}, + "mykey2": {}, + }, + }}, + Image: "myImage", + }, + expectedError: apis.FieldError{ + Message: fmt.Sprintf("Object param name and key name format is invalid: %v", map[string][]string{ + "invalid.name1": {"invalid.key1"}, + }), + Paths: []string{"params"}, + Details: "Object Names: \nMust only contain alphanumeric characters, hyphens (-), underscores (_) \nMust begin with a letter or an underscore (_)", + }, + }, { + name: "duplicated param names", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "foo", + Type: v1.ParamTypeString, + Description: "parameter", + Default: v1.NewStructuredValues("value1"), + }, { + Name: "foo", + Type: v1.ParamTypeString, + Description: "parameter", + Default: v1.NewStructuredValues("value2"), + }}, + Image: "myImage", + }, + expectedError: apis.FieldError{ + Message: `parameter appears more than once`, + Paths: []string{"params[foo]"}, + }, + }, { + name: "invalid param type", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "validparam", + Type: v1.ParamTypeString, + Description: "parameter", + Default: v1.NewStructuredValues("default"), + }, { + Name: "param-with-invalid-type", + Type: "invalidtype", + Description: "invalidtypedesc", + Default: v1.NewStructuredValues("default"), + }}, + Image: "myImage", + }, + expectedError: apis.FieldError{ + Message: `invalid value: invalidtype`, + Paths: []string{"params.param-with-invalid-type.type"}, + }, + }, { + name: "param mismatching default/type 1", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "task", + Type: v1.ParamTypeArray, + Description: "param", + Default: v1.NewStructuredValues("default"), + }}, + Image: "myImage", + }, + expectedError: apis.FieldError{ + Message: `"array" type does not match default value's type: "string"`, + Paths: []string{"params.task.type", "params.task.default.type"}, + }, + }, { + name: "param mismatching default/type 2", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "task", + Type: v1.ParamTypeString, + Description: "param", + Default: v1.NewStructuredValues("default", "array"), + }}, + Image: "myImage", + }, + expectedError: apis.FieldError{ + Message: `"string" type does not match default value's type: "array"`, + Paths: []string{"params.task.type", "params.task.default.type"}, + }, + }, { + name: "param mismatching default/type 3", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "task", + Type: v1.ParamTypeArray, + Description: "param", + Default: v1.NewObject(map[string]string{ + "key1": "var1", + "key2": "var2", + }), + }}, + Image: "myImage", + }, + expectedError: apis.FieldError{ + Message: `"array" type does not match default value's type: "object"`, + Paths: []string{"params.task.type", "params.task.default.type"}, + }, + }, { + name: "param mismatching default/type 4", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "task", + Type: v1.ParamTypeObject, + Description: "param", + Properties: map[string]v1.PropertySpec{"key1": {}}, + Default: v1.NewStructuredValues("var"), + }}, + Image: "myImage", + }, + expectedError: apis.FieldError{ + Message: `"object" type does not match default value's type: "string"`, + Paths: []string{"params.task.type", "params.task.default.type"}, + }, + }, { + name: "PropertySpec type is set with unsupported type", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "task", + Type: v1.ParamTypeObject, + Description: "param", + Properties: map[string]v1.PropertySpec{ + "key1": {Type: "number"}, + "key2": {Type: "string"}, + }, + }}, + Image: "myImage", + }, + expectedError: apis.FieldError{ + Message: fmt.Sprintf("The value type specified for these keys %v is invalid", []string{"key1"}), + Paths: []string{"params.task.properties"}, + }, + }, { + name: "Properties is missing", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "task", + Type: v1.ParamTypeObject, + Description: "param", + }}, + Image: "myImage", + }, + expectedError: apis.FieldError{ + Message: "missing field(s)", + Paths: []string{"task.properties"}, + }, + }, { + name: "array used in unaccepted field", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "baz", + Type: v1.ParamTypeArray, + }, { + Name: "foo-is-baz", + Type: v1.ParamTypeArray, + }}, + Image: "$(params.baz)", + Command: []string{"$(params.foo-is-baz)"}, + Args: []string{"$(params.baz)", "middle string", "url"}, + }, + expectedError: apis.FieldError{ + Message: `variable type invalid in "$(params.baz)"`, + Paths: []string{"image"}, + }, + }, { + name: "array star used in unaccepted field", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "baz", + Type: v1.ParamTypeArray, + }, { + Name: "foo-is-baz", + Type: v1.ParamTypeArray, + }}, + Image: "$(params.baz[*])", + Command: []string{"$(params.foo-is-baz)"}, + Args: []string{"$(params.baz)", "middle string", "url"}, + }, + expectedError: apis.FieldError{ + Message: `variable type invalid in "$(params.baz[*])"`, + Paths: []string{"image"}, + }, + }, { + name: "array not properly isolated", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "baz", + Type: v1.ParamTypeArray, + }, { + Name: "foo-is-baz", + Type: v1.ParamTypeArray, + }}, + Image: "someimage", + Command: []string{"$(params.foo-is-baz)"}, + Args: []string{"not isolated: $(params.baz)", "middle string", "url"}, + }, + expectedError: apis.FieldError{ + Message: `variable is not properly isolated in "not isolated: $(params.baz)"`, + Paths: []string{"args[0]"}, + }, + }, { + name: "array star not properly isolated", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "baz", + Type: v1.ParamTypeArray, + }, { + Name: "foo-is-baz", + Type: v1.ParamTypeArray, + }}, + Image: "someimage", + Command: []string{"$(params.foo-is-baz)"}, + Args: []string{"not isolated: $(params.baz[*])", "middle string", "url"}, + }, + expectedError: apis.FieldError{ + Message: `variable is not properly isolated in "not isolated: $(params.baz[*])"`, + Paths: []string{"args[0]"}, + }, + }, { + name: "inferred array not properly isolated", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "baz", + Default: v1.NewStructuredValues("implied", "array", "type"), + }, { + Name: "foo-is-baz", + Default: v1.NewStructuredValues("implied", "array", "type"), + }}, + Image: "someimage", + Command: []string{"$(params.foo-is-baz)"}, + Args: []string{"not isolated: $(params.baz)", "middle string", "url"}, + }, + expectedError: apis.FieldError{ + Message: `variable is not properly isolated in "not isolated: $(params.baz)"`, + Paths: []string{"args[0]"}, + }, + }, { + name: "inferred array star not properly isolated", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "baz", + Default: v1.NewStructuredValues("implied", "array", "type"), + }, { + Name: "foo-is-baz", + Default: v1.NewStructuredValues("implied", "array", "type"), + }}, + Image: "someimage", + Command: []string{"$(params.foo-is-baz)"}, + Args: []string{"not isolated: $(params.baz[*])", "middle string", "url"}, + }, + expectedError: apis.FieldError{ + Message: `variable is not properly isolated in "not isolated: $(params.baz[*])"`, + Paths: []string{"args[0]"}, + }, + }, { + name: "params used in script field", + fields: fields{ + Params: []v1.ParamSpec{{ + Name: "baz", + Type: v1.ParamTypeArray, + }, { + Name: "foo-is-baz", + Type: v1.ParamTypeString, + }}, + Script: "$(params.baz[0]), $(params.foo-is-baz)", + Image: "my-image", + }, + expectedError: apis.FieldError{ + Message: `param substitution in scripts is not allowed.`, + Paths: []string{"script"}, + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sa := v1beta1.StepActionSpec{ + Image: tt.fields.Image, + Command: tt.fields.Command, + Args: tt.fields.Args, + Script: tt.fields.Script, + Env: tt.fields.Env, + Params: tt.fields.Params, + Results: tt.fields.Results, + } + ctx := context.Background() + sa.SetDefaults(ctx) + err := sa.Validate(ctx) + if err == nil { + t.Fatalf("Expected an error, got nothing for %v", sa) + } + if d := cmp.Diff(tt.expectedError.Error(), err.Error(), cmpopts.IgnoreUnexported(apis.FieldError{})); d != "" { + t.Errorf("StepActionSpec.Validate() errors diff %s", diff.PrintWantGot(d)) + } + }) + } +} diff --git a/pkg/apis/pipeline/v1beta1/swagger.json b/pkg/apis/pipeline/v1beta1/swagger.json index 622b1d680c3..261b6878662 100644 --- a/pkg/apis/pipeline/v1beta1/swagger.json +++ b/pkg/apis/pipeline/v1beta1/swagger.json @@ -2255,6 +2255,141 @@ } } }, + "v1beta1.StepAction": { + "description": "StepAction represents the actionable components of Step. The Step can only reference it from the cluster or using remote resolution.", + "type": "object", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "$ref": "#/definitions/v1.ObjectMeta" + }, + "spec": { + "description": "Spec holds the desired state of the Step from the client", + "default": {}, + "$ref": "#/definitions/v1beta1.StepActionSpec" + } + } + }, + "v1beta1.StepActionList": { + "description": "StepActionList contains a list of StepActions", + "type": "object", + "required": [ + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/v1beta1.StepAction" + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "$ref": "#/definitions/v1.ListMeta" + } + } + }, + "v1beta1.StepActionSpec": { + "description": "StepActionSpec contains the actionable components of a step.", + "type": "object", + "properties": { + "args": { + "description": "Arguments to the entrypoint. The image's CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", + "type": "array", + "items": { + "type": "string", + "default": "" + }, + "x-kubernetes-list-type": "atomic" + }, + "command": { + "description": "Entrypoint array. Not executed within a shell. The image's ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", + "type": "array", + "items": { + "type": "string", + "default": "" + }, + "x-kubernetes-list-type": "atomic" + }, + "description": { + "description": "Description is a user-facing description of the stepaction that may be used to populate a UI.", + "type": "string" + }, + "env": { + "description": "List of environment variables to set in the container. Cannot be updated.", + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/v1.EnvVar" + }, + "x-kubernetes-list-type": "atomic", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "image": { + "description": "Image reference name to run for this StepAction. More info: https://kubernetes.io/docs/concepts/containers/images", + "type": "string" + }, + "params": { + "description": "Params is a list of input parameters required to run the stepAction. Params must be supplied as inputs in Steps unless they declare a defaultvalue.", + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/v1.ParamSpec" + }, + "x-kubernetes-list-type": "atomic" + }, + "results": { + "description": "Results are values that this StepAction can output", + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/v1.StepResult" + }, + "x-kubernetes-list-type": "atomic" + }, + "script": { + "description": "Script is the contents of an executable file to execute.\n\nIf Script is not empty, the Step cannot have an Command and the Args will be passed to the Script.", + "type": "string" + }, + "securityContext": { + "description": "SecurityContext defines the security options the Step should be run with. If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ The value set in StepAction will take precedence over the value from Task.", + "$ref": "#/definitions/v1.SecurityContext" + }, + "volumeMounts": { + "description": "Volumes to mount into the Step's filesystem. Cannot be updated.", + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/v1.VolumeMount" + }, + "x-kubernetes-list-type": "atomic", + "x-kubernetes-patch-merge-key": "mountPath", + "x-kubernetes-patch-strategy": "merge" + }, + "workingDir": { + "description": "Step's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated.", + "type": "string" + } + } + }, "v1beta1.StepOutputConfig": { "description": "StepOutputConfig stores configuration for a step output stream.", "type": "object", diff --git a/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go b/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go index 807595a1925..04d3a09ad2e 100644 --- a/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go @@ -1871,6 +1871,125 @@ func (in *Step) DeepCopy() *Step { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StepAction) DeepCopyInto(out *StepAction) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StepAction. +func (in *StepAction) DeepCopy() *StepAction { + if in == nil { + return nil + } + out := new(StepAction) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *StepAction) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StepActionList) DeepCopyInto(out *StepActionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]StepAction, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StepActionList. +func (in *StepActionList) DeepCopy() *StepActionList { + if in == nil { + return nil + } + out := new(StepActionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *StepActionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StepActionSpec) DeepCopyInto(out *StepActionSpec) { + *out = *in + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Args != nil { + in, out := &in.Args, &out.Args + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]corev1.EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Params != nil { + in, out := &in.Params, &out.Params + *out = make(pipelinev1.ParamSpecs, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Results != nil { + in, out := &in.Results, &out.Results + *out = make([]pipelinev1.StepResult, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(corev1.SecurityContext) + (*in).DeepCopyInto(*out) + } + if in.VolumeMounts != nil { + in, out := &in.VolumeMounts, &out.VolumeMounts + *out = make([]corev1.VolumeMount, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StepActionSpec. +func (in *StepActionSpec) DeepCopy() *StepActionSpec { + if in == nil { + return nil + } + out := new(StepActionSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StepOutputConfig) DeepCopyInto(out *StepOutputConfig) { *out = *in diff --git a/pkg/client/clientset/versioned/typed/pipeline/v1beta1/fake/fake_pipeline_client.go b/pkg/client/clientset/versioned/typed/pipeline/v1beta1/fake/fake_pipeline_client.go index a142026b2ba..326e2fbb252 100644 --- a/pkg/client/clientset/versioned/typed/pipeline/v1beta1/fake/fake_pipeline_client.go +++ b/pkg/client/clientset/versioned/typed/pipeline/v1beta1/fake/fake_pipeline_client.go @@ -44,6 +44,10 @@ func (c *FakeTektonV1beta1) PipelineRuns(namespace string) v1beta1.PipelineRunIn return &FakePipelineRuns{c, namespace} } +func (c *FakeTektonV1beta1) StepActions(namespace string) v1beta1.StepActionInterface { + return &FakeStepActions{c, namespace} +} + func (c *FakeTektonV1beta1) Tasks(namespace string) v1beta1.TaskInterface { return &FakeTasks{c, namespace} } diff --git a/pkg/client/clientset/versioned/typed/pipeline/v1beta1/fake/fake_stepaction.go b/pkg/client/clientset/versioned/typed/pipeline/v1beta1/fake/fake_stepaction.go new file mode 100644 index 00000000000..048c9e56c34 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/pipeline/v1beta1/fake/fake_stepaction.go @@ -0,0 +1,129 @@ +/* +Copyright 2020 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeStepActions implements StepActionInterface +type FakeStepActions struct { + Fake *FakeTektonV1beta1 + ns string +} + +var stepactionsResource = v1beta1.SchemeGroupVersion.WithResource("stepactions") + +var stepactionsKind = v1beta1.SchemeGroupVersion.WithKind("StepAction") + +// Get takes name of the stepAction, and returns the corresponding stepAction object, and an error if there is any. +func (c *FakeStepActions) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.StepAction, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(stepactionsResource, c.ns, name), &v1beta1.StepAction{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.StepAction), err +} + +// List takes label and field selectors, and returns the list of StepActions that match those selectors. +func (c *FakeStepActions) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.StepActionList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(stepactionsResource, stepactionsKind, c.ns, opts), &v1beta1.StepActionList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.StepActionList{ListMeta: obj.(*v1beta1.StepActionList).ListMeta} + for _, item := range obj.(*v1beta1.StepActionList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested stepActions. +func (c *FakeStepActions) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(stepactionsResource, c.ns, opts)) + +} + +// Create takes the representation of a stepAction and creates it. Returns the server's representation of the stepAction, and an error, if there is any. +func (c *FakeStepActions) Create(ctx context.Context, stepAction *v1beta1.StepAction, opts v1.CreateOptions) (result *v1beta1.StepAction, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(stepactionsResource, c.ns, stepAction), &v1beta1.StepAction{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.StepAction), err +} + +// Update takes the representation of a stepAction and updates it. Returns the server's representation of the stepAction, and an error, if there is any. +func (c *FakeStepActions) Update(ctx context.Context, stepAction *v1beta1.StepAction, opts v1.UpdateOptions) (result *v1beta1.StepAction, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(stepactionsResource, c.ns, stepAction), &v1beta1.StepAction{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.StepAction), err +} + +// Delete takes name of the stepAction and deletes it. Returns an error if one occurs. +func (c *FakeStepActions) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(stepactionsResource, c.ns, name, opts), &v1beta1.StepAction{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeStepActions) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(stepactionsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.StepActionList{}) + return err +} + +// Patch applies the patch and returns the patched stepAction. +func (c *FakeStepActions) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.StepAction, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(stepactionsResource, c.ns, name, pt, data, subresources...), &v1beta1.StepAction{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.StepAction), err +} diff --git a/pkg/client/clientset/versioned/typed/pipeline/v1beta1/generated_expansion.go b/pkg/client/clientset/versioned/typed/pipeline/v1beta1/generated_expansion.go index b9f3554be3e..87f277c5c7a 100644 --- a/pkg/client/clientset/versioned/typed/pipeline/v1beta1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/pipeline/v1beta1/generated_expansion.go @@ -26,6 +26,8 @@ type PipelineExpansion interface{} type PipelineRunExpansion interface{} +type StepActionExpansion interface{} + type TaskExpansion interface{} type TaskRunExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/pipeline/v1beta1/pipeline_client.go b/pkg/client/clientset/versioned/typed/pipeline/v1beta1/pipeline_client.go index 0974d31771a..fcd65e7ce35 100644 --- a/pkg/client/clientset/versioned/typed/pipeline/v1beta1/pipeline_client.go +++ b/pkg/client/clientset/versioned/typed/pipeline/v1beta1/pipeline_client.go @@ -32,6 +32,7 @@ type TektonV1beta1Interface interface { CustomRunsGetter PipelinesGetter PipelineRunsGetter + StepActionsGetter TasksGetter TaskRunsGetter } @@ -57,6 +58,10 @@ func (c *TektonV1beta1Client) PipelineRuns(namespace string) PipelineRunInterfac return newPipelineRuns(c, namespace) } +func (c *TektonV1beta1Client) StepActions(namespace string) StepActionInterface { + return newStepActions(c, namespace) +} + func (c *TektonV1beta1Client) Tasks(namespace string) TaskInterface { return newTasks(c, namespace) } diff --git a/pkg/client/clientset/versioned/typed/pipeline/v1beta1/stepaction.go b/pkg/client/clientset/versioned/typed/pipeline/v1beta1/stepaction.go new file mode 100644 index 00000000000..388f0629540 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/pipeline/v1beta1/stepaction.go @@ -0,0 +1,178 @@ +/* +Copyright 2020 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "context" + "time" + + v1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + scheme "github.com/tektoncd/pipeline/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// StepActionsGetter has a method to return a StepActionInterface. +// A group's client should implement this interface. +type StepActionsGetter interface { + StepActions(namespace string) StepActionInterface +} + +// StepActionInterface has methods to work with StepAction resources. +type StepActionInterface interface { + Create(ctx context.Context, stepAction *v1beta1.StepAction, opts v1.CreateOptions) (*v1beta1.StepAction, error) + Update(ctx context.Context, stepAction *v1beta1.StepAction, opts v1.UpdateOptions) (*v1beta1.StepAction, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta1.StepAction, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta1.StepActionList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.StepAction, err error) + StepActionExpansion +} + +// stepActions implements StepActionInterface +type stepActions struct { + client rest.Interface + ns string +} + +// newStepActions returns a StepActions +func newStepActions(c *TektonV1beta1Client, namespace string) *stepActions { + return &stepActions{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the stepAction, and returns the corresponding stepAction object, and an error if there is any. +func (c *stepActions) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.StepAction, err error) { + result = &v1beta1.StepAction{} + err = c.client.Get(). + Namespace(c.ns). + Resource("stepactions"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of StepActions that match those selectors. +func (c *stepActions) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.StepActionList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta1.StepActionList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("stepactions"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested stepActions. +func (c *stepActions) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("stepactions"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a stepAction and creates it. Returns the server's representation of the stepAction, and an error, if there is any. +func (c *stepActions) Create(ctx context.Context, stepAction *v1beta1.StepAction, opts v1.CreateOptions) (result *v1beta1.StepAction, err error) { + result = &v1beta1.StepAction{} + err = c.client.Post(). + Namespace(c.ns). + Resource("stepactions"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(stepAction). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a stepAction and updates it. Returns the server's representation of the stepAction, and an error, if there is any. +func (c *stepActions) Update(ctx context.Context, stepAction *v1beta1.StepAction, opts v1.UpdateOptions) (result *v1beta1.StepAction, err error) { + result = &v1beta1.StepAction{} + err = c.client.Put(). + Namespace(c.ns). + Resource("stepactions"). + Name(stepAction.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(stepAction). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the stepAction and deletes it. Returns an error if one occurs. +func (c *stepActions) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("stepactions"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *stepActions) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("stepactions"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched stepAction. +func (c *stepActions) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.StepAction, err error) { + result = &v1beta1.StepAction{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("stepactions"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index cec37e05376..fe44a25ab11 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -81,6 +81,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Tekton().V1beta1().Pipelines().Informer()}, nil case v1beta1.SchemeGroupVersion.WithResource("pipelineruns"): return &genericInformer{resource: resource.GroupResource(), informer: f.Tekton().V1beta1().PipelineRuns().Informer()}, nil + case v1beta1.SchemeGroupVersion.WithResource("stepactions"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Tekton().V1beta1().StepActions().Informer()}, nil case v1beta1.SchemeGroupVersion.WithResource("tasks"): return &genericInformer{resource: resource.GroupResource(), informer: f.Tekton().V1beta1().Tasks().Informer()}, nil case v1beta1.SchemeGroupVersion.WithResource("taskruns"): diff --git a/pkg/client/informers/externalversions/pipeline/v1beta1/interface.go b/pkg/client/informers/externalversions/pipeline/v1beta1/interface.go index 307843a8014..2821b942ca9 100644 --- a/pkg/client/informers/externalversions/pipeline/v1beta1/interface.go +++ b/pkg/client/informers/externalversions/pipeline/v1beta1/interface.go @@ -32,6 +32,8 @@ type Interface interface { Pipelines() PipelineInformer // PipelineRuns returns a PipelineRunInformer. PipelineRuns() PipelineRunInformer + // StepActions returns a StepActionInformer. + StepActions() StepActionInformer // Tasks returns a TaskInformer. Tasks() TaskInformer // TaskRuns returns a TaskRunInformer. @@ -69,6 +71,11 @@ func (v *version) PipelineRuns() PipelineRunInformer { return &pipelineRunInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// StepActions returns a StepActionInformer. +func (v *version) StepActions() StepActionInformer { + return &stepActionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Tasks returns a TaskInformer. func (v *version) Tasks() TaskInformer { return &taskInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/informers/externalversions/pipeline/v1beta1/stepaction.go b/pkg/client/informers/externalversions/pipeline/v1beta1/stepaction.go new file mode 100644 index 00000000000..4ec8578199e --- /dev/null +++ b/pkg/client/informers/externalversions/pipeline/v1beta1/stepaction.go @@ -0,0 +1,90 @@ +/* +Copyright 2020 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "context" + time "time" + + pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + versioned "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" + internalinterfaces "github.com/tektoncd/pipeline/pkg/client/informers/externalversions/internalinterfaces" + v1beta1 "github.com/tektoncd/pipeline/pkg/client/listers/pipeline/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// StepActionInformer provides access to a shared informer and lister for +// StepActions. +type StepActionInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta1.StepActionLister +} + +type stepActionInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewStepActionInformer constructs a new informer for StepAction type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewStepActionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredStepActionInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredStepActionInformer constructs a new informer for StepAction type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredStepActionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.TektonV1beta1().StepActions(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.TektonV1beta1().StepActions(namespace).Watch(context.TODO(), options) + }, + }, + &pipelinev1beta1.StepAction{}, + resyncPeriod, + indexers, + ) +} + +func (f *stepActionInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredStepActionInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *stepActionInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&pipelinev1beta1.StepAction{}, f.defaultInformer) +} + +func (f *stepActionInformer) Lister() v1beta1.StepActionLister { + return v1beta1.NewStepActionLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/injection/informers/pipeline/v1beta1/stepaction/fake/fake.go b/pkg/client/injection/informers/pipeline/v1beta1/stepaction/fake/fake.go new file mode 100644 index 00000000000..7372bf60da5 --- /dev/null +++ b/pkg/client/injection/informers/pipeline/v1beta1/stepaction/fake/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2020 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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package fake + +import ( + context "context" + + fake "github.com/tektoncd/pipeline/pkg/client/injection/informers/factory/fake" + stepaction "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1beta1/stepaction" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = stepaction.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Tekton().V1beta1().StepActions() + return context.WithValue(ctx, stepaction.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/pipeline/v1beta1/stepaction/filtered/fake/fake.go b/pkg/client/injection/informers/pipeline/v1beta1/stepaction/filtered/fake/fake.go new file mode 100644 index 00000000000..209d758a1de --- /dev/null +++ b/pkg/client/injection/informers/pipeline/v1beta1/stepaction/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +Copyright 2020 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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package fake + +import ( + context "context" + + factoryfiltered "github.com/tektoncd/pipeline/pkg/client/injection/informers/factory/filtered" + filtered "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1beta1/stepaction/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Tekton().V1beta1().StepActions() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/pipeline/v1beta1/stepaction/filtered/stepaction.go b/pkg/client/injection/informers/pipeline/v1beta1/stepaction/filtered/stepaction.go new file mode 100644 index 00000000000..efc517d9ded --- /dev/null +++ b/pkg/client/injection/informers/pipeline/v1beta1/stepaction/filtered/stepaction.go @@ -0,0 +1,65 @@ +/* +Copyright 2020 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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package filtered + +import ( + context "context" + + v1beta1 "github.com/tektoncd/pipeline/pkg/client/informers/externalversions/pipeline/v1beta1" + filtered "github.com/tektoncd/pipeline/pkg/client/injection/informers/factory/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Tekton().V1beta1().StepActions() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1beta1.StepActionInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch github.com/tektoncd/pipeline/pkg/client/informers/externalversions/pipeline/v1beta1.StepActionInformer with selector %s from context.", selector) + } + return untyped.(v1beta1.StepActionInformer) +} diff --git a/pkg/client/injection/informers/pipeline/v1beta1/stepaction/stepaction.go b/pkg/client/injection/informers/pipeline/v1beta1/stepaction/stepaction.go new file mode 100644 index 00000000000..ffb873d1965 --- /dev/null +++ b/pkg/client/injection/informers/pipeline/v1beta1/stepaction/stepaction.go @@ -0,0 +1,52 @@ +/* +Copyright 2020 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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package stepaction + +import ( + context "context" + + v1beta1 "github.com/tektoncd/pipeline/pkg/client/informers/externalversions/pipeline/v1beta1" + factory "github.com/tektoncd/pipeline/pkg/client/injection/informers/factory" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Tekton().V1beta1().StepActions() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1beta1.StepActionInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch github.com/tektoncd/pipeline/pkg/client/informers/externalversions/pipeline/v1beta1.StepActionInformer from context.") + } + return untyped.(v1beta1.StepActionInformer) +} diff --git a/pkg/client/injection/reconciler/pipeline/v1beta1/stepaction/controller.go b/pkg/client/injection/reconciler/pipeline/v1beta1/stepaction/controller.go new file mode 100644 index 00000000000..b5caba0fdb6 --- /dev/null +++ b/pkg/client/injection/reconciler/pipeline/v1beta1/stepaction/controller.go @@ -0,0 +1,167 @@ +/* +Copyright 2020 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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package stepaction + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + versionedscheme "github.com/tektoncd/pipeline/pkg/client/clientset/versioned/scheme" + client "github.com/tektoncd/pipeline/pkg/client/injection/client" + stepaction "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1beta1/stepaction" + zap "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + scheme "k8s.io/client-go/kubernetes/scheme" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + record "k8s.io/client-go/tools/record" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "stepaction-controller" + defaultFinalizerName = "stepactions.tekton.dev" +) + +// NewImpl returns a controller.Impl that handles queuing and feeding work from +// the queue through an implementation of controller.Reconciler, delegating to +// the provided Interface and optional Finalizer methods. OptionsFn is used to return +// controller.ControllerOptions to be used by the internal reconciler. +func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl { + logger := logging.FromContext(ctx) + + // Check the options function input. It should be 0 or 1. + if len(optionsFns) > 1 { + logger.Fatal("Up to one options function is supported, found: ", len(optionsFns)) + } + + stepactionInformer := stepaction.Get(ctx) + + lister := stepactionInformer.Lister() + + var promoteFilterFunc func(obj interface{}) bool + var promoteFunc = func(bkt reconciler.Bucket) {} + + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + + // Signal promotion event + promoteFunc(bkt) + + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + if promoteFilterFunc != nil { + if ok := promoteFilterFunc(elt); !ok { + continue + } + } + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, + Client: client.Get(ctx), + Lister: lister, + reconciler: r, + finalizerName: defaultFinalizerName, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "tekton.dev.StepAction"), + ) + + impl := controller.NewContext(ctx, rec, controller.ControllerOptions{WorkQueueName: ctrTypeName, Logger: logger}) + agentName := defaultControllerAgentName + + // Pass impl to the options. Save any optional results. + for _, fn := range optionsFns { + opts := fn(impl) + if opts.ConfigStore != nil { + rec.configStore = opts.ConfigStore + } + if opts.FinalizerName != "" { + rec.finalizerName = opts.FinalizerName + } + if opts.AgentName != "" { + agentName = opts.AgentName + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + if opts.PromoteFilterFunc != nil { + promoteFilterFunc = opts.PromoteFilterFunc + } + if opts.PromoteFunc != nil { + promoteFunc = opts.PromoteFunc + } + } + + rec.Recorder = createRecorder(ctx, agentName) + + return impl +} + +func createRecorder(ctx context.Context, agentName string) record.EventRecorder { + logger := logging.FromContext(ctx) + + recorder := controller.GetEventRecorder(ctx) + if recorder == nil { + // Create event broadcaster + logger.Debug("Creating event broadcaster") + eventBroadcaster := record.NewBroadcaster() + watches := []watch.Interface{ + eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof), + eventBroadcaster.StartRecordingToSink( + &v1.EventSinkImpl{Interface: kubeclient.Get(ctx).CoreV1().Events("")}), + } + recorder = eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: agentName}) + go func() { + <-ctx.Done() + for _, w := range watches { + w.Stop() + } + }() + } + + return recorder +} + +func init() { + versionedscheme.AddToScheme(scheme.Scheme) +} diff --git a/pkg/client/injection/reconciler/pipeline/v1beta1/stepaction/reconciler.go b/pkg/client/injection/reconciler/pipeline/v1beta1/stepaction/reconciler.go new file mode 100644 index 00000000000..db1600a9be0 --- /dev/null +++ b/pkg/client/injection/reconciler/pipeline/v1beta1/stepaction/reconciler.go @@ -0,0 +1,365 @@ +/* +Copyright 2020 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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package stepaction + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + v1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + versioned "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" + pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/client/listers/pipeline/v1beta1" + zap "go.uber.org/zap" + v1 "k8s.io/api/core/v1" + errors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + sets "k8s.io/apimachinery/pkg/util/sets" + record "k8s.io/client-go/tools/record" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" +) + +// Interface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta1.StepAction. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1beta1.StepAction. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1beta1.StepAction) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1beta1.StepAction. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1beta1.StepAction. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1beta1.StepAction) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1beta1.StepAction if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1beta1.StepAction. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1beta1.StepAction) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1beta1.StepAction) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1beta1.StepAction resources. +type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware. + reconciler.LeaderAwareFuncs + + // Client is used to write back status updates. + Client versioned.Interface + + // Listers index properties about resources. + Lister pipelinev1beta1.StepActionLister + + // Recorder is an event recorder for recording Event resources to the + // Kubernetes API. + Recorder record.EventRecorder + + // configStore allows for decorating a context with config maps. + // +optional + configStore reconciler.ConfigStore + + // reconciler is the implementation of the business logic of the resource. + reconciler Interface + + // finalizerName is the name of the finalizer to reconcile. + finalizerName string +} + +// Check that our Reconciler implements controller.Reconciler. +var _ controller.Reconciler = (*reconcilerImpl)(nil) + +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + +func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister pipelinev1beta1.StepActionLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler { + // Check the options function input. It should be 0 or 1. + if len(options) > 1 { + logger.Fatal("Up to one options struct is supported, found: ", len(options)) + } + + // Fail fast when users inadvertently implement the other LeaderAware interface. + // For the typed reconcilers, Promote shouldn't take any arguments. + if _, ok := r.(reconciler.LeaderAware); ok { + logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r) + } + + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, + Client: client, + Lister: lister, + Recorder: recorder, + reconciler: r, + finalizerName: defaultFinalizerName, + } + + for _, opts := range options { + if opts.ConfigStore != nil { + rec.configStore = opts.ConfigStore + } + if opts.FinalizerName != "" { + rec.finalizerName = opts.FinalizerName + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + return rec +} + +// Reconcile implements controller.Reconciler +func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { + logger := logging.FromContext(ctx) + + // Initialize the reconciler state. This will convert the namespace/name + // string into a distinct namespace and name, determine if this instance of + // the reconciler is the leader, and any additional interfaces implemented + // by the reconciler. Returns an error is the resource key is invalid. + s, err := newState(key, r) + if err != nil { + logger.Error("Invalid resource key: ", key) + return nil + } + + // If we are not the leader, and we don't implement either ReadOnly + // observer interfaces, then take a fast-path out. + if s.isNotLeaderNorObserver() { + return controller.NewSkipKey(key) + } + + // If configStore is set, attach the frozen configuration to the context. + if r.configStore != nil { + ctx = r.configStore.ToContext(ctx) + } + + // Add the recorder to context. + ctx = controller.WithEventRecorder(ctx, r.Recorder) + + // Get the resource with this namespace/name. + + getter := r.Lister.StepActions(s.namespace) + + original, err := getter.Get(s.name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + return nil + } else if err != nil { + return err + } + + // Don't modify the informers copy. + resource := original.DeepCopy() + + var reconcileEvent reconciler.Event + + name, do := s.reconcileMethodFor(resource) + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", name)) + switch name { + case reconciler.DoReconcileKind: + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + return fmt.Errorf("failed to set finalizers: %w", err) + } + + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = do(ctx, resource) + + case reconciler.DoFinalizeKind: + // For finalizing reconcilers, if this resource being marked for deletion + // and reconciled cleanly (nil or normal event), remove the finalizer. + reconcileEvent = do(ctx, resource) + + if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { + return fmt.Errorf("failed to clear finalizers: %w", err) + } + + case reconciler.DoObserveKind: + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = do(ctx, resource) + + } + + // Report the reconciler event, if any. + if reconcileEvent != nil { + var event *reconciler.ReconcilerEvent + if reconciler.EventAs(reconcileEvent, &event) { + logger.Infow("Returned an event", zap.Any("event", reconcileEvent)) + r.Recorder.Event(resource, event.EventType, event.Reason, event.Error()) + + // the event was wrapped inside an error, consider the reconciliation as failed + if _, isEvent := reconcileEvent.(*reconciler.ReconcilerEvent); !isEvent { + return reconcileEvent + } + return nil + } + + if controller.IsSkipKey(reconcileEvent) { + // This is a wrapped error, don't emit an event. + } else if ok, _ := controller.IsRequeueKey(reconcileEvent); ok { + // This is a wrapped error, don't emit an event. + } else { + logger.Errorw("Returned an error", zap.Error(reconcileEvent)) + r.Recorder.Event(resource, v1.EventTypeWarning, "InternalError", reconcileEvent.Error()) + } + return reconcileEvent + } + + return nil +} + +// updateFinalizersFiltered will update the Finalizers of the resource. +// TODO: this method could be generic and sync all finalizers. For now it only +// updates defaultFinalizerName or its override. +func (r *reconcilerImpl) updateFinalizersFiltered(ctx context.Context, resource *v1beta1.StepAction, desiredFinalizers sets.Set[string]) (*v1beta1.StepAction, error) { + // Don't modify the informers copy. + existing := resource.DeepCopy() + + var finalizers []string + + // If there's nothing to update, just return. + existingFinalizers := sets.New[string](existing.Finalizers...) + + if desiredFinalizers.Has(r.finalizerName) { + if existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Add the finalizer. + finalizers = append(existing.Finalizers, r.finalizerName) + } else { + if !existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Remove the finalizer. + existingFinalizers.Delete(r.finalizerName) + finalizers = sets.List(existingFinalizers) + } + + mergePatch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "finalizers": finalizers, + "resourceVersion": existing.ResourceVersion, + }, + } + + patch, err := json.Marshal(mergePatch) + if err != nil { + return resource, err + } + + patcher := r.Client.TektonV1beta1().StepActions(resource.Namespace) + + resourceName := resource.Name + updated, err := patcher.Patch(ctx, resourceName, types.MergePatchType, patch, metav1.PatchOptions{}) + if err != nil { + r.Recorder.Eventf(existing, v1.EventTypeWarning, "FinalizerUpdateFailed", + "Failed to update finalizers for %q: %v", resourceName, err) + } else { + r.Recorder.Eventf(updated, v1.EventTypeNormal, "FinalizerUpdate", + "Updated %q finalizers", resource.GetName()) + } + return updated, err +} + +func (r *reconcilerImpl) setFinalizerIfFinalizer(ctx context.Context, resource *v1beta1.StepAction) (*v1beta1.StepAction, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.New[string](resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource, finalizers) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1beta1.StepAction, reconcileEvent reconciler.Event) (*v1beta1.StepAction, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + if resource.GetDeletionTimestamp().IsZero() { + return resource, nil + } + + finalizers := sets.New[string](resource.Finalizers...) + + if reconcileEvent != nil { + var event *reconciler.ReconcilerEvent + if reconciler.EventAs(reconcileEvent, &event) { + if event.EventType == v1.EventTypeNormal { + finalizers.Delete(r.finalizerName) + } + } + } else { + finalizers.Delete(r.finalizerName) + } + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource, finalizers) +} diff --git a/pkg/client/injection/reconciler/pipeline/v1beta1/stepaction/state.go b/pkg/client/injection/reconciler/pipeline/v1beta1/stepaction/state.go new file mode 100644 index 00000000000..fa6e4b914f8 --- /dev/null +++ b/pkg/client/injection/reconciler/pipeline/v1beta1/stepaction/state.go @@ -0,0 +1,97 @@ +/* +Copyright 2020 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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package stepaction + +import ( + fmt "fmt" + + v1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + reconciler "knative.dev/pkg/reconciler" +) + +// state is used to track the state of a reconciler in a single run. +type state struct { + // key is the original reconciliation key from the queue. + key string + // namespace is the namespace split from the reconciliation key. + namespace string + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi is the read only interface cast of the reconciler. + roi ReadOnlyInterface + // isROI (Read Only Interface) the reconciler only observes reconciliation. + isROI bool + // isLeader the instance of the reconciler is the elected leader. + isLeader bool +} + +func newState(key string, r *reconcilerImpl) (*state, error) { + // Convert the namespace/name string into a distinct namespace and name. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, fmt.Errorf("invalid resource key: %s", key) + } + + roi, isROI := r.reconciler.(ReadOnlyInterface) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + isLeader: isLeader, + }, nil +} + +// isNotLeaderNorObserver checks to see if this reconciler with the current +// state is enabled to do any work or not. +// isNotLeaderNorObserver returns true when there is no work possible for the +// reconciler. +func (s *state) isNotLeaderNorObserver() bool { + if !s.isLeader && !s.isROI { + // If we are not the leader, and we don't implement the ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1beta1.StepAction) (string, doReconcile) { + if o.GetDeletionTimestamp().IsZero() { + if s.isLeader { + return reconciler.DoReconcileKind, s.reconciler.ReconcileKind + } else if s.isROI { + return reconciler.DoObserveKind, s.roi.ObserveKind + } + } else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok { + return reconciler.DoFinalizeKind, fin.FinalizeKind + } + return "unknown", nil +} diff --git a/pkg/client/listers/pipeline/v1beta1/expansion_generated.go b/pkg/client/listers/pipeline/v1beta1/expansion_generated.go index db5d996e615..0fe1994d1d8 100644 --- a/pkg/client/listers/pipeline/v1beta1/expansion_generated.go +++ b/pkg/client/listers/pipeline/v1beta1/expansion_generated.go @@ -46,6 +46,14 @@ type PipelineRunListerExpansion interface{} // PipelineRunNamespaceLister. type PipelineRunNamespaceListerExpansion interface{} +// StepActionListerExpansion allows custom methods to be added to +// StepActionLister. +type StepActionListerExpansion interface{} + +// StepActionNamespaceListerExpansion allows custom methods to be added to +// StepActionNamespaceLister. +type StepActionNamespaceListerExpansion interface{} + // TaskListerExpansion allows custom methods to be added to // TaskLister. type TaskListerExpansion interface{} diff --git a/pkg/client/listers/pipeline/v1beta1/stepaction.go b/pkg/client/listers/pipeline/v1beta1/stepaction.go new file mode 100644 index 00000000000..1925738258d --- /dev/null +++ b/pkg/client/listers/pipeline/v1beta1/stepaction.go @@ -0,0 +1,99 @@ +/* +Copyright 2020 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +import ( + v1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// StepActionLister helps list StepActions. +// All objects returned here must be treated as read-only. +type StepActionLister interface { + // List lists all StepActions in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.StepAction, err error) + // StepActions returns an object that can list and get StepActions. + StepActions(namespace string) StepActionNamespaceLister + StepActionListerExpansion +} + +// stepActionLister implements the StepActionLister interface. +type stepActionLister struct { + indexer cache.Indexer +} + +// NewStepActionLister returns a new StepActionLister. +func NewStepActionLister(indexer cache.Indexer) StepActionLister { + return &stepActionLister{indexer: indexer} +} + +// List lists all StepActions in the indexer. +func (s *stepActionLister) List(selector labels.Selector) (ret []*v1beta1.StepAction, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.StepAction)) + }) + return ret, err +} + +// StepActions returns an object that can list and get StepActions. +func (s *stepActionLister) StepActions(namespace string) StepActionNamespaceLister { + return stepActionNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// StepActionNamespaceLister helps list and get StepActions. +// All objects returned here must be treated as read-only. +type StepActionNamespaceLister interface { + // List lists all StepActions in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.StepAction, err error) + // Get retrieves the StepAction from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1beta1.StepAction, error) + StepActionNamespaceListerExpansion +} + +// stepActionNamespaceLister implements the StepActionNamespaceLister +// interface. +type stepActionNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all StepActions in the indexer for a given namespace. +func (s stepActionNamespaceLister) List(selector labels.Selector) (ret []*v1beta1.StepAction, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.StepAction)) + }) + return ret, err +} + +// Get retrieves the StepAction from the indexer for a given namespace and name. +func (s stepActionNamespaceLister) Get(name string) (*v1beta1.StepAction, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta1.Resource("stepaction"), name) + } + return obj.(*v1beta1.StepAction), nil +} diff --git a/pkg/reconciler/apiserver/apiserver.go b/pkg/reconciler/apiserver/apiserver.go index 8489f6e12af..6367d6da719 100644 --- a/pkg/reconciler/apiserver/apiserver.go +++ b/pkg/reconciler/apiserver/apiserver.go @@ -65,6 +65,13 @@ func DryRunValidate(ctx context.Context, namespace string, obj runtime.Object, t if _, err := tekton.TektonV1alpha1().StepActions(namespace).Create(ctx, dryRunObj, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}); err != nil { return handleDryRunCreateErr(err, obj.Name) } + case *v1beta1.StepAction: + dryRunObj := obj.DeepCopy() + dryRunObj.Name = dryRunObjName + dryRunObj.Namespace = namespace // Make sure the namespace is the same as the StepAction + if _, err := tekton.TektonV1beta1().StepActions(namespace).Create(ctx, dryRunObj, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}); err != nil { + return handleDryRunCreateErr(err, obj.Name) + } default: return fmt.Errorf("unsupported object GVK %s", obj.GetObjectKind().GroupVersionKind()) } diff --git a/pkg/reconciler/apiserver/apiserver_test.go b/pkg/reconciler/apiserver/apiserver_test.go index 7233a145d27..1ed2aa3eac6 100644 --- a/pkg/reconciler/apiserver/apiserver_test.go +++ b/pkg/reconciler/apiserver/apiserver_test.go @@ -38,6 +38,9 @@ func TestDryRunCreate_Valid_DifferentGVKs(t *testing.T) { }, { name: "v1alpha1 stepaction", obj: &v1alpha1.StepAction{}, + }, { + name: "v1beta1 stepaction", + obj: &v1beta1.StepAction{}, }, { name: "unsupported gvk", obj: &v1beta1.ClusterTask{}, @@ -79,6 +82,10 @@ func TestDryRunCreate_Invalid_DifferentGVKs(t *testing.T) { name: "v1alpha1 stepaction", obj: &v1alpha1.StepAction{}, wantErr: apiserver.ErrReferencedObjectValidationFailed, + }, { + name: "v1beta1 stepaction", + obj: &v1beta1.StepAction{}, + wantErr: apiserver.ErrReferencedObjectValidationFailed, }, { name: "unsupported gvk", obj: &v1beta1.ClusterTask{}, diff --git a/pkg/reconciler/taskrun/resources/taskref.go b/pkg/reconciler/taskrun/resources/taskref.go index 872af8a787a..fa7e1200c05 100644 --- a/pkg/reconciler/taskrun/resources/taskref.go +++ b/pkg/reconciler/taskrun/resources/taskref.go @@ -141,7 +141,7 @@ func GetStepActionFunc(tekton clientset.Interface, k8s kubernetes.Interface, req if step.Ref != nil && step.Ref.Resolver != "" && requester != nil { // Return an inline function that implements GetStepAction by calling Resolver.Get with the specified StepAction type and // casting it to a StepAction. - return func(ctx context.Context, name string) (*v1alpha1.StepAction, *v1.RefSource, error) { + return func(ctx context.Context, name string) (*v1beta1.StepAction, *v1.RefSource, error) { // Perform params replacements for StepAction resolver params ApplyParameterSubstitutionInResolverParams(tr, step) resolverPayload := remoteresource.ResolverPayload{ @@ -221,17 +221,33 @@ func resolveTask(ctx context.Context, resolver remote.Resolver, name, namespace return taskObj, refSource, vr, nil } -func resolveStepAction(ctx context.Context, resolver remote.Resolver, name, namespace string, k8s kubernetes.Interface, tekton clientset.Interface) (*v1alpha1.StepAction, *v1.RefSource, error) { +func resolveStepAction(ctx context.Context, resolver remote.Resolver, name, namespace string, k8s kubernetes.Interface, tekton clientset.Interface) (*v1beta1.StepAction, *v1.RefSource, error) { obj, refSource, err := resolver.Get(ctx, "StepAction", name) if err != nil { return nil, nil, err } - switch obj := obj.(type) { //nolint:gocritic - case *v1alpha1.StepAction: + switch obj := obj.(type) { + case *v1beta1.StepAction: if err := apiserver.DryRunValidate(ctx, namespace, obj, tekton); err != nil { return nil, nil, err } return obj, refSource, nil + case *v1alpha1.StepAction: + obj.SetDefaults(ctx) + if err := apiserver.DryRunValidate(ctx, namespace, obj, tekton); err != nil { + return nil, nil, err + } + v1BetaStepAction := v1beta1.StepAction{ + TypeMeta: metav1.TypeMeta{ + Kind: "StepAction", + APIVersion: "tekton.dev/v1beta1", + }, + } + err := obj.ConvertTo(ctx, &v1BetaStepAction) + if err != nil { + return nil, nil, err + } + return &v1BetaStepAction, refSource, nil } return nil, nil, errors.New("resource is not a StepAction") } @@ -329,12 +345,12 @@ type LocalStepActionRefResolver struct { // GetStepAction will resolve a StepAction from the local cluster using a versioned Tekton client. // It will return an error if it can't find an appropriate StepAction for any reason. -func (l *LocalStepActionRefResolver) GetStepAction(ctx context.Context, name string) (*v1alpha1.StepAction, *v1.RefSource, error) { +func (l *LocalStepActionRefResolver) GetStepAction(ctx context.Context, name string) (*v1beta1.StepAction, *v1.RefSource, error) { // If we are going to resolve this reference locally, we need a namespace scope. if l.Namespace == "" { return nil, nil, fmt.Errorf("must specify namespace to resolve reference to step action %s", name) } - stepAction, err := l.Tektonclient.TektonV1alpha1().StepActions(l.Namespace).Get(ctx, name, metav1.GetOptions{}) + stepAction, err := l.Tektonclient.TektonV1beta1().StepActions(l.Namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return nil, nil, err } diff --git a/pkg/reconciler/taskrun/resources/taskref_test.go b/pkg/reconciler/taskrun/resources/taskref_test.go index 9bf6c18ca38..643c5754e66 100644 --- a/pkg/reconciler/taskrun/resources/taskref_test.go +++ b/pkg/reconciler/taskrun/resources/taskref_test.go @@ -54,16 +54,16 @@ import ( ) var ( - simpleNamespacedStepAction = &v1alpha1.StepAction{ + simpleNamespacedStepAction = &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "simple", Namespace: "default", }, TypeMeta: metav1.TypeMeta{ - APIVersion: "tekton.dev/v1alpha1", + APIVersion: "tekton.dev/v1beta1", Kind: "StepAction", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "something", }, } @@ -608,13 +608,13 @@ func TestStepActionRef(t *testing.T) { name: "local-step-action", namespace: "default", stepactions: []runtime.Object{ - &v1alpha1.StepAction{ + &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "simple", Namespace: "default", }, }, - &v1alpha1.StepAction{ + &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "sample", Namespace: "default", @@ -624,7 +624,7 @@ func TestStepActionRef(t *testing.T) { ref: &v1.Ref{ Name: "simple", }, - expected: &v1alpha1.StepAction{ + expected: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "simple", Namespace: "default", @@ -681,7 +681,7 @@ func TestStepActionRef_Error(t *testing.T) { name: "local-step-action-missing-namespace", namespace: "", stepactions: []runtime.Object{ - &v1alpha1.StepAction{ + &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "simple", Namespace: "default", @@ -887,16 +887,24 @@ func TestGetStepActionFunc_RemoteResolution_Success(t *testing.T) { testcases := []struct { name string stepActionYAML string - wantStepAction *v1alpha1.StepAction + wantStepAction *v1beta1.StepAction wantErr bool }{{ - name: "remote StepAction", + name: "remote StepAction v1alpha1", stepActionYAML: strings.Join([]string{ "kind: StepAction", "apiVersion: tekton.dev/v1alpha1", stepActionYAMLString, }, "\n"), - wantStepAction: parse.MustParseV1alpha1StepAction(t, stepActionYAMLString), + wantStepAction: parse.MustParseV1beta1StepAction(t, stepActionYAMLString), + }, { + name: "remote StepAction v1beta1", + stepActionYAML: strings.Join([]string{ + "kind: StepAction", + "apiVersion: tekton.dev/v1beta1", + stepActionYAMLString, + }, "\n"), + wantStepAction: parse.MustParseV1beta1StepAction(t, stepActionYAMLString), }} for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/reconciler/taskrun/resources/taskspec.go b/pkg/reconciler/taskrun/resources/taskspec.go index 955154911c9..f8f856da4ff 100644 --- a/pkg/reconciler/taskrun/resources/taskspec.go +++ b/pkg/reconciler/taskrun/resources/taskspec.go @@ -22,7 +22,7 @@ import ( "fmt" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" - "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" clientset "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" resolutionutil "github.com/tektoncd/pipeline/pkg/internal/resolution" remoteresource "github.com/tektoncd/pipeline/pkg/remoteresolution/resource" @@ -42,7 +42,7 @@ type ResolvedTask struct { } // GetStepAction is a function used to retrieve StepActions. -type GetStepAction func(context.Context, string) (*v1alpha1.StepAction, *v1.RefSource, error) +type GetStepAction func(context.Context, string) (*v1beta1.StepAction, *v1.RefSource, error) // GetTask is a function used to retrieve Tasks. // VerificationResult is the result from trusted resources if the feature is enabled. diff --git a/pkg/reconciler/taskrun/resources/taskspec_test.go b/pkg/reconciler/taskrun/resources/taskspec_test.go index 361ce2f0050..0c53fff2330 100644 --- a/pkg/reconciler/taskrun/resources/taskspec_test.go +++ b/pkg/reconciler/taskrun/resources/taskspec_test.go @@ -25,7 +25,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" - "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "github.com/tektoncd/pipeline/pkg/client/clientset/versioned/fake" "github.com/tektoncd/pipeline/pkg/reconciler/taskrun/resources" "github.com/tektoncd/pipeline/pkg/trustedresources" @@ -303,7 +303,7 @@ func TestGetStepActionsData(t *testing.T) { tests := []struct { name string tr *v1.TaskRun - stepAction *v1alpha1.StepAction + stepAction *v1beta1.StepAction want []v1.Step }{{ name: "step-action-with-command-args", @@ -323,12 +323,12 @@ func TestGetStepActionsData(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{ + stepAction: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "stepAction", Namespace: "default", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "myimage", Command: []string{"ls"}, Args: []string{"-lh"}, @@ -365,12 +365,12 @@ func TestGetStepActionsData(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{ + stepAction: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "stepActionWithScript", Namespace: "default", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "myimage", Script: "ls", }, @@ -396,12 +396,12 @@ func TestGetStepActionsData(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{ + stepAction: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "stepActionWithEnv", Namespace: "default", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "myimage", Env: []corev1.EnvVar{{ Name: "env1", @@ -433,12 +433,12 @@ func TestGetStepActionsData(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{ + stepAction: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "stepActionWithScript", Namespace: "default", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "myimage", Script: "ls", Results: []v1.StepResult{{ @@ -472,12 +472,12 @@ func TestGetStepActionsData(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{ + stepAction: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "stepAction", Namespace: "default", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "myimage", Command: []string{"ls"}, Args: []string{"-lh"}, @@ -510,12 +510,12 @@ func TestGetStepActionsData(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{ + stepAction: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "stepAction", Namespace: "default", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "myimage", Command: []string{"ls"}, Args: []string{"-lh"}, @@ -574,12 +574,12 @@ func TestGetStepActionsData(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{ + stepAction: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "stepAction", Namespace: "default", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "myimage", Args: []string{"$(params.string-param)", "$(params.array-param[0])", "$(params.array-param[1])", "$(params.array-param[*])", "$(params.object-param.key)"}, Params: v1.ParamSpecs{{ @@ -645,12 +645,12 @@ func TestGetStepActionsData(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{ + stepAction: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "stepAction", Namespace: "default", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "myimage", Args: []string{"$(params.string-param)", "$(params.array-param[0])", "$(params.array-param[1])", "$(params.array-param[*])", "$(params.object-param.key)"}, Params: v1.ParamSpecs{{ @@ -687,12 +687,12 @@ func TestGetStepActionsData(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{ + stepAction: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "stepAction", Namespace: "default", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "myimage", Args: []string{"$(params.string-param)", "$(params.array-param[0])", "$(params.array-param[1])", "$(params.array-param[*])", "$(params.object-param.key)"}, Params: v1.ParamSpecs{{ @@ -777,12 +777,12 @@ func TestGetStepActionsData(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{ + stepAction: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "stepAction", Namespace: "default", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "myimage", Args: []string{"$(params.string-param)", "$(params.array-param[0])", "$(params.array-param[1])", "$(params.array-param[*])", "$(params.object-param.key)", "$(params.object-param.key2)", "$(params.object-param.key3)"}, Params: v1.ParamSpecs{{ @@ -848,12 +848,12 @@ func TestGetStepActionsData(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{ + stepAction: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "stepAction", Namespace: "default", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "myimage", Args: []string{"echo", "$(params.stringparam)"}, Params: v1.ParamSpecs{{ @@ -908,12 +908,12 @@ func TestGetStepActionsData(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{ + stepAction: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "stepAction", Namespace: "default", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "myimage", Args: []string{"$(params.string-param)", "$(params.array-param[0])", "$(params.array-param[1])", "$(params.array-param[*])", "$(params.object-param.key)"}, Command: []string{"$(params[\"string-param\"])", "$(params[\"array-param\"][0])"}, @@ -980,7 +980,7 @@ func TestGetStepActionsData_Error(t *testing.T) { tests := []struct { name string tr *v1.TaskRun - stepAction *v1alpha1.StepAction + stepAction *v1beta1.StepAction expectedError error }{{ name: "namespace missing error", @@ -998,7 +998,7 @@ func TestGetStepActionsData_Error(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{}, + stepAction: &v1beta1.StepAction{}, expectedError: errors.New("must specify namespace to resolve reference to step action stepActionError"), }, { name: "params missing", @@ -1017,12 +1017,12 @@ func TestGetStepActionsData_Error(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{ + stepAction: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "stepaction", Namespace: "default", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "myimage", Params: v1.ParamSpecs{{ Name: "string-param", @@ -1052,12 +1052,12 @@ func TestGetStepActionsData_Error(t *testing.T) { }, }, }, - stepAction: &v1alpha1.StepAction{ + stepAction: &v1beta1.StepAction{ ObjectMeta: metav1.ObjectMeta{ Name: "stepaction", Namespace: "default", }, - Spec: v1alpha1.StepActionSpec{ + Spec: v1beta1.StepActionSpec{ Image: "myimage", }, }, diff --git a/pkg/reconciler/taskrun/taskrun_test.go b/pkg/reconciler/taskrun/taskrun_test.go index ce23ebfa491..85a4848d22d 100644 --- a/pkg/reconciler/taskrun/taskrun_test.go +++ b/pkg/reconciler/taskrun/taskrun_test.go @@ -2099,7 +2099,7 @@ spec: resolver: bar `) - stepAction := parse.MustParseV1alpha1StepAction(t, ` + stepAction := parse.MustParseV1beta1StepAction(t, ` metadata: name: stepAction namespace: foo @@ -2134,7 +2134,7 @@ spec: clients := testAssets.Clients err = c.Reconciler.Reconcile(testAssets.Ctx, fmt.Sprintf("%s/%s", tr.Namespace, tr.Name)) if controller.IsPermanentError(err) { - t.Errorf("Not expected permanent error but got %t", err) + t.Errorf("Not expected permanent error but got %v", err) } reconciledRun, err := clients.Pipeline.TektonV1().TaskRuns(tr.Namespace).Get(testAssets.Ctx, tr.Name, metav1.GetOptions{}) if err != nil { @@ -2159,7 +2159,7 @@ spec: resolver: bar `)} - stepAction := parse.MustParseV1alpha1StepAction(t, ` + stepAction := parse.MustParseV1beta1StepAction(t, ` metadata: name: stepAction namespace: foo @@ -3549,7 +3549,7 @@ spec: - name: inlined-step image: "inlined-image" `) - stepAction := parse.MustParseV1alpha1StepAction(t, ` + stepAction := parse.MustParseV1beta1StepAction(t, ` metadata: name: stepAction namespace: foo @@ -3559,7 +3559,7 @@ spec: securityContext: privileged: true `) - stepAction2 := parse.MustParseV1alpha1StepAction(t, ` + stepAction2 := parse.MustParseV1beta1StepAction(t, ` metadata: name: stepAction2 namespace: foo @@ -3569,7 +3569,7 @@ spec: `) d := test.Data{ TaskRuns: []*v1.TaskRun{taskRun}, - StepActions: []*v1alpha1.StepAction{stepAction, stepAction2}, + StepActions: []*v1beta1.StepAction{stepAction, stepAction2}, ConfigMaps: []*corev1.ConfigMap{ { ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName(), Namespace: system.Namespace()}, @@ -3659,7 +3659,7 @@ func TestStepActionRefParams(t *testing.T) { tests := []struct { name string taskRun *v1.TaskRun - stepAction *v1alpha1.StepAction + stepAction *v1beta1.StepAction want []v1.Step }{{ name: "params propagated from taskrun", @@ -3689,7 +3689,7 @@ spec: - name: object-param value: $(params.objectparam[*]) `), - stepAction: parse.MustParseV1alpha1StepAction(t, ` + stepAction: parse.MustParseV1beta1StepAction(t, ` metadata: name: stepAction namespace: foo @@ -3734,7 +3734,7 @@ spec: - name: stringparam value: "step string param" `), - stepAction: parse.MustParseV1alpha1StepAction(t, ` + stepAction: parse.MustParseV1beta1StepAction(t, ` metadata: name: stepAction namespace: foo @@ -3767,7 +3767,7 @@ spec: name: stepAction name: step1 `), - stepAction: parse.MustParseV1alpha1StepAction(t, ` + stepAction: parse.MustParseV1beta1StepAction(t, ` metadata: name: stepAction namespace: foo @@ -3798,7 +3798,7 @@ spec: name: stepAction name: step1 `), - stepAction: parse.MustParseV1alpha1StepAction(t, ` + stepAction: parse.MustParseV1beta1StepAction(t, ` metadata: name: stepAction namespace: foo @@ -3844,7 +3844,7 @@ spec: - name: object-param value: $(params.objectparam[*]) `), - stepAction: parse.MustParseV1alpha1StepAction(t, ` + stepAction: parse.MustParseV1beta1StepAction(t, ` metadata: name: stepAction namespace: foo @@ -3881,7 +3881,7 @@ spec: name: stepAction name: step1 `), - stepAction: parse.MustParseV1alpha1StepAction(t, ` + stepAction: parse.MustParseV1beta1StepAction(t, ` metadata: name: stepAction namespace: foo @@ -3918,7 +3918,7 @@ spec: t.Run(tt.name, func(t *testing.T) { d := test.Data{ TaskRuns: []*v1.TaskRun{tt.taskRun}, - StepActions: []*v1alpha1.StepAction{tt.stepAction}, + StepActions: []*v1beta1.StepAction{tt.stepAction}, ConfigMaps: []*corev1.ConfigMap{ { ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName(), Namespace: system.Namespace()}, diff --git a/test/clients.go b/test/clients.go index 0a0a4f21400..dc0424a5275 100644 --- a/test/clients.go +++ b/test/clients.go @@ -68,7 +68,7 @@ type clients struct { V1TaskClient v1.TaskInterface V1TaskRunClient v1.TaskRunInterface V1PipelineRunClient v1.PipelineRunInterface - V1alpha1StepActionClient v1alpha1.StepActionInterface + V1beta1StepActionClient v1beta1.StepActionInterface } // newClients instantiates and returns several clientsets required for making requests to the @@ -110,6 +110,6 @@ func newClients(t *testing.T, configPath, clusterName, namespace string) *client c.V1TaskClient = cs.TektonV1().Tasks(namespace) c.V1TaskRunClient = cs.TektonV1().TaskRuns(namespace) c.V1PipelineRunClient = cs.TektonV1().PipelineRuns(namespace) - c.V1alpha1StepActionClient = cs.TektonV1alpha1().StepActions(namespace) + c.V1beta1StepActionClient = cs.TektonV1beta1().StepActions(namespace) return c } diff --git a/test/controller.go b/test/controller.go index 93a3840ae86..d4cc5506135 100644 --- a/test/controller.go +++ b/test/controller.go @@ -37,10 +37,10 @@ import ( fakepipelineruninformer "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1/pipelinerun/fake" faketaskinformer "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1/task/fake" faketaskruninformer "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1/taskrun/fake" - fakestepactioninformer "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1alpha1/stepaction/fake" fakeverificationpolicyinformer "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1alpha1/verificationpolicy/fake" fakeclustertaskinformer "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1beta1/clustertask/fake" fakecustomruninformer "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1beta1/customrun/fake" + fakestepactioninformer "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1beta1/stepaction/fake" fakeresolutionclientset "github.com/tektoncd/pipeline/pkg/client/resolution/clientset/versioned/fake" resolutioninformersv1alpha1 "github.com/tektoncd/pipeline/pkg/client/resolution/informers/externalversions/resolution/v1beta1" fakeresolutionrequestclient "github.com/tektoncd/pipeline/pkg/client/resolution/injection/client/fake" @@ -74,7 +74,7 @@ type Data struct { Pipelines []*v1.Pipeline TaskRuns []*v1.TaskRun Tasks []*v1.Task - StepActions []*v1alpha1.StepAction + StepActions []*v1beta1.StepAction ClusterTasks []*v1beta1.ClusterTask CustomRuns []*v1beta1.CustomRun Pods []*corev1.Pod @@ -104,7 +104,7 @@ type Informers struct { Run informersv1alpha1.RunInformer CustomRun informersv1beta1.CustomRunInformer Task informersv1.TaskInformer - StepAction informersv1alpha1.StepActionInformer + StepAction informersv1beta1.StepActionInformer ClusterTask informersv1beta1.ClusterTaskInformer Pod coreinformers.PodInformer ConfigMap coreinformers.ConfigMapInformer @@ -236,7 +236,7 @@ func SeedTestData(t *testing.T, ctx context.Context, d Data) (Clients, Informers c.Pipeline.PrependReactor("*", "stepactions", AddToInformer(t, i.StepAction.Informer().GetIndexer())) for _, sa := range d.StepActions { sa := sa.DeepCopy() // Avoid assumptions that the informer's copy is modified. - if _, err := c.Pipeline.TektonV1alpha1().StepActions(sa.Namespace).Create(ctx, sa, metav1.CreateOptions{}); err != nil { + if _, err := c.Pipeline.TektonV1beta1().StepActions(sa.Namespace).Create(ctx, sa, metav1.CreateOptions{}); err != nil { t.Fatal(err) } } diff --git a/test/e2e-tests-kind-prow-beta.env b/test/e2e-tests-kind-prow-beta.env index 8dae8c618fe..516f4675938 100644 --- a/test/e2e-tests-kind-prow-beta.env +++ b/test/e2e-tests-kind-prow-beta.env @@ -2,5 +2,6 @@ SKIP_INITIALIZE=true PIPELINE_FEATURE_GATE=beta EMBEDDED_STATUS_GATE=minimal RUN_YAML_TESTS=true +ENABLE_STEP_ACTIONS=true KO_DOCKER_REPO=registry.local:5000 E2E_GO_TEST_TIMEOUT=40m diff --git a/test/featureflags.go b/test/featureflags.go index 673d82cb75f..6ffa121f0ed 100644 --- a/test/featureflags.go +++ b/test/featureflags.go @@ -123,7 +123,8 @@ func getFeatureFlagsBaseOnAPIFlag(t *testing.T) *config.FeatureFlags { t.Fatalf("error creating alpha feature flags configmap: %v", err) } betaFeatureFlags, err := config.NewFeatureFlagsFromMap(map[string]string{ - "enable-api-fields": "beta", + "enable-api-fields": "beta", + "enable-step-actions": "true", }) if err != nil { t.Fatalf("error creating beta feature flags configmap: %v", err) diff --git a/test/parse/yaml.go b/test/parse/yaml.go index 68bc16e14b5..847084b9c26 100644 --- a/test/parse/yaml.go +++ b/test/parse/yaml.go @@ -35,6 +35,17 @@ kind: StepAction return &sa } +// MustParseV1beta1StepAction takes YAML and parses it into a *v1alpha1.StepAction +func MustParseV1beta1StepAction(t *testing.T, yaml string) *v1beta1.StepAction { + t.Helper() + var sa v1beta1.StepAction + yaml = `apiVersion: tekton.dev/v1beta1 +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() diff --git a/test/per_feature_flags_test.go b/test/per_feature_flags_test.go index 4555d2010c9..b9b25d0e98e 100644 --- a/test/per_feature_flags_test.go +++ b/test/per_feature_flags_test.go @@ -47,8 +47,8 @@ const ( ) var ( - alphaFeatureFlags = []string{"enable-param-enum", "enable-step-actions", "keep-pod-enabled-cancel", "enable-cel-in-whenexpression", "enable-artifacts"} - betaFeatureFlags = []string{} + alphaFeatureFlags = []string{"enable-param-enum", "keep-pod-enabled-cancel", "enable-cel-in-whenexpression", "enable-artifacts"} + betaFeatureFlags = []string{"enable-step-actions"} perFeatureFlags = map[string][]string{ "alpha": alphaFeatureFlags, "beta": betaFeatureFlags, diff --git a/test/stepaction_results_test.go b/test/stepaction_results_test.go index 5c3fafa50a2..c1dd23a162b 100644 --- a/test/stepaction_results_test.go +++ b/test/stepaction_results_test.go @@ -28,7 +28,7 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/tektoncd/pipeline/pkg/apis/config" v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" - v1alpha1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "github.com/tektoncd/pipeline/test/parse" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "knative.dev/pkg/system" @@ -49,7 +49,7 @@ func TestStepResultsStepActions(t *testing.T) { type tests struct { name string taskRunFunc func(*testing.T, string) (*v1.TaskRun, *v1.TaskRun) - stepActionFunc func(*testing.T, string) *v1alpha1.StepAction + stepActionFunc func(*testing.T, string) *v1beta1.StepAction } tds := []tests{{ @@ -80,7 +80,7 @@ func TestStepResultsStepActions(t *testing.T) { trName := taskRun.Name - _, err := c.V1alpha1StepActionClient.Create(ctx, stepAction, metav1.CreateOptions{}) + _, err := c.V1beta1StepActionClient.Create(ctx, stepAction, metav1.CreateOptions{}) if err != nil { t.Fatalf("Failed to create StepAction : %s", err) } @@ -114,9 +114,9 @@ func TestStepResultsStepActions(t *testing.T) { } } -func getStepAction(t *testing.T, namespace string) *v1alpha1.StepAction { +func getStepAction(t *testing.T, namespace string) *v1beta1.StepAction { t.Helper() - return parse.MustParseV1alpha1StepAction(t, fmt.Sprintf(` + return parse.MustParseV1beta1StepAction(t, fmt.Sprintf(` metadata: name: step-action namespace: %s