diff --git a/docs/artifacts.md b/docs/artifacts.md index 5b58d8b0471..1365dd1b1ed 100644 --- a/docs/artifacts.md +++ b/docs/artifacts.md @@ -155,6 +155,55 @@ spec: It is recommended to use [purl format](https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst) for artifacts uri as shown in the example. +### Output Artifacts in SLSA Provenance + +Artifacts are classified as either: + +- Build Outputs - packages, images, etc. that are being published by the build. +- Build Byproducts - logs, caches, etc. that are incidental artifacts that are produced by the build. + +By default, Tekton Chains will consider all output artifacts as `byProducts` when generating in the [SLSA provenance](https://slsa.dev/spec/v1.0/provenance). In order to treat an artifact as a [subject](https://slsa.dev/spec/v1.0/provenance#schema) of the build, you must set a boolean field `"buildOutput": true` for the output artifact. + +e.g. +```yaml +apiVersion: tekton.dev/v1 +kind: TaskRun +metadata: + generateName: step-artifacts- +spec: + taskSpec: + description: | + A simple task that populates artifacts to TaskRun stepState + steps: + - name: artifacts-producer + image: bash:latest + script: | + cat > $(artifacts.path) << EOF + { + "outputs":[ + { + "name":"image", + "buildOutput": true, + "values":[ + { + "uri":"pkg:oci/nginx:stable-alpine3.17-slim?repository_url=docker.io/library", + "digest":{ + "sha256":"df85b9e3983fe2ce20ef76ad675ecf435cc99fc9350adc54fa230bae8c32ce48", + "sha1":"95588b8f34c31eb7d62c92aaa4e6506639b06ef2" + } + } + ] + } + ] + } + EOF +``` + +This informs Tekton Chains your desire to handle the artifact. + +> [!TIP] +> When authoring a `StepAction` or a `Task`, you can parametrize this field to allow users to indicate their desire depending on what they are uploading - this can be useful for actions that may produce either a build output or a byproduct depending on the context! + ### Passing Artifacts between Steps You can pass artifacts from one step to the next using: - Specific Artifact: `$(steps..inputs.)` or `$(steps..outputs.)` diff --git a/docs/pipeline-api.md b/docs/pipeline-api.md index 44d7d3fcd53..270e78c133c 100644 --- a/docs/pipeline-api.md +++ b/docs/pipeline-api.md @@ -1370,6 +1370,7 @@ string +

The artifact’s identifying category name

@@ -1382,7 +1383,18 @@ string -

The artifact’s identifying category name

+

A collection of values related to the artifact

+ + + + +buildOutput
+ +bool + + + +

Indicate if the artifact is a build output or a by-product

@@ -10047,6 +10059,7 @@ string +

The artifact’s identifying category name

@@ -10059,7 +10072,18 @@ string -

The artifact’s identifying category name

+

A collection of values related to the artifact

+ + + + +buildOutput
+ +bool + + + +

Indicate if the artifact is a build output or a by-product

