diff --git a/docs/variables.md b/docs/variables.md index 974c6642c6a..1e9a5599538 100644 --- a/docs/variables.md +++ b/docs/variables.md @@ -16,6 +16,7 @@ This page documents the variable substitions supported by `Tasks` and `Pipelines | `tasks..results.` | 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` . | @@ -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` diff --git a/examples/v1beta1/pipelineruns/using_context_variables.yaml b/examples/v1beta1/pipelineruns/using_context_variables.yaml new file mode 100644 index 00000000000..13b3823c3b2 --- /dev/null +++ b/examples/v1beta1/pipelineruns/using_context_variables.yaml @@ -0,0 +1,36 @@ +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)" + echo "PipelineRun name and uid from params: $(params.pipelineRun-name-uid)" \ No newline at end of file diff --git a/examples/v1beta1/taskruns/using_context_variables.yaml b/examples/v1beta1/taskruns/using_context_variables.yaml new file mode 100644 index 00000000000..46f1a3af01e --- /dev/null +++ b/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)" diff --git a/internal/builder/v1beta1/owner_reference.go b/internal/builder/v1beta1/owner_reference.go index 93b2ecc0c6b..448e9b8ae6b 100644 --- a/internal/builder/v1beta1/owner_reference.go +++ b/internal/builder/v1beta1/owner_reference.go @@ -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. @@ -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") +} \ No newline at end of file diff --git a/internal/builder/v1beta1/pipeline.go b/internal/builder/v1beta1/pipeline.go index e2a1379c8da..dc9b3d48f89 100644 --- a/internal/builder/v1beta1/pipeline.go +++ b/internal/builder/v1beta1/pipeline.go @@ -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" ) @@ -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 { diff --git a/internal/builder/v1beta1/task.go b/internal/builder/v1beta1/task.go index f5605f1259a..7d6deaa3e62 100644 --- a/internal/builder/v1beta1/task.go +++ b/internal/builder/v1beta1/task.go @@ -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" ) @@ -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) { diff --git a/pkg/apis/pipeline/v1beta1/task_validation.go b/pkg/apis/pipeline/v1beta1/task_validation.go index 846faf315d8..19a3c47b461 100644 --- a/pkg/apis/pipeline/v1beta1/task_validation.go +++ b/pkg/apis/pipeline/v1beta1/task_validation.go @@ -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) @@ -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 { @@ -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 diff --git a/pkg/reconciler/pipelinerun/pipelinerun.go b/pkg/reconciler/pipelinerun/pipelinerun.go index 01ca1e31fe9..2689391dd56 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun.go +++ b/pkg/reconciler/pipelinerun/pipelinerun.go @@ -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. @@ -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, diff --git a/pkg/reconciler/pipelinerun/pipelinerun_test.go b/pkg/reconciler/pipelinerun/pipelinerun_test.go index 50687bd3c6f..b87acc79ab6 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun_test.go +++ b/pkg/reconciler/pipelinerun/pipelinerun_test.go @@ -159,6 +159,7 @@ func TestReconcile(t *testing.T) { const pipelineRunName = "test-pipeline-run-success" prs := []*v1beta1.PipelineRun{ tb.PipelineRun(pipelineRunName, + tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunSpec("test-pipeline", tb.PipelineRunServiceAccountName("test-sa"), @@ -181,6 +182,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, @@ -193,7 +195,7 @@ 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"), @@ -201,7 +203,7 @@ func TestReconcile(t *testing.T) { ), // 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"), @@ -213,7 +215,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"), @@ -223,8 +225,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), @@ -237,8 +240,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), @@ -296,7 +300,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"), @@ -309,6 +313,7 @@ func TestReconcile(t *testing.T) { tb.TaskRunParam("templatedparam", "$(inputs.workspace.revision)"), tb.TaskRunParam("contextRunParam", pipelineRunName), tb.TaskRunParam("contextPipelineParam", pipelineName), + tb.TaskRunParam("contextRunUID", "uid-1"), tb.TaskRunResources( tb.TaskRunResourcesInput("workspace", tb.TaskResourceBindingRef("some-repo")), tb.TaskRunResourcesOutput("image-to-use", @@ -378,6 +383,7 @@ func TestReconcile_PipelineSpecTaskSpec(t *testing.T) { prs := []*v1beta1.PipelineRun{ tb.PipelineRun("test-pipeline-run-success", + tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunSpec("test-pipeline"), ), @@ -430,7 +436,7 @@ func TestReconcile_PipelineSpecTaskSpec(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"), @@ -986,6 +992,7 @@ func TestReconcileOnCompletedPipelineRun(t *testing.T) { // a successful event is triggered taskRunName := "test-pipeline-run-completed-hello-world" prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-completed", + tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunSpec("test-pipeline", tb.PipelineRunServiceAccountName("test-sa")), tb.PipelineRunStatus(tb.PipelineRunStatusCondition(apis.Condition{ @@ -1094,6 +1101,7 @@ func TestReconcileOnCancelledPipelineRun(t *testing.T) { // TestReconcileOnCancelledPipelineRun runs "Reconcile" on a PipelineRun that has been cancelled. // It verifies that reconcile is successful, the pipeline status updated and events generated. prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-cancelled", + tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunSpec("test-pipeline", tb.PipelineRunServiceAccountName("test-sa"), tb.PipelineRunCancelled, @@ -1164,6 +1172,7 @@ func TestReconcileWithTimeout(t *testing.T) { tb.PipelineTask("hello-world-1", "hello-world"), ))} prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-with-timeout", + tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunSpec("test-pipeline", tb.PipelineRunServiceAccountName("test-sa"), @@ -1232,7 +1241,7 @@ func TestReconcileWithoutPVC(t *testing.T) { tb.PipelineTask("hello-world-2", "hello-world"), ))} - prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run", tb.PipelineRunNamespace("foo"), + prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run", tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunSpec("test-pipeline")), } ts := []*v1beta1.Task{tb.Task("hello-world", tb.TaskNamespace("foo"))} @@ -1415,7 +1424,7 @@ func TestReconcilePropagateLabels(t *testing.T) { ps := []*v1beta1.Pipeline{tb.Pipeline("test-pipeline", tb.PipelineNamespace("foo"), tb.PipelineSpec( tb.PipelineTask(taskName, "hello-world"), ))} - prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-with-labels", tb.PipelineRunNamespace("foo"), + prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-with-labels", tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunLabel("PipelineRunLabel", "PipelineRunValue"), tb.PipelineRunLabel("tekton.dev/pipeline", "WillNotBeUsed"), tb.PipelineRunSpec("test-pipeline", @@ -1434,7 +1443,7 @@ func TestReconcilePropagateLabels(t *testing.T) { tb.TaskRunNamespace("foo"), tb.TaskRunOwnerReference("PipelineRun", "test-pipeline-run-with-labels", tb.OwnerReferenceAPIVersion("tekton.dev/v1beta1"), - tb.Controller, tb.BlockOwnerDeletion, + tb.Controller, tb.BlockOwnerDeletion, tb.OwnerUID, ), tb.TaskRunLabel("tekton.dev/pipeline", "test-pipeline"), tb.TaskRunLabel(pipeline.GroupName+pipeline.PipelineTaskLabelKey, "hello-world-1"), @@ -1480,7 +1489,7 @@ func TestReconcileWithDifferentServiceAccounts(t *testing.T) { tb.PipelineTask("hello-world-0", "hello-world-task"), tb.PipelineTask("hello-world-1", "hello-world-task"), ))} - prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-different-service-accs", tb.PipelineRunNamespace("foo"), + prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-different-service-accs", tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunSpec("test-pipeline", tb.PipelineRunServiceAccountName("test-sa-0"), tb.PipelineRunServiceAccountNameTask("hello-world-1", "test-sa-1"), @@ -1519,7 +1528,7 @@ func TestReconcileWithDifferentServiceAccounts(t *testing.T) { tb.TaskRunNamespace("foo"), tb.TaskRunOwnerReference("PipelineRun", "test-pipeline-run-different-service-accs", tb.OwnerReferenceAPIVersion("tekton.dev/v1beta1"), - tb.Controller, tb.BlockOwnerDeletion, + tb.Controller, tb.BlockOwnerDeletion, tb.OwnerUID, ), tb.TaskRunSpec( tb.TaskRunTaskRef("hello-world-task"), @@ -1533,7 +1542,7 @@ func TestReconcileWithDifferentServiceAccounts(t *testing.T) { tb.TaskRunNamespace("foo"), tb.TaskRunOwnerReference("PipelineRun", "test-pipeline-run-different-service-accs", tb.OwnerReferenceAPIVersion("tekton.dev/v1beta1"), - tb.Controller, tb.BlockOwnerDeletion, + tb.Controller, tb.BlockOwnerDeletion, tb.OwnerUID, ), tb.TaskRunSpec( tb.TaskRunTaskRef("hello-world-task"), @@ -1676,7 +1685,7 @@ func TestReconcilePropagateAnnotations(t *testing.T) { ps := []*v1beta1.Pipeline{tb.Pipeline("test-pipeline", tb.PipelineNamespace("foo"), tb.PipelineSpec( tb.PipelineTask("hello-world-1", "hello-world"), ))} - prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-with-annotations", tb.PipelineRunNamespace("foo"), + prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-with-annotations", tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunAnnotation("PipelineRunAnnotation", "PipelineRunValue"), tb.PipelineRunSpec("test-pipeline", tb.PipelineRunServiceAccountName("test-sa"), @@ -1715,7 +1724,7 @@ func TestReconcilePropagateAnnotations(t *testing.T) { tb.TaskRunNamespace("foo"), tb.TaskRunOwnerReference("PipelineRun", "test-pipeline-run-with-annotations", tb.OwnerReferenceAPIVersion("tekton.dev/v1beta1"), - tb.Controller, tb.BlockOwnerDeletion, + tb.Controller, tb.BlockOwnerDeletion, tb.OwnerUID, ), tb.TaskRunLabel("tekton.dev/pipeline", "test-pipeline"), tb.TaskRunLabel(pipeline.GroupName+pipeline.PipelineTaskLabelKey, "hello-world-1"), @@ -1832,7 +1841,7 @@ func TestReconcileAndPropagateCustomPipelineTaskRunSpec(t *testing.T) { ps := []*v1beta1.Pipeline{tb.Pipeline("test-pipeline", tb.PipelineNamespace("foo"), tb.PipelineSpec( tb.PipelineTask("hello-world-1", "hello-world"), ))} - prs := []*v1beta1.PipelineRun{tb.PipelineRun(prName, tb.PipelineRunNamespace("foo"), + prs := []*v1beta1.PipelineRun{tb.PipelineRun(prName, tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunAnnotation("PipelineRunAnnotation", "PipelineRunValue"), tb.PipelineRunSpec("test-pipeline", tb.PipelineRunServiceAccountName("test-sa"), @@ -1880,7 +1889,7 @@ func TestReconcileAndPropagateCustomPipelineTaskRunSpec(t *testing.T) { tb.TaskRunNamespace("foo"), tb.TaskRunOwnerReference("PipelineRun", "test-pipeline-run", tb.OwnerReferenceAPIVersion("tekton.dev/v1beta1"), - tb.Controller, tb.BlockOwnerDeletion, + tb.Controller, tb.BlockOwnerDeletion, tb.OwnerUID, ), tb.TaskRunLabel(pipeline.GroupName+pipeline.PipelineLabelKey, "test-pipeline"), tb.TaskRunLabel(pipeline.GroupName+pipeline.PipelineTaskLabelKey, "hello-world-1"), @@ -1939,7 +1948,7 @@ func TestReconcileWithConditionChecks(t *testing.T) { tb.PipelineTaskCondition("cond-1"), tb.PipelineTaskCondition("cond-2")), ))} - prs := []*v1beta1.PipelineRun{tb.PipelineRun(prName, tb.PipelineRunNamespace("foo"), + prs := []*v1beta1.PipelineRun{tb.PipelineRun(prName, tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunAnnotation("PipelineRunAnnotation", "PipelineRunValue"), tb.PipelineRunSpec("test-pipeline", tb.PipelineRunServiceAccountName("test-sa"), @@ -2025,7 +2034,7 @@ func TestReconcileWithFailingConditionChecks(t *testing.T) { tb.PipelineTask("task-3", "hello-world", tb.RunAfter("task-1")), ))} - prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-with-conditions", tb.PipelineRunNamespace("foo"), + prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-with-conditions", tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunAnnotation("PipelineRunAnnotation", "PipelineRunValue"), tb.PipelineRunSpec("test-pipeline", tb.PipelineRunServiceAccountName("test-sa"), @@ -2107,7 +2116,7 @@ func TestReconcileWithFailingConditionChecks(t *testing.T) { tb.TaskRunNamespace("foo"), tb.TaskRunOwnerReference("PipelineRun", "test-pipeline-run-with-conditions", tb.OwnerReferenceAPIVersion("tekton.dev/v1beta1"), - tb.Controller, tb.BlockOwnerDeletion, + tb.Controller, tb.BlockOwnerDeletion, tb.OwnerUID, ), tb.TaskRunLabel(pipeline.GroupName+pipeline.PipelineLabelKey, "test-pipeline"), tb.TaskRunLabel(pipeline.GroupName+pipeline.PipelineTaskLabelKey, "task-3"), @@ -2138,7 +2147,7 @@ func makeExpectedTr(condName, ccName string, labels, annotations map[string]stri tb.TaskRunNamespace("foo"), tb.TaskRunOwnerReference("PipelineRun", "test-pipeline-run", tb.OwnerReferenceAPIVersion("tekton.dev/v1beta1"), - tb.Controller, tb.BlockOwnerDeletion, + tb.Controller, tb.BlockOwnerDeletion, tb.OwnerUID, ), tb.TaskRunLabel(pipeline.GroupName+pipeline.PipelineLabelKey, "test-pipeline"), tb.TaskRunLabel(pipeline.GroupName+pipeline.PipelineTaskLabelKey, "hello-world-1"), @@ -2488,7 +2497,7 @@ func TestReconcileWithTaskResults(t *testing.T) { tb.PipelineTaskParam("bParam", "$(tasks.a-task.results.aResult)"), ), ))} - prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-different-service-accs", tb.PipelineRunNamespace("foo"), + prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-different-service-accs", tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunSpec("test-pipeline", tb.PipelineRunServiceAccountName("test-sa-0"), ), @@ -2506,7 +2515,7 @@ func TestReconcileWithTaskResults(t *testing.T) { tb.TaskRunNamespace("foo"), tb.TaskRunOwnerReference("PipelineRun", "test-pipeline-run-different-service-accs", 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-different-service-accs"), @@ -2550,7 +2559,7 @@ func TestReconcileWithTaskResults(t *testing.T) { tb.TaskRunNamespace("foo"), tb.TaskRunOwnerReference("PipelineRun", "test-pipeline-run-different-service-accs", 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-different-service-accs"), @@ -2581,7 +2590,7 @@ func TestReconcileWithTaskResults(t *testing.T) { func TestReconcileWithTaskResultsEmbeddedNoneStarted(t *testing.T) { names.TestingSeed() - prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-different-service-accs", tb.PipelineRunNamespace("foo"), + prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-different-service-accs", tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunSpec("test-pipeline", tb.PipelineRunPipelineSpec( tb.PipelineParamSpec("foo", v1beta1.ParamTypeString), @@ -2634,7 +2643,7 @@ func TestReconcileWithTaskResultsEmbeddedNoneStarted(t *testing.T) { tb.TaskRunNamespace("foo"), tb.TaskRunOwnerReference("PipelineRun", "test-pipeline-run-different-service-accs", tb.OwnerReferenceAPIVersion("tekton.dev/v1beta1"), - tb.Controller, tb.BlockOwnerDeletion, + tb.Controller, tb.BlockOwnerDeletion, tb.OwnerUID, ), tb.TaskRunLabel("tekton.dev/pipeline", "test-pipeline-run-different-service-accs"), tb.TaskRunLabel("tekton.dev/pipelineRun", "test-pipeline-run-different-service-accs"), @@ -2672,7 +2681,7 @@ func TestReconcileWithPipelineResults(t *testing.T) { tb.TaskRunNamespace("foo"), tb.TaskRunOwnerReference("PipelineRun", "test-pipeline-run-different-service-accs", 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-different-service-accs"), @@ -2692,7 +2701,7 @@ func TestReconcileWithPipelineResults(t *testing.T) { ), ), } - prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-different-service-accs", tb.PipelineRunNamespace("foo"), + prs := []*v1beta1.PipelineRun{tb.PipelineRun("test-pipeline-run-different-service-accs", tb.PipelineRunUID("uid-1"), tb.PipelineRunNamespace("foo"), tb.PipelineRunSpec("test-pipeline", tb.PipelineRunServiceAccountName("test-sa-0"), ), diff --git a/pkg/reconciler/pipelinerun/resources/apply.go b/pkg/reconciler/pipelinerun/resources/apply.go index 4962f727b05..dd12c092151 100644 --- a/pkg/reconciler/pipelinerun/resources/apply.go +++ b/pkg/reconciler/pipelinerun/resources/apply.go @@ -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 diff --git a/pkg/reconciler/pipelinerun/resources/apply_test.go b/pkg/reconciler/pipelinerun/resources/apply_test.go index 003b5beef6b..389f9e93b93 100644 --- a/pkg/reconciler/pipelinerun/resources/apply_test.go +++ b/pkg/reconciler/pipelinerun/resources/apply_test.go @@ -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) diff --git a/pkg/reconciler/pipelinerun/resources/validate_contexts.go b/pkg/reconciler/pipelinerun/resources/validate_contexts.go new file mode 100644 index 00000000000..0ff3f042455 --- /dev/null +++ b/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(¶mValues); 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 +} diff --git a/pkg/reconciler/pipelinerun/resources/validate_contexts_test.go b/pkg/reconciler/pipelinerun/resources/validate_contexts_test.go new file mode 100644 index 00000000000..2b4dca87e95 --- /dev/null +++ b/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") + } + }) + } +} diff --git a/pkg/reconciler/taskrun/resources/apply.go b/pkg/reconciler/taskrun/resources/apply.go index e8706acfb69..6893a5a481d 100644 --- a/pkg/reconciler/taskrun/resources/apply.go +++ b/pkg/reconciler/taskrun/resources/apply.go @@ -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 diff --git a/pkg/reconciler/taskrun/resources/apply_test.go b/pkg/reconciler/taskrun/resources/apply_test.go index e30587b0013..3b70937d2b8 100644 --- a/pkg/reconciler/taskrun/resources/apply_test.go +++ b/pkg/reconciler/taskrun/resources/apply_test.go @@ -929,6 +929,32 @@ func TestContext(t *testing.T) { }, }}, }, + }, { + description: "context UID replacement", + rtr: resources.ResolvedTaskResources{ + TaskName: "Task1", + }, + tr: v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + UID: "UID-1", + }, + }, + spec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Container: corev1.Container{ + Name: "ImageName", + Image: "$(context.taskRun.uid)", + }, + }}, + }, + want: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Container: corev1.Container{ + Name: "ImageName", + Image: "UID-1", + }, + }}, + }, }} { t.Run(tc.description, func(t *testing.T) { got := resources.ApplyContexts(&tc.spec, &tc.rtr, &tc.tr) diff --git a/pkg/reconciler/taskrun/taskrun.go b/pkg/reconciler/taskrun/taskrun.go index cee6d5cb489..7fa4aac5bcd 100644 --- a/pkg/reconciler/taskrun/taskrun.go +++ b/pkg/reconciler/taskrun/taskrun.go @@ -294,6 +294,12 @@ func (c *Reconciler) prepare(ctx context.Context, tr *v1beta1.TaskRun) (*v1beta1 return nil, nil, controller.NewPermanentError(err) } + if err := validateContextVariables(taskSpec, tr); err != nil { + logger.Errorf("TaskRun %q context is invalid: %v", tr.Name, err) + tr.Status.MarkResourceFailed(podconvert.ReasonFailedValidation, err) + return nil, nil, controller.NewPermanentError(err) + } + // Initialize the cloud events if at least a CloudEventResource is defined // and they have not been initialized yet. // FIXME(afrittoli) This resource specific logic will have to be replaced diff --git a/pkg/reconciler/taskrun/validate_contexts.go b/pkg/reconciler/taskrun/validate_contexts.go new file mode 100644 index 00000000000..2130936d99b --- /dev/null +++ b/pkg/reconciler/taskrun/validate_contexts.go @@ -0,0 +1,40 @@ +/* +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 taskrun + +import ( + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + "k8s.io/apimachinery/pkg/util/sets" + "knative.dev/pkg/apis" +) + +func validateContextVariables(ts *v1beta1.TaskSpec, tr *v1beta1.TaskRun) *apis.FieldError { + taskRunContextNames := sets.NewString().Insert( + "name", + "namespace", + "uid", + ) + taskContextNames := sets.NewString().Insert( + "name", + ) + if err := v1beta1.ValidateVariables(ts.Steps, "context\\.taskRun", taskRunContextNames); err != nil { + return err + } + + return v1beta1.ValidateVariables(ts.Steps, "context\\.task", taskContextNames) + +} diff --git a/pkg/reconciler/taskrun/validate_contexts_test.go b/pkg/reconciler/taskrun/validate_contexts_test.go new file mode 100644 index 00000000000..6856f3ebba6 --- /dev/null +++ b/pkg/reconciler/taskrun/validate_contexts_test.go @@ -0,0 +1,211 @@ +/* +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 taskrun + +import ( + "testing" + + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" + "github.com/tektoncd/pipeline/pkg/reconciler/taskrun/resources" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestContextValid(t *testing.T) { + for _, tc := range []struct { + description string + rtr resources.ResolvedTaskResources + tr v1beta1.TaskRun + spec v1beta1.TaskSpec + }{{ + description: "context taskName replacement without taskRun in spec container", + rtr: resources.ResolvedTaskResources{ + TaskName: "Task1", + }, + tr: v1beta1.TaskRun{}, + spec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Container: corev1.Container{ + Name: "ImageName", + Image: "$(context.task.name)-1", + }, + }}, + }, + }, { + description: "context taskName replacement with taskRun in spec container", + rtr: resources.ResolvedTaskResources{ + TaskName: "Task1", + }, + tr: v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "taskrunName", + }, + }, + spec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Container: corev1.Container{ + Name: "ImageName", + Image: "$(context.task.name)-1", + }, + }}, + }, + }, { + description: "context taskRunName replacement with defined taskRun in spec container", + rtr: resources.ResolvedTaskResources{ + TaskName: "Task1", + }, + tr: v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "taskrunName", + }, + }, + spec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Container: corev1.Container{ + Name: "ImageName", + Image: "$(context.taskRun.name)-1", + }, + }}, + }, + }, { + description: "context taskRunName replacement with no defined taskRun name in spec container", + rtr: resources.ResolvedTaskResources{ + TaskName: "Task1", + }, + tr: v1beta1.TaskRun{}, + spec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Container: corev1.Container{ + Name: "ImageName", + Image: "$(context.taskRun.name)-1", + }, + }}, + }, + }, { + description: "context taskRun namespace replacement with no defined namespace in spec container", + rtr: resources.ResolvedTaskResources{ + TaskName: "Task1", + }, + tr: v1beta1.TaskRun{}, + spec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Container: corev1.Container{ + Name: "ImageName", + Image: "$(context.taskRun.namespace)-1", + }, + }}, + }, + }, { + description: "context taskRun namespace replacement with defined namespace in spec container", + rtr: resources.ResolvedTaskResources{ + TaskName: "Task1", + }, + tr: v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "taskrunName", + Namespace: "trNamespace", + }, + }, + spec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Container: corev1.Container{ + Name: "ImageName", + Image: "$(context.taskRun.namespace)-1", + }, + }}, + }, + }, { + description: "context taskRunName replacement with no defined taskName in spec container", + rtr: resources.ResolvedTaskResources{}, + tr: v1beta1.TaskRun{}, + spec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Container: corev1.Container{ + Name: "ImageName", + Image: "$(context.task.name)-1", + }, + }}, + }, + }, { + description: "context UID replacement valid", + rtr: resources.ResolvedTaskResources{ + TaskName: "Task1", + }, + tr: v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + UID: "UID-1", + }, + }, + spec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Container: corev1.Container{ + Name: "ImageName", + Image: "$(context.taskRun.uid)", + }, + }}, + }, + }} { + t.Run(tc.description, func(t *testing.T) { + if err := validateContextVariables(&tc.spec, &tc.tr); err != nil { + t.Fatalf("Did not expect to see error when validating valid contexts but saw %v", err) + } + }) + } +} + +func TestContextInvalid(t *testing.T) { + for _, tc := range []struct { + description string + rtr resources.ResolvedTaskResources + tr v1beta1.TaskRun + spec v1beta1.TaskSpec + }{{ + description: "context taskRun replacement invalid", + rtr: resources.ResolvedTaskResources{ + TaskName: "Task1", + }, + tr: v1beta1.TaskRun{}, + spec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Container: corev1.Container{ + Name: "ImageName", + Image: "$(context.taskRun.missing)", + }, + }}, + }, + }, { + description: "context task replacement invalid", + rtr: resources.ResolvedTaskResources{ + TaskName: "Task1", + }, + tr: v1beta1.TaskRun{}, + spec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Container: corev1.Container{ + Name: "ImageName", + Image: "$(context.task.missing)", + }, + }}, + }, + }} { + t.Run(tc.description, func(t *testing.T) { + if err := validateContextVariables(&tc.spec, &tc.tr); err == nil { + t.Fatalf("expected to see error when validating invalid contexts but saw %v", err) + } + }) + } +} diff --git a/pkg/substitution/substitution.go b/pkg/substitution/substitution.go index 30d027074a1..06d5b96c0a5 100644 --- a/pkg/substitution/substitution.go +++ b/pkg/substitution/substitution.go @@ -27,7 +27,7 @@ import ( const parameterSubstitution = `[_a-zA-Z][_a-zA-Z0-9.-]*(\[\*\])?` -const braceMatchingRegex = "(\\$(\\(%s.(?P%s)\\)))" +const braceMatchingRegex = "(\\$(\\(%s\\.(?P%s)\\)))" func ValidateVariable(name, value, prefix, locationName, path string, vars sets.String) *apis.FieldError { if vs, present := extractVariablesFromString(value, prefix); present { diff --git a/pkg/substitution/substitution_test.go b/pkg/substitution/substitution_test.go index c218fda0612..1c7f497b579 100644 --- a/pkg/substitution/substitution_test.go +++ b/pkg/substitution/substitution_test.go @@ -49,6 +49,16 @@ func TestValidateVariables(t *testing.T) { vars: sets.NewString("baz"), }, expectedError: nil, + }, { + name: "valid variable uid", + args: args{ + input: "--flag=$(context.taskRun.uid)", + prefix: "context.taskRun", + locationName: "step", + path: "taskspec.steps", + vars: sets.NewString("uid"), + }, + expectedError: nil, }, { name: "multiple variables", args: args{