diff --git a/examples/conditionals.yaml b/examples/conditionals.yaml index 02bd44a9538c..9ace0538e691 100644 --- a/examples/conditionals.yaml +++ b/examples/conditionals.yaml @@ -1,35 +1,41 @@ # Conditionals provide a way to affect the control flow of a # workflow at runtime, depending on parameters. In this example -# the 'print-hello' template may or may not be executed depending -# on the input parameter, 'should-print'. When submitted with: +# the 'printHello' template may or may not be executed depending +# on the input parameter, 'shouldPrint'. When submitted with: # argo submit examples/conditionals.yaml -# the step will be skipped since 'should-print' will evaluate false. +# the step will be skipped since 'shouldPrint' will evaluate false. # When submitted with: -# argo submit examples/conditionals.yaml -p should-print=true -# the step will be executed since 'should-print' will evaluate true. +# argo submit examples/conditionals.yaml -p shouldPrint=true +# the step will be executed since 'shouldPrint' will evaluate true. apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: conditional- spec: - entrypoint: conditional-example + entrypoint: conditionalExample arguments: parameters: - - name: should-print - value: "false" + - name: shouldPrint + value: "true" templates: - - name: conditional-example + - name: conditionalExample inputs: parameters: - - name: should-print + - name: shouldPrint steps: - - - name: print-hello - template: whalesay - when: "{{inputs.parameters.should-print}} == true" + - - name: printHello-govaluate + template: argosay + when: "{{inputs.parameters.shouldPrint}} == true" # govaluate form + - name: printHello-expr + template: argosay + when: "{{= inputs.parameters.shouldPrint == 'true'}}" # expr form + - name: printHello-expr-json + template: argosay + when: "{{=jsonpath(workflow.parameters.json, '$[0].value') == 'true'}}" # expr form - - name: whalesay + - name: argosay container: - image: docker/whalesay:latest + image: argoproj/argosay:v1 command: [sh, -c] args: ["cowsay hello"] diff --git a/test/e2e/functional_test.go b/test/e2e/functional_test.go index defac6d78af7..35e8c5e89d8f 100644 --- a/test/e2e/functional_test.go +++ b/test/e2e/functional_test.go @@ -89,6 +89,44 @@ spec: }) } +func (s *FunctionalSuite) TestWhenExpressions() { + s.Given(). + Workflow("@functional/conditionals.yaml"). + When(). + SubmitWorkflow(). + WaitForWorkflow(fixtures.ToBeSucceeded, 2*time.Minute). + Then(). + ExpectWorkflowNode(wfv1.NodeWithDisplayName("printHello-govaluate"), func(t *testing.T, n *wfv1.NodeStatus, p *apiv1.Pod) { + assert.NotEqual(t, wfv1.NodeTypeSkipped, n.Type) + }). + ExpectWorkflowNode(wfv1.NodeWithDisplayName("printHello-expr"), func(t *testing.T, n *wfv1.NodeStatus, p *apiv1.Pod) { + assert.NotEqual(t, wfv1.NodeTypeSkipped, n.Type) + }). + ExpectWorkflowNode(wfv1.NodeWithDisplayName("printHello-expr-json"), func(t *testing.T, n *wfv1.NodeStatus, p *apiv1.Pod) { + assert.NotEqual(t, wfv1.NodeTypeSkipped, n.Type) + }) +} + +func (s *FunctionalSuite) TestJSONVariables() { + + s.Given(). + Workflow("@testdata/json-variables.yaml"). + When(). + SubmitWorkflow(). + WaitForWorkflow(). + Then(). + ExpectWorkflowNode(wfv1.SucceededPodNode, func(t *testing.T, n *wfv1.NodeStatus, p *apiv1.Pod) { + for _, c := range p.Spec.Containers { + if c.Name == "main" { + assert.Equal(t, 3, len(c.Args)) + assert.Equal(t, "myLabelValue", c.Args[0]) + assert.Equal(t, "myAnnotationValue", c.Args[1]) + assert.Equal(t, "myParamValue", c.Args[2]) + } + } + }) +} + func (s *FunctionalSuite) TestWorkflowTTL() { s.Given(). Workflow(` diff --git a/test/e2e/testdata/json-variables.yaml b/test/e2e/testdata/json-variables.yaml new file mode 100644 index 000000000000..1312aed84422 --- /dev/null +++ b/test/e2e/testdata/json-variables.yaml @@ -0,0 +1,26 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Workflow + +metadata: + generateName: json-variables- + + labels: + myLabel: myLabelValue + annotations: + myAnnotation: myAnnotationValue +spec: + entrypoint: argosay1 + arguments: + parameters: + - name: myParam + value: myParamValue + + templates: + - name: argosay1 + container: + image: argoproj/argosay:v1 + command: [echo] + args: + - "{{=jsonpath(workflow.labels.json, '$.myLabel')}}" + - "{{=jsonpath(workflow.annotations.json, '$.myAnnotation')}}" + - "{{=jsonpath(workflow.parameters.json, '$[0].value')}}" \ No newline at end of file diff --git a/workflow/common/common.go b/workflow/common/common.go index 9647e162e2a2..f4c16ec5ad52 100644 --- a/workflow/common/common.go +++ b/workflow/common/common.go @@ -175,12 +175,18 @@ const ( GlobalVarWorkflowFailures = "workflow.failures" // GlobalVarWorkflowDuration is the current duration of this workflow GlobalVarWorkflowDuration = "workflow.duration" - // GlobalVarWorkflowAnnotations is a JSON string containing all workflow annotations + // GlobalVarWorkflowAnnotations is a JSON string containing all workflow annotations - which will be deprecated in favor of GlobalVarWorkflowAnnotationsJSON GlobalVarWorkflowAnnotations = "workflow.annotations" - // GlobalVarWorkflowLabels is a JSON string containing all workflow labels + // GlobalVarWorkflowAnnotationsJSON is a JSON string containing all workflow annotations + GlobalVarWorkflowAnnotationsJSON = "workflow.annotations.json" + // GlobalVarWorkflowLabels is a JSON string containing all workflow labels - which will be deprecated in favor of GlobalVarWorkflowLabelsJSON GlobalVarWorkflowLabels = "workflow.labels" - // GlobalVarWorkflowParameters is a JSON string containing all workflow parameters + // GlobalVarWorkflowLabelsJSON is a JSON string containing all workflow labels + GlobalVarWorkflowLabelsJSON = "workflow.labels.json" + // GlobalVarWorkflowParameters is a JSON string containing all workflow parameters - which will be deprecated in favor of GlobalVarWorkflowParametersJSON GlobalVarWorkflowParameters = "workflow.parameters" + // GlobalVarWorkflowParametersJSON is a JSON string containing all workflow parameters + GlobalVarWorkflowParametersJSON = "workflow.parameters.json" // GlobalVarWorkflowCronScheduleTime is the scheduled timestamp of a Workflow started by a CronWorkflow GlobalVarWorkflowCronScheduleTime = "workflow.scheduledTime" diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go index f8722a46d118..d3d24276e047 100644 --- a/workflow/controller/operator.go +++ b/workflow/controller/operator.go @@ -570,6 +570,7 @@ func (woc *wfOperationCtx) setGlobalParameters(executionParameters wfv1.Argument if workflowParameters, err := json.Marshal(woc.execWf.Spec.Arguments.Parameters); err == nil { woc.globalParams[common.GlobalVarWorkflowParameters] = string(workflowParameters) + woc.globalParams[common.GlobalVarWorkflowParametersJSON] = string(workflowParameters) } for _, param := range executionParameters.Parameters { if param.Value == nil && param.ValueFrom == nil { @@ -605,12 +606,14 @@ func (woc *wfOperationCtx) setGlobalParameters(executionParameters wfv1.Argument if workflowAnnotations, err := json.Marshal(woc.wf.ObjectMeta.Annotations); err == nil { woc.globalParams[common.GlobalVarWorkflowAnnotations] = string(workflowAnnotations) + woc.globalParams[common.GlobalVarWorkflowAnnotationsJSON] = string(workflowAnnotations) } for k, v := range woc.wf.ObjectMeta.Annotations { woc.globalParams["workflow.annotations."+k] = v } if workflowLabels, err := json.Marshal(woc.wf.ObjectMeta.Labels); err == nil { woc.globalParams[common.GlobalVarWorkflowLabels] = string(workflowLabels) + woc.globalParams[common.GlobalVarWorkflowLabelsJSON] = string(workflowLabels) } for k, v := range woc.wf.ObjectMeta.Labels { // if the Label will get overridden by a LabelsFrom expression later, don't set it now diff --git a/workflow/validate/validate.go b/workflow/validate/validate.go index a36c09542666..983272b111b7 100644 --- a/workflow/validate/validate.go +++ b/workflow/validate/validate.go @@ -161,6 +161,7 @@ func ValidateWorkflow(wftmplGetter templateresolution.WorkflowTemplateNamespaced } if len(wfArgs.Parameters) > 0 { ctx.globalParams[common.GlobalVarWorkflowParameters] = placeholderGenerator.NextPlaceholder() + ctx.globalParams[common.GlobalVarWorkflowParametersJSON] = placeholderGenerator.NextPlaceholder() } for _, param := range wfArgs.Parameters { @@ -190,11 +191,13 @@ func ValidateWorkflow(wftmplGetter templateresolution.WorkflowTemplateNamespaced ctx.globalParams["workflow.annotations."+k] = placeholderGenerator.NextPlaceholder() } ctx.globalParams[common.GlobalVarWorkflowAnnotations] = placeholderGenerator.NextPlaceholder() + ctx.globalParams[common.GlobalVarWorkflowAnnotationsJSON] = placeholderGenerator.NextPlaceholder() for k := range mergedLabels { ctx.globalParams["workflow.labels."+k] = placeholderGenerator.NextPlaceholder() } ctx.globalParams[common.GlobalVarWorkflowLabels] = placeholderGenerator.NextPlaceholder() + ctx.globalParams[common.GlobalVarWorkflowLabelsJSON] = placeholderGenerator.NextPlaceholder() if wf.Spec.Priority != nil { ctx.globalParams[common.GlobalVarWorkflowPriority] = strconv.Itoa(int(*wf.Spec.Priority)) @@ -822,10 +825,6 @@ func (ctx *templateValidationCtx) validateSteps(scope map[string]interface{}, tm return errors.Errorf(errors.CodeBadRequest, "templates.%s.steps[%d].%s %s", tmpl.Name, i, step.Name, err.Error()) } - if !validateWhenExpression(step.When) { - return errors.Errorf(errors.CodeBadRequest, "templates.%s when expression doesn't support 'expr' format '{{='. 'When' expression is only support govaluate format {{", tmpl.Name) - } - if step.HasExitHook() { ctx.addOutputsToScope(resolvedTmpl, fmt.Sprintf("steps.%s", step.Name), scope, false, false) } @@ -1147,10 +1146,6 @@ func (d *dagValidationContext) GetTaskFinishedAtTime(taskName string) time.Time return time.Now() } -func validateWhenExpression(when string) bool { - return !strings.HasPrefix(when, "{{=") -} - func (ctx *templateValidationCtx) validateDAG(scope map[string]interface{}, tmplCtx *templateresolution.Context, tmpl *wfv1.Template) error { err := validateNonLeaf(tmpl) if err != nil { @@ -1199,10 +1194,6 @@ func (ctx *templateValidationCtx) validateDAG(scope map[string]interface{}, tmpl return errors.Errorf(errors.CodeBadRequest, "templates.%s cannot use 'continueOn' when using 'depends'. Instead use 'dep-task.Failed'/'dep-task.Errored'", tmpl.Name) } - if !validateWhenExpression(task.When) { - return errors.Errorf(errors.CodeBadRequest, "templates.%s when doesn't support 'expr' expression '{{='. 'When' expression is only support govaluate format {{", tmpl.Name) - } - resolvedTmpl, err := ctx.validateTemplateHolder(&task, tmplCtx, &FakeArguments{}) if err != nil { return errors.Errorf(errors.CodeBadRequest, "templates.%s.tasks.%s %s", tmpl.Name, task.Name, err.Error())