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

Release v0.11.x results cherry pick #2477

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions examples/v1beta1/pipelineruns/pipeline-result-conditions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
apiVersion: tekton.dev/v1alpha1
kind: Condition
metadata:
name: condition-1
spec:
check:
args:
- EXITCODE=$(python -c "import sys; input1=str.rstrip(sys.argv[1]); input2=str.rstrip(sys.argv[2]); print(0) if (input1 == 'heads') else
print(1)" '$(params.flip-coin)' 'heads'); exit $EXITCODE
command:
- sh
- -c
image: python:alpine3.6
params:
- name: flip-coin
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: flip-coin
spec:
results:
- description: /tmp/output
name: output
steps:
- args:
- python -c "import random; result = 'heads' if random.randint(0,1) == 0 else
'tails'; result='heads'; print(result)" | tee $(results.output.path)
command:
- sh
- -c
image: python:alpine3.6
name: flip-coin
---
apiVersion: tekton.dev/v1alpha1
kind: Task
metadata:
name: condition-check
spec:
params:
- name: flip-coin
results:
- description: /tmp/output
name: output
steps:
- args:
- EXITCODE=$(python -c "import sys; input1=str.rstrip(sys.argv[1]); input2=str.rstrip(sys.argv[2]); print(input1) if (input1 == 'heads') else
print(input1)" '$(params.flip-coin)' 'heads'); echo $EXITCODE | tee $(results.output.path)
command:
- sh
- -c
image: python:alpine3.6
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: generate-random-number
spec:
results:
- description: /tmp/output
name: output
steps:
- args:
- python -c "import random; print(random.randint($0, $1))" | tee $2
- '0'
- '9'
- $(results.output.path)
command:
- sh
- -c
image: python:alpine3.6
name: generate-random-number
---
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
annotations:
pipelines.kubeflow.org/pipeline_spec: '{"description": "Shows how to use dsl.Condition().",
"name": "Conditional execution pipeline"}'
name: flip-cond-gen-pipeline
spec:
params: []
tasks:
- name: flip-coin
params: []
taskRef:
name: flip-coin
- name: condition-check
params:
- name: flip-coin
value: $(tasks.flip-coin.results.output)
taskRef:
name: condition-check
- conditions:
- conditionRef: condition-1
params:
- name: flip-coin
value: $(tasks.flip-coin.results.output)
name: generate-random-number
params: []
taskRef:
name: generate-random-number
---
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: flip-cond-gen-pipeline-run
spec:
pipelineRef:
name: flip-cond-gen-pipeline
34 changes: 34 additions & 0 deletions examples/v1beta1/pipelineruns/pipelinerun-results-with-params.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: test-case
name: test-case-run
spec:
params:
- name: prefix
value: prefix
pipelineSpec:
params:
- name: prefix
tasks:
- name: generate-suffix
taskSpec:
results:
- name: suffix
steps:
- name: generate-suffix
image: alpine
script: |
echo -n "suffix" > $(results.suffix.path)
- name: do-something
taskSpec:
params:
- name: arg
steps:
- name: do-something
image: alpine
script: |
echo "$(params.arg)" | grep "prefix:suffix"
params:
- name: arg
value: "$(params.prefix):$(tasks.generate-suffix.results.suffix)"
14 changes: 13 additions & 1 deletion pkg/apis/pipeline/v1alpha1/pipeline_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,27 @@ func (pt PipelineTask) Deps() []string {
for _, rd := range cond.Resources {
deps = append(deps, rd.From...)
}
for _, param := range cond.Params {
expressions, ok := v1beta1.GetVarSubstitutionExpressionsForParam(param)
if ok {
resultRefs := v1beta1.NewResultRefs(expressions)
for _, resultRef := range resultRefs {
deps = append(deps, resultRef.PipelineTask)
}
}
}
}
// Add any dependents from task results
for _, param := range pt.Params {
if resultRefs, err := v1beta1.NewResultRefs(param); err == nil {
expressions, ok := v1beta1.GetVarSubstitutionExpressionsForParam(param)
if ok {
resultRefs := v1beta1.NewResultRefs(expressions)
for _, resultRef := range resultRefs {
deps = append(deps, resultRef.PipelineTask)
}
}
}

return deps
}

Expand Down
19 changes: 0 additions & 19 deletions pkg/apis/pipeline/v1alpha1/pipeline_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"fmt"
"strings"

"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"github.com/tektoncd/pipeline/pkg/apis/validate"
"github.com/tektoncd/pipeline/pkg/list"
"github.com/tektoncd/pipeline/pkg/reconciler/pipeline/dag"
Expand Down Expand Up @@ -136,20 +135,6 @@ func validateGraph(tasks []PipelineTask) error {
return nil
}

// validateParamResults ensure that task result variables are properly configured
func validateParamResults(tasks []PipelineTask) error {
for _, task := range tasks {
for _, param := range task.Params {
if v1beta1.LooksLikeContainsResultRefs(param) {
if _, err := v1beta1.NewResultRefs(param); err != nil {
return err
}
}
}
}
return nil
}

// Validate checks that taskNames in the Pipeline are valid and that the graph
// of Tasks expressed in the Pipeline makes sense.
func (ps *PipelineSpec) Validate(ctx context.Context) *apis.FieldError {
Expand Down Expand Up @@ -214,10 +199,6 @@ func (ps *PipelineSpec) Validate(ctx context.Context) *apis.FieldError {
return apis.ErrInvalidValue(err.Error(), "spec.tasks")
}

if err := validateParamResults(ps.Tasks); err != nil {
return apis.ErrInvalidValue(err.Error(), "spec.tasks.params.value")
}

// The parameter variables should be valid
if err := validatePipelineParameterVariables(ps.Tasks, ps.Params); err != nil {
return err
Expand Down
9 changes: 0 additions & 9 deletions pkg/apis/pipeline/v1alpha1/pipeline_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,15 +369,6 @@ func TestPipeline_Validate(t *testing.T) {
tb.PipelineWorkspaceDeclaration("foo"),
)),
failureExpected: true,
}, {
name: "task params results malformed variable substitution expression",
p: tb.Pipeline("name", "namespace", tb.PipelineSpec(
tb.PipelineTask("a-task", "a-task"),
tb.PipelineTask("b-task", "b-task",
tb.PipelineTaskParam("b-param", "$(tasks.a-task.resultTypo.bResult)"),
),
)),
failureExpected: true,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
107 changes: 0 additions & 107 deletions pkg/apis/pipeline/v1beta1/param_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"

resource "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1"
)
Expand Down Expand Up @@ -142,108 +140,3 @@ func NewArrayOrString(value string, values ...string) ArrayOrString {
StringVal: value,
}
}

