-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(pipeline): allow variable substitution in pipeline.tasks[].onError #8600
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
feat(pipeline): allow variable substitution in pipeline.tasks[].onError #8600
Conversation
|
The following is the coverage report on the affected files.
|
| | `TaskRun` | `spec.workspaces[].csi.driver` | | ||
| | `TaskRun` | `spec.workspaces[].csi.nodePublishSecretRef.name` | | ||
| | `Pipeline` | `spec.tasks[].params[].value` | | ||
| | `Pipeline` | `spec.tasks[].conditions[].params[].value` | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The replacement of spec.tasks[].conditions[].params[].value is a capability of v1alpha1, which no longer has that field.
The current complete definition of the pipelinetask structure is as follows:
pipeline/pkg/apis/pipeline/v1/pipeline_types.go
Lines 179 to 257 in aec465e
| // PipelineTask defines a task in a Pipeline, passing inputs from both | |
| // Params and from the output of previous tasks. | |
| type PipelineTask struct { | |
| // Name is the name of this task within the context of a Pipeline. Name is | |
| // used as a coordinate with the `from` and `runAfter` fields to establish | |
| // the execution order of tasks relative to one another. | |
| Name string `json:"name,omitempty"` | |
| // DisplayName is the display name of this task within the context of a Pipeline. | |
| // This display name may be used to populate a UI. | |
| // +optional | |
| DisplayName string `json:"displayName,omitempty"` | |
| // Description is the description of this task within the context of a Pipeline. | |
| // This description may be used to populate a UI. | |
| // +optional | |
| Description string `json:"description,omitempty"` | |
| // TaskRef is a reference to a task definition. | |
| // +optional | |
| TaskRef *TaskRef `json:"taskRef,omitempty"` | |
| // TaskSpec is a specification of a task | |
| // Specifying TaskSpec can be disabled by setting | |
| // `disable-inline-spec` feature flag.. | |
| // +optional | |
| TaskSpec *EmbeddedTask `json:"taskSpec,omitempty"` | |
| // When is a list of when expressions that need to be true for the task to run | |
| // +optional | |
| When WhenExpressions `json:"when,omitempty"` | |
| // Retries represents how many times this task should be retried in case of task failure: ConditionSucceeded set to False | |
| // +optional | |
| Retries int `json:"retries,omitempty"` | |
| // RunAfter is the list of PipelineTask names that should be executed before | |
| // this Task executes. (Used to force a specific ordering in graph execution.) | |
| // +optional | |
| // +listType=atomic | |
| RunAfter []string `json:"runAfter,omitempty"` | |
| // Parameters declares parameters passed to this task. | |
| // +optional | |
| // +listType=atomic | |
| Params Params `json:"params,omitempty"` | |
| // Matrix declares parameters used to fan out this task. | |
| // +optional | |
| Matrix *Matrix `json:"matrix,omitempty"` | |
| // Workspaces maps workspaces from the pipeline spec to the workspaces | |
| // declared in the Task. | |
| // +optional | |
| // +listType=atomic | |
| Workspaces []WorkspacePipelineTaskBinding `json:"workspaces,omitempty"` | |
| // Time after which the TaskRun times out. Defaults to 1 hour. | |
| // Refer Go's ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration | |
| // +optional | |
| Timeout *metav1.Duration `json:"timeout,omitempty"` | |
| // PipelineRef is a reference to a pipeline definition | |
| // Note: PipelineRef is in preview mode and not yet supported | |
| // +optional | |
| PipelineRef *PipelineRef `json:"pipelineRef,omitempty"` | |
| // PipelineSpec is a specification of a pipeline | |
| // Note: PipelineSpec is in preview mode and not yet supported | |
| // Specifying PipelineSpec can be disabled by setting | |
| // `disable-inline-spec` feature flag.. | |
| // +optional | |
| PipelineSpec *PipelineSpec `json:"pipelineSpec,omitempty"` | |
| // OnError defines the exiting behavior of a PipelineRun on error | |
| // can be set to [ continue | stopAndFail ] | |
| // +optional | |
| OnError PipelineTaskOnErrorType `json:"onError,omitempty"` | |
| } |
The previous code for replacing variables in PipelineTask is as follows:
pipeline/pkg/reconciler/pipelinerun/resources/apply.go
Lines 301 to 352 in aec465e
| // ApplyReplacements replaces placeholders for declared parameters with the specified replacements. | |
| func ApplyReplacements(p *v1.PipelineSpec, replacements map[string]string, arrayReplacements map[string][]string, objectReplacements map[string]map[string]string) *v1.PipelineSpec { | |
| p = p.DeepCopy() | |
| for i := range p.Tasks { | |
| p.Tasks[i].Params = p.Tasks[i].Params.ReplaceVariables(replacements, arrayReplacements, objectReplacements) | |
| if p.Tasks[i].IsMatrixed() { | |
| p.Tasks[i].Matrix.Params = p.Tasks[i].Matrix.Params.ReplaceVariables(replacements, arrayReplacements, nil) | |
| for j := range p.Tasks[i].Matrix.Include { | |
| p.Tasks[i].Matrix.Include[j].Params = p.Tasks[i].Matrix.Include[j].Params.ReplaceVariables(replacements, nil, nil) | |
| } | |
| } else { | |
| p.Tasks[i].DisplayName = substitution.ApplyReplacements(p.Tasks[i].DisplayName, replacements) | |
| } | |
| for j := range p.Tasks[i].Workspaces { | |
| p.Tasks[i].Workspaces[j].SubPath = substitution.ApplyReplacements(p.Tasks[i].Workspaces[j].SubPath, replacements) | |
| } | |
| p.Tasks[i].When = p.Tasks[i].When.ReplaceVariables(replacements, arrayReplacements) | |
| if p.Tasks[i].TaskRef != nil { | |
| if p.Tasks[i].TaskRef.Params != nil { | |
| p.Tasks[i].TaskRef.Params = p.Tasks[i].TaskRef.Params.ReplaceVariables(replacements, arrayReplacements, objectReplacements) | |
| } | |
| p.Tasks[i].TaskRef.Name = substitution.ApplyReplacements(p.Tasks[i].TaskRef.Name, replacements) | |
| } | |
| p.Tasks[i] = propagateParams(p.Tasks[i], replacements, arrayReplacements, objectReplacements) | |
| } | |
| for i := range p.Finally { | |
| p.Finally[i].Params = p.Finally[i].Params.ReplaceVariables(replacements, arrayReplacements, objectReplacements) | |
| if p.Finally[i].IsMatrixed() { | |
| p.Finally[i].Matrix.Params = p.Finally[i].Matrix.Params.ReplaceVariables(replacements, arrayReplacements, nil) | |
| for j := range p.Finally[i].Matrix.Include { | |
| p.Finally[i].Matrix.Include[j].Params = p.Finally[i].Matrix.Include[j].Params.ReplaceVariables(replacements, nil, nil) | |
| } | |
| } else { | |
| p.Finally[i].DisplayName = substitution.ApplyReplacements(p.Finally[i].DisplayName, replacements) | |
| } | |
| for j := range p.Finally[i].Workspaces { | |
| p.Finally[i].Workspaces[j].SubPath = substitution.ApplyReplacements(p.Finally[i].Workspaces[j].SubPath, replacements) | |
| } | |
| p.Finally[i].When = p.Finally[i].When.ReplaceVariables(replacements, arrayReplacements) | |
| if p.Finally[i].TaskRef != nil { | |
| if p.Finally[i].TaskRef.Params != nil { | |
| p.Finally[i].TaskRef.Params = p.Finally[i].TaskRef.Params.ReplaceVariables(replacements, arrayReplacements, objectReplacements) | |
| } | |
| p.Finally[i].TaskRef.Name = substitution.ApplyReplacements(p.Finally[i].TaskRef.Name, replacements) | |
| } | |
| p.Finally[i] = propagateParams(p.Finally[i], replacements, arrayReplacements, objectReplacements) | |
| } | |
| return p | |
| } |
| } | ||
|
|
||
| // ValidateOnError validates the OnError field of a PipelineTask | ||
| func (pt PipelineTask) ValidateOnError(ctx context.Context) (errs *apis.FieldError) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's convenient to reuse this verification logic in the reconcile logic of PipelineRun.
| pr.Status.PipelineSpec = pipelineSpec | ||
|
|
||
| // validate pipelineSpec after apply parameters | ||
| if err := validatePipelineSpecAfterApplyParameters(ctx, pipelineSpec); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what is called deferred validation.
Targeted validation is performed after applying parameters.
The reason for not calling pipelineSpec.Validate again is that many field validations are unnecessary.
pipeline/pkg/reconciler/pipelinerun/pipelinerun.go
Lines 481 to 487 in aec465e
| if err := pipelineSpec.Validate(ctx); err != nil { | |
| // This Run has failed, so we need to mark it as failed and stop reconciling it | |
| pr.Status.MarkFailed(v1.PipelineRunReasonFailedValidation.String(), | |
| "Pipeline %s/%s can't be Run; it has an invalid spec: %s", | |
| pipelineMeta.Namespace, pipelineMeta.Name, pipelineErrors.WrapUserError(err)) | |
| return controller.NewPermanentError(err) | |
| } |
| for j := range p.Tasks[i].Matrix.Include { | ||
| p.Tasks[i].Matrix.Include[j].Params = p.Tasks[i].Matrix.Include[j].Params.ReplaceVariables(replacements, nil, nil) | ||
| // replaceVariablesInPipelineTasks handles variable replacement for a slice of PipelineTasks in-place | ||
| func replaceVariablesInPipelineTasks(tasks []v1.PipelineTask, replacements map[string]string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The previous code contained a lot of duplication; here it has been simply refactored.
| tasks[i].TaskRef.Name = substitution.ApplyReplacements(tasks[i].TaskRef.Name, replacements) | ||
| } | ||
| p.Tasks[i] = propagateParams(p.Tasks[i], replacements, arrayReplacements, objectReplacements) | ||
| tasks[i].OnError = v1.PipelineTaskOnErrorType(substitution.ApplyReplacements(string(tasks[i].OnError), replacements)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The most important line of code in this new update is the support for variable replacement logic in OnError. 😆
f62cd92 to
aedd707
Compare
|
The following is the coverage report on the affected files.
|
aedd707 to
9fa4f9f
Compare
|
The following is the coverage report on the affected files.
|
vdemeester
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏼
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: vdemeester The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
fix tektoncd#8564 In the reconcile logic of PipelineRun, add the ability to replace the `pipeline.tasks[].onError` field. At the same time, the validation of pipelineSpec legality has been delayed.
9fa4f9f to
fa78954
Compare
|
The following is the coverage report on the affected files.
|
|
/lgtm |
|
Arf, this is not implemented for v1beta1, at least the validation. We need to fix this. |
Okay, I’ll take a look tomorrow. |
Thanks 🤗 |
fix #8564
In the reconcile logic of PipelineRun, add the ability to replace the
pipeline.tasks[].onErrorfield. At the same time, the validation of pipelineSpec legality has been delayed.Changes
Submitter Checklist
As the author of this PR, please check off the items in this checklist:
/kind <type>. Valid types are bug, cleanup, design, documentation, feature, flake, misc, question, tepRelease Notes
/kind feature