Skip to content

Commit

Permalink
kind/feat: passing artifacts between tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
ericzzzzzzz committed May 23, 2024
1 parent 5c40712 commit 1636de1
Show file tree
Hide file tree
Showing 23 changed files with 447 additions and 30 deletions.
17 changes: 17 additions & 0 deletions docs/pipeline-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,9 @@ string
</table>
<h3 id="tekton.dev/v1.Artifacts">Artifacts
</h3>
<p>
(<em>Appears on:</em><a href="#tekton.dev/v1.TaskRunStatusFields">TaskRunStatusFields</a>)
</p>
<div>
<p>Artifacts represents the collection of input and output artifacts associated with
a task run or a similar process. Artifacts in this context are units of data or resources
Expand Down Expand Up @@ -5856,6 +5859,20 @@ All TaskRunStatus stored in RetriesStatus will have no date within the RetriesSt
</tr>
<tr>
<td>
<code>artifacts</code><br/>
<em>
<a href="#tekton.dev/v1.Artifacts">
Artifacts
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Results are the list of artifacts written out by the task&rsquo;s containers</p>
</td>
</tr>
<tr>
<td>
<code>sidecars</code><br/>
<em>
<a href="#tekton.dev/v1.SidecarState">
Expand Down
57 changes: 57 additions & 0 deletions examples/v1/pipelineruns/alpha/consume-artifacts-from-task.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
generateName: pipelinerun-consume-tasks-artifacts
spec:
pipelineSpec:
tasks:
- name: produce-artifacts-task
taskSpec:
description: |
A simple task that produces artifacts
steps:
- name: produce-artifacts
image: bash:latest
script: |
#!/usr/bin/env bash
cat > $(artifacts.path) << EOF
{
"inputs":[
{
"name":"input-artifacts",
"values":[
{
"uri":"pkg:example.github.com/inputs",
"digest":{
"sha256":"b35cacccfdb1e24dc497d15d553891345fd155713ffe647c281c583269eaaae0"
}
}
]
}
],
"outputs":[
{
"name":"image",
"values":[
{
"uri":"pkg:github/package-url/purl-spec@244fd47e07d1004f0aed9c",
"digest":{
"sha256":"df85b9e3983fe2ce20ef76ad675ecf435cc99fc9350adc54fa230bae8c32ce48",
"sha1":"95588b8f34c31eb7d62c92aaa4e6506639b06ef2"
}
}
]
}
]
}
EOF
- name: consume-artifacts
runAfter:
- produce-artifacts-task
taskSpec:
steps:
- name: write
image: bash:latest
script: |
#!/usr/bin/env bash
echo $(tasks.produce-artifacts-task.outputs)
44 changes: 44 additions & 0 deletions examples/v1/taskruns/alpha/task-artifacts.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
generateName: task-run-artifacts
spec:
taskSpec:
description: |
A simple task that produces artifacts
steps:
- name: produce-artifacts
image: bash:latest
script: |
#!/usr/bin/env bash
cat > $(artifacts.path) << EOF
{
"inputs":[
{
"name":"input-artifacts",
"values":[
{
"uri":"pkg:example.github.com/inputs",
"digest":{
"sha256":"b35cacccfdb1e24dc497d15d553891345fd155713ffe647c281c583269eaaae0"
}
}
]
}
],
"outputs":[
{
"name":"image",
"values":[
{
"uri":"pkg:github/package-url/purl-spec@244fd47e07d1004f0aed9c",
"digest":{
"sha256":"df85b9e3983fe2ce20ef76ad675ecf435cc99fc9350adc54fa230bae8c32ce48",
"sha1":"95588b8f34c31eb7d62c92aaa4e6506639b06ef2"
}
}
]
}
]
}
EOF
7 changes: 7 additions & 0 deletions internal/artifactref/artifactref.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,11 @@ import "regexp"
// case 4: steps.<step-name>.outputs.<artifact-category-name>
const stepArtifactUsagePattern = `\$\(steps\.([^.]+)\.(?:inputs|outputs)(?:\.([^.^\)]+))?\)`

