From 1a7bc7d2cc7b67c7460cde38a0939c5730c9f08a Mon Sep 17 00:00:00 2001 From: Alan Clucas Date: Fri, 19 May 2023 12:58:07 +0100 Subject: [PATCH] feat: mainEntrypoint variable Issue #6772 asks for the template name, but we cannot always know what this is (simplest unknowable is kubectl apply of a workflow with a generateName). This is a split from PR #10745 to just provide the variable mainEndpoint, which is set to the name of the invoked entrypoint, which is controllable, knowable, and could be used in the same way, and seems like a useful thing to have anyway. Signed-off-by: Alan Clucas --- docs/variables.md | 64 ++++++++++++++++++- .../functional/entrypointName-template.yaml | 16 +++++ .../functional/entrypointName-workflow.yaml | 13 ++++ test/e2e/functional_test.go | 16 +++++ workflow/common/common.go | 2 + workflow/controller/operator.go | 1 + workflow/controller/operator_test.go | 30 ++++++++- workflow/validate/validate.go | 1 + 8 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 test/e2e/functional/entrypointName-template.yaml create mode 100644 test/e2e/functional/entrypointName-workflow.yaml diff --git a/docs/variables.md b/docs/variables.md index c450e0052e61..1a554be13eea 100644 --- a/docs/variables.md +++ b/docs/variables.md @@ -44,7 +44,7 @@ The tag is substituted with the variable that has a name the same as the tag. Simple tags **may** have white-space between the brackets and variable as seen below. However, there is a known issue where variables may fail to interpolate with white-space, so it is recommended to avoid using white-space until this issue is resolved. [Please report](https://github.com/argoproj/argo-workflows/issues/8960) unexpected behavior with reproducible examples. ```yaml -args: [ "{{ inputs.parameters.message }}" ] +args: [ "{{ inputs.parameters.message }}" ] ``` ### Expression @@ -247,6 +247,7 @@ For `Template`-level metrics: |----------|------------| | `workflow.name` | Workflow name | | `workflow.namespace` | Workflow namespace | +| `workflow.mainEntrypoint` | Workflow's initial entrypoint | | `workflow.serviceAccountName` | Workflow service account name | | `workflow.uid` | Workflow UID. Useful for setting ownership reference to a resource, or a unique artifact location | | `workflow.parameters.` | Input parameter to the workflow | @@ -271,3 +272,64 @@ For `Template`-level metrics: |----------|------------| | `workflow.status` | Workflow status. One of: `Succeeded`, `Failed`, `Error` | | `workflow.failures` | A list of JSON objects containing information about nodes that failed or errored during execution. Available fields: `displayName`, `message`, `templateName`, `phase`, `podName`, and `finishedAt`. | + +### Knowing where you are + +The idea with creating a `WorkflowTemplate` is that they are reusable bits of code you will using in many actual Workflows. Sometimes it is useful to know which workflow you are part of. + +`workflow.mainEntrypoint` is one way you can do this. If each of your actual workflows has a differing entrypoint, you can identify the workflow you're part of. Given this use in a `WorkflowTemplate`: + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowTemplate +metadata: + name: say-main-entrypoint +spec: + entrypoint: echo + templates: + - name: echo + container: + image: alpine + command: [echo] + args: ["{{workflow.mainEntrypoint}}"] +``` + +I can distinguish my caller: + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: foo- +spec: + entrypoint: foo + templates: + - name: foo + steps: + - - name: step + templateRef: + name: say-main-entrypoint + template: echo +``` + +results in a log of `foo` + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: bar- +spec: + entrypoint: bar + templates: + - name: bar + steps: + - - name: step + templateRef: + name: say-main-entrypoint + template: echo +``` + +results in a log of `bar` + +This shouldn't that helpful in logging, you should be able to identify workflows through other labels in your clusters log tool, but can be helpful when generating metrics for the workflow for example. diff --git a/test/e2e/functional/entrypointName-template.yaml b/test/e2e/functional/entrypointName-template.yaml new file mode 100644 index 000000000000..eec2e0156efa --- /dev/null +++ b/test/e2e/functional/entrypointName-template.yaml @@ -0,0 +1,16 @@ +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowTemplate +metadata: + name: next-template +spec: + entrypoint: next + templates: + - name: next + inputs: + parameters: + - name: test + value: "{{ workflow.mainEntrypoint }}" + container: + image: alpine + command: [echo] + args: ["We got here!"] diff --git a/test/e2e/functional/entrypointName-workflow.yaml b/test/e2e/functional/entrypointName-workflow.yaml new file mode 100644 index 000000000000..b54b348a3e17 --- /dev/null +++ b/test/e2e/functional/entrypointName-workflow.yaml @@ -0,0 +1,13 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: bar- +spec: + entrypoint: bar + templates: + - name: bar + steps: + - - name: step + templateRef: + name: next-template + template: next diff --git a/test/e2e/functional_test.go b/test/e2e/functional_test.go index fa9031267a9d..1f060e91815e 100644 --- a/test/e2e/functional_test.go +++ b/test/e2e/functional_test.go @@ -1184,3 +1184,19 @@ func (s *FunctionalSuite) TestTemplateDefaultImage() { SubmitWorkflow(). WaitForWorkflow(fixtures.ToBeSucceeded) } + +func (s *FunctionalSuite) TestEntrypointName() { + s.Given(). + WorkflowTemplate(`@functional/entrypointName-template.yaml`). + Workflow(`@functional/entrypointName-workflow.yaml`). + When(). + CreateWorkflowTemplates(). + SubmitWorkflow(). + WaitForWorkflow(). + Then(). + ExpectWorkflowNode(wfv1.NodeWithDisplayName("step"), func(t *testing.T, n *wfv1.NodeStatus, p *apiv1.Pod) { + assert.Equal(t, wfv1.NodeSucceeded, n.Phase) + assert.Equal(t, "bar", n.Inputs.Parameters[0].Value.String()) + }) + +} diff --git a/workflow/common/common.go b/workflow/common/common.go index b9ffd8e768b5..e05434887a80 100644 --- a/workflow/common/common.go +++ b/workflow/common/common.go @@ -167,6 +167,8 @@ const ( GlobalVarWorkflowName = "workflow.name" // GlobalVarWorkflowNamespace is a global workflow variable referencing the workflow's metadata.namespace field GlobalVarWorkflowNamespace = "workflow.namespace" + // GlobalVarWorkflowMainEntrypoint is a global workflow variable referencing the workflow's top level entrypoint name + GlobalVarWorkflowMainEntrypoint = "workflow.mainEntrypoint" // GlobalVarWorkflowServiceAccountName is a global workflow variable referencing the workflow's spec.serviceAccountName field GlobalVarWorkflowServiceAccountName = "workflow.serviceAccountName" // GlobalVarWorkflowUID is a global workflow variable referencing the workflow's metadata.uid field diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go index 4f049cf1ccef..87a7a6436461 100644 --- a/workflow/controller/operator.go +++ b/workflow/controller/operator.go @@ -581,6 +581,7 @@ func (woc *wfOperationCtx) getWorkflowDeadline() *time.Time { func (woc *wfOperationCtx) setGlobalParameters(executionParameters wfv1.Arguments) error { woc.globalParams[common.GlobalVarWorkflowName] = woc.wf.ObjectMeta.Name woc.globalParams[common.GlobalVarWorkflowNamespace] = woc.wf.ObjectMeta.Namespace + woc.globalParams[common.GlobalVarWorkflowMainEntrypoint] = woc.execWf.Spec.Entrypoint woc.globalParams[common.GlobalVarWorkflowServiceAccountName] = woc.execWf.Spec.ServiceAccountName woc.globalParams[common.GlobalVarWorkflowUID] = string(woc.wf.ObjectMeta.UID) woc.globalParams[common.GlobalVarWorkflowCreationTimestamp] = woc.wf.ObjectMeta.CreationTimestamp.Format(time.RFC3339) diff --git a/workflow/controller/operator_test.go b/workflow/controller/operator_test.go index ddeff1f2e8ba..74d4fb2221d1 100644 --- a/workflow/controller/operator_test.go +++ b/workflow/controller/operator_test.go @@ -367,6 +367,7 @@ func TestGlobalParams(t *testing.T) { assert.Contains(t, woc.globalParams, "workflow.duration") assert.Contains(t, woc.globalParams, "workflow.name") assert.Contains(t, woc.globalParams, "workflow.namespace") + assert.Contains(t, woc.globalParams, "workflow.mainEntrypoint") assert.Contains(t, woc.globalParams, "workflow.parameters") assert.Contains(t, woc.globalParams, "workflow.annotations") assert.Contains(t, woc.globalParams, "workflow.labels") @@ -6704,7 +6705,7 @@ spec: container: image: docker/whalesay:latest command: [cowsay] - args: ["{{workflows.scheduledTime}}"] + args: ["{{workflow.scheduledTime}}"] ` func TestWorkflowScheduledTimeVariable(t *testing.T) { @@ -6718,6 +6719,33 @@ func TestWorkflowScheduledTimeVariable(t *testing.T) { assert.Equal(t, "2006-01-02T15:04:05-07:00", woc.globalParams[common.GlobalVarWorkflowCronScheduleTime]) } +var wfMainEntrypointVariable = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + name: hello-world +spec: + entrypoint: whalesay + shutdown: "Stop" + templates: + - name: whalesay + container: + image: docker/whalesay:latest + command: [cowsay] + args: ["{{workflow.mainEntrypoint}}"] +` + +func TestWorkflowMainEntrypointVariable(t *testing.T) { + wf := wfv1.MustUnmarshalWorkflow(wfMainEntrypointVariable) + cancel, controller := newController(wf) + defer cancel() + + ctx := context.Background() + woc := newWorkflowOperationCtx(wf, controller) + woc.operate(ctx) + assert.Equal(t, "whalesay", woc.globalParams[common.GlobalVarWorkflowMainEntrypoint]) +} + var wfNodeNameField = ` apiVersion: argoproj.io/v1alpha1 kind: Workflow diff --git a/workflow/validate/validate.go b/workflow/validate/validate.go index cb8508310c79..82c3cd604519 100644 --- a/workflow/validate/validate.go +++ b/workflow/validate/validate.go @@ -66,6 +66,7 @@ func newTemplateValidationCtx(wf *wfv1.Workflow, opts ValidateOpts) *templateVal globalParams := make(map[string]string) globalParams[common.GlobalVarWorkflowName] = placeholderGenerator.NextPlaceholder() globalParams[common.GlobalVarWorkflowNamespace] = placeholderGenerator.NextPlaceholder() + globalParams[common.GlobalVarWorkflowMainEntrypoint] = placeholderGenerator.NextPlaceholder() globalParams[common.GlobalVarWorkflowServiceAccountName] = placeholderGenerator.NextPlaceholder() globalParams[common.GlobalVarWorkflowUID] = placeholderGenerator.NextPlaceholder() return &templateValidationCtx{