diff --git a/examples/v1/taskruns/alpha/produce-consume-artifacts.yaml b/examples/v1/taskruns/alpha/produce-consume-artifacts.yaml index 627530ef87f..3238649b2ea 100644 --- a/examples/v1/taskruns/alpha/produce-consume-artifacts.yaml +++ b/examples/v1/taskruns/alpha/produce-consume-artifacts.yaml @@ -6,6 +6,9 @@ spec: taskSpec: description: | A simple task that populates artifacts to TaskRun stepState + params: + - name: buildOutput + default: true steps: - name: artifacts-producer image: mirror.gcr.io/bash @@ -28,6 +31,7 @@ spec: "outputs":[ { "name":"image", + "buildOutput": $(params.buildOutput), "values":[ { "uri":"pkg:github/package-url/purl-spec@244fd47e07d1004f0aed9c", diff --git a/go.mod b/go.mod index 8904a8f7b08..f85747c91b4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/tektoncd/pipeline go 1.22 + toolchain go1.22.5 require ( diff --git a/pkg/apis/pipeline/v1/artifact_types.go b/pkg/apis/pipeline/v1/artifact_types.go index 1cd898d2ef5..21a0d8fc2f9 100644 --- a/pkg/apis/pipeline/v1/artifact_types.go +++ b/pkg/apis/pipeline/v1/artifact_types.go @@ -26,8 +26,12 @@ type Algorithm string // Artifact represents an artifact within a system, potentially containing multiple values // associated with it. type Artifact struct { - Name string `json:"name,omitempty"` // The artifact's identifying category name - Values []ArtifactValue `json:"values,omitempty"` // A collection of values related to the artifact + // The artifact's identifying category name + Name string `json:"name,omitempty"` + // A collection of values related to the artifact + Values []ArtifactValue `json:"values,omitempty"` + // Indicate if the artifact is a build output or a by-product + BuildOutput bool `json:"buildOutput,omitempty"` } // ArtifactValue represents a specific value or data element within an Artifact. @@ -82,35 +86,45 @@ func (a *Artifacts) Merge(another Artifacts) { }) } - outputMap := make(map[string][]ArtifactValue) + outputMap := make(map[string]Artifact) var newOutputs []Artifact for _, v := range a.Outputs { - outputMap[v.Name] = v.Values + outputMap[v.Name] = v } for _, v := range another.Outputs { _, ok := outputMap[v.Name] if !ok { - outputMap[v.Name] = []ArtifactValue{} + outputMap[v.Name] = Artifact{Name: v.Name, Values: []ArtifactValue{}, BuildOutput: v.BuildOutput} + } + // only update buildOutput to true. + // Do not convert to false if it was true before. + if v.BuildOutput { + art := outputMap[v.Name] + art.BuildOutput = v.BuildOutput + outputMap[v.Name] = art } for _, vv := range v.Values { exists := false - for _, av := range outputMap[v.Name] { + for _, av := range outputMap[v.Name].Values { if cmp.Equal(vv, av) { exists = true break } } if !exists { - outputMap[v.Name] = append(outputMap[v.Name], vv) + art := outputMap[v.Name] + art.Values = append(art.Values, vv) + outputMap[v.Name] = art } } } - for k, v := range outputMap { + for _, v := range outputMap { newOutputs = append(newOutputs, Artifact{ - Name: k, - Values: v, + Name: v.Name, + Values: v.Values, + BuildOutput: v.BuildOutput, }) } a.Inputs = newInputs diff --git a/pkg/apis/pipeline/v1/artifact_types_test.go b/pkg/apis/pipeline/v1/artifact_types_test.go index 895244d6363..30a35879c08 100644 --- a/pkg/apis/pipeline/v1/artifact_types_test.go +++ b/pkg/apis/pipeline/v1/artifact_types_test.go @@ -95,7 +95,8 @@ func TestArtifactsMerge(t *testing.T) { }, Outputs: []Artifact{ { - Name: "output1", + Name: "output1", + BuildOutput: true, Values: []ArtifactValue{ { Digest: map[Algorithm]string{"sha256": "698c4539633943f7889f41605003d7fa63833722ebd2b37c7e75df1d3d06941a"}, @@ -104,7 +105,8 @@ func TestArtifactsMerge(t *testing.T) { }, }, { - Name: "output2", + Name: "output2", + BuildOutput: true, Values: []ArtifactValue{ { Digest: map[Algorithm]string{"sha256": "7e406d83706c7193df3e38b66d350e55df6f13d2a28a1d35917a043533a70f5c"}, @@ -150,7 +152,8 @@ func TestArtifactsMerge(t *testing.T) { }, Outputs: []Artifact{ { - Name: "output1", + Name: "output1", + BuildOutput: true, Values: []ArtifactValue{ { Digest: map[Algorithm]string{"sha256": "47de7a85905970a45132f48a9247879a15c483477e23a637504694e611135b40e"}, @@ -163,7 +166,8 @@ func TestArtifactsMerge(t *testing.T) { }, }, { - Name: "output2", + Name: "output2", + BuildOutput: true, Values: []ArtifactValue{ { Digest: map[Algorithm]string{"sha256": "7e406d83706c7193df3e38b66d350e55df6f13d2a28a1d35917a043533a70f5c"}, diff --git a/pkg/apis/pipeline/v1/openapi_generated.go b/pkg/apis/pipeline/v1/openapi_generated.go index 47ec4d806fc..42bf6748b1d 100644 --- a/pkg/apis/pipeline/v1/openapi_generated.go +++ b/pkg/apis/pipeline/v1/openapi_generated.go @@ -395,13 +395,14 @@ func schema_pkg_apis_pipeline_v1_Artifact(ref common.ReferenceCallback) common.O Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", + Description: "The artifact's identifying category name", + Type: []string{"string"}, + Format: "", }, }, "values": { SchemaProps: spec.SchemaProps{ - Description: "The artifact's identifying category name", + Description: "A collection of values related to the artifact", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -413,6 +414,13 @@ func schema_pkg_apis_pipeline_v1_Artifact(ref common.ReferenceCallback) common.O }, }, }, + "buildOutput": { + SchemaProps: spec.SchemaProps{ + Description: "Indicate if the artifact is a build output or a by-product", + Type: []string{"boolean"}, + Format: "", + }, + }, }, }, }, diff --git a/pkg/apis/pipeline/v1/swagger.json b/pkg/apis/pipeline/v1/swagger.json index 844372e1491..584220d0b8c 100644 --- a/pkg/apis/pipeline/v1/swagger.json +++ b/pkg/apis/pipeline/v1/swagger.json @@ -155,11 +155,16 @@ "description": "TaskRunStepArtifact represents an artifact produced or used by a step within a task run. It directly uses the Artifact type for its structure.", "type": "object", "properties": { + "buildOutput": { + "description": "Indicate if the artifact is a build output or a by-product", + "type": "boolean" + }, "name": { + "description": "The artifact's identifying category name", "type": "string" }, "values": { - "description": "The artifact's identifying category name", + "description": "A collection of values related to the artifact", "type": "array", "items": { "default": {}, diff --git a/pkg/apis/pipeline/v1beta1/artifact_types.go b/pkg/apis/pipeline/v1beta1/artifact_types.go index ec50bde8a16..23778a90bbc 100644 --- a/pkg/apis/pipeline/v1beta1/artifact_types.go +++ b/pkg/apis/pipeline/v1beta1/artifact_types.go @@ -6,8 +6,12 @@ type Algorithm string // Artifact represents an artifact within a system, potentially containing multiple values // associated with it. type Artifact struct { - Name string `json:"name,omitempty"` // The artifact's identifying category name - Values []ArtifactValue `json:"values,omitempty"` // A collection of values related to the artifact + // The artifact's identifying category name + Name string `json:"name,omitempty"` + // A collection of values related to the artifact + Values []ArtifactValue `json:"values,omitempty"` + // Indicate if the artifact is a build output or a by-product + BuildOutput bool `json:"buildOutput,omitempty"` } // ArtifactValue represents a specific value or data element within an Artifact. diff --git a/pkg/apis/pipeline/v1beta1/openapi_generated.go b/pkg/apis/pipeline/v1beta1/openapi_generated.go index 6f4b8a5c912..0e2f436ca66 100644 --- a/pkg/apis/pipeline/v1beta1/openapi_generated.go +++ b/pkg/apis/pipeline/v1beta1/openapi_generated.go @@ -422,13 +422,14 @@ func schema_pkg_apis_pipeline_v1beta1_Artifact(ref common.ReferenceCallback) com Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", + Description: "The artifact's identifying category name", + Type: []string{"string"}, + Format: "", }, }, "values": { SchemaProps: spec.SchemaProps{ - Description: "The artifact's identifying category name", + Description: "A collection of values related to the artifact", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -440,6 +441,13 @@ func schema_pkg_apis_pipeline_v1beta1_Artifact(ref common.ReferenceCallback) com }, }, }, + "buildOutput": { + SchemaProps: spec.SchemaProps{ + Description: "Indicate if the artifact is a build output or a by-product", + Type: []string{"boolean"}, + Format: "", + }, + }, }, }, }, diff --git a/pkg/apis/pipeline/v1beta1/swagger.json b/pkg/apis/pipeline/v1beta1/swagger.json index 057cf2ad0ad..a0b5a9f9ddb 100644 --- a/pkg/apis/pipeline/v1beta1/swagger.json +++ b/pkg/apis/pipeline/v1beta1/swagger.json @@ -155,11 +155,16 @@ "description": "TaskRunStepArtifact represents an artifact produced or used by a step within a task run. It directly uses the Artifact type for its structure.", "type": "object", "properties": { + "buildOutput": { + "description": "Indicate if the artifact is a build output or a by-product", + "type": "boolean" + }, "name": { + "description": "The artifact's identifying category name", "type": "string" }, "values": { - "description": "The artifact's identifying category name", + "description": "A collection of values related to the artifact", "type": "array", "items": { "default": {}, diff --git a/test/artifacts_test.go b/test/artifacts_test.go index 1ae89116803..14bb523ea7b 100644 --- a/test/artifacts_test.go +++ b/test/artifacts_test.go @@ -117,7 +117,7 @@ spec: }}, taskrun.Status.Steps[0].Inputs); d != "" { t.Fatalf(`The expected stepState Inputs does not match created taskrun stepState Inputs. Here is the diff: %v`, d) } - if d := cmp.Diff([]v1.TaskRunStepArtifact{{Name: "image", + if d := cmp.Diff([]v1.TaskRunStepArtifact{{Name: "image", BuildOutput: true, Values: []v1.ArtifactValue{{Digest: map[v1.Algorithm]string{"sha1": "95588b8f34c31eb7d62c92aaa4e6506639b06ef2", "sha256": "df85b9e3983fe2ce20ef76ad675ecf435cc99fc9350adc54fa230bae8c32ce48"}, Uri: "pkg:balba", }}, @@ -191,7 +191,7 @@ spec: }}, taskrun.Status.Steps[0].Inputs); d != "" { t.Fatalf(`The expected stepState Inputs does not match created taskrun stepState Inputs. Here is the diff: %v`, d) } - if d := cmp.Diff([]v1.TaskRunStepArtifact{{Name: "build-result", + if d := cmp.Diff([]v1.TaskRunStepArtifact{{Name: "build-result", BuildOutput: false, Values: []v1.ArtifactValue{{Digest: map[v1.Algorithm]string{"sha1": "95588b8f34c31eb7d62c92aaa4e6506639b06ef2", "sha256": "df85b9e3983fe2ce20ef76ad675ecf435cc99fc9350adc54fa230bae8c32ce48"}, Uri: "pkg:balba", }}, @@ -425,6 +425,7 @@ spec: "outputs":[ { "name":"image", + "buildOutput":true, "values":[ { "uri":"pkg:balba", @@ -523,6 +524,7 @@ spec: "outputs":[ { "name":"build-result", + "buildOutput":false, "values":[ { "uri":"pkg:balba",