// ResultRef is a type that represents a reference to a task run result
type ResultRef struct {
PipelineTask string
Result string
}

const (
resultExpressionFormat = "tasks.<taskName>.results.<resultName>"
// ResultTaskPart Constant used to define the "tasks" part of a pipeline result reference
ResultTaskPart = "tasks"
// ResultResultPart Constant used to define the "results" part of a pipeline result reference
ResultResultPart = "results"
variableSubstitutionFormat = `\$\([A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\)`
)

var variableSubstitutionRegex = regexp.MustCompile(variableSubstitutionFormat)

// NewResultRefs extracts all ResultReferences from param.
// If the ResultReference can be extracted, they are returned. Otherwise an error is returned
func NewResultRefs(param Param) ([]*ResultRef, error) {
substitutionExpressions, ok := getVarSubstitutionExpressions(param)
if !ok {
return nil, fmt.Errorf("Invalid result reference expression: must contain variable substitution %q", resultExpressionFormat)
}
var resultRefs []*ResultRef
for _, expression := range substitutionExpressions {
pipelineTask, result, err := parseExpression(expression)
if err != nil {
return nil, fmt.Errorf("Invalid result reference expression: %v", err)
}
resultRefs = append(resultRefs, &ResultRef{
PipelineTask: pipelineTask,
Result: result,
})
}
return resultRefs, nil
}

// LooksLikeContainsResultRefs attempts to check if param looks like it contains any
// result references.
// This is useful if we want to make sure the param looks like a ResultReference before
// performing strict validation
func LooksLikeContainsResultRefs(param Param) bool {
extractedExpressions, ok := getVarSubstitutionExpressions(param)
if !ok {
return false
}
for _, expression := range extractedExpressions {
if looksLikeResultRef(expression) {
return true
}
}
return false
}

func looksLikeResultRef(expression string) bool {
return strings.HasPrefix(expression, "task") && strings.Contains(expression, ".result")
}

// getVarSubstitutionExpressions extracts all the value between "$(" and ")""
func getVarSubstitutionExpressions(param Param) ([]string, bool) {
var allExpressions []string
switch param.Value.Type {
case ParamTypeArray:
// array type
for _, value := range param.Value.ArrayVal {
expressions := variableSubstitutionRegex.FindAllString(value, -1)
if expressions == nil {
continue
}
for _, expression := range expressions {
allExpressions = append(allExpressions, stripVarSubExpression(expression))
}
}
if len(allExpressions) == 0 {
return nil, false
}
return allExpressions, true
case ParamTypeString:
// string type
expressions := variableSubstitutionRegex.FindAllString(param.Value.StringVal, -1)
if expressions == nil {
return nil, false
}
for _, expression := range expressions {
allExpressions = append(allExpressions, stripVarSubExpression(expression))
}
return allExpressions, true
default:
return nil, false
}
}

func stripVarSubExpression(expression string) string {
return strings.TrimSuffix(strings.TrimPrefix(expression, "$("), ")")
}

func parseExpression(substitutionExpression string) (string, string, error) {
subExpressions := strings.Split(substitutionExpression, ".")
if len(subExpressions) != 4 || subExpressions[0] != ResultTaskPart || subExpressions[2] != ResultResultPart {
return "", "", fmt.Errorf("Must be of the form %q", resultExpressionFormat)
}
return subExpressions[1], subExpressions[3], nil
}
Loading