diff --git a/workflow/validate/validate.go b/workflow/validate/validate.go index 17a3a0e759a2..61f4dfb387b4 100644 --- a/workflow/validate/validate.go +++ b/workflow/validate/validate.go @@ -279,6 +279,38 @@ func ValidateWorkflow(ctx context.Context, wftmplGetter templateresolution.Workf } if tmplHolder != nil { tctx.globalParams[common.GlobalVarWorkflowFailures] = placeholderGenerator.NextPlaceholder() + + // Check if any template has parametrized global artifacts, if so enable global artifact resolution for exit handlers + hasParametrizedGlobalArtifacts := false + for _, tmpl := range wf.Spec.Templates { + for _, art := range tmpl.Outputs.Artifacts { + if art.GlobalName != "" && isParameter(art.GlobalName) { + hasParametrizedGlobalArtifacts = true + break + } + } + if hasParametrizedGlobalArtifacts { + break + } + } + if hasWorkflowTemplateRef && !hasParametrizedGlobalArtifacts { + // Also check the referenced workflow template + for _, tmpl := range wfSpecHolder.GetWorkflowSpec().Templates { + for _, art := range tmpl.Outputs.Artifacts { + if art.GlobalName != "" && isParameter(art.GlobalName) { + hasParametrizedGlobalArtifacts = true + break + } + } + if hasParametrizedGlobalArtifacts { + break + } + } + } + if hasParametrizedGlobalArtifacts { + tctx.globalParams[anyWorkflowOutputArtifactMagicValue] = "true" + } + _, err = tctx.validateTemplateHolder(ctx, tmplHolder, tmplCtx, &wf.Spec.Arguments, opts.WorkflowTemplateValidation) if err != nil { return err diff --git a/workflow/validate/validate_test.go b/workflow/validate/validate_test.go index d38f3c1d14c1..911343df9787 100644 --- a/workflow/validate/validate_test.go +++ b/workflow/validate/validate_test.go @@ -3435,3 +3435,131 @@ func TestDynamicWorkflowTemplateRef(t *testing.T) { _ = deleteWorkflowTemplate(ctx, wftmplA.Name) _ = deleteWorkflowTemplate(ctx, wftmplB.Name) } + +var parameterizedGlobalArtifactsWorkflow = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: test-parameterized-global-artifacts- +spec: + entrypoint: main + onExit: exit-handler + arguments: + parameters: + - name: variable + value: "car" + templates: + - name: main + container: + image: alpine:latest + command: [sh, -c] + args: ["echo 'test data' > /tmp/result.txt"] + outputs: + artifacts: + - name: result + globalName: output-result-{{workflow.parameters.variable}} + path: /tmp/result.txt + archive: + none: {} + - name: exit-handler + container: + image: alpine:latest + command: [sh, -c] + args: ["echo 'Access artifact: {{workflow.outputs.artifacts.output-result-car}}'"] +` + +var parameterizedGlobalArtifactsWithWorkflowTemplateRef = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: test-wft-ref- +spec: + onExit: exit-handler + arguments: + parameters: + - name: variable + value: "test" + workflowTemplateRef: + name: parameterized-artifacts-template +` + +var parameterizedArtifactsWorkflowTemplate = ` +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowTemplate +metadata: + name: parameterized-artifacts-template +spec: + entrypoint: main + onExit: exit-handler + templates: + - name: main + container: + image: alpine:latest + command: [sh, -c] + args: ["echo 'test data' > /tmp/result.txt"] + outputs: + artifacts: + - name: result + globalName: output-result-{{workflow.parameters.variable}} + path: /tmp/result.txt + archive: + none: {} + - name: exit-handler + container: + image: alpine:latest + command: [sh, -c] + args: ["echo 'Access artifact: {{workflow.outputs.artifacts.output-result-test}}'"] +` + +func TestParameterizedGlobalArtifactsInExitHandler(t *testing.T) { + // Test that parameterized global artifacts can be referenced in exit handlers + err := validate(logging.TestContext(t.Context()), parameterizedGlobalArtifactsWorkflow) + require.NoError(t, err) +} + +func TestParameterizedGlobalArtifactsWithWorkflowTemplateRef(t *testing.T) { + ctx := logging.TestContext(t.Context()) + + // Create the workflow template with parameterized artifacts + err := createWorkflowTemplateFromSpec(ctx, parameterizedArtifactsWorkflowTemplate) + require.NoError(t, err) + + // Test that parameterized global artifacts from workflow template refs can be referenced in exit handlers + err = validate(ctx, parameterizedGlobalArtifactsWithWorkflowTemplateRef) + require.NoError(t, err) +} + +var workflowWithoutParameterizedArtifacts = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: test-no-parameterized- +spec: + entrypoint: main + onExit: exit-handler + templates: + - name: main + container: + image: alpine:latest + command: [sh, -c] + args: ["echo 'test data' > /tmp/result.txt"] + outputs: + artifacts: + - name: result + globalName: simple-artifact + path: /tmp/result.txt + archive: + none: {} + - name: exit-handler + container: + image: alpine:latest + command: [sh, -c] + args: ["echo 'Access artifact: {{workflow.outputs.artifacts.nonexistent}}'"] +` + +func TestWorkflowWithoutParameterizedArtifactsFails(t *testing.T) { + // Test that referencing non-existent global artifacts still fails validation when there are no parameterized artifacts + err := validate(logging.TestContext(t.Context()), workflowWithoutParameterizedArtifacts) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to resolve {{workflow.outputs.artifacts.nonexistent}}") +}