Skip to content

Commit

Permalink
Adding pipeline results
Browse files Browse the repository at this point in the history
  • Loading branch information
othomann committed Feb 20, 2020
1 parent 4e9380f commit 88c637c
Show file tree
Hide file tree
Showing 14 changed files with 961 additions and 3 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.13

require (
cloud.google.com/go v0.47.0 // indirect
cloud.google.com/go/storage v1.0.0
contrib.go.opencensus.io/exporter/stackdriver v0.12.8 // indirect
github.com/GoogleCloudPlatform/cloud-builders/gcs-fetcher v0.0.0-20191203181535-308b93ad1f39
github.com/cloudevents/sdk-go v1.0.0
Expand Down Expand Up @@ -38,6 +39,7 @@ require (
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
google.golang.org/api v0.15.0
google.golang.org/appengine v1.6.5 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.2.5 // indirect
Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/pipeline/v1alpha1/pipeline_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ func (pt PipelineTask) Deps() []string {
deps = append(deps, rd.From...)
}
}
// Add any dependents from task results
for _, param := range pt.Params {
if resultRef, ok := v1alpha2.HasResultReference(param); ok {
deps = append(deps, resultRef.PipelineTask)
}
}
return deps
}

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

"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha2"
"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 @@ -128,6 +129,20 @@ 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 v1alpha2.LooksLikeResultRef(param) {
if _, err := v1alpha2.NewResultReference(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 @@ -185,6 +200,10 @@ 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: 9 additions & 0 deletions pkg/apis/pipeline/v1alpha1/pipeline_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,15 @@ 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
75 changes: 75 additions & 0 deletions pkg/apis/pipeline/v1alpha2/param_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"encoding/json"
"fmt"
"strings"

resource "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1"
)
Expand Down Expand Up @@ -124,3 +125,77 @@ func (arrayOrString *ArrayOrString) ApplyReplacements(stringReplacements map[str
arrayOrString.ArrayVal = newArrayVal
}
}

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

const (
resultExpressionFormat = "tasks.<taskName>.results.<resultName>"
resultTaskPart = "tasks"
resultResultPart = "results"
)

// NewResultReference extracts a ResultReference form param.
// If the ResultReference cab be extracted, it is returned. Otherwise an error is returned
func NewResultReference(param Param) (*ResultReference, error) {
substitutionExpression, ok := getVarSubstitutionExpression(param)
if !ok {
return nil, fmt.Errorf("Invalid result reference expression: must contain variable substitution %q", resultExpressionFormat)
}
pipelineTask, result, err := parseExpression(substitutionExpression)
if err != nil {
return nil, fmt.Errorf("Invalid result reference expression: %v", err)
}
return &ResultReference{
PipelineTask: pipelineTask,
Result: result,
}, nil
}

// HasResultReference is similar to NewResultReference, but it is used
// when the error is not relevant. i.e. we simply want to check if param contains
// a result reference
func HasResultReference(param Param) (*ResultReference, bool) {
if resultRef, err := NewResultReference(param); err == nil {
return resultRef, true
}
return nil, false
}

// LooksLikeResultRef attempts to check if param looks like a result reference.
// This is useful if we want to make sure the param looks like a ResultReference before
// performing strict validation
func LooksLikeResultRef(param Param) bool {
if param.Value.Type != ParamTypeString {
return false
}
extractedExpression, ok := getVarSubstitutionExpression(param)
if !ok {
return false
}

return strings.HasPrefix(extractedExpression, "task") && strings.Contains(extractedExpression, ".result")
}

// getVarSubstitutionExpression extracts the value between "$(" and ")""
func getVarSubstitutionExpression(param Param) (string, bool) {
if param.Value.Type != ParamTypeString {
return "", false
}
value := param.Value.StringVal
if !strings.HasPrefix(value, "$(") || !strings.HasSuffix(value, ")") {
return "", false
}
return strings.TrimSuffix(strings.TrimPrefix(value, "$("), ")"), true
}

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

0 comments on commit 88c637c

Please sign in to comment.