Skip to content

Commit

Permalink
TEP-0106: Support Specifying Metadata per Task in Runtime
Browse files Browse the repository at this point in the history
While currently Tekton supports specifying metadata for a Task in a Pipeline during "authoring time", specific requirements from a running context lead to this work to offer a flexibility to add metadata during "runtime".

The chosen approach is to add a Metadata field under PipelineTaskRunSpec as for its proper runtime context and easier for a user to follow and utilize, rather than a whole new field. Further consideration and tradeoff can be found in the TEP-0106.
  • Loading branch information
austinzhao-go committed May 11, 2022
1 parent 1c8447d commit 4efa93f
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 41 deletions.
52 changes: 35 additions & 17 deletions docs/pipelineruns.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,28 @@ weight: 500
<!-- toc -->
- [PipelineRuns](#pipelineruns)
- [Overview](#overview)
- [Configuring a <code>PipelineRun</code>](#configuring-a-pipelinerun)
- [Specifying the target <code>Pipeline</code>](#specifying-the-target-pipeline)
- [Configuring a `PipelineRun`](#configuring-a-pipelinerun)
- [Specifying the target `Pipeline`](#specifying-the-target-pipeline)
- [Tekton Bundles](#tekton-bundles)
- [Remote Pipelines](#remote-pipelines)
- [Specifying <code>Resources</code>](#specifying-resources)
- [Specifying <code>Parameters</code>](#specifying-parameters)
- [Specifying `Resources`](#specifying-resources)
- [Specifying `Parameters`](#specifying-parameters)
- [Implicit Parameters](#implicit-parameters)
- [Specifying custom <code>ServiceAccount</code> credentials](#specifying-custom-serviceaccount-credentials)
- [Mapping <code>ServiceAccount</code> credentials to <code>Tasks</code>](#mapping-serviceaccount-credentials-to-tasks)
- [Specifying a <code>Pod</code> template](#specifying-a-pod-template)
- [Specifying custom `ServiceAccount` credentials](#specifying-custom-serviceaccount-credentials)
- [Mapping `ServiceAccount` credentials to `Tasks`](#mapping-serviceaccount-credentials-to-tasks)
- [Specifying a `Pod` template](#specifying-a-pod-template)
- [Specifying taskRunSpecs](#specifying-taskrunspecs)
- [Specifying <code>Workspaces</code>](#specifying-workspaces)
- [Specifying <code>LimitRange</code> values](#specifying-limitrange-values)
- [Specifying `Workspaces`](#specifying-workspaces)
- [Specifying `LimitRange` values](#specifying-limitrange-values)
- [Configuring a failure timeout](#configuring-a-failure-timeout)
- [<code>PipelineRun</code> status](#pipelinerun-status)
- [The <code>status</code> field](#the-status-field)
- [Configuring usage of <code>TaskRun</code> and <code>Run</code> embedded statuses](#configuring-usage-of-taskrun-and-run-embedded-statuses)
- [`PipelineRun` status](#pipelinerun-status)
- [The `status` field](#the-status-field)
- [Configuring usage of `TaskRun` and `Run` embedded statuses](#configuring-usage-of-taskrun-and-run-embedded-statuses)
- [Monitoring execution status](#monitoring-execution-status)
- [Cancelling a <code>PipelineRun</code>](#cancelling-a-pipelinerun)
- [Gracefully cancelling a <code>PipelineRun</code>](#gracefully-cancelling-a-pipelinerun)
- [Gracefully stopping a <code>PipelineRun</code>](#gracefully-stopping-a-pipelinerun)
- [Pending <code>PipelineRuns</code>](#pending-pipelineruns)
- [Cancelling a `PipelineRun`](#cancelling-a-pipelinerun)
- [Gracefully cancelling a `PipelineRun`](#gracefully-cancelling-a-pipelinerun)
- [Gracefully stopping a `PipelineRun`](#gracefully-stopping-a-pipelinerun)
- [Pending `PipelineRuns`](#pending-pipelineruns)
<!-- /toc -->


Expand Down Expand Up @@ -71,7 +71,7 @@ A `PipelineRun` definition supports the following fields:
- [`serviceAccountNames`](#mapping-serviceaccount-credentials-to-tasks) - Maps specific `serviceAccountName` values
to `Tasks` in the `Pipeline`. This overrides the credentials set for the entire `Pipeline`.
- [`status`](#cancelling-a-pipelinerun) - Specifies options for cancelling a `PipelineRun`.
- [`taskRunSpecs`](#specifying-taskrunspecs) - Specifies a list of `PipelineRunTaskSpec` which allows for setting `ServiceAccountName` and [`Pod` template](./podtemplates.md) for each task. This overrides the `Pod` template set for the entire `Pipeline`.
- [`taskRunSpecs`](#specifying-taskrunspecs) - Specifies a list of `PipelineRunTaskSpec` which allows for setting `ServiceAccountName`, [`Pod` template](./podtemplates.md), and `Metadata` for each task. This overrides the `Pod` template set for the entire `Pipeline`.
- [`timeout`](#configuring-a-failure-timeout) - Specifies the timeout before the `PipelineRun` fails. `timeout` is deprecated and will eventually be removed, so consider using `timeouts` instead.
- [`timeouts`](#configuring-a-failure-timeout) - Specifies the timeout before the `PipelineRun` fails. `timeouts` allows more granular timeout configuration, at the pipeline, tasks, and finally levels
- [`podTemplate`](#specifying-a-pod-template) - Specifies a [`Pod` template](./podtemplates.md) to use as the basis for the configuration of the `Pod` that executes each `Task`.
Expand Down Expand Up @@ -480,6 +480,24 @@ If used with this `Pipeline`, `build-task` will use the task specific `PodTempl
`PipelineTaskRunSpec` may also contain `StepOverrides` and `SidecarOverrides`; see
[Overriding `Task` `Steps` and `Sidecars`](./taskruns.md#overriding-task-steps-and-sidecars) for more information.

The optional annotations and labels can be added under a `Metadata` field as for a specific running context.

An example for rendering needed secrets with Vault:

```yaml
spec:
pipelineRef:
name: pipeline-name
taskRunSpecs:
- pipelineTaskName: task-name
metadata:
annotations:
vault.hashicorp.com/agent-inject-secret-foo: "/path/to/foo"
vault.hashicorp.com/role: role-name
```

If the same key is present, the value will be kept by the precedence order as `PipelineRun.spec.taskRunSpec.metadata` > `PipelineRun.metadata` > `Pipeline.spec.tasks.taskSpec.metadata`.

### Specifying `Workspaces`

If your `Pipeline` specifies one or more `Workspaces`, you must map those `Workspaces` to
Expand Down
8 changes: 7 additions & 1 deletion 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/pipelinerun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,9 @@ type PipelineTaskRunSpec struct {
StepOverrides []TaskRunStepOverride `json:"stepOverrides,omitempty"`
// +listType=atomic
SidecarOverrides []TaskRunSidecarOverride `json:"sidecarOverrides,omitempty"`

// +optional
Metadata PipelineTaskMetadata `json:"metadata,omitempty"`
}

// GetTaskRunSpec returns the task specific spec for a given
Expand All @@ -621,6 +624,7 @@ func (pr *PipelineRun) GetTaskRunSpec(pipelineTaskName string) PipelineTaskRunSp
}
s.StepOverrides = task.StepOverrides
s.SidecarOverrides = task.SidecarOverrides
s.Metadata = task.Metadata
}
}
return s
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/pipeline/v1beta1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1609,6 +1609,10 @@
"description": "PipelineTaskRunSpec can be used to configure specific specs for a concrete Task",
"type": "object",
"properties": {
"metadata": {
"default": {},
"$ref": "#/definitions/v1beta1.PipelineTaskMetadata"
},
"pipelineTaskName": {
"type": "string"
},
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go

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

46 changes: 23 additions & 23 deletions pkg/reconciler/pipelinerun/pipelinerun.go
Original file line number Diff line number Diff line change
Expand Up @@ -988,43 +988,43 @@ func getTaskrunLabels(pr *v1beta1.PipelineRun, pipelineTaskName string, includeP
}

func combineTaskRunAndTaskSpecLabels(pr *v1beta1.PipelineRun, pipelineTask *v1beta1.PipelineTask) map[string]string {
var tsLabels map[string]string
trLabels := getTaskrunLabels(pr, pipelineTask.Name, true)
labels := make(map[string]string)

taskRunSpec := pr.GetTaskRunSpec(pipelineTask.Name)
addMetadataByPrecedence(labels, taskRunSpec.Metadata.Labels)

addMetadataByPrecedence(labels, getTaskrunLabels(pr, pipelineTask.Name, true))

if pipelineTask.TaskSpec != nil {
tsLabels = pipelineTask.TaskSpecMetadata().Labels
addMetadataByPrecedence(labels, pipelineTask.TaskSpecMetadata().Labels)
}

// labels from TaskRun takes higher precedence over the ones specified in Pipeline through TaskSpec
// initialize labels with TaskRun labels
labels := trLabels
for key, value := range tsLabels {
// add labels from TaskSpec if the label does not exist
if _, ok := labels[key]; !ok {
labels[key] = value
}
}
return labels
}

func combineTaskRunAndTaskSpecAnnotations(pr *v1beta1.PipelineRun, pipelineTask *v1beta1.PipelineTask) map[string]string {
var tsAnnotations map[string]string
trAnnotations := getTaskrunAnnotations(pr)
annotations := make(map[string]string)

taskRunSpec := pr.GetTaskRunSpec(pipelineTask.Name)
addMetadataByPrecedence(annotations, taskRunSpec.Metadata.Annotations)

addMetadataByPrecedence(annotations, getTaskrunAnnotations(pr))

if pipelineTask.TaskSpec != nil {
tsAnnotations = pipelineTask.TaskSpecMetadata().Annotations
addMetadataByPrecedence(annotations, pipelineTask.TaskSpecMetadata().Annotations)
}

// annotations from TaskRun takes higher precedence over the ones specified in Pipeline through TaskSpec
// initialize annotations with TaskRun annotations
annotations := trAnnotations
for key, value := range tsAnnotations {
// add annotations from TaskSpec if the annotation does not exist
if _, ok := annotations[key]; !ok {
annotations[key] = value
return annotations
}

// addMetadataByPrecedence() adds the elements in addedMetadata to metadata. If the same key is present in both maps, the value from metadata will be used.
func addMetadataByPrecedence(metadata map[string]string, addedMetadata map[string]string) {
for key, value := range addedMetadata {
// add new annotations if the key not exists in current ones
if _, ok := metadata[key]; !ok {
metadata[key] = value
}
}
return annotations
}

// getFinallyTaskRunTimeout returns the timeout to set when creating the ResolvedPipelineRunTask, which is a finally Task.
Expand Down
123 changes: 123 additions & 0 deletions pkg/reconciler/pipelinerun/pipelinerun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7395,3 +7395,126 @@ func checkPipelineRunConditionStatusAndReason(t *testing.T, reconciledRun *v1bet
t.Errorf("Expected reason %s but was %s", conditionReason, condition.Reason)
}
}

func TestReconcile_PropagatePipelineTaskRunSpecMetadata(t *testing.T) {
names.TestingSeed()
prName := "test-pipeline-run"
ps := []*v1beta1.Pipeline{simpleHelloWorldPipeline}
prs := []*v1beta1.PipelineRun{parse.MustParsePipelineRun(t, `
metadata:
name: test-pipeline-run
namespace: foo
spec:
pipelineRef:
name: test-pipeline
taskRunSpecs:
- pipelineTaskName: hello-world-1
metadata:
labels:
PipelineTaskRunSpecLabel: PipelineTaskRunSpecValue
annotations:
PipelineTaskRunSpecAnnotation: PipelineTaskRunSpecValue
taskServiceAccountName: custom-sa
`)}
ts := []*v1beta1.Task{simpleHelloWorldTask}

d := test.Data{
PipelineRuns: prs,
Pipelines: ps,
Tasks: ts,
}
prt := newPipelineRunTest(d, t)
defer prt.Cancel()

_, clients := prt.reconcileRun("foo", prName, []string{}, false)

actual := getTaskRunCreations(t, clients.Pipeline.Actions(), 2)[0]
expectedTaskRunObjectMeta := taskRunObjectMeta("test-pipeline-run-hello-world-1", "foo", "test-pipeline-run", "test-pipeline", "hello-world-1", false)
expectedTaskRunObjectMeta.Labels["PipelineTaskRunSpecLabel"] = "PipelineTaskRunSpecValue"
expectedTaskRunObjectMeta.Annotations["PipelineTaskRunSpecAnnotation"] = "PipelineTaskRunSpecValue"
expectedTaskRun := mustParseTaskRunWithObjectMeta(t, expectedTaskRunObjectMeta, `
spec:
resources: {}
serviceAccountName: custom-sa
taskRef:
name: hello-world
timeout: 1h0m0s
`)

if d := cmp.Diff(actual, expectedTaskRun, ignoreTypeMeta); d != "" {
t.Errorf("expected to see propagated metadata from PipelineTaskRunSpec in TaskRun %v created. Diff %s", expectedTaskRun, diff.PrintWantGot(d))
}
}

func TestReconcile_AddMetadataByPrecedence(t *testing.T) {
names.TestingSeed()
prName := "test-pipeline-run"
ps := []*v1beta1.Pipeline{parse.MustParsePipeline(t, `
metadata:
name: test-pipeline
namespace: foo
spec:
tasks:
- name: hello-world-1
taskSpec:
steps:
- name: foo-step
image: foo-image
metadata:
labels:
TestPrecedenceLabel: PipelineTaskSpecValue
annotations:
TestPrecedenceAnnotation: PipelineTaskSpecValue
`)}
prs := []*v1beta1.PipelineRun{parse.MustParsePipelineRun(t, `
metadata:
name: test-pipeline-run
namespace: foo
metadata:
labels:
TestPrecedenceLabel: PipelineRunValue
annotations:
TestPrecedenceAnnotation: PipelineRunValue
spec:
pipelineRef:
name: test-pipeline
taskRunSpecs:
- pipelineTaskName: hello-world-1
metadata:
labels:
TestPrecedenceLabel: PipelineTaskRunSpecValue
annotations:
TestPrecedenceAnnotation: PipelineTaskRunSpecValue
taskServiceAccountName: custom-sa
`)}
ts := []*v1beta1.Task{simpleHelloWorldTask}

d := test.Data{
PipelineRuns: prs,
Pipelines: ps,
Tasks: ts,
}
prt := newPipelineRunTest(d, t)
defer prt.Cancel()

_, clients := prt.reconcileRun("foo", prName, []string{}, false)

actual := getTaskRunCreations(t, clients.Pipeline.Actions(), 2)[0]
expectedTaskRunObjectMeta := taskRunObjectMeta("test-pipeline-run-hello-world-1", "foo", "test-pipeline-run", "test-pipeline", "hello-world-1", false)
expectedTaskRunObjectMeta.Labels["TestPrecedenceLabel"] = "PipelineTaskRunSpecValue"
expectedTaskRunObjectMeta.Annotations["TestPrecedenceAnnotation"] = "PipelineTaskRunSpecValue"
expectedTaskRun := mustParseTaskRunWithObjectMeta(t, expectedTaskRunObjectMeta, `
spec:
resources: {}
serviceAccountName: custom-sa
taskSpec:
steps:
- name: foo-step
image: foo-image
timeout: 1h0m0s
`)

if d := cmp.Diff(actual, expectedTaskRun, ignoreTypeMeta); d != "" {
t.Errorf("expected to see propagated metadata by the precedence from PipelineTaskRunSpec in TaskRun %v created. Diff %s", expectedTaskRun, diff.PrintWantGot(d))
}
}

0 comments on commit 4efa93f

Please sign in to comment.