Skip to content

Commit

Permalink
Add context variables for PipelineRun and TaskRun UIDs
Browse files Browse the repository at this point in the history
A user may want to tag an oci image with the TaskRun or PipelineRun UIDs.
Currently, they can't do that because `metadata.uid` for TaskRuns and
PipelineRuns are not exposed.

In this PR, we add the UID context variable for TaskRuns and
PipelineRun. Users can now use `$(context.taskRun.uid)` and
`$(context.pipelineRun.uid)` to access and use UIDs.

In addition, we add validation for all context variables that are
supported so far -- `context.task.name`, `context.taskRun.name`,
`context.taskRun.namespace`, `context.taskRun.uid`,
`context.pipeline.name`, `context.pipelineRun.name`,
`context.pipelineRun.namespace`, `context.pipelineRun.uid`.

Partially fixes #2958
  • Loading branch information
Scott authored and jerop committed Jul 27, 2020
1 parent 7453c40 commit 1623109
Show file tree
Hide file tree
Showing 16 changed files with 618 additions and 13 deletions.
2 changes: 2 additions & 0 deletions docs/variables.md
Expand Up @@ -16,6 +16,7 @@ This page documents the variable substitions supported by `Tasks` and `Pipelines
| `tasks.<taskName>.results.<resultName>` | The value of the `Task's` result. Can alter `Task` execution order within a `Pipeline`.) |
| `context.pipelineRun.name` | The name of the `PipelineRun` that this `Pipeline` is running in. |
| `context.pipelineRun.namespace` | The namespace of the `PipelineRun` that this `Pipeline` is running in. |
| `context.pipelineRun.uid` | The uid of the `PipelineRun` that this `Pipeline` is running in. |
| `context.pipeline.name` | The name of this `Pipeline` . |


