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 committed Jul 8, 2024
1 parent 0db5ca2 commit 5555e6c
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 19 deletions.
76 changes: 76 additions & 0 deletions docs/artifacts.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,82 @@ 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
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 `"isBuildArtifact": 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",
"isBuildArtifact": 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.

When authoring a `StepAction` or a `Task`, you can even parametrize this field and let the user of the `StepAction`/`Task` indicate their desire. Generally, things like "gcs-upload" might want to set this value by default to `false`, however, a `StepAction/Task` like "kaniko" (since it is uploading images to container registries, which are normally build products) might want to set the default to `true`.

e.g.

```yaml
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: artifact-producer
spec:
params:
- name: isBuildArtifact
default: true
steps:
image: bash:latest
script: |
cat > $(artifacts.path) << EOF
{
"outputs":[
{
"name":"image",
"isBuildArtifact": $(params.isBuildArtifact),
"values":[
{
"uri":"pkg:oci/nginx:stable-alpine3.17-slim?repository_url=docker.io/library",
"digest":{
"sha256":"df85b9e3983fe2ce20ef76ad675ecf435cc99fc9350adc54fa230bae8c32ce48",
"sha1":"95588b8f34c31eb7d62c92aaa4e6506639b06ef2"
}
}
]
}
]
}
EOF
```

### 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
22 changes: 22 additions & 0 deletions docs/pipeline-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1385,6 +1385,17 @@ string
<p>The artifact&rsquo;s identifying category name</p>
</td>
</tr>
<tr>
<td>
<code>isBuildArtifact</code><br/>
<em>
bool
</em>
</td>
<td>
<p>A collection of values related to the artifact</p>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1.ArtifactValue">ArtifactValue
Expand Down Expand Up @@ -10033,6 +10044,17 @@ string
<p>The artifact&rsquo;s identifying category name</p>
</td>
</tr>
<tr>
<td>
<code>isBuildArtifact</code><br/>
<em>
bool
</em>
</td>
<td>
<p>A collection of values related to the artifact</p>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1beta1.ArtifactValue">ArtifactValue
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: isBuildArtifact
default: true
steps:
- name: artifacts-producer
image: docker.io/library/bash:latest
Expand All @@ -28,6 +31,7 @@ spec:
"outputs":[
{
"name":"image",
"isBuildArtifact": $(params.isBuildArtifact),
"values":[
{
"uri":"pkg:github/package-url/purl-spec@244fd47e07d1004f0aed9c",
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module github.com/tektoncd/pipeline

go 1.22
go 1.22.3

toolchain go1.22.4

require (
Expand Down
31 changes: 21 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,9 @@ 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
Name string `json:"name,omitempty"` // The artifact's identifying category name
Values []ArtifactValue `json:"values,omitempty"` // A collection of values related to the artifact
IsBuildArtifact bool `json:"isBuildArtifact,omitempty"` // Indicate if the artifact is a build artifact or a by product
}

// ArtifactValue represents a specific value or data element within an Artifact.
Expand Down Expand Up @@ -82,35 +83,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{}, IsBuildArtifact: v.IsBuildArtifact}
}
// only update isBuildArtifact to true.
// Do not convert to false if it was true before.
if v.IsBuildArtifact {
art := outputMap[v.Name]
art.IsBuildArtifact = v.IsBuildArtifact
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,
IsBuildArtifact: v.IsBuildArtifact,
})
}
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",
IsBuildArtifact: true,
Values: []ArtifactValue{
{
Digest: map[Algorithm]string{"sha256": "698c4539633943f7889f41605003d7fa63833722ebd2b37c7e75df1d3d06941a"},
Expand All @@ -104,7 +105,8 @@ func TestArtifactsMerge(t *testing.T) {
},
},
{
Name: "output2",
Name: "output2",
IsBuildArtifact: 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",
IsBuildArtifact: true,
Values: []ArtifactValue{
{
Digest: map[Algorithm]string{"sha256": "47de7a85905970a45132f48a9247879a15c483477e23a637504694e611135b40e"},
Expand All @@ -163,7 +166,8 @@ func TestArtifactsMerge(t *testing.T) {
},
},
{
Name: "output2",
Name: "output2",
IsBuildArtifact: true,
Values: []ArtifactValue{
{
Digest: map[Algorithm]string{"sha256": "7e406d83706c7193df3e38b66d350e55df6f13d2a28a1d35917a043533a70f5c"},
Expand Down
7 changes: 7 additions & 0 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.

4 changes: 4 additions & 0 deletions pkg/apis/pipeline/v1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@
"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": {
"isBuildArtifact": {
"description": "A collection of values related to the artifact",
"type": "boolean"
},
"name": {
"type": "string"
},
Expand Down
5 changes: 3 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,9 @@ 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
Name string `json:"name,omitempty"` // The artifact's identifying category name
Values []ArtifactValue `json:"values,omitempty"` // A collection of values related to the artifact
IsBuildArtifact bool `json:"isBuildArtifact,omitempty"` // Indicate if the artifact is a build artifact or a by product
}

// ArtifactValue represents a specific value or data element within an Artifact.
Expand Down
7 changes: 7 additions & 0 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.

4 changes: 4 additions & 0 deletions pkg/apis/pipeline/v1beta1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@
"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": {
"isBuildArtifact": {
"description": "A collection of values related to the artifact",
"type": "boolean"
},
"name": {
"type": "string"
},
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", IsBuildArtifact: 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", IsBuildArtifact: 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",
"isBuildArtifact":true,
"values":[
{
"uri":"pkg:balba",
Expand Down Expand Up @@ -523,6 +524,7 @@ spec:
"outputs":[
{
"name":"build-result",
"isBuildArtifact":false,
"values":[
{
"uri":"pkg:balba",
Expand Down

0 comments on commit 5555e6c

Please sign in to comment.