Repository: argoproj/argo (go)

Issue:
Weird argument arrangement when using script template
# Summary

I wanted to add a template that is activated on a workflow exit (the `onExit` field) with a purpose of notifying the user about the workflow status. I decided to use a template of type `script`, like this:

```YAML
name: "workflow-status-test"
script: 
 image: "python:3.6-alpine"
 command:
  - python3
 args:
  - "{{workflow.status}}"
 source: "import sys; import my_notify_func; my_notify_func(sys.argv[1])"
```

This failed. After inspecting the pod, I saw that Argo passed the arguments to the python container in the following way `Succeeded /argo/staging/script`, I would expect it to do it like this `/argo/staging/script Succeeded`.

Later I noticed in one of your examples in git repo that I could embed a template directly in the `source` field. But this wasn't documented anywhere (at least I didnt see it). So, this solves my issue:

```YAML
name: "workflow-status-test"
script: 
 image: "python:3.6-alpine"
 command:
  - python3
 source: "import my_notify_func; my_notify_func('{{workflow.status}}')"
```

The order in which the arguments are passed is unintuitive and I don't see a case where this particular order would be useful. My suggestion is to change the order (so first goes the script path, and then the user args) or put a notice/disclaimer in the docs. Also, you could provide an example for using templates [here](https://argoproj.github.io/argo/examples/), or document it.

# Use Cases

When would you use this?

---
<!-- Issue Author: Don't delete this message to encourage other users to support your issue! -->
**Message from the maintainers**:

Impacted by this bug? Give it a 👍. We prioritise the issues with the most 👍.

Pull request:
fix(controller): prepend script path to the script template args. Resolves #4481
Signed-off-by: ivancili <ivandotilic@gmail.com>

Checklist:

* [x] My organization is added to [USERS.md](https://github.com/argoproj/argo/blob/master/USERS.md).

<!--

Please leave your PR in draft if you don't need a review yet. 

To fix failing `CI / Codegen`, run `make codegen`. 

-->
Resolves #4481

Patch:
diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go
index 005a819b6..a04483b08 100644
--- a/workflow/controller/operator.go
+++ b/workflow/controller/operator.go
@@ -2286,6 +2286,16 @@ func getStepOrDAGTaskName(nodeName string) string {
 	return nodeName
 }
 
+func extractMainCtrFromScriptTemplate(tmpl *wfv1.Template) apiv1.Container {
+	mainCtr := tmpl.Script.Container
+	// If script source is provided then pass all container args to the
+	// script instead of passing them to the container command directly
+	if tmpl.Script.Source != "" {
+		mainCtr.Args = append([]string{common.ExecutorScriptSourcePath}, mainCtr.Args...)
+	}
+	return mainCtr
+}
+
 func (woc *wfOperationCtx) executeScript(nodeName string, templateScope string, tmpl *wfv1.Template, orgTmpl wfv1.TemplateReferenceHolder, opts *executeTemplateOpts) (*wfv1.NodeStatus, error) {
 	node := woc.wf.GetNodeByName(nodeName)
 	if node == nil {
@@ -2301,8 +2311,7 @@ func (woc *wfOperationCtx) executeScript(nodeName string, templateScope string,
 		return node, err
 	}
 
-	mainCtr := tmpl.Script.Container
-	mainCtr.Args = append(mainCtr.Args, common.ExecutorScriptSourcePath)
+	mainCtr := extractMainCtrFromScriptTemplate(tmpl)
 	_, err = woc.createWorkflowPod(nodeName, mainCtr, tmpl, &createWorkflowPodOpts{
 		includeScriptOutput: includeScriptOutput,
 		onExitPod:           opts.onExitTemplate,


Test patch:
diff --git a/workflow/controller/workflowpod_test.go b/workflow/controller/workflowpod_test.go
index 6a4734bd4..019c07ea8 100644
--- a/workflow/controller/workflowpod_test.go
+++ b/workflow/controller/workflowpod_test.go
@@ -69,6 +69,42 @@ func TestScriptTemplateWithVolume(t *testing.T) {
 	assert.NoError(t, err)
 }
 
+var scriptTemplateWithArgsAndWithSource = `
+name: script-with-args-and-with-source
+script:
+  image: alpine:latest
+  command: [sh]
+  args: ["hello world"]
+  source: |
+    echo $@
+`
+
+// TestScriptTemplateMainCtrArgsWhenArgsAndWhenSource ensure order of merged args
+// and script path is correct when both args and script source are specified
+func TestScriptTemplateMainCtrArgsWhenArgsAndWhenSource(t *testing.T) {
+	tmpl := unmarshalTemplate(scriptTemplateWithArgsAndWithSource)
+	mainCtr := extractMainCtrFromScriptTemplate(tmpl)
+	assert.Equal(t, []string{"sh"}, mainCtr.Command)
+	assert.Equal(t, []string{common.ExecutorScriptSourcePath, "hello world"}, mainCtr.Args)
+}
+
+var scriptTemplateWithArgsAndWithoutSource = `
+name: script-with-args-and-without-source
+script:
+  image: alpine:latest
+  command: [echo]
+  args: ["hello world"]
+`
+
+// TestScriptTemplateMainCtrArgsWhenArgsAndWhenNoSource ensure only args are passed
+// to the resulting container when script source is empty
+func TestScriptTemplateMainCtrArgsWhenArgsAndWhenNoSource(t *testing.T) {
+	tmpl := unmarshalTemplate(scriptTemplateWithArgsAndWithoutSource)
+	mainCtr := extractMainCtrFromScriptTemplate(tmpl)
+	assert.Equal(t, []string{"echo"}, mainCtr.Command)
+	assert.Equal(t, []string{"hello world"}, mainCtr.Args)
+}
+
 var scriptTemplateWithOptionalInputArtifactProvided = `
 name: script-with-input-artifact
 inputs: