diff --git a/pkg/apis/pipeline/v1/container_types.go b/pkg/apis/pipeline/v1/container_types.go index 407c05cc436..f6e75960ff0 100644 --- a/pkg/apis/pipeline/v1/container_types.go +++ b/pkg/apis/pipeline/v1/container_types.go @@ -135,6 +135,19 @@ type Step struct { // stopAndFail indicates exit the taskRun if the container exits with non-zero exit code // continue indicates continue executing the rest of the steps irrespective of the container exit code OnError string `json:"onError,omitempty"` + // Stores configuration for the stdout stream of the step. + // +optional + StdoutConfig *StepOutputConfig `json:"stdoutConfig,omitempty"` + // Stores configuration for the stderr stream of the step. + // +optional + StderrConfig *StepOutputConfig `json:"stderrConfig,omitempty"` +} + +// StepOutputConfig stores configuration for a step output stream. +type StepOutputConfig struct { + // Path to duplicate stdout stream to on container's local filesystem. + // +optional + Path string `json:"path,omitempty"` } // ToK8sContainer converts the Step to a Kubernetes Container struct diff --git a/pkg/apis/pipeline/v1/merge.go b/pkg/apis/pipeline/v1/merge.go index 6075ac04980..b7995ae90ae 100644 --- a/pkg/apis/pipeline/v1/merge.go +++ b/pkg/apis/pipeline/v1/merge.go @@ -58,7 +58,7 @@ func MergeStepsWithStepTemplate(template *StepTemplate, steps []Step) ([]Step, e } // Pass through original step Script, for later conversion. - newStep := Step{Script: s.Script, OnError: s.OnError, Timeout: s.Timeout} + newStep := Step{Script: s.Script, OnError: s.OnError, Timeout: s.Timeout, StdoutConfig: s.StdoutConfig, StderrConfig: s.StderrConfig} newStep.SetContainerFields(merged) steps[i] = newStep } diff --git a/pkg/apis/pipeline/v1/merge_test.go b/pkg/apis/pipeline/v1/merge_test.go index 8fa836f7bab..ee604ee9631 100644 --- a/pkg/apis/pipeline/v1/merge_test.go +++ b/pkg/apis/pipeline/v1/merge_test.go @@ -105,6 +105,28 @@ func TestMergeStepsWithStepTemplate(t *testing.T) { Value: "NEW_VALUE", }}, }}, + }, { + name: "workspace-and-output-config", + template: &v1.StepTemplate{ + VolumeMounts: []corev1.VolumeMount{{ + Name: "data", + MountPath: "/workspace/data", + }}, + }, + steps: []v1.Step{{ + Image: "some-image", + StdoutConfig: &v1.StepOutputConfig{Path: "stdout.txt"}, + StderrConfig: &v1.StepOutputConfig{Path: "stderr.txt"}, + }}, + expected: []v1.Step{{ + Image: "some-image", + StdoutConfig: &v1.StepOutputConfig{Path: "stdout.txt"}, + StderrConfig: &v1.StepOutputConfig{Path: "stderr.txt"}, + VolumeMounts: []corev1.VolumeMount{{ + Name: "data", + MountPath: "/workspace/data", + }}, + }}, }} { t.Run(tc.name, func(t *testing.T) { result, err := v1.MergeStepsWithStepTemplate(tc.template, tc.steps) diff --git a/pkg/apis/pipeline/v1/openapi_generated.go b/pkg/apis/pipeline/v1/openapi_generated.go index d389b93a804..15ba0a4f8c5 100644 --- a/pkg/apis/pipeline/v1/openapi_generated.go +++ b/pkg/apis/pipeline/v1/openapi_generated.go @@ -41,6 +41,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.ResolverRef": schema_pkg_apis_pipeline_v1_ResolverRef(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Sidecar": schema_pkg_apis_pipeline_v1_Sidecar(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Step": schema_pkg_apis_pipeline_v1_Step(ref), + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.StepOutputConfig": schema_pkg_apis_pipeline_v1_StepOutputConfig(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.StepTemplate": schema_pkg_apis_pipeline_v1_StepTemplate(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Task": schema_pkg_apis_pipeline_v1_Task(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskList": schema_pkg_apis_pipeline_v1_TaskList(ref), @@ -1076,12 +1077,44 @@ func schema_pkg_apis_pipeline_v1_Step(ref common.ReferenceCallback) common.OpenA Format: "", }, }, + "stdoutConfig": { + SchemaProps: spec.SchemaProps{ + Description: "Stores configuration for the stdout stream of the step.", + Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.StepOutputConfig"), + }, + }, + "stderrConfig": { + SchemaProps: spec.SchemaProps{ + Description: "Stores configuration for the stderr stream of the step.", + Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.StepOutputConfig"), + }, + }, }, Required: []string{"name"}, }, }, Dependencies: []string{ - "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.WorkspaceUsage", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.SecurityContext", "k8s.io/api/core/v1.VolumeDevice", "k8s.io/api/core/v1.VolumeMount", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.StepOutputConfig", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.WorkspaceUsage", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.SecurityContext", "k8s.io/api/core/v1.VolumeDevice", "k8s.io/api/core/v1.VolumeMount", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, + } +} + +func schema_pkg_apis_pipeline_v1_StepOutputConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "StepOutputConfig stores configuration for a step output stream.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Path to duplicate stdout stream to on container's local filesystem.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, } } diff --git a/pkg/apis/pipeline/v1/swagger.json b/pkg/apis/pipeline/v1/swagger.json index a945319cde9..87ba8b238ff 100644 --- a/pkg/apis/pipeline/v1/swagger.json +++ b/pkg/apis/pipeline/v1/swagger.json @@ -523,6 +523,14 @@ "description": "SecurityContext defines the security options the container 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/", "$ref": "#/definitions/v1.SecurityContext" }, + "stderrConfig": { + "description": "Stores configuration for the stderr stream of the step.", + "$ref": "#/definitions/v1.StepOutputConfig" + }, + "stdoutConfig": { + "description": "Stores configuration for the stdout stream of the step.", + "$ref": "#/definitions/v1.StepOutputConfig" + }, "timeout": { "description": "Timeout is the time after which the step times out. Defaults to never. Refer to Go's ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration", "$ref": "#/definitions/v1.Duration" @@ -564,6 +572,16 @@ } } }, + "v1.StepOutputConfig": { + "description": "StepOutputConfig stores configuration for a step output stream.", + "type": "object", + "properties": { + "path": { + "description": "Path to duplicate stdout stream to on container's local filesystem.", + "type": "string" + } + } + }, "v1.StepTemplate": { "description": "StepTemplate is a template for a Step", "type": "object", diff --git a/pkg/apis/pipeline/v1/task_validation.go b/pkg/apis/pipeline/v1/task_validation.go index 75b32e2096e..10d44700473 100644 --- a/pkg/apis/pipeline/v1/task_validation.go +++ b/pkg/apis/pipeline/v1/task_validation.go @@ -256,6 +256,16 @@ func validateStep(ctx context.Context, s Step, names sets.String) (errs *apis.Fi errs = errs.Also(version.ValidateEnabledAPIFields(ctx, "windows script support", config.AlphaAPIFields).ViaField("script")) } } + // StdoutConfig is an alpha feature and will fail validation if it's used in a task spec + // when the enable-api-fields feature gate is not "alpha". + if s.StdoutConfig != nil { + errs = errs.Also(version.ValidateEnabledAPIFields(ctx, "step stdout stream support", config.AlphaAPIFields).ViaField("stdoutconfig")) + } + // StderrConfig is an alpha feature and will fail validation if it's used in a task spec + // when the enable-api-fields feature gate is not "alpha". + if s.StderrConfig != nil { + errs = errs.Also(version.ValidateEnabledAPIFields(ctx, "step stderr stream support", config.AlphaAPIFields).ViaField("stderrconfig")) + } return errs } diff --git a/pkg/apis/pipeline/v1/task_validation_test.go b/pkg/apis/pipeline/v1/task_validation_test.go index b05c9287043..000540d7600 100644 --- a/pkg/apis/pipeline/v1/task_validation_test.go +++ b/pkg/apis/pipeline/v1/task_validation_test.go @@ -1408,7 +1408,29 @@ func TestIncompatibleAPIVersions(t *testing.T) { script-1`, }}, }, - }} + }, { + name: "stdout stream support requires alpha", + requiredVersion: "alpha", + spec: v1.TaskSpec{ + Steps: []v1.Step{{ + Image: "foo", + StdoutConfig: &v1.StepOutputConfig{ + Path: "/tmp/stdout.txt", + }, + }}, + }, + }, { + name: "stderr stream support requires alpha", + requiredVersion: "alpha", + spec: v1.TaskSpec{ + Steps: []v1.Step{{ + Image: "foo", + StderrConfig: &v1.StepOutputConfig{ + Path: "/tmp/stderr.txt", + }, + }}, + }}, + } versions := []string{"alpha", "stable"} for _, tt := range tests { for _, version := range versions { diff --git a/pkg/apis/pipeline/v1/zz_generated.deepcopy.go b/pkg/apis/pipeline/v1/zz_generated.deepcopy.go index 55bd52f8892..f618a687483 100644 --- a/pkg/apis/pipeline/v1/zz_generated.deepcopy.go +++ b/pkg/apis/pipeline/v1/zz_generated.deepcopy.go @@ -312,6 +312,16 @@ func (in *Step) DeepCopyInto(out *Step) { *out = make([]WorkspaceUsage, len(*in)) copy(*out, *in) } + if in.StdoutConfig != nil { + in, out := &in.StdoutConfig, &out.StdoutConfig + *out = new(StepOutputConfig) + **out = **in + } + if in.StderrConfig != nil { + in, out := &in.StderrConfig, &out.StderrConfig + *out = new(StepOutputConfig) + **out = **in + } return } @@ -325,6 +335,22 @@ 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 *StepOutputConfig) DeepCopyInto(out *StepOutputConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StepOutputConfig. +func (in *StepOutputConfig) DeepCopy() *StepOutputConfig { + if in == nil { + return nil + } + out := new(StepOutputConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StepTemplate) DeepCopyInto(out *StepTemplate) { *out = *in