Skip to content

Commit

Permalink
Introduce Value in TaskResults
Browse files Browse the repository at this point in the history
This PR introduces the field `Value` in `TaskResults`.
This field is necessary to capture the results produced by `StepActions`
if the Task needs to resolve name conflicts.

This is part of issue #7259.
Following this PR, we will add support for extracting StepAction results
via termination message and sidecar logs.
  • Loading branch information
chitrangpatel authored and tekton-robot committed Nov 10, 2023
1 parent 6bacbdc commit f975869
Show file tree
Hide file tree
Showing 15 changed files with 575 additions and 18 deletions.
32 changes: 30 additions & 2 deletions docs/pipeline-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1741,7 +1741,7 @@ Used to distinguish between a single string and an array of strings.</p>
<h3 id="tekton.dev/v1.ParamValue">ParamValue
</h3>
<p>
(<em>Appears on:</em><a href="#tekton.dev/v1.Param">Param</a>, <a href="#tekton.dev/v1.ParamSpec">ParamSpec</a>, <a href="#tekton.dev/v1.PipelineResult">PipelineResult</a>, <a href="#tekton.dev/v1.PipelineRunResult">PipelineRunResult</a>, <a href="#tekton.dev/v1.TaskRunResult">TaskRunResult</a>)
(<em>Appears on:</em><a href="#tekton.dev/v1.Param">Param</a>, <a href="#tekton.dev/v1.ParamSpec">ParamSpec</a>, <a href="#tekton.dev/v1.PipelineResult">PipelineResult</a>, <a href="#tekton.dev/v1.PipelineRunResult">PipelineRunResult</a>, <a href="#tekton.dev/v1.TaskResult">TaskResult</a>, <a href="#tekton.dev/v1.TaskRunResult">TaskRunResult</a>)
</p>
<div>
<p>ResultValue is a type alias of ParamValue</p>
Expand Down Expand Up @@ -4931,6 +4931,20 @@ string
<p>Description is a human-readable description of the result</p>
</td>
</tr>
<tr>
<td>
<code>value</code><br/>
<em>
<a href="#tekton.dev/v1.ParamValue">
ParamValue
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Value the expression used to retrieve the value of the result from an underlying Step.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1.TaskRunDebug">TaskRunDebug
Expand Down Expand Up @@ -10232,7 +10246,7 @@ Used to distinguish between a single string and an array of strings.</p>
<h3 id="tekton.dev/v1beta1.ParamValue">ParamValue
</h3>
<p>
(<em>Appears on:</em><a href="#tekton.dev/v1beta1.Param">Param</a>, <a href="#tekton.dev/v1beta1.ParamSpec">ParamSpec</a>, <a href="#tekton.dev/v1beta1.PipelineResult">PipelineResult</a>, <a href="#tekton.dev/v1beta1.PipelineRunResult">PipelineRunResult</a>, <a href="#tekton.dev/v1beta1.TaskRunResult">TaskRunResult</a>)
(<em>Appears on:</em><a href="#tekton.dev/v1beta1.Param">Param</a>, <a href="#tekton.dev/v1beta1.ParamSpec">ParamSpec</a>, <a href="#tekton.dev/v1beta1.PipelineResult">PipelineResult</a>, <a href="#tekton.dev/v1beta1.PipelineRunResult">PipelineRunResult</a>, <a href="#tekton.dev/v1beta1.TaskResult">TaskResult</a>, <a href="#tekton.dev/v1beta1.TaskRunResult">TaskRunResult</a>)
</p>
<div>
<p>ResultValue is a type alias of ParamValue</p>
Expand Down Expand Up @@ -14135,6 +14149,20 @@ string
<p>Description is a human-readable description of the result</p>
</td>
</tr>
<tr>
<td>
<code>value</code><br/>
<em>
<a href="#tekton.dev/v1beta1.ParamValue">
ParamValue
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Value the expression used to retrieve the value of the result from an underlying Step.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1beta1.TaskRunConditionType">TaskRunConditionType
Expand Down
8 changes: 7 additions & 1 deletion pkg/apis/pipeline/v1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 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,10 @@ type TaskResult struct {
// Description is a human-readable description of the result
// +optional
Description string `json:"description,omitempty"`

// Value the expression used to retrieve the value of the result from an underlying Step.
// +optional
Value *ResultValue `json:"value,omitempty"`
}