// case 1: tasks.<task-name>.inputs
// case 2: tasks.<task-name>.outputs
// case 3: tasks.<task-name>.inputs.<artifact-category-name>
// case 4: tasks.<task-name>.outputs.<artifact-category-name>
const taskArtifactUsagePattern = `\$\(tasks\.([^.]+)\.(?:inputs|outputs)(?:\.([^.^\)]+))?\)`

var StepArtifactRegex = regexp.MustCompile(stepArtifactUsagePattern)
var TaskArtifactRegex = regexp.MustCompile(taskArtifactUsagePattern)
2 changes: 2 additions & 0 deletions pkg/apis/pipeline/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ const (
StepsDir = "/tekton/steps"

ScriptDir = "/tekton/scripts"

ArtifactsDir = "/tekton/artifacts"
)
73 changes: 73 additions & 0 deletions pkg/apis/pipeline/v1/artifact_types.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package v1

import (
"github.com/google/go-cmp/cmp"
)

// Algorithm Standard cryptographic hash algorithm
type Algorithm string

Expand Down Expand Up @@ -27,3 +31,72 @@ type Artifacts struct {
Inputs []Artifact `json:"inputs,omitempty"`
Outputs []Artifact `json:"outputs,omitempty"`
}

func (a *Artifacts) Merge(another Artifacts) {
inputMap := make(map[string][]ArtifactValue)
var newInputs []Artifact

for _, v := range a.Inputs {
inputMap[v.Name] = v.Values
}

for _, v := range another.Inputs {
_, ok := inputMap[v.Name]
if !ok {
inputMap[v.Name] = []ArtifactValue{}
}
for _, vv := range v.Values {
exists := false
for _, av := range inputMap[v.Name] {
if cmp.Equal(vv, av) {
exists = true
break
}
}
if !exists {
inputMap[v.Name] = append(inputMap[v.Name], vv)
}
}
}

for k, v := range inputMap {
newInputs = append(newInputs, Artifact{
Name: k,
Values: v,
})
}

outputMap := make(map[string][]ArtifactValue)
var newOutputs []Artifact
for _, v := range a.Outputs {
outputMap[v.Name] = v.Values
}

for _, v := range another.Outputs {
_, ok := outputMap[v.Name]
if !ok {
outputMap[v.Name] = []ArtifactValue{}
}
for _, vv := range v.Values {
exists := false
for _, av := range outputMap[v.Name] {
if cmp.Equal(vv, av) {
exists = true
break
}
}
if !exists {
outputMap[v.Name] = append(outputMap[v.Name], vv)
}
}
}

for k, v := range outputMap {
newOutputs = append(newOutputs, Artifact{
Name: k,
Values: v,
})
}
a.Inputs = newInputs
a.Outputs = newOutputs
}
28 changes: 26 additions & 2 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.

24 changes: 24 additions & 0 deletions pkg/apis/pipeline/v1/pipeline_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"slices"
"strings"

