Skip to content

Commit

Permalink
[TEP-0048] Add support for default results
Browse files Browse the repository at this point in the history
  • Loading branch information
vinamra28 committed Oct 9, 2022
1 parent 99238a5 commit 0150c97
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 0 deletions.
24 changes: 24 additions & 0 deletions examples/v1beta1/taskruns/alpha/default-result.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: default-result
spec:
results:
- name: my-result
description: with default value
default: "default result"
steps:
- name: build-sources
image: ubuntu
script: |
#!/usr/bin/env bash
echo -n "This script is not going to produce result."
echo -n "Still the Task is not going to fail as the result has a default value."
---
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
generateName: default-result-run-
spec:
taskRef:
name: default-result
2 changes: 2 additions & 0 deletions pkg/apis/pipeline/v1/result_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type TaskResult struct {
// Description is a human-readable description of the result
// +optional
Description string `json:"description,omitempty"`

Default *ResultValue `json:"default,omitempty"`
}

// TaskRunResult used to describe the results of a task
Expand Down
18 changes: 18 additions & 0 deletions pkg/apis/pipeline/v1beta1/result_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,22 @@ func (r *TaskResult) convertFrom(ctx context.Context, source v1.TaskResult) {
properties[k] = PropertySpec{Type: ParamType(v.Type)}
}
r.Properties = properties
if source.Default != nil {
r.Default = &ParamValue{
Type: ParamType(source.Default.Type),
}

if source.Default.StringVal != "" {
r.Default.StringVal = source.Default.StringVal
}

if len(source.Default.ArrayVal) != 0 {
r.Default.ArrayVal = source.Default.ArrayVal
}

if len(source.Default.ObjectVal) != 0 {
r.Default.ObjectVal = source.Default.ObjectVal
}

}
}
4 changes: 4 additions & 0 deletions pkg/apis/pipeline/v1beta1/result_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ type TaskResult struct {
// Description is a human-readable description of the result
// +optional
Description string `json:"description,omitempty"`

// Default is the value a result produces if no result is produced by Task.
// +optional
Default *ResultValue `json:"default,omitempty"`
}

// TaskRunResult used to describe the results of a task
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/pipeline/v1beta1/result_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ func (tr TaskResult) Validate(ctx context.Context) (errs *apis.FieldError) {
if !resultNameFormatRegex.MatchString(tr.Name) {
return apis.ErrInvalidKeyName(tr.Name, "name", fmt.Sprintf("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 '%s')", ResultNameFormat))
}
// Default results in alpha feature
if tr.Default != nil {
errs = errs.Also(version.ValidateEnabledAPIFields(ctx, "default results", config.AlphaAPIFields))
return errs
}
// Array and Object is alpha feature
if tr.Type == ResultsTypeArray || tr.Type == ResultsTypeObject {
errs := validateObjectResult(tr)
Expand Down
21 changes: 21 additions & 0 deletions pkg/apis/pipeline/v1beta1/result_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ func TestResultsValidate(t *testing.T) {
Properties: map[string]v1beta1.PropertySpec{"hello": {Type: v1beta1.ParamTypeString}},
},
apiFields: "alpha",
}, {
name: "valid result with default value",
Result: v1beta1.TaskResult{
Name: "MY-RESULT",
Type: v1beta1.ResultsTypeString,
Description: "my great result",
Default: &v1beta1.ParamValue{StringVal: "string value"},
},
apiFields: "alpha",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -161,6 +170,18 @@ func TestResultsValidateError(t *testing.T) {
Message: "missing field(s)",
Paths: []string{"MY-RESULT.properties"},
},
}, {
name: "valid result with default value",
Result: v1beta1.TaskResult{
Name: "MY-RESULT",
Type: v1beta1.ResultsTypeString,
Description: "my great result",
Default: &v1beta1.ParamValue{StringVal: "string value"},
},
apiFields: "stable",
expectedError: apis.FieldError{
Message: "default results requires \"enable-api-fields\" feature gate to be \"alpha\" but it is \"stable\"",
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
42 changes: 42 additions & 0 deletions pkg/reconciler/taskrun/taskrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,20 @@ func (c *Reconciler) prepare(ctx context.Context, tr *v1beta1.TaskRun) (*v1beta1
return nil, nil, controller.NewPermanentError(err)
}

if tr.Status.TaskRunResults == nil || len(tr.Status.TaskRunResults) == 0 {
tr.Status.TaskRunResults = make([]v1beta1.TaskRunResult, 0)
}

for _, tR := range rtr.TaskSpec.Results {
if tR.Default != nil {
tr.Status.TaskRunResults = append(tr.Status.TaskRunResults, v1beta1.TaskRunResult{
Name: tR.Name,
Type: tR.Type,
Value: *tR.Default,
})
}
}

// 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
Expand Down Expand Up @@ -544,6 +558,34 @@ func (c *Reconciler) reconcile(ctx context.Context, tr *v1beta1.TaskRun, rtr *re
return err
}

if string(tr.Status.Conditions[0].Type) == string(apis.ConditionSucceeded) && string(tr.Status.Conditions[0].Status) != string(corev1.ConditionUnknown) {
if len(tr.Status.TaskRunResults) == 0 && len(rtr.TaskSpec.Results) != 0 {
noValueErr := fmt.Errorf("%s TaskRun was expected to produce result but didn't", tr.Name)
logger.Errorf("%s TaskRun was expected to produce result but didn't", tr.Name)
tr.Status.MarkResourceFailed(v1beta1.TaskRunReasonFailed, noValueErr)
return noValueErr
}

for _, trResult := range tr.Status.TaskRunResults {
noValueErr := fmt.Errorf("expected value for Result %s but found nil", trResult.Name)
if trResult.Type == v1beta1.ResultsTypeString && trResult.Value.StringVal == "" {
tr.Status.MarkResourceFailed(v1beta1.TaskRunReasonFailed, noValueErr)
return noValueErr
}

if trResult.Type == v1beta1.ResultsTypeArray && len(trResult.Value.ArrayVal) == 0 {
tr.Status.MarkResourceFailed(v1beta1.TaskRunReasonFailed, noValueErr)
return noValueErr
}

if trResult.Type == v1beta1.ResultsTypeObject && len(trResult.Value.ObjectVal) == 0 {
tr.Status.MarkResourceFailed(v1beta1.TaskRunReasonFailed, noValueErr)
return noValueErr
}

}
}

logger.Infof("Successfully reconciled taskrun %s/%s with status: %#v", tr.Name, tr.Namespace, tr.Status.GetCondition(apis.ConditionSucceeded))
return nil
}
Expand Down
92 changes: 92 additions & 0 deletions pkg/reconciler/taskrun/taskrun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4996,3 +4996,95 @@ status:
t.Errorf("expected Status.TaskSpec to match, but differed: %s", diff.PrintWantGot(d))
}
}

