From 39376a51a94959e3ee621317717a47845efa5278 Mon Sep 17 00:00:00 2001 From: Simon Behar Date: Wed, 16 Oct 2019 11:38:08 -0700 Subject: [PATCH 1/8] Done --- workflow/controller/operator.go | 4 ++++ workflow/validate/validate.go | 1 + 2 files changed, 5 insertions(+) diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go index 5731552bd662..bce46a23265e 100644 --- a/workflow/controller/operator.go +++ b/workflow/controller/operator.go @@ -1589,6 +1589,10 @@ func (woc *wfOperationCtx) processNodeOutputs(scope *wfScope, prefix string, nod key := fmt.Sprintf("%s.ip", prefix) scope.addParamToScope(key, node.PodIP) } + if node.Phase != "" { + key := fmt.Sprintf("%s.status", prefix) + scope.addParamToScope(key, string(node.Phase)) + } woc.addOutputsToScope(prefix, node.Outputs, scope) } diff --git a/workflow/validate/validate.go b/workflow/validate/validate.go index f38e61a9960c..55d433a15391 100644 --- a/workflow/validate/validate.go +++ b/workflow/validate/validate.go @@ -551,6 +551,7 @@ func (ctx *templateValidationCtx) validateSteps(scope map[string]interface{}, tm } stepNames[step.Name] = true prefix := fmt.Sprintf("steps.%s", step.Name) + scope[fmt.Sprintf("%s.status", prefix)] = true err := addItemsToScope(prefix, step.WithItems, step.WithParam, step.WithSequence, scope) if err != nil { return errors.Errorf(errors.CodeBadRequest, "templates.%s.steps[%d].%s %s", tmpl.Name, i, step.Name, err.Error()) From 89dfb4642a09b4341b5ed88f3073ce60132c1daa Mon Sep 17 00:00:00 2001 From: Simon Behar Date: Wed, 16 Oct 2019 12:03:20 -0700 Subject: [PATCH 2/8] Docs and tests --- docs/variables.md | 2 + examples/status-reference.yaml | 41 +++++++++++++ workflow/validate/validate.go | 1 + workflow/validate/validate_dag_test.go | 39 ++++++++++++ workflow/validate/validate_test.go | 83 ++++++++++++++++++++++++++ 5 files changed, 166 insertions(+) create mode 100644 examples/status-reference.yaml diff --git a/docs/variables.md b/docs/variables.md index b8127834b165..81dc4942b13f 100644 --- a/docs/variables.md +++ b/docs/variables.md @@ -13,6 +13,7 @@ The following variables are made available to reference various metadata of a wo | Variable | Description| |----------|------------| | `steps..ip` | IP address of a previous daemon container step | +| `steps..status` | Phase status of a previous script step | | `steps..outputs.result` | Output result of a previous script step | | `steps..outputs.parameters.` | Output parameter of a previous step | | `steps..outputs.artifacts.` | Output artifact of a previous step | @@ -21,6 +22,7 @@ The following variables are made available to reference various metadata of a wo | Variable | Description| |----------|------------| | `tasks..ip` | IP address of a previous daemon container task | +| `tasks..status` | Phase status of a previous task step | | `tasks..outputs.result` | Output result of a previous script task | | `tasks..outputs.parameters.` | Output parameter of a previous task | | `tasks..outputs.artifacts.` | Output artifact of a previous task | diff --git a/examples/status-reference.yaml b/examples/status-reference.yaml new file mode 100644 index 000000000000..e02ebe399931 --- /dev/null +++ b/examples/status-reference.yaml @@ -0,0 +1,41 @@ +# The status reference example combines the use of a status result, +# along with conditionals, to take a dynamic path in the +# workflow. In this example, depending on the status of 'flakey-container' +# the template will either run the 'succeeded' step or the 'failed' step. +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: status-reference- +spec: + entrypoint: status-reference + templates: + - name: status-reference + steps: + - - name: flakey-container + template: flakey-container + continueOn: + failed: true + - - name: failed + template: failed + when: "{{steps.flakey-container.status}} == Failed" + - name: succeeded + template: succeeded + when: "{{steps.flakey-container.status}} == Succeeded" + + - name: flakey-container + script: + image: alpine:3.6 + command: [sh, -c] + args: ["exit 1"] + + - name: failed + container: + image: alpine:3.6 + command: [sh, -c] + args: ["echo \"the flakey container failed\""] + + - name: succeeded + container: + image: alpine:3.6 + command: [sh, -c] + args: ["echo \"the flakey container passed\""] diff --git a/workflow/validate/validate.go b/workflow/validate/validate.go index 55d433a15391..0f337c4ec830 100644 --- a/workflow/validate/validate.go +++ b/workflow/validate/validate.go @@ -879,6 +879,7 @@ func (ctx *templateValidationCtx) validateDAG(scope map[string]interface{}, tmpl return errors.Errorf(errors.CodeBadRequest, "templates.%s.tasks.%s %s", tmpl.Name, task.Name, err.Error()) } prefix := fmt.Sprintf("tasks.%s", task.Name) + scope[fmt.Sprintf("%s.status", prefix)] = true ctx.addOutputsToScope(resolvedTmpl, prefix, scope, false) resolvedTemplates[task.Name] = resolvedTmpl dupDependencies := make(map[string]bool) diff --git a/workflow/validate/validate_dag_test.go b/workflow/validate/validate_dag_test.go index 1beb9c60bd32..42d2e91fb783 100644 --- a/workflow/validate/validate_dag_test.go +++ b/workflow/validate/validate_dag_test.go @@ -290,6 +290,45 @@ func TestDAGArtifactResolution(t *testing.T) { assert.Nil(t, err) } +var dagStatusReference = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: dag-arg-passing- +spec: + entrypoint: dag-arg-passing + templates: + - name: echo + inputs: + parameters: + - name: message + container: + image: alpine:3.7 + command: [echo, "{{inputs.parameters.message}}"] + + - name: dag-arg-passing + dag: + tasks: + - name: A + template: echo + arguments: + parameters: + - name: message + value: "Hello!" + - name: B + dependencies: [A] + template: echo + arguments: + parameters: + - name: message + value: "{{tasks.A.status}}" +` + +func TestDAGStatusReference(t *testing.T) { + err := validate(dagStatusReference) + assert.Nil(t, err) +} + var dagNonexistantTarget = ` apiVersion: argoproj.io/v1alpha1 kind: Workflow diff --git a/workflow/validate/validate_test.go b/workflow/validate/validate_test.go index fe5aaa2a19ad..e6719941c8b4 100644 --- a/workflow/validate/validate_test.go +++ b/workflow/validate/validate_test.go @@ -302,6 +302,89 @@ func TestStepOutputReference(t *testing.T) { assert.Nil(t, err) } + +var stepStatusReferences = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: step-output-ref- +spec: + entrypoint: whalesay + templates: + - name: whalesay + inputs: + parameters: + - name: message + value: "value" + container: + image: docker/whalesay:latest + outputs: + parameters: + - name: outparam + valueFrom: + path: /etc/hosts + - name: stepref + steps: + - - name: one + template: whalesay + - - name: two + template: whalesay + arguments: + parameters: + - name: message + value: "{{steps.one.status}}" +` + +func TestStepStatusReference(t *testing.T) { + err := validate(stepStatusReferences) + assert.Nil(t, err) +} + + +var stepStatusReferencesNoFutureReference = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: step-output-ref- +spec: + entrypoint: whalesay + templates: + - name: whalesay + inputs: + parameters: + - name: message + value: "value" + container: + image: docker/whalesay:latest + outputs: + parameters: + - name: outparam + valueFrom: + path: /etc/hosts + - name: stepref + steps: + - - name: one + template: whalesay + arguments: + parameters: + - name: message + value: "{{steps.two.status}}" + - - name: two + template: whalesay + arguments: + parameters: + - name: message + value: "{{steps.one.status}}" +` + +func TestStepStatusReferenceNoFutureReference(t *testing.T) { + err := validate(stepStatusReferencesNoFutureReference) + // Can't reference the status of steps that have not run yet + if assert.NotNil(t, err) { + assert.Contains(t, err.Error(), "failed to resolve {{steps.two.status}}") + } +} + var stepArtReferences = ` apiVersion: argoproj.io/v1alpha1 kind: Workflow From 5cc711a59cb5d35fb403c1b82bf767502e0f2433 Mon Sep 17 00:00:00 2001 From: Simon Behar Date: Wed, 16 Oct 2019 12:56:52 -0700 Subject: [PATCH 3/8] Should be done --- workflow/controller/operator.go | 13 +++++-- workflow/controller/operator_test.go | 52 ++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go index bce46a23265e..91302aa938c4 100644 --- a/workflow/controller/operator.go +++ b/workflow/controller/operator.go @@ -1531,12 +1531,20 @@ func getTemplateOutputsFromScope(tmpl *wfv1.Template, scope *wfScope) (*wfv1.Out // hasOutputResultRef will check given template output has any reference func hasOutputResultRef(name string, parentTmpl *wfv1.Template) bool { + return hasRef(name, parentTmpl, "outputs.result") +} + +func hasStatusRef(name string, parentTmpl *wfv1.Template) bool { + return hasRef(name, parentTmpl, "status") +} + +func hasRef(name string, parentTmpl *wfv1.Template, refName string) bool { var variableRefName string if parentTmpl.DAG != nil { - variableRefName = "{{tasks." + name + ".outputs.result}}" + variableRefName = "{{tasks." + name + "." + refName + "}}" } else if parentTmpl.Steps != nil { - variableRefName = "{{steps." + name + ".outputs.result}}" + variableRefName = "{{steps." + name + "." + refName + "}}" } jsonValue, err := json.Marshal(parentTmpl) @@ -1544,6 +1552,7 @@ func hasOutputResultRef(name string, parentTmpl *wfv1.Template) bool { log.Warnf("Unable to marshal the template. %v, %v", parentTmpl, err) } + fmt.Println(string(jsonValue)) return strings.Contains(string(jsonValue), variableRefName) } diff --git a/workflow/controller/operator_test.go b/workflow/controller/operator_test.go index d47512dff796..b93932dca320 100644 --- a/workflow/controller/operator_test.go +++ b/workflow/controller/operator_test.go @@ -1102,6 +1102,58 @@ func TestResolvePlaceholdersInOutputValues(t *testing.T) { assert.Equal(t, "output-value-placeholders-wf", *parameterValue) } +var outputStatuses = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: scripts-bash- +spec: + entrypoint: bash-script-example + templates: + - name: bash-script-example + steps: + - - name: first + template: flakey-container + continueOn: + failed: true + - - name: print + template: print-message + arguments: + parameters: + - name: message + value: "{{steps.first.status}}" + + + - name: flakey-container + script: + image: busybox + command: [sh, -c] + args: ["exit 0"] + + - name: print-message + inputs: + parameters: + - name: message + container: + image: alpine:latest + command: [sh, -c] + args: ["echo result was: {{inputs.parameters.message}}"] +` + +func TestResolveStatuses(t *testing.T) { + + controller := newController() + wfcset := controller.wfclientset.ArgoprojV1alpha1().Workflows("") + + // operate the workflow. it should create a pod. + wf := unmarshalWF(outputStatuses) + wf, err := wfcset.Create(wf) + assert.Nil(t, err) + assert.True(t, hasStatusRef("first", &wf.Spec.Templates[0])) + assert.False(t, hasStatusRef("print", &wf.Spec.Templates[0])) + fmt.Println( &wf.Spec.Templates[0]) +} + var resourceTemplate = ` apiVersion: argoproj.io/v1alpha1 kind: Workflow From f80ef2c5ec52a483ad882b07d66780849ac080e5 Mon Sep 17 00:00:00 2001 From: Simon Behar Date: Wed, 16 Oct 2019 13:03:49 -0700 Subject: [PATCH 4/8] Linter --- workflow/controller/operator.go | 13 ++----------- workflow/controller/operator_test.go | 10 ++++++---- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go index 91302aa938c4..bce46a23265e 100644 --- a/workflow/controller/operator.go +++ b/workflow/controller/operator.go @@ -1531,20 +1531,12 @@ func getTemplateOutputsFromScope(tmpl *wfv1.Template, scope *wfScope) (*wfv1.Out // hasOutputResultRef will check given template output has any reference func hasOutputResultRef(name string, parentTmpl *wfv1.Template) bool { - return hasRef(name, parentTmpl, "outputs.result") -} - -func hasStatusRef(name string, parentTmpl *wfv1.Template) bool { - return hasRef(name, parentTmpl, "status") -} - -func hasRef(name string, parentTmpl *wfv1.Template, refName string) bool { var variableRefName string if parentTmpl.DAG != nil { - variableRefName = "{{tasks." + name + "." + refName + "}}" + variableRefName = "{{tasks." + name + ".outputs.result}}" } else if parentTmpl.Steps != nil { - variableRefName = "{{steps." + name + "." + refName + "}}" + variableRefName = "{{steps." + name + ".outputs.result}}" } jsonValue, err := json.Marshal(parentTmpl) @@ -1552,7 +1544,6 @@ func hasRef(name string, parentTmpl *wfv1.Template, refName string) bool { log.Warnf("Unable to marshal the template. %v, %v", parentTmpl, err) } - fmt.Println(string(jsonValue)) return strings.Contains(string(jsonValue), variableRefName) } diff --git a/workflow/controller/operator_test.go b/workflow/controller/operator_test.go index b93932dca320..ebaa83752a02 100644 --- a/workflow/controller/operator_test.go +++ b/workflow/controller/operator_test.go @@ -6,11 +6,11 @@ import ( "strings" "testing" - "sigs.k8s.io/yaml" "github.com/stretchr/testify/assert" apiv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1" "github.com/argoproj/argo/test" @@ -1149,9 +1149,11 @@ func TestResolveStatuses(t *testing.T) { wf := unmarshalWF(outputStatuses) wf, err := wfcset.Create(wf) assert.Nil(t, err) - assert.True(t, hasStatusRef("first", &wf.Spec.Templates[0])) - assert.False(t, hasStatusRef("print", &wf.Spec.Templates[0])) - fmt.Println( &wf.Spec.Templates[0]) + jsonValue, err := json.Marshal(&wf.Spec.Templates[0]) + assert.NoError(t, err) + + assert.Contains(t, string(jsonValue), "{{steps.first.status}}") + assert.NotContains(t, string(jsonValue), "{{steps.print.status}}") } var resourceTemplate = ` From 85779bfa1965b920cce4e486fea1bbbe1c2baae6 Mon Sep 17 00:00:00 2001 From: Simon Behar Date: Wed, 16 Oct 2019 13:44:45 -0700 Subject: [PATCH 5/8] Minor language update --- docs/variables.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/variables.md b/docs/variables.md index 81dc4942b13f..1018e5f4107f 100644 --- a/docs/variables.md +++ b/docs/variables.md @@ -13,19 +13,19 @@ The following variables are made available to reference various metadata of a wo | Variable | Description| |----------|------------| | `steps..ip` | IP address of a previous daemon container step | -| `steps..status` | Phase status of a previous script step | -| `steps..outputs.result` | Output result of a previous script step | -| `steps..outputs.parameters.` | Output parameter of a previous step | -| `steps..outputs.artifacts.` | Output artifact of a previous step | +| `steps..status` | Phase status of any previous script step | +| `steps..outputs.result` | Output result of any previous script step | +| `steps..outputs.parameters.` | Output parameter of any previous step | +| `steps..outputs.artifacts.` | Output artifact of any previous step | ## DAG Templates: | Variable | Description| |----------|------------| | `tasks..ip` | IP address of a previous daemon container task | -| `tasks..status` | Phase status of a previous task step | -| `tasks..outputs.result` | Output result of a previous script task | -| `tasks..outputs.parameters.` | Output parameter of a previous task | -| `tasks..outputs.artifacts.` | Output artifact of a previous task | +| `tasks..status` | Phase status of any previous task step | +| `tasks..outputs.result` | Output result of any previous script task | +| `tasks..outputs.parameters.` | Output parameter of any previous task | +| `tasks..outputs.artifacts.` | Output artifact of any previous task | ## Container/Script Templates: | Variable | Description| From 2af5d4df12b4a343b4d8f3f47c641a9044146b6b Mon Sep 17 00:00:00 2001 From: Simon Behar Date: Wed, 16 Oct 2019 15:09:14 -0700 Subject: [PATCH 6/8] Reworked test --- workflow/validate/validate_test.go | 35 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/workflow/validate/validate_test.go b/workflow/validate/validate_test.go index e6719941c8b4..4290b8c9ba68 100644 --- a/workflow/validate/validate_test.go +++ b/workflow/validate/validate_test.go @@ -307,32 +307,33 @@ var stepStatusReferences = ` apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: - generateName: step-output-ref- + generateName: status-ref- spec: - entrypoint: whalesay + entrypoint: statusref templates: - - name: whalesay - inputs: - parameters: - - name: message - value: "value" - container: - image: docker/whalesay:latest - outputs: - parameters: - - name: outparam - valueFrom: - path: /etc/hosts - - name: stepref + - name: statusref steps: - - name: one - template: whalesay + template: say + arguments: + parameters: + - name: message + value: "Hello, world" - - name: two - template: whalesay + template: say arguments: parameters: - name: message value: "{{steps.one.status}}" + - name: say + inputs: + parameters: + - name: message + value: "value" + container: + image: alpine:latest + command: [sh, -c] + args: ["echo {{inputs.parameters.message}}"] ` func TestStepStatusReference(t *testing.T) { From 7abbd8459b44cdf85a11a482b055f689b7cf06fd Mon Sep 17 00:00:00 2001 From: Simon Behar Date: Wed, 16 Oct 2019 16:45:45 -0700 Subject: [PATCH 7/8] Reworked test --- workflow/validate/validate_dag_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/workflow/validate/validate_dag_test.go b/workflow/validate/validate_dag_test.go index 42d2e91fb783..fb69549791c6 100644 --- a/workflow/validate/validate_dag_test.go +++ b/workflow/validate/validate_dag_test.go @@ -311,6 +311,8 @@ spec: tasks: - name: A template: echo + continueOn: + failed: true arguments: parameters: - name: message From 94545d2d7ceb048182c531bef7a0c56fd66d3f8b Mon Sep 17 00:00:00 2001 From: Simon Behar Date: Thu, 17 Oct 2019 09:34:30 -0700 Subject: [PATCH 8/8] Enhanced DAG validation --- workflow/validate/validate.go | 14 +- workflow/validate/validate_dag_test.go | 215 +++++++++++++++++++++++++ workflow/validate/validate_test.go | 31 ++-- 3 files changed, 237 insertions(+), 23 deletions(-) diff --git a/workflow/validate/validate.go b/workflow/validate/validate.go index 0f337c4ec830..344e5b7ab0a5 100644 --- a/workflow/validate/validate.go +++ b/workflow/validate/validate.go @@ -577,7 +577,7 @@ func (ctx *templateValidationCtx) validateSteps(scope map[string]interface{}, tm for i, step := range stepGroup { aggregate := len(step.WithItems) > 0 || step.WithParam != "" resolvedTmpl := resolvedTemplates[step.Name] - ctx.addOutputsToScope(resolvedTmpl, fmt.Sprintf("steps.%s", step.Name), scope, aggregate) + ctx.addOutputsToScope(resolvedTmpl, fmt.Sprintf("steps.%s", step.Name), scope, aggregate, false) // Validate the template again with actual arguments. _, err = ctx.validateTemplateHolder(&step, tmplCtx, &step.Arguments, scope) @@ -630,7 +630,7 @@ func addItemsToScope(prefix string, withItems []wfv1.Item, withParam string, wit return nil } -func (ctx *templateValidationCtx) addOutputsToScope(tmpl *wfv1.Template, prefix string, scope map[string]interface{}, aggregate bool) { +func (ctx *templateValidationCtx) addOutputsToScope(tmpl *wfv1.Template, prefix string, scope map[string]interface{}, aggregate bool, isAncestor bool) { if tmpl.Daemon != nil && *tmpl.Daemon { scope[fmt.Sprintf("%s.ip", prefix)] = true } @@ -661,6 +661,9 @@ func (ctx *templateValidationCtx) addOutputsToScope(tmpl *wfv1.Template, prefix scope[fmt.Sprintf("%s.outputs.parameters", prefix)] = true } } + if isAncestor { + scope[fmt.Sprintf("%s.status", prefix)] = true + } } func validateOutputs(scope map[string]interface{}, tmpl *wfv1.Template) error { @@ -879,8 +882,7 @@ func (ctx *templateValidationCtx) validateDAG(scope map[string]interface{}, tmpl return errors.Errorf(errors.CodeBadRequest, "templates.%s.tasks.%s %s", tmpl.Name, task.Name, err.Error()) } prefix := fmt.Sprintf("tasks.%s", task.Name) - scope[fmt.Sprintf("%s.status", prefix)] = true - ctx.addOutputsToScope(resolvedTmpl, prefix, scope, false) + ctx.addOutputsToScope(resolvedTmpl, prefix, scope, false, false) resolvedTemplates[task.Name] = resolvedTmpl dupDependencies := make(map[string]bool) for j, depName := range task.Dependencies { @@ -914,7 +916,7 @@ func (ctx *templateValidationCtx) validateDAG(scope map[string]interface{}, tmpl resolvedTmpl := resolvedTemplates[task.Name] // add all tasks outputs to scope so that a nested DAGs can have outputs prefix := fmt.Sprintf("tasks.%s", task.Name) - ctx.addOutputsToScope(resolvedTmpl, prefix, scope, false) + ctx.addOutputsToScope(resolvedTmpl, prefix, scope, false, false) taskBytes, err := json.Marshal(task) if err != nil { return errors.InternalWrapError(err) @@ -929,7 +931,7 @@ func (ctx *templateValidationCtx) validateDAG(scope map[string]interface{}, tmpl resolvedTmpl := resolvedTemplates[ancestor] ancestorPrefix := fmt.Sprintf("tasks.%s", ancestor) aggregate := len(ancestorTask.WithItems) > 0 || ancestorTask.WithParam != "" - ctx.addOutputsToScope(resolvedTmpl, ancestorPrefix, taskScope, aggregate) + ctx.addOutputsToScope(resolvedTmpl, ancestorPrefix, taskScope, aggregate, true) } err = addItemsToScope(prefix, task.WithItems, task.WithParam, task.WithSequence, taskScope) if err != nil { diff --git a/workflow/validate/validate_dag_test.go b/workflow/validate/validate_dag_test.go index fb69549791c6..6507125ba5d7 100644 --- a/workflow/validate/validate_dag_test.go +++ b/workflow/validate/validate_dag_test.go @@ -326,9 +326,224 @@ spec: value: "{{tasks.A.status}}" ` +var dagStatusNoFutureReferenceSimple = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: dag-arg-passing- +spec: + entrypoint: dag-arg-passing + templates: + - name: echo + inputs: + parameters: + - name: message + container: + image: alpine:3.7 + command: [echo, "{{inputs.parameters.message}}"] + + - name: dag-arg-passing + dag: + tasks: + - name: A + template: echo + continueOn: + failed: true + arguments: + parameters: + - name: message + value: "{{tasks.B.status}}" + - name: B + dependencies: [A] + template: echo + arguments: + parameters: + - name: message + value: "{{tasks.A.status}}" +` + +var dagStatusNoFutureReferenceWhenFutureReferenceHasChild = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: dag-arg-passing- +spec: + entrypoint: dag-arg-passing + templates: + - name: echo + inputs: + parameters: + - name: message + container: + image: alpine:3.7 + command: [echo, "{{inputs.parameters.message}}"] + + - name: dag-arg-passing + dag: + tasks: + - name: A + template: echo + continueOn: + failed: true + arguments: + parameters: + - name: message + value: "{{tasks.B.status}}" + - name: B + dependencies: [A] + template: echo + arguments: + parameters: + - name: message + value: "{{tasks.A.status}}" + - name: C + dependencies: [B] + template: echo + arguments: + parameters: + - name: message + value: "{{tasks.B.status}}" +` + +var dagStatusPastReferenceChain = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: dag-arg-passing- +spec: + entrypoint: dag-arg-passing + templates: + - name: echo + inputs: + parameters: + - name: message + container: + image: alpine:3.7 + command: [echo, "{{inputs.parameters.message}}"] + + - name: dag-arg-passing + dag: + tasks: + - name: A + template: echo + continueOn: + failed: true + arguments: + parameters: + - name: message + value: "Hello" + - name: B + dependencies: [A] + template: echo + arguments: + parameters: + - name: message + value: "{{tasks.A.status}}" + - name: C + dependencies: [B] + template: echo + arguments: + parameters: + - name: message + value: "{{tasks.B.status}}" + - name: D + dependencies: [A] + template: echo + arguments: + parameters: + - name: message + value: "{{tasks.A.status}}" + - name: E + dependencies: [D] + template: echo + arguments: + parameters: + - name: message + value: "{{tasks.D.status}}" +` + +var dagStatusOnlyDirectAncestors = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: dag-arg-passing- +spec: + entrypoint: dag-arg-passing + templates: + - name: echo + inputs: + parameters: + - name: message + container: + image: alpine:3.7 + command: [echo, "{{inputs.parameters.message}}"] + + - name: dag-arg-passing + dag: + tasks: + - name: A + template: echo + continueOn: + failed: true + arguments: + parameters: + - name: message + value: "Hello" + - name: B + dependencies: [A] + template: echo + arguments: + parameters: + - name: message + value: "{{tasks.A.status}}" + - name: C + dependencies: [B] + template: echo + arguments: + parameters: + - name: message + value: "{{tasks.B.status}}" + - name: D + dependencies: [A] + template: echo + arguments: + parameters: + - name: message + value: "{{tasks.A.status}}" + - name: E + dependencies: [D] + template: echo + arguments: + parameters: + - name: message + value: "{{tasks.B.status}}" +` + func TestDAGStatusReference(t *testing.T) { err := validate(dagStatusReference) assert.Nil(t, err) + + err = validate(dagStatusNoFutureReferenceSimple) + // Can't reference the status of steps that have not run yet + if assert.NotNil(t, err) { + assert.Contains(t, err.Error(), "failed to resolve {{tasks.B.status}}") + } + + err = validate(dagStatusNoFutureReferenceWhenFutureReferenceHasChild) + // Can't reference the status of steps that have not run yet, even if the referenced steps have children + if assert.NotNil(t, err) { + assert.Contains(t, err.Error(), "failed to resolve {{tasks.B.status}}") + } + + err = validate(dagStatusPastReferenceChain) + assert.Nil(t, err) + + err = validate(dagStatusOnlyDirectAncestors) + // Can't reference steps that are not direct ancestors of node + // Here Node E references the status of Node B, even though it is not its descendent + if assert.NotNil(t, err) { + assert.Contains(t, err.Error(), "failed to resolve {{tasks.B.status}}") + } } var dagNonexistantTarget = ` diff --git a/workflow/validate/validate_test.go b/workflow/validate/validate_test.go index 4290b8c9ba68..bfed6658cd26 100644 --- a/workflow/validate/validate_test.go +++ b/workflow/validate/validate_test.go @@ -346,36 +346,33 @@ var stepStatusReferencesNoFutureReference = ` apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: - generateName: step-output-ref- + generateName: status-ref- spec: - entrypoint: whalesay + entrypoint: statusref templates: - - name: whalesay - inputs: - parameters: - - name: message - value: "value" - container: - image: docker/whalesay:latest - outputs: - parameters: - - name: outparam - valueFrom: - path: /etc/hosts - - name: stepref + - name: statusref steps: - - name: one - template: whalesay + template: say arguments: parameters: - name: message value: "{{steps.two.status}}" - - name: two - template: whalesay + template: say arguments: parameters: - name: message value: "{{steps.one.status}}" + - name: say + inputs: + parameters: + - name: message + value: "value" + container: + image: alpine:latest + command: [sh, -c] + args: ["echo {{inputs.parameters.message}}"] ` func TestStepStatusReferenceNoFutureReference(t *testing.T) {