diff --git a/docs/variables.md b/docs/variables.md index bf7a2f334848..34f3729addd3 100644 --- a/docs/variables.md +++ b/docs/variables.md @@ -243,7 +243,9 @@ For `Template`-level metrics: | Variable | Description| |----------|------------| | `workflow.name` | Workflow name | +| `workflow.templateName` | Workflow template name. This works only for template names, it may give wrong answers for non-generated names | | `workflow.namespace` | Workflow namespace | +| `workflow.main entrypoint` | 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 | diff --git a/workflow/common/common.go b/workflow/common/common.go index 65b5df466996..85984235cb2f 100644 --- a/workflow/common/common.go +++ b/workflow/common/common.go @@ -165,8 +165,12 @@ const ( // GlobalVarWorkflowName is a global workflow variable referencing the workflow's metadata.name field GlobalVarWorkflowName = "workflow.name" + // GlobalVarWorkflowTemplateName is a global workflow variable referencing the workflow's name reverse engineered to the template name from a generateName + GlobalVarWorkflowTemplateName = "workflow.templateName" // 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 7e16809fcc84..e8f2a4454ef9 100644 --- a/workflow/controller/operator.go +++ b/workflow/controller/operator.go @@ -552,10 +552,23 @@ func (woc *wfOperationCtx) getWorkflowDeadline() *time.Time { return &deadline } +// templateNameFromName tries to strip the suffix for a cronworkflow [\d]{10} or from a +// generateName [a-z0-9]{5} from the name. +var templateNameFromName = regexp.MustCompile(`^(.{3,}?)(\-?[\d]{10})?(\-?[a-z0-9]{5})?$`) + +func getTemplateNameFromName(name string) string { + if templateNameFromName.MatchString(name) { + return templateNameFromName.ReplaceAllString(name, "$1") + } + return name +} + // setGlobalParameters sets the globalParam map with global parameters func (woc *wfOperationCtx) setGlobalParameters(executionParameters wfv1.Arguments) error { woc.globalParams[common.GlobalVarWorkflowName] = woc.wf.ObjectMeta.Name + woc.globalParams[common.GlobalVarWorkflowTemplateName] = getTemplateNameFromName(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 2f069ca484dd..6ee09e5d9d94 100644 --- a/workflow/controller/operator_test.go +++ b/workflow/controller/operator_test.go @@ -363,6 +363,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.templateName") assert.Contains(t, woc.globalParams, "workflow.namespace") assert.Contains(t, woc.globalParams, "workflow.parameters") assert.Contains(t, woc.globalParams, "workflow.annotations") @@ -375,6 +376,22 @@ func TestGlobalParams(t *testing.T) { assert.Contains(t, woc.globalParams, "workflow.labels.workflows.argoproj.io/phase") } +func TestTemplateName(t *testing.T) { + for input, expected := range map[string]string{ + `template-name-1234567890`: `template-name`, // Cronworkflows run on a cron + `template-name1234567890`: `template-name`, // Cronworkflow without trailing - + `foobar-fs4hy`: `foobar`, // Standard template submission + `foobar-foobar-ashda`: `foobar-foobar`, + `foobarfs4hy`: `foobar`, // Template without trailing - + `foobar`: `foobar`, // We don't replace the end of this + `foo-bar`: `foo-bar`, + `foobar-foobar-0987654321-4ab1a`: `foobar-foobar`, // Cronworkflow, resubmitted + `foobar-foobar`: `foobar-f`, // Can't really prevent this kind of thing, not desired though + } { + assert.Equal(t, expected, getTemplateNameFromName(input)) + } +} + // TestSidecarWithVolume verifies ia sidecar can have a volumeMount reference to both existing or volumeClaimTemplate volumes func TestSidecarWithVolume(t *testing.T) { wf := wfv1.MustUnmarshalWorkflow(sidecarWithVol) @@ -6620,7 +6637,7 @@ spec: container: image: docker/whalesay:latest command: [cowsay] - args: ["{{workflows.scheduledTime}}"] + args: ["{{workflow.scheduledTime}}"] ` func TestWorkflowScheduledTimeVariable(t *testing.T) { @@ -6634,6 +6651,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 5d3e8e519370..79b078904329 100644 --- a/workflow/validate/validate.go +++ b/workflow/validate/validate.go @@ -65,7 +65,9 @@ type templateValidationCtx struct { func newTemplateValidationCtx(wf *wfv1.Workflow, opts ValidateOpts) *templateValidationCtx { globalParams := make(map[string]string) globalParams[common.GlobalVarWorkflowName] = placeholderGenerator.NextPlaceholder() + globalParams[common.GlobalVarWorkflowTemplateName] = placeholderGenerator.NextPlaceholder() globalParams[common.GlobalVarWorkflowNamespace] = placeholderGenerator.NextPlaceholder() + globalParams[common.GlobalVarWorkflowMainEntrypoint] = placeholderGenerator.NextPlaceholder() globalParams[common.GlobalVarWorkflowServiceAccountName] = placeholderGenerator.NextPlaceholder() globalParams[common.GlobalVarWorkflowUID] = placeholderGenerator.NextPlaceholder() return &templateValidationCtx{