// TaskRunResult used to describe the results of a task
Expand Down
70 changes: 63 additions & 7 deletions pkg/apis/pipeline/v1/result_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ package v1
import (
"context"
"fmt"
"regexp"

"github.com/tektoncd/pipeline/pkg/apis/config"
"k8s.io/apimachinery/pkg/util/validation"
"knative.dev/pkg/apis"
)

Expand All @@ -28,20 +31,16 @@ func (tr TaskResult) Validate(ctx context.Context) (errs *apis.FieldError) {

switch {
case tr.Type == ResultsTypeObject:
errs := validateObjectResult(tr)
return errs
errs = errs.Also(validateObjectResult(tr))
case tr.Type == ResultsTypeArray:
return nil
// Resources created before the result. Type was introduced may not have Type set
// and should be considered valid
case tr.Type == "":
return nil
// By default, the result type is string
case tr.Type != ResultsTypeString:
return apis.ErrInvalidValue(tr.Type, "type", "type must be string")
errs = errs.Also(apis.ErrInvalidValue(tr.Type, "type", "type must be string"))
}

return nil
return errs.Also(tr.validateValue(ctx))
}

// validateObjectResult validates the object result and check if the Properties is missing
Expand All @@ -66,3 +65,60 @@ func validateObjectResult(tr TaskResult) (errs *apis.FieldError) {
}
return nil
}

// validateValue validates the value of the TaskResult.
// It requires that enable-step-actions is true, the value is of type string
// and format $(steps.<stepName>.results.<resultName>)
func (tr TaskResult) validateValue(ctx context.Context) (errs *apis.FieldError) {
if tr.Value == nil {
return nil
}
if !config.FromContextOrDefaults(ctx).FeatureFlags.EnableStepActions {
return apis.ErrGeneric("feature flag %s should be set to true to fetch Results from Steps using StepActions.", config.EnableStepActions)
}
if tr.Value.Type != ParamTypeString {
return &apis.FieldError{
Message: fmt.Sprintf(
"Invalid Type. Wanted string but got: \"%v\"", tr.Value.Type),
Paths: []string{
fmt.Sprintf("%s.type", tr.Name),
},
}
}
if tr.Value.StringVal != "" {
stepName, resultName, err := ExtractStepResultName(tr.Value.StringVal)
if err != nil {
return &apis.FieldError{
Message: fmt.Sprintf("%v", err),
Paths: []string{fmt.Sprintf("%s.value", tr.Name)},
}
}
if e := validation.IsDNS1123Label(stepName); len(e) > 0 {
errs = errs.Also(&apis.FieldError{
Message: fmt.Sprintf("invalid extracted step name %q", stepName),
Paths: []string{fmt.Sprintf("%s.value", tr.Name)},
Details: "stepName in $(steps.<stepName>.results.<resultName>) must be a valid DNS Label, For more info refer to https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names",
})
}
if !resultNameFormatRegex.MatchString(resultName) {
errs = errs.Also(&apis.FieldError{
Message: fmt.Sprintf("invalid extracted result name %q", resultName),
Paths: []string{fmt.Sprintf("%s.value", tr.Name)},
Details: fmt.Sprintf("resultName in $(steps.<stepName>.results.<resultName>) 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),
})
}
}
return errs
}

// ExtractStepResultName extracts the step name and result name from a string matching
// formtat $(steps.<stepName>.results.<resultName>).
// If a match is not found, an error is retured.
func ExtractStepResultName(value string) (string, string, error) {
re := regexp.MustCompile(`\$\(steps\.(.*?)\.results\.(.*?)\)`)
rs := re.FindStringSubmatch(value)
if len(rs) != 3 {
return "", "", fmt.Errorf("Could not extract step name and result name. Expected value to look like $(steps.<stepName>.results.<resultName>) but got \"%v\"", value)
}
return rs[1], rs[2], nil
}
Loading

0 comments on commit f975869

Please sign in to comment.