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

Co-authored-by: Jerop <jerop@google.com>
Co-authored-by: Scott <sbws@google.com>
  • Loading branch information
Scott and jerop committed Jul 27, 2020
1 parent 7453c40 commit f0c51cd
Show file tree
Hide file tree
Showing 20 changed files with 653 additions and 21 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: 6 additions & 0 deletions internal/builder/v1beta1/owner_reference.go
Expand Up @@ -18,6 +18,7 @@ package builder

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)

// OwnerReferenceOp is an operation which modifies an OwnerReference struct.
Expand All @@ -39,3 +40,8 @@ func Controller(o *metav1.OwnerReference) {
func BlockOwnerDeletion(o *metav1.OwnerReference) {
o.BlockOwnerDeletion = &trueB
}

// OwnerUID sets the UID to the OwnerReference.
func OwnerUID(o *metav1.OwnerReference){
o.UID = types.UID("uid-1")
}
7 changes: 7 additions & 0 deletions internal/builder/v1beta1/pipeline.go
Expand Up @@ -24,6 +24,7 @@ import (
resource "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"knative.dev/pkg/apis"
)

Expand Down Expand Up @@ -353,6 +354,12 @@ func PipelineRun(name string, ops ...PipelineRunOp) *v1beta1.PipelineRun {

return pr
}
// PipelineRunUID sets the namespace on a PipelineRun
func PipelineRunUID(uid string) PipelineRunOp {
return func(t *v1beta1.PipelineRun) {
t.ObjectMeta.UID = types.UID(uid)
}
}

// PipelineRunNamespace sets the namespace on a PipelineRun
func PipelineRunNamespace(namespace string) PipelineRunOp {
Expand Down
8 changes: 8 additions & 0 deletions internal/builder/v1beta1/task.go
Expand Up @@ -24,6 +24,7 @@ import (
resource "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"knative.dev/pkg/apis"
)

Expand Down Expand Up @@ -356,6 +357,13 @@ func TaskRunNamespace(namespace string) TaskRunOp {
}
}

// TaskRunUID sets the uid for the TaskRun.
func TaskRunUID(uid string) TaskRunOp {
return func(t *v1beta1.TaskRun) {
t.ObjectMeta.UID = types.UID(uid)
}
}

// TaskRunStatus sets the TaskRunStatus to tshe TaskRun
func TaskRunStatus(ops ...TaskRunStatusOp) TaskRunOp {
return func(tr *v1beta1.TaskRun) {
Expand Down
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
22 changes: 14 additions & 8 deletions pkg/reconciler/pipelinerun/pipelinerun_test.go
Expand Up @@ -157,8 +157,10 @@ func TestReconcile(t *testing.T) {
// It verifies that the TaskRun is created, it checks the resulting API actions, status and events.
names.TestingSeed()
const pipelineRunName = "test-pipeline-run-success"
const pipelineRunUID = "uid-1"
prs := []*v1beta1.PipelineRun{
tb.PipelineRun(pipelineRunName,
tb.PipelineRunUID(pipelineRunUID),
tb.PipelineRunNamespace("foo"),
tb.PipelineRunSpec("test-pipeline",
tb.PipelineRunServiceAccountName("test-sa"),
Expand All @@ -181,6 +183,7 @@ func TestReconcile(t *testing.T) {
templatedParam := tb.PipelineTaskParam("templatedparam", "$(inputs.workspace.$(params.rev-param))")
contextRunParam := tb.PipelineTaskParam("contextRunParam", "$(context.pipelineRun.name)")
contextPipelineParam := tb.PipelineTaskParam("contextPipelineParam", "$(context.pipeline.name)")
contextRunUID := tb.PipelineTaskParam("contextRunUID", "$(context.pipelineRun.uid)")
const pipelineName = "test-pipeline"
ps := []*v1beta1.Pipeline{
tb.Pipeline(pipelineName,
Expand All @@ -193,15 +196,15 @@ func TestReconcile(t *testing.T) {
tb.PipelineParamSpec("bar", v1beta1.ParamTypeString),
// unit-test-3 uses runAfter to indicate it should run last
tb.PipelineTask("unit-test-3", "unit-test-task",
funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam,
funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam, contextRunUID,
tb.RunAfter("unit-test-2"),
tb.PipelineTaskInputResource("workspace", "git-repo"),
tb.PipelineTaskOutputResource("image-to-use", "best-image"),
tb.PipelineTaskOutputResource("workspace", "git-repo"),
),
// unit-test-1 can run right away because it has no dependencies
tb.PipelineTask("unit-test-1", "unit-test-task",
funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam,
funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam, contextRunUID,
tb.PipelineTaskInputResource("workspace", "git-repo"),
tb.PipelineTaskOutputResource("image-to-use", "best-image"),
tb.PipelineTaskOutputResource("workspace", "git-repo"),
Expand All @@ -213,7 +216,7 @@ func TestReconcile(t *testing.T) {
// unit-test-cluster-task can run right away because it has no dependencies
tb.PipelineTask("unit-test-cluster-task", "unit-test-cluster-task",
tb.PipelineTaskRefKind(v1beta1.ClusterTaskKind),
funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam,
funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam, contextRunUID,
tb.PipelineTaskInputResource("workspace", "git-repo"),
tb.PipelineTaskOutputResource("image-to-use", "best-image"),
tb.PipelineTaskOutputResource("workspace", "git-repo"),
Expand All @@ -223,8 +226,9 @@ func TestReconcile(t *testing.T) {
}
ts := []*v1beta1.Task{
tb.Task("unit-test-task", tb.TaskSpec(
tb.TaskParam("foo", v1beta1.ParamTypeString), tb.TaskParam("bar", v1beta1.ParamTypeString), tb.TaskParam("templatedparam", v1beta1.ParamTypeString),
tb.TaskParam("contextRunParam", v1beta1.ParamTypeString), tb.TaskParam("contextPipelineParam", v1beta1.ParamTypeString),
tb.TaskParam("foo", v1beta1.ParamTypeString), tb.TaskParam("bar", v1beta1.ParamTypeString),
tb.TaskParam("templatedparam", v1beta1.ParamTypeString), tb.TaskParam("contextRunParam", v1beta1.ParamTypeString),
tb.TaskParam("contextPipelineParam", v1beta1.ParamTypeString), tb.TaskParam("contextRunUID", v1beta1.ParamTypeString),
tb.TaskResources(
tb.TaskResourcesInput("workspace", resourcev1alpha1.PipelineResourceTypeGit),
tb.TaskResourcesOutput("image-to-use", resourcev1alpha1.PipelineResourceTypeImage),
Expand All @@ -237,8 +241,9 @@ func TestReconcile(t *testing.T) {
}
clusterTasks := []*v1beta1.ClusterTask{
tb.ClusterTask("unit-test-cluster-task", tb.ClusterTaskSpec(
tb.TaskParam("foo", v1beta1.ParamTypeString), tb.TaskParam("bar", v1beta1.ParamTypeString), tb.TaskParam("templatedparam", v1beta1.ParamTypeString),
tb.TaskParam("contextRunParam", v1beta1.ParamTypeString), tb.TaskParam("contextPipelineParam", v1beta1.ParamTypeString),
tb.TaskParam("foo", v1beta1.ParamTypeString), tb.TaskParam("bar", v1beta1.ParamTypeString),
tb.TaskParam("templatedparam", v1beta1.ParamTypeString),tb.TaskParam("contextRunParam", v1beta1.ParamTypeString),
tb.TaskParam("contextPipelineParam", v1beta1.ParamTypeString), tb.TaskParam("contextRunUID", v1beta1.ParamTypeString),
tb.TaskResources(
tb.TaskResourcesInput("workspace", resourcev1alpha1.PipelineResourceTypeGit),
tb.TaskResourcesOutput("image-to-use", resourcev1alpha1.PipelineResourceTypeImage),
Expand Down Expand Up @@ -296,7 +301,7 @@ func TestReconcile(t *testing.T) {
tb.TaskRunNamespace("foo"),
tb.TaskRunOwnerReference("PipelineRun", "test-pipeline-run-success",
tb.OwnerReferenceAPIVersion("tekton.dev/v1beta1"),
tb.Controller, tb.BlockOwnerDeletion,
tb.Controller, tb.BlockOwnerDeletion, tb.OwnerUID,
),
tb.TaskRunLabel("tekton.dev/pipeline", "test-pipeline"),
tb.TaskRunLabel("tekton.dev/pipelineRun", "test-pipeline-run-success"),
Expand All @@ -309,6 +314,7 @@ func TestReconcile(t *testing.T) {
tb.TaskRunParam("templatedparam", "$(inputs.workspace.revision)"),
tb.TaskRunParam("contextRunParam", pipelineRunName),
tb.TaskRunParam("contextPipelineParam", pipelineName),
tb.TaskRunParam("contextRunUID", pipelineRunUID),
tb.TaskRunResources(
tb.TaskRunResourcesInput("workspace", tb.TaskResourceBindingRef("some-repo")),
tb.TaskRunResourcesOutput("image-to-use",
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
}

0 comments on commit f0c51cd

Please sign in to comment.