"github.com/tektoncd/pipeline/internal/artifactref"
"github.com/tektoncd/pipeline/pkg/apis/config"
"github.com/tektoncd/pipeline/pkg/apis/validate"
"github.com/tektoncd/pipeline/pkg/internal/resultref"
Expand Down Expand Up @@ -89,6 +90,7 @@ func (ps *PipelineSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
errs = errs.Also(validateTasksAndFinallySection(ps))
errs = errs.Also(validateFinalTasks(ps.Tasks, ps.Finally))
errs = errs.Also(validateWhenExpressions(ctx, ps.Tasks, ps.Finally))
errs = errs.Also(validateArtifactReference(ctx, ps.Tasks, ps.Finally))
errs = errs.Also(validateMatrix(ctx, ps.Tasks).ViaField("tasks"))
errs = errs.Also(validateMatrix(ctx, ps.Finally).ViaField("finally"))
return errs
Expand Down Expand Up @@ -882,6 +884,28 @@ func validateStringResults(results []TaskResult, resultName string) (errs *apis.
return errs
}

// validateArtifactReference ensure that the feature flag enableArtifacts is set to true when using artifacts
func validateArtifactReference(ctx context.Context, tasks []PipelineTask, finalTasks []PipelineTask) (errs *apis.FieldError) {
if config.FromContextOrDefaults(ctx).FeatureFlags.EnableArtifacts {
return errs
}
for _, t := range tasks {
for _, v := range t.Params.extractValues() {
if len(artifactref.TaskArtifactRegex.FindAllStringSubmatch(v, -1)) > 0 {
return errs.Also(apis.ErrGeneric(fmt.Sprintf("feature flag %s should be set to true to use artifacts feature.", config.EnableArtifacts), ""))
}
}
}
for _, t := range finalTasks {
for _, v := range t.Params.extractValues() {
if len(artifactref.TaskArtifactRegex.FindAllStringSubmatch(v, -1)) > 0 {
return errs.Also(apis.ErrGeneric(fmt.Sprintf("feature flag %s should be set to true to use artifacts feature.", config.EnableArtifacts), ""))
}
}
}
return errs
}

// GetIndexingReferencesToArrayParams returns all strings referencing indices of PipelineRun array parameters
// from parameters, workspaces, and when expressions defined in the Pipeline's Tasks and Finally Tasks.
// For example, if a Task in the Pipeline has a parameter with a value "$(params.array-param-name[1])",
Expand Down
12 changes: 12 additions & 0 deletions pkg/apis/pipeline/v1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -2108,6 +2108,12 @@
"default": ""
}
},
"artifacts": {
"description": "Results are the list of artifacts written out by the task's containers",
"default": {},
"$ref": "#/definitions/v1.Artifacts",
"x-kubernetes-list-type": "atomic"
},
"completionTime": {
"description": "CompletionTime is the time the build completed.",
"$ref": "#/definitions/v1.Time"
Expand Down Expand Up @@ -2197,6 +2203,12 @@
"podName"
],
"properties": {
"artifacts": {
"description": "Results are the list of artifacts written out by the task's containers",
"default": {},
"$ref": "#/definitions/v1.Artifacts",
"x-kubernetes-list-type": "atomic"
},
"completionTime": {
"description": "CompletionTime is the time the build completed.",
"$ref": "#/definitions/v1.Time"
Expand Down
5 changes: 4 additions & 1 deletion pkg/apis/pipeline/v1/task_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,9 @@ func errorIfStepResultReferenceinField(value, fieldName string) (errs *apis.Fiel
func stepArtifactReferenceExists(src string) bool {
return len(artifactref.StepArtifactRegex.FindAllStringSubmatch(src, -1)) > 0 || strings.Contains(src, "$(step.artifacts.path)")
}
func taskArtifactReferenceExists(src string) bool {
return len(artifactref.TaskArtifactRegex.FindAllStringSubmatch(src, -1)) > 0 || strings.Contains(src, "$(task.artifacts.path)")
}
func errorIfStepArtifactReferencedInField(value, fieldName string) (errs *apis.FieldError) {
if stepArtifactReferenceExists(value) {
errs = errs.Also(&apis.FieldError{
Expand Down Expand Up @@ -386,7 +389,7 @@ func validateStep(ctx context.Context, s Step, names sets.String) (errs *apis.Fi
for _, e := range s.Env {
t = append(t, e.Value)
}
if slices.ContainsFunc(t, stepArtifactReferenceExists) {
if slices.ContainsFunc(t, stepArtifactReferenceExists) || slices.ContainsFunc(t, taskArtifactReferenceExists) {
return errs.Also(apis.ErrGeneric(fmt.Sprintf("feature flag %s should be set to true to use artifacts feature.", config.EnableArtifacts), ""))
}
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/pipeline/v1/taskrun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,11 @@ type TaskRunStatusFields struct {
// +listType=atomic
Results []TaskRunResult `json:"results,omitempty"`

// Results are the list of artifacts written out by the task's containers
// +optional
// +listType=atomic
Artifacts Artifacts `json:"artifacts,omitempty"`

// The list has one entry per sidecar in the manifest. Each entry is
// represents the imageid of the corresponding sidecar.
// +listType=atomic
Expand Down
Loading

0 comments on commit 1636de1

Please sign in to comment.