Expand All @@ -33,6 +34,7 @@ This page documents the variable substitions supported by `Tasks` and `Pipelines
| `credentials.path` | The path to credentials injected from Secrets with matching annotations. |
| `context.taskRun.name` | The name of the `TaskRun` that this `Task` is running in. |
| `context.taskRun.namespace` | The namespace of the `TaskRun` that this `Task` is running in. |
| `context.taskRun.uid` | The uid of the `TaskRun` that this `Task` is running in. |
| `context.task.name` | The name of this `Task`. |

### `PipelineResource` variables available in a `Task`
Expand Down
35 changes: 35 additions & 0 deletions examples/v1beta1/pipelineruns/using_context_variables.yaml
@@ -0,0 +1,35 @@
kind: PipelineRun
apiVersion: tekton.dev/v1beta1
metadata:
generateName: test-pipelinerun-
spec:
pipelineSpec:
tasks:
- name: task1
params:
- name: pipeline-uid
value: "$(context.pipelineRun.uid)"
- name: pipeline-name
value: "$(context.pipeline.name)"
- name: pipelineRun-name
value: "$(context.pipelineRun.name)"
- name: pipelineRun-name-uid
value: ["$(context.pipelineRun.name)", "$(context.pipelineRun.uid)"]
taskSpec:
params:
- name: pipeline-uid
- name: pipeline-name
- name: pipelineRun-name
steps:
- image: ubuntu
name: print-uid
script: |
echo "TaskRun UID: $(context.taskRun.uid)"
echo "PipelineRun UID from params: $(params.pipeline-uid)"
- image: ubuntu
name: print-names
script: |
echo "Task name: $(context.task.name)"
echo "TaskRun name: $(context.taskRun.name)"
echo "Pipeline name from params: $(params.pipeline-name)"
echo "PipelineRun name from params: $(params.pipelineRun-name)"
16 changes: 16 additions & 0 deletions examples/v1beta1/taskruns/using_context_variables.yaml
@@ -0,0 +1,16 @@
kind: TaskRun
apiVersion: tekton.dev/v1beta1
metadata:
generateName: test-taskrun-
spec:
taskSpec:
steps:
- image: ubuntu
name: print-uid
script: |
echo "TaskRunUID name: $(context.taskRun.uid)"
- image: ubuntu
name: print-names
script: |
echo "Task name: $(context.task.name)"
echo "TaskRun name: $(context.taskRun.name)"
6 changes: 3 additions & 3 deletions pkg/apis/pipeline/v1beta1/task_validation.go
Expand Up @@ -242,7 +242,7 @@ func ValidateParameterVariables(steps []Step, params []ParamSpec) *apis.FieldErr
}
}

if err := validateVariables(steps, "params", parameterNames); err != nil {
if err := ValidateVariables(steps, "params", parameterNames); err != nil {
return err
}
return validateArrayUsage(steps, "params", arrayParameterNames)
Expand All @@ -263,7 +263,7 @@ func ValidateResourcesVariables(steps []Step, resources *TaskResources) *apis.Fi
resourceNames.Insert(r.Name)
}
}
return validateVariables(steps, "resources.(?:inputs|outputs)", resourceNames)
return ValidateVariables(steps, "resources.(?:inputs|outputs)", resourceNames)
}

func validateArrayUsage(steps []Step, prefix string, vars sets.String) *apis.FieldError {
Expand Down Expand Up @@ -310,7 +310,7 @@ func validateArrayUsage(steps []Step, prefix string, vars sets.String) *apis.Fie
return nil
}

func validateVariables(steps []Step, prefix string, vars sets.String) *apis.FieldError {
func ValidateVariables(steps []Step, prefix string, vars sets.String) *apis.FieldError {
for _, step := range steps {
if err := validateTaskVariable("name", step.Name, prefix, vars); err != nil {
return err
Expand Down
11 changes: 11 additions & 0 deletions pkg/reconciler/pipelinerun/pipelinerun.go
Expand Up @@ -97,6 +97,9 @@ const (
// ReasonCouldntCancel indicates that a PipelineRun was cancelled but attempting to update
// all of the running TaskRuns as cancelled failed.
ReasonCouldntCancel = "PipelineRunCouldntCancel"
// ReasonFailedContextValidation indicates that the reason for failure is that the PipelineRun
// failed to resolve the contexts.
ReasonFailedContextValidation = "ReasonFailedContextValidation"
)

// Reconciler implements controller.Reconciler for Configuration resources.
Expand Down Expand Up @@ -362,6 +365,14 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1beta1.PipelineRun) err
return controller.NewPermanentError(err)
}

// Ensure that the context variables in the PipelineRun parameters are valid.
if err := resources.ValidateContextVariables(pipelineSpec, pr); err != nil {
pr.Status.MarkFailed(ReasonFailedContextValidation,
"PipelineRun %s/%s parameters has invalid context variable %s/%s's: %s",
pr.Namespace, pr.Name, pr.Namespace, pipelineMeta.Name, err)
return controller.NewPermanentError(err)
}

// Ensure that the ServiceAccountNames defined correct.
if err := resources.ValidateServiceaccountMapping(pipelineSpec, pr); err != nil {
pr.Status.MarkFailed(ReasonInvalidServiceAccountMapping,
Expand Down
12 changes: 7 additions & 5 deletions pkg/reconciler/pipelinerun/resources/apply.go
Expand Up @@ -56,11 +56,13 @@ func ApplyParameters(p *v1beta1.PipelineSpec, pr *v1beta1.PipelineRun) *v1beta1.
// ApplyContexts applies the substitution from $(context.(pipelineRun|pipeline).*) with the specified values.
// Currently supports only name substitution. Uses "" as a default if name is not specified.
func ApplyContexts(spec *v1beta1.PipelineSpec, pipelineName string, pr *v1beta1.PipelineRun) *v1beta1.PipelineSpec {
return ApplyReplacements(spec,
map[string]string{"context.pipelineRun.name": pr.Name,
"context.pipeline.name": pipelineName,
"context.pipelineRun.namespace": pr.Namespace},
map[string][]string{})
replacements := map[string]string{
"context.pipelineRun.name": pr.Name,
"context.pipeline.name": pipelineName,
"context.pipelineRun.namespace": pr.Namespace,
"context.pipelineRun.uid": string(pr.ObjectMeta.UID),
}
return ApplyReplacements(spec, replacements, map[string][]string{})
}

// ApplyTaskResults applies the ResolvedResultRef to each PipelineTask.Params in targets
Expand Down
17 changes: 17 additions & 0 deletions pkg/reconciler/pipelinerun/resources/apply_test.go
Expand Up @@ -490,6 +490,23 @@ func TestContext(t *testing.T) {
tb.PipelineTask("first-task-1", "first-task",
tb.PipelineTaskParam("first-task-first-param", "-1"),
))),
}, {
description: "context pipeline name replacement with pipelinerun uid",
pr: &v1beta1.PipelineRun{
ObjectMeta: metav1.ObjectMeta{
UID: "UID-1",
},
},
original: tb.Pipeline("test-pipeline",
tb.PipelineSpec(
tb.PipelineTask("first-task-1", "first-task",
tb.PipelineTaskParam("first-task-first-param", "$(context.pipelineRun.uid)"),
))),
expected: tb.Pipeline("test-pipeline",
tb.PipelineSpec(
tb.PipelineTask("first-task-1", "first-task",
tb.PipelineTaskParam("first-task-first-param", "UID-1"),
))),
}} {
t.Run(tc.description, func(t *testing.T) {
got := ApplyContexts(&tc.original.Spec, tc.original.Name, tc.pr)
Expand Down
87 changes: 87 additions & 0 deletions pkg/reconciler/pipelinerun/resources/validate_contexts.go
@@ -0,0 +1,87 @@
/*
Copyright 2020 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package resources

import (
"fmt"

"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"github.com/tektoncd/pipeline/pkg/substitution"
"k8s.io/apimachinery/pkg/util/sets"
"knative.dev/pkg/apis"
)

// ValidateContextVariables validate that context variables used in Pipelines and PipelineRuns are valid
func ValidateContextVariables(ps *v1beta1.PipelineSpec, pr *v1beta1.PipelineRun) *apis.FieldError {
var paramValues []string

for _, param := range(pr.Spec.Params) {
newParamValues := getParameterValues(param)
paramValues = append(paramValues, newParamValues...)
}

if err := validatePipelineRunContextVariables(paramValues); err != nil {
return err
}
if err := validatePipelineContextVariables(&paramValues); err != nil {
return err
}
return nil
}


func validatePipelineRunContextVariables(paramValues []string) *apis.FieldError {
pipelineRunContextNames := sets.NewString().Insert(
"name",
"namespace",
"uid",
)

for _, paramValue := range(paramValues) {
if err := substitution.ValidateVariable(fmt.Sprintf("param[%s]", paramValue), paramValue, "context\\.pipelineRun", "params", "pipelinespec.params", pipelineRunContextNames); err != nil {
return err
}
}

return nil
}

func validatePipelineContextVariables(paramValues *[]string) *apis.FieldError {
pipelineContextNames := sets.NewString().Insert(
"name",
)

for _, paramValue := range(*paramValues) {
if err := substitution.ValidateVariable(fmt.Sprintf("param[%s]", paramValue), paramValue, "context\\.pipeline", "params", "pipelinespec.params", pipelineContextNames); err != nil {
return err
}
}

return nil
}

func getParameterValues(param v1beta1.Param) []string{
var paramValues []string

if param.Value.Type == v1beta1.ParamTypeString {
paramValues = append(paramValues, param.Value.StringVal)
} else {
paramValues = append(paramValues, param.Value.ArrayVal...)
}

return paramValues
}
110 changes: 110 additions & 0 deletions pkg/reconciler/pipelinerun/resources/validate_contexts_test.go
@@ -0,0 +1,110 @@
/*
Copyright 2020 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package resources

import (
"testing"

tb "github.com/tektoncd/pipeline/internal/builder/v1beta1"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
)

func TestContextValid(t *testing.T) {
tcs := []struct {
name string
p *v1beta1.Pipeline
original *v1beta1.PipelineRun
}{{
name: "param pipeline name context variable",
p: tb.Pipeline("a-pipeline", tb.PipelineSpec(
tb.PipelineParamSpec("pipeline-name", v1beta1.ParamTypeString))),
original: tb.PipelineRun("a-pipelinerun", tb.PipelineRunSpec(
"test-pipeline",
tb.PipelineRunParam("pipeline-name", "$(context.pipeline.name)"))),
}, {
name: "param pipelinerun name context variable",
p: tb.Pipeline("a-pipeline", tb.PipelineSpec(
tb.PipelineParamSpec("pipelinerun-name", v1beta1.ParamTypeString))),
original: tb.PipelineRun("a-pipelinerun", tb.PipelineRunSpec(
"test-pipeline",
tb.PipelineRunParam("pipelinerun-name", "$(context.pipelineRun.name)"))),
}, {
name: "param pipelinerun uid context variable",
p: tb.Pipeline("a-pipeline", tb.PipelineSpec(
tb.PipelineParamSpec("pipelinerun-uid", v1beta1.ParamTypeString))),
original: tb.PipelineRun("a-pipelinerun", tb.PipelineRunSpec(
"test-pipeline",
tb.PipelineRunParam("pipelinerun-uid", "$(context.pipelineRun.uid)"))),
}, {
name: "param pipelinerun name, uid context variable",
p: tb.Pipeline("a-pipeline", tb.PipelineSpec(
tb.PipelineParamSpec("pipelinerun-name-uid", v1beta1.ParamTypeString))),
original: tb.PipelineRun("a-pipelinerun", tb.PipelineRunSpec(
"test-pipeline",
tb.PipelineRunParam("pipelinerun-name-uid", "$(context.pipelineRun.name)", "$(context.pipelineRun.uid)"))),
}}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
if err := ValidateContextVariables(&tc.p.Spec, tc.original); err != nil {
t.Errorf("Did not expect to see error when validating valid contexts but saw %v", err)
}
})
}
}

func TestContextInvalid(t *testing.T) {
tcs := []struct {
name string
p *v1beta1.Pipeline
pr *v1beta1.PipelineRun
}{{
name: "param pipeline name context variable",
p: tb.Pipeline("a-pipeline", tb.PipelineSpec(
tb.PipelineParamSpec("pipeline-name", v1beta1.ParamTypeString))),
pr: tb.PipelineRun("a-pipelinerun", tb.PipelineRunSpec(
"test-pipeline",
tb.PipelineRunParam("pipeline-name", "$(context.pipeline.missing)"))),
}, {
name: "param pipelinerun name context variable",
p: tb.Pipeline("a-pipeline", tb.PipelineSpec(
tb.PipelineParamSpec("pipelinerun-name", v1beta1.ParamTypeString))),
pr: tb.PipelineRun("a-pipelinerun", tb.PipelineRunSpec(
"test-pipeline",
tb.PipelineRunParam("pipelinerun-name", "$(context.pipelineRun.missing)"))),
}, {
name: "param pipelinerun uid context variable",
p: tb.Pipeline("a-pipeline", tb.PipelineSpec(
tb.PipelineParamSpec("pipelinerun-uid", v1beta1.ParamTypeString))),
pr: tb.PipelineRun("a-pipelinerun", tb.PipelineRunSpec(
"test-pipeline",
tb.PipelineRunParam("pipelinerun-uid", "$(context.pipelineRun.missing)"))),
}, {
name: "param pipelinerun name, uid context variable",
p: tb.Pipeline("a-pipeline", tb.PipelineSpec(
tb.PipelineParamSpec("pipelinerun-name-uid", v1beta1.ParamTypeString))),
pr: tb.PipelineRun("a-pipelinerun", tb.PipelineRunSpec(
"test-pipeline",
tb.PipelineRunParam("pipelinerun-name-uid", "$(context.pipelineRun.missing)", "$(context.pipelineRun.missing)"))),
}}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
if err := ValidateContextVariables(&tc.p.Spec, tc.pr); err == nil {
t.Errorf("expected to see error when validating invalid contexts")
}
})
}
}
12 changes: 8 additions & 4 deletions pkg/reconciler/taskrun/resources/apply.go
Expand Up @@ -97,11 +97,15 @@ func ApplyResources(spec *v1beta1.TaskSpec, resolvedResources map[string]v1beta1
}

// ApplyContexts applies the substitution from $(context.(taskRun|task).*) with the specified values.
// Currently supports only name substitution. Uses "" as a default if name is not specified.
// Uses "" as a default if a value is not available.
func ApplyContexts(spec *v1beta1.TaskSpec, rtr *ResolvedTaskResources, tr *v1beta1.TaskRun) *v1beta1.TaskSpec {
return ApplyReplacements(spec,
map[string]string{"context.taskRun.name": tr.Name, "context.task.name": rtr.TaskName, "context.taskRun.namespace": tr.Namespace},
map[string][]string{})
replacements := map[string]string{
"context.taskRun.name": tr.Name,
"context.task.name": rtr.TaskName,
"context.taskRun.namespace": tr.Namespace,
"context.taskRun.uid": string(tr.ObjectMeta.UID),
}
return ApplyReplacements(spec, replacements, map[string][]string{})
}

// ApplyWorkspaces applies the substitution from paths that the workspaces in w are mounted to, the
Expand Down

0 comments on commit 1623109

Please sign in to comment.