Skip to content

Commit

Permalink
Add isBuildArtifact field to Artifacts
Browse files Browse the repository at this point in the history
This PR adds isBuildArtifact field to Artifacts.
This field will allow Tekton Chains to understand user's desire
and appropriate add the artifact as a subject or a byProduct in
the SLSA provenance.
  • Loading branch information
chitrangpatel authored and tekton-robot committed Jul 24, 2024
1 parent a9b204e commit db4ac21
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 28 deletions.
49 changes: 49 additions & 0 deletions docs/artifacts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<step-name>.inputs.<artifact-category-name>)` or `$(steps.<step-name>.outputs.<artifact-category-name>)`
Expand Down
28 changes: 26 additions & 2 deletions docs/pipeline-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1370,6 +1370,7 @@ string
</em>
</td>
<td>
<p>The artifact&rsquo;s identifying category name</p>
</td>
</tr>
<tr>
Expand All @@ -1382,7 +1383,18 @@ string
</em>
</td>
<td>
<p>The artifact&rsquo;s identifying category name</p>
<p>A collection of values related to the artifact</p>
</td>
</tr>
<tr>
<td>
<code>buildOutput</code><br/>
<em>
bool
</em>
</td>
<td>
<p>Indicate if the artifact is a build output or a by-product</p>
</td>
</tr>
</tbody>
Expand Down Expand Up @@ -10047,6 +10059,7 @@ string
</em>
</td>
<td>
<p>The artifact&rsquo;s identifying category name</p>
</td>
</tr>
<tr>
Expand All @@ -10059,7 +10072,18 @@ string
</em>
</td>
<td>
<p>The artifact&rsquo;s identifying category name</p>
<p>A collection of values related to the artifact</p>
</td>
</tr>
<tr>
<td>
<code>buildOutput</code><br/>
<em>
bool
</em>
</td>
<td>
<p>Indicate if the artifact is a build output or a by-product</p>
</td>
</tr>
</tbody>
Expand Down
4 changes: 4 additions & 0 deletions examples/v1/taskruns/alpha/produce-consume-artifacts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,6 +31,7 @@ spec:
"outputs":[
{
"name":"image",
"buildOutput": $(params.buildOutput),
"values":[
{
"uri":"pkg:github/package-url/purl-spec@244fd47e07d1004f0aed9c",
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module github.com/tektoncd/pipeline

go 1.22

toolchain go1.22.5

require (
Expand Down
34 changes: 24 additions & 10 deletions pkg/apis/pipeline/v1/artifact_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions pkg/apis/pipeline/v1/artifact_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand All @@ -104,7 +105,8 @@ func TestArtifactsMerge(t *testing.T) {
},
},
{
Name: "output2",
Name: "output2",
BuildOutput: true,
Values: []ArtifactValue{
{
Digest: map[Algorithm]string{"sha256": "7e406d83706c7193df3e38b66d350e55df6f13d2a28a1d35917a043533a70f5c"},
Expand Down Expand Up @@ -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"},
Expand All @@ -163,7 +166,8 @@ func TestArtifactsMerge(t *testing.T) {
},
},
{
Name: "output2",
Name: "output2",
BuildOutput: true,
Values: []ArtifactValue{
{
Digest: map[Algorithm]string{"sha256": "7e406d83706c7193df3e38b66d350e55df6f13d2a28a1d35917a043533a70f5c"},
Expand Down
14 changes: 11 additions & 3 deletions pkg/apis/pipeline/v1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion pkg/apis/pipeline/v1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {},
Expand Down
8 changes: 6 additions & 2 deletions pkg/apis/pipeline/v1beta1/artifact_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 11 additions & 3 deletions pkg/apis/pipeline/v1beta1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion pkg/apis/pipeline/v1beta1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {},
Expand Down
6 changes: 4 additions & 2 deletions test/artifacts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}},
Expand Down Expand Up @@ -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",
}},
Expand Down Expand Up @@ -425,6 +425,7 @@ spec:
"outputs":[
{
"name":"image",
"buildOutput":true,
"values":[
{
"uri":"pkg:balba",
Expand Down Expand Up @@ -523,6 +524,7 @@ spec:
"outputs":[
{
"name":"build-result",
"buildOutput":false,
"values":[
{
"uri":"pkg:balba",
Expand Down

0 comments on commit db4ac21

Please sign in to comment.