Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add context variables for PipelineRun & TaskRun UIDs and add validation for all context variables #3017

Merged
merged 1 commit into from
Aug 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
34 changes: 34 additions & 0 deletions examples/v1beta1/pipelineruns/using_context_variables.yaml
@@ -0,0 +1,34 @@
kind: PipelineRun
apiVersion: tekton.dev/v1beta1
metadata:
generateName: test-pipelinerun-
spec:
serviceAccountName: 'default'
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)"
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)"
35 changes: 35 additions & 0 deletions pkg/apis/pipeline/v1beta1/pipeline_validation.go
Expand Up @@ -190,6 +190,10 @@ func (ps *PipelineSpec) Validate(ctx context.Context) *apis.FieldError {
return err
}

if err := validatePipelineContextVariables(ps.Tasks); err != nil {
return err
}

// Validate the pipeline's workspaces.
if err := validatePipelineWorkspaces(ps.Workspaces, ps.Tasks, ps.Finally); err != nil {
return err
Expand Down Expand Up @@ -392,6 +396,37 @@ func validatePipelineArraysIsolated(name, value, prefix string, vars sets.String
return substitution.ValidateVariableIsolated(name, value, prefix, "task parameter", "pipelinespec.params", vars)
}

func validatePipelineContextVariables(tasks []PipelineTask) *apis.FieldError {
pipelineRunContextNames := sets.NewString().Insert(
"name",
"namespace",
"uid",
)
pipelineContextNames := sets.NewString().Insert(
"name",
)
var paramValues []string
for _, task := range tasks {
for _, param := range task.Params {
paramValues = append(paramValues, param.Value.StringVal)
paramValues = append(paramValues, param.Value.ArrayVal...)
}
}
if err := validatePipelineContextVariablesInParamValues(paramValues, "context\\.pipelineRun", pipelineRunContextNames); err != nil {
return err
}
return validatePipelineContextVariablesInParamValues(paramValues, "context\\.pipeline", pipelineContextNames)
}

func validatePipelineContextVariablesInParamValues(paramValues []string, prefix string, contextNames sets.String) *apis.FieldError {
for _, paramValue := range paramValues {
if err := substitution.ValidateVariable(fmt.Sprintf("param[%s]", paramValue), paramValue, prefix, "params", "pipelinespec.params", contextNames); err != nil {
return err
}
}
return nil
}

// validateParamResults ensures that task result variables are properly configured
func validateParamResults(tasks []PipelineTask) error {
for _, task := range tasks {
Expand Down
100 changes: 100 additions & 0 deletions pkg/apis/pipeline/v1beta1/pipeline_validation_test.go
Expand Up @@ -1391,3 +1391,103 @@ func TestValidateFinalTasks_Failure(t *testing.T) {
})
}
}

func TestContextValid(t *testing.T) {
tests := []struct {
name string
tasks []PipelineTask
}{{
name: "valid string context variable for task name",
tasks: []PipelineTask{{
Name: "bar",
TaskRef: &TaskRef{Name: "bar-task"},
Params: []Param{{
Name: "a-param", Value: ArrayOrString{StringVal: "$(context.pipeline.name)"},
}},
}},
}, {
name: "valid string context variable for taskrun name",
tasks: []PipelineTask{{
Name: "bar",
TaskRef: &TaskRef{Name: "bar-task"},
Params: []Param{{
Name: "a-param", Value: ArrayOrString{StringVal: "$(context.pipelineRun.name)"},
}},
}},
}, {
name: "valid string context variable for taskRun namespace",
tasks: []PipelineTask{{
Name: "bar",
TaskRef: &TaskRef{Name: "bar-task"},
Params: []Param{{
Name: "a-param", Value: ArrayOrString{StringVal: "$(context.pipelineRun.namespace)"},
}},
}},
}, {
name: "valid string context variable for taskRun uid",
tasks: []PipelineTask{{
Name: "bar",
TaskRef: &TaskRef{Name: "bar-task"},
Params: []Param{{
Name: "a-param", Value: ArrayOrString{StringVal: "$(context.pipelineRun.uid)"},
}},
}},
}, {
name: "valid array context variables for task and taskRun names",
tasks: []PipelineTask{{
Name: "bar",
TaskRef: &TaskRef{Name: "bar-task"},
Params: []Param{{
Name: "a-param", Value: ArrayOrString{ArrayVal: []string{"$(context.pipeline.name)", "and", "$(context.pipelineRun.name)"}},
}},
}},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := validatePipelineContextVariables(tt.tasks); err != nil {
t.Errorf("Pipeline.validatePipelineContextVariables() returned error for valid pipeline context variables: %s: %v", tt.name, err)
}
})
}
}