func TestReconcile_TestDefaultResults(t *testing.T) {

defaultResultsTask := &v1beta1.Task{
ObjectMeta: objectMeta("test-results-task", "foo"),
Spec: v1beta1.TaskSpec{
Steps: []v1beta1.Step{simpleStep},
Results: []v1beta1.TaskResult{
{
Name: "aResult",
Type: v1beta1.ResultsTypeString,
Default: &v1beta1.ParamValue{StringVal: "default-result"},
},
{
Name: "bResult",
Type: v1beta1.ResultsTypeArray,
Default: &v1beta1.ParamValue{ArrayVal: []string{"default-result-a", "default-result-b"}},
},
{
Name: "cResult",
Type: v1beta1.ResultsTypeObject,
Properties: map[string]v1beta1.PropertySpec{"url": {Type: "string"}, "commit": {Type: "string"}},
Default: &v1beta1.ParamValue{ObjectVal: map[string]string{"url": "foobar", "commit": "commit"}},
},
},
},
}

taskRunWithResults := parse.MustParseTaskRun(t, `
metadata:
name: test-taskrun-results-type-valid
namespace: foo
spec:
taskRef:
name: test-results-task
status:
taskResults:
- name: aResult
type: string
value: aResultValue
`)

d := test.Data{
TaskRuns: []*v1beta1.TaskRun{taskRunWithResults},
Tasks: []*v1beta1.Task{defaultResultsTask},
ConfigMaps: []*corev1.ConfigMap{{
ObjectMeta: metav1.ObjectMeta{Namespace: system.Namespace(), Name: config.GetFeatureFlagsConfigName()},
Data: map[string]string{
"enable-api-fields": config.AlphaAPIFields,
},
}},
}
for _, tc := range []struct {
name string
taskRun *v1beta1.TaskRun
}{{
name: "taskrun with default results",
taskRun: taskRunWithResults,
}} {
t.Run(tc.name, func(t *testing.T) {
testAssets, cancel := getTaskRunController(t, d)
defer cancel()
createServiceAccount(t, testAssets, tc.taskRun.Spec.ServiceAccountName, tc.taskRun.Namespace)

// Reconcile the TaskRun. This creates a Pod.
if err := testAssets.Controller.Reconciler.Reconcile(testAssets.Ctx, getRunName(tc.taskRun)); err == nil {
t.Error("Wanted a wrapped requeue error, but got nil.")
} else if ok, _ := controller.IsRequeueKey(err); !ok {
t.Errorf("Error reconciling TaskRun. Got error %v", err)
}

tr, err := testAssets.Clients.Pipeline.TektonV1beta1().TaskRuns(tc.taskRun.Namespace).Get(testAssets.Ctx, tc.taskRun.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("getting updated taskrun: %v", err)
}

condition := tr.Status.GetCondition(apis.ConditionSucceeded)
if condition.Type != apis.ConditionSucceeded || condition.Reason != "Running" {
t.Errorf("Expected TaskRun to succeed but it did not. Final conditions were:\n%#v", tr.Status.Conditions)
}

fmt.Println(tr.Status.TaskRunResults)

fmt.Println(tr.Status.TaskRunResults[0].Value.StringVal)

// if !strings.EqualFold(tr.Status.TaskRunResults[0].Value.StringVal, "default-result") {
// t.Errorf("Expected default result but found something else")
// }

})
}
}

0 comments on commit 0150c97

Please sign in to comment.