func TestContextInvalid(t *testing.T) {
tests := []struct {
name string
tasks []PipelineTask
}{{
name: "invalid string context variable for pipeline",
tasks: []PipelineTask{{
Name: "bar",
TaskRef: &TaskRef{Name: "bar-task"},
Params: []Param{{
Name: "a-param", Value: ArrayOrString{StringVal: "$(context.pipeline.missing)"},
}},
}},
}, {
name: "invalid string context variable for pipelineRun",
tasks: []PipelineTask{{
Name: "bar",
TaskRef: &TaskRef{Name: "bar-task"},
Params: []Param{{
Name: "a-param", Value: ArrayOrString{StringVal: "$(context.pipelineRun.missing)"},
}},
}},
}, {
name: "invalid array context variables for pipeline and pipelineRun",
tasks: []PipelineTask{{
Name: "bar",
TaskRef: &TaskRef{Name: "bar-task"},
Params: []Param{{
Name: "a-param", Value: ArrayOrString{ArrayVal: []string{"$(context.pipeline.missing)", "and", "$(context.pipelineRun.missing)"}},
}},
}},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := validatePipelineContextVariables(tt.tasks); err == nil {
t.Errorf("Pipeline.validatePipelineContextVariables() did not return error for invalid pipeline parameters: %s, %s", tt.name, tt.tasks[0].Params)
}
})
}
}
20 changes: 20 additions & 0 deletions pkg/apis/pipeline/v1beta1/task_validation.go
Expand Up @@ -94,6 +94,11 @@ func (ts *TaskSpec) Validate(ctx context.Context) *apis.FieldError {
if err := ValidateResults(ts.Results); err != nil {
return err
}

if err := validateTaskContextVariables(ts.Steps); err != nil {
return err
}

return nil
}

Expand Down Expand Up @@ -248,6 +253,21 @@ func ValidateParameterVariables(steps []Step, params []ParamSpec) *apis.FieldErr
return validateArrayUsage(steps, "params", arrayParameterNames)
}

func validateTaskContextVariables(steps []Step) *apis.FieldError {
taskRunContextNames := sets.NewString().Insert(
"name",
"namespace",
"uid",
)
taskContextNames := sets.NewString().Insert(
"name",
)
if err := validateVariables(steps, "context\\.taskRun", taskRunContextNames); err != nil {
return err
}
return validateVariables(steps, "context\\.task", taskContextNames)
}

func ValidateResourcesVariables(steps []Step, resources *TaskResources) *apis.FieldError {
if resources == nil {
return nil
Expand Down
69 changes: 69 additions & 0 deletions pkg/apis/pipeline/v1beta1/task_validation_test.go
Expand Up @@ -265,6 +265,58 @@ func TestTaskSpecValidate(t *testing.T) {
Description: "my great result",
}},
},
}, {
name: "valid task name context",
fields: fields{
Steps: []v1beta1.Step{{
Container: corev1.Container{
Image: "my-image",
Args: []string{"arg"},
},
Script: `
#!/usr/bin/env bash
hello "$(context.task.name)"`,
}},
},
}, {
name: "valid taskrun name context",
fields: fields{
Steps: []v1beta1.Step{{
Container: corev1.Container{
Image: "my-image",
Args: []string{"arg"},
},
Script: `
#!/usr/bin/env bash
hello "$(context.taskRun.name)"`,
}},
},
}, {
name: "valid taskrun uid context",
fields: fields{
Steps: []v1beta1.Step{{
Container: corev1.Container{
Image: "my-image",
Args: []string{"arg"},
},
Script: `
#!/usr/bin/env bash
hello "$(context.taskRun.uid)"`,
}},
},
}, {
name: "valid context",
fields: fields{
Steps: []v1beta1.Step{{
Container: corev1.Container{
Image: "my-image",
Args: []string{"arg"},
},
Script: `
#!/usr/bin/env bash
hello "$(context.taskRun.namespace)"`,
}},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -852,6 +904,23 @@ func TestTaskSpecValidateError(t *testing.T) {
Paths: []string{"results[0].name"},
Details: "Name must consist of alphanumeric characters, '-', '_', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my-name', or 'my_name', regex used for validation is '^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$')",
},
}, {
name: "context not validate",
fields: fields{
Steps: []v1beta1.Step{{
Container: corev1.Container{
Image: "my-image",
Args: []string{"arg"},
},
Script: `
#!/usr/bin/env bash
hello "$(context.task.missing)"`,
}},
},
expectedError: apis.FieldError{
Message: `non-existent variable in "\n\t\t\t\t#!/usr/bin/env bash\n\t\t\t\thello \"$(context.task.missing)\"" for step script`,
Paths: []string{"taskspec.steps.script"},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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