Repository: argoproj/argo (go)

Issue:
When using script template input artifact is not able to save in volume mount
**Is this a BUG REPORT or FEATURE REQUEST?**: Bug

**What happened**:
When using script template input artifact is not able to save in volume mount

**How to reproduce it (as minimally and precisely as possible)**:

```
- name: execute
    inputs:
      artifacts:
      - name: config
        path: /mnt/vol/.aws/config
        mode: 0777
        raw:
          data: |
            [profile default]
            role_arn = arn:abc
            credential_source = Ec2InstanceMetadata
    script:
      volumeMounts:
      - mountPath: /mnt/vol/.aws
        name: aws
      image: my-image
      env:
      - name: AWS_DEFAULT_REGION
        value: us-west-2
      - name: AWS_CONFIG_FILE
        value: /mnt/vol/.aws/config
      command: [sh]
      source: |
        echo "Hello World"
```

**Anything else we need to know?**:

```
time="2019-10-31T23:50:37Z" level=info msg="Start loading input artifacts..."
time="2019-10-31T23:50:37Z" level=info msg="Downloading artifact: config"
time="2019-10-31T23:50:37Z" level=info msg="Specified artifact path /mnt/volume/.aws/config overlaps with volume mount at /mnt/volume/.aws. Extracting to volume mount"
time="2019-10-31T23:50:37Z" level=error msg="executor error: open /mainctrfs/mnt/volume/.aws/config.tmp: no such file or directory"
time="2019-10-31T23:50:37Z" level=info msg="Alloc=4745 TotalAlloc=9854 Sys=70334 NumGC=3 Goroutines=4"
time="2019-10-31T23:50:37Z" level=fatal msg="open /mainctrfs/mnt/volume/.aws/config.tmp: no such file or directory"
```



**Environment**:
- Argo version:
```
$ argo version

argo: v2.4.2
  BuildDate: 2019-10-21T18:39:52Z
  GitCommit: 675c66267f0c916de0f233d8101aa0646acb46d4
  GitTreeState: clean
  GitTag: v2.4.2
  GoVersion: go1.11.5
  Compiler: gc
  Platform: darwin/amd64
```

Pull request description:
fix(controller): Mount volumes defined in script templates. Closes #1722
Description:

Closes #1722.

The controller has a behavior that when a template has an artifact whose path overlaps with any of the volumes to be mounted in this template, it will not load the artifact into the default artifact directory but to the volume to be mounted. It should mount the volume to the init container as well before doing so. However, when we check for volumes that should be mounted to the init container, we miss out volumes defined in script templates, and thus artifacts cannot be loaded properly only for this type of template. To be safe, I also checked other types of templates, and I think we've covered all cases, i.e., container templates and script templates.

Updated E2E to cover this case and tested manually with the following template (basically fetched from the issue):
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: artifact-passing-
spec:
  entrypoint: artifact-example
  volumes:
    - name: aws
  templates:
  - name: artifact-example
    steps:
    - - name: generate-artifact
        template: execute

  - name: execute
    inputs:
      artifacts:
      - name: config
        path: /mnt/vol/.aws/config
        mode: 0777
        raw:
          data: |
            [profile default]
            role_arn = arn:abc
            credential_source = Ec2InstanceMetadata
    script:
      volumeMounts:
      - mountPath: /mnt/vol/.aws
        name: aws
      image: alpine:latest
      env:
      - name: AWS_DEFAULT_REGION
        value: us-west-2
      - name: AWS_CONFIG_FILE
        value: /mnt/vol/.aws/config
      command: [sh]
      source: |
        echo "Hello World"
```


Checklist:

* [x] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this is a chore.
* [x] The title of the PR is (a) [conventional](https://www.conventionalcommits.org/en/v1.0.0/), (b) states what changed, and (c) suffixes the related issues number. E.g. `"fix(controller): Updates such and such. Fixes #1234"`.  
* [x] I have written unit and/or e2e tests for my change. PRs without these are unlike to be merged.
* [x] Optional. I've added My organization is added to the README.
* [x] I've signed the CLA and required builds are green. 


Patch:
diff --git a/examples/README.md b/examples/README.md
index 6a7059bdd..4d918dadf 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -769,7 +769,7 @@ spec:
       limit: 10
       retryOn: "Always"
       backoff:
-        duration: 1         # Default unit is seconds. Could also be a Duration, e.g.: "2m", "6h", "1d"
+        duration: "1m"         # Default unit is seconds. Could also be a Duration, e.g.: "2m", "6h", "1d"
         factor: 2
         maxDuration: "1m"   # Default unit is seconds. Could also be a Duration, e.g.: "2m", "6h", "1d"
     container:
diff --git a/ui/README.md b/ui/README.md
index 6987e1188..9bdaf80a7 100644
--- a/ui/README.md
+++ b/ui/README.md
@@ -1,10 +1,12 @@
 # Argo UI
 
-![Argo Image](https://github.com/argoproj/argo/blob/master/argo.png?raw=true)
+![Argo Image](../docs/assets/argo.png)
 
 A web-based UI for the Argo Workflow engine. 
 
 The UI has the following features:
 * View live Argo Workflows running in the cluster
 * View completed Argo Workflows
+* Create new Argo Workflow templates
+* View and create Argo Cron Workflows
 * View container logs
diff --git a/ui/src/app/shared/services/workflows-service.ts b/ui/src/app/shared/services/workflows-service.ts
index 01613ead5..9429c6c75 100644
--- a/ui/src/app/shared/services/workflows-service.ts
+++ b/ui/src/app/shared/services/workflows-service.ts
@@ -68,7 +68,7 @@ export class WorkflowsService {
         return requests
             .loadEventSource(
                 `api/v1/workflows/${workflow.metadata.namespace}/${workflow.metadata.name}/${nodeId}/log` +
-                    `?logOptions.container=${container}&logOptions.tailLines=20&logOptions.follow=true`
+                    `?logOptions.container=${container}&logOptions.follow=true`
             )
             .pipe(
                 map(line => JSON.parse(line).result.content),
diff --git a/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx b/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx
index c4c261ebf..9e61040d5 100644
--- a/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx
+++ b/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx
@@ -68,11 +68,12 @@ export class WorkflowLogsViewer extends React.Component<WorkflowLogsViewerProps,
                             <i className='fa fa-exclamation-triangle status-icon--failed' /> Failed to load logs: {this.state.error.message}
                         </p>
                     )}
-                    {!this.state.error && this.state.lines.length === 0 && (
+                    {!this.state.error && this.state.lines.length === 0 && this.isCurrentNodeRunningOrPending() && (
                         <p>
                             <i className='fa fa-circle-notch fa-spin' /> Waiting for data...
                         </p>
                     )}
+                    {!this.state.error && this.state.lines.length === 0 && !this.isCurrentNodeRunningOrPending() && <p>Pod did not output any logs.</p>}
                     {this.state.lines.length > 0 && (
                         <div className='log-box'>
                             <i className='fa fa-chevron-down' />
@@ -100,4 +101,8 @@ export class WorkflowLogsViewer extends React.Component<WorkflowLogsViewerProps,
             </div>
         );
     }
+
+    private isCurrentNodeRunningOrPending(): boolean {
+        return this.props.workflow.status.nodes[this.props.nodeId].phase === 'Running' || this.props.workflow.status.nodes[this.props.nodeId].phase === 'Pending';
+    }
 }
diff --git a/workflow/config/config.go b/workflow/config/config.go
index a22fa2fbd..f6424862d 100644
--- a/workflow/config/config.go
+++ b/workflow/config/config.go
@@ -64,6 +64,8 @@ type WorkflowControllerConfig struct {
 
 	// Config customized Docker Sock path
 	DockerSockPath string `json:"dockerSockPath,omitempty"`
+
+	DefautWorkflowSpec *wfv1.WorkflowSpec `json:"workflowDefaults,omitempty"`
 }
 
 // KubeConfig is used for wait & init sidecar containers to communicate with a k8s apiserver by a outofcluster method,
diff --git a/workflow/controller/controller.go b/workflow/controller/controller.go
index 901305f7c..a35b23fcc 100644
--- a/workflow/controller/controller.go
+++ b/workflow/controller/controller.go
@@ -2,6 +2,7 @@ package controller
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"os"
 	"strings"
@@ -17,6 +18,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/selection"
 	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/strategicpatch"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/apimachinery/pkg/watch"
 	"k8s.io/client-go/kubernetes"
@@ -45,8 +47,10 @@ type WorkflowController struct {
 	// namespace of the workflow controller
 	namespace        string
 	managedNamespace string
+
 	// configMap is the name of the config map in which to derive configuration of the controller from
 	configMap string
+
 	// Config is the workflow controller's configuration
 	Config config.WorkflowControllerConfig
 
@@ -355,6 +359,14 @@ func (wfc *WorkflowController) processNextItem() bool {
 		wfc.throttler.Remove(key)
 		return true
 	}
+	err = wfc.addingWorkflowDefaultValueIfValueNotExist(wf)
+	if err != nil {
+		log.Warnf("Failed to unmarshal key '%s' to workflow object: %v", key, err)
+		woc := newWorkflowOperationCtx(wf, wfc)
+		woc.markWorkflowFailed(fmt.Sprintf("invalid spec: %s", err.Error()))
+		woc.persistUpdates()
+		wfc.throttler.Remove(key)
+	}
 
 	if wf.ObjectMeta.Labels[common.LabelKeyCompleted] == "true" {
 		wfc.throttler.Remove(key)
@@ -362,9 +374,7 @@ func (wfc *WorkflowController) processNextItem() bool {
 		// but we are still draining the controller's workflow workqueue
 		return true
 	}
-
 	woc := newWorkflowOperationCtx(wf, wfc)
-
 	// Loading running workflow from persistence storage if nodeStatusOffload enabled
 	if wf.Status.IsOffloadNodeStatus() {
 		nodes, err := wfc.offloadNodeStatusRepo.Get(string(wf.UID), wf.GetOffloadNodeStatusVersion())
@@ -417,6 +427,33 @@ func (wfc *WorkflowController) processNextItem() bool {
 	return true
 }
 
+// addingWorkflowDefaultValueIfValueNotExist sets values in the workflow.Spec with defaults from the
+// workflowController. Values in the workflow will be given the upper hand over the defaults.
+// The defaults for the workflow controller is set in the WorkflowController.Config.DefautWorkflowSpec
+func (wfc *WorkflowController) addingWorkflowDefaultValueIfValueNotExist(wf *wfv1.Workflow) error {
+	//var workflowSpec *wfv1.WorkflowSpec = &wf.Spec
+	if wfc.Config.DefautWorkflowSpec != nil {
+		defaultsSpec, err := json.Marshal(*wfc.Config.DefautWorkflowSpec)
+		if err != nil {
+			return err
+		}
+		workflowSpec, err := json.Marshal(wf.Spec)
+		if err != nil {
+			return err
+		}
+		// https://github.com/kubernetes/apimachinery/blob/2373d029717c4d169463414a6127cd1d0d12680e/pkg/util/strategicpatch/patch.go#L94
+		new, err := strategicpatch.StrategicMergePatch(defaultsSpec, workflowSpec, wfv1.WorkflowSpec{})
+		if err != nil {
+			return err
+		}
+		err = json.Unmarshal(new, &wf.Spec)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 func (wfc *WorkflowController) podWorker() {
 	for wfc.processNextPodItem() {
 	}
diff --git a/workflow/controller/workflowpod.go b/workflow/controller/workflowpod.go
index 1ce2c560e..12a4771f5 100644
--- a/workflow/controller/workflowpod.go
+++ b/workflow/controller/workflowpod.go
@@ -732,12 +732,17 @@ func (woc *wfOperationCtx) addInputArtifactsVolumes(pod *apiv1.Pod, tmpl *wfv1.T
 			// We also add the user supplied mount paths to the init container,
 			// in case the executor needs to load artifacts to this volume
 			// instead of the artifacts volume
+			tmplVolumeMounts := []apiv1.VolumeMount{}
 			if tmpl.Container != nil {
-				for _, mnt := range tmpl.Container.VolumeMounts {
-					mnt.MountPath = filepath.Join(common.ExecutorMainFilesystemDir, mnt.MountPath)
-					initCtr.VolumeMounts = append(initCtr.VolumeMounts, mnt)
-				}
+				tmplVolumeMounts = tmpl.Container.VolumeMounts
+			} else if tmpl.Script != nil {
+				tmplVolumeMounts = tmpl.Script.Container.VolumeMounts
+			}
+			for _, mnt := range tmplVolumeMounts {
+				mnt.MountPath = filepath.Join(common.ExecutorMainFilesystemDir, mnt.MountPath)
+				initCtr.VolumeMounts = append(initCtr.VolumeMounts, mnt)
 			}
+
 			pod.Spec.InitContainers[i] = initCtr
 			break
 		}


Test patch:
diff --git a/test/e2e/smoke/artifact-passing.yaml b/test/e2e/smoke/artifact-passing.yaml
index e850eb16f..8f484492b 100644
--- a/test/e2e/smoke/artifact-passing.yaml
+++ b/test/e2e/smoke/artifact-passing.yaml
@@ -6,6 +6,8 @@ metadata:
     argo-e2e: true
 spec:
   entrypoint: artifact-example
+  volumes:
+    - name: test-volume
   templates:
     - name: artifact-example
       steps:
@@ -17,6 +19,12 @@ spec:
               artifacts:
                 - name: message
                   from: "{{steps.generate-artifact.outputs.artifacts.hello-art}}"
+          - name: consume-artifact-2
+            template: print-message-with-volume
+            arguments:
+              artifacts:
+                - name: message
+                  from: "{{steps.generate-artifact.outputs.artifacts.hello-art}}"
 
     - name: generate-message
       container:
@@ -39,3 +47,17 @@ spec:
         imagePullPolicy: IfNotPresent
         command: [sh, -c]
         args: ["cat /tmp/message"]
+
+    - name: print-message-with-volume
+      inputs:
+        artifacts:
+          - name: message
+            path: /test-volume/message
+      script:
+        volumeMounts:
+        - mountPath: /test-volume
+          name: test-volume
+        image: cowsay:v1
+        imagePullPolicy: IfNotPresent
+        command: [sh, -c]
+        args: ["cat /test-volume/message"]
diff --git a/workflow/controller/controller_test.go b/workflow/controller/controller_test.go
index d981f60b4..dbc363965 100644
--- a/workflow/controller/controller_test.go
+++ b/workflow/controller/controller_test.go
@@ -43,6 +43,55 @@ spec:
       args: ["hello world"]
 `
 
+var testDefaultWf = `
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  name: hello-world
+spec:
+  entrypoint: whalesay
+  serviceAccountName: whalesay
+  templates:
+  - name: whalesay
+    metadata:
+      annotations:
+        annotationKey1: "annotationValue1"
+        annotationKey2: "annotationValue2"
+      labels:
+        labelKey1: "labelValue1"
+        labelKey2: "labelValue2"
+    container:
+      image: docker/whalesay:latest
+      command: [cowsay]
+      args: ["hello world"]
+`
+
+var testDefaultWfTTL = `
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  name: hello-world
+spec:
+  entrypoint: whalesay
+  serviceAccountName: whalesay
+  ttlSecondsAfterFinished: 7
+  ttlStrategy:
+    secondsAfterCompletion: 5
+  templates:
+  - name: whalesay
+    metadata:
+      annotations:
+        annotationKey1: "annotationValue1"
+        annotationKey2: "annotationValue2"
+      labels:
+        labelKey1: "labelValue1"
+        labelKey2: "labelValue2"
+    container:
+      image: docker/whalesay:latest
+      command: [cowsay]
+      args: ["hello world"]
+`
+
 func newController() *WorkflowController {
 	wfclientset := fakewfclientset.NewSimpleClientset()
 	informerFactory := wfextv.NewSharedInformerFactory(wfclientset, 10*time.Minute)
@@ -65,6 +114,68 @@ func newController() *WorkflowController {
 	}
 }
 
+func newControllerWithDefaults() *WorkflowController {
+	wfclientset := fakewfclientset.NewSimpleClientset()
+	informerFactory := wfextv.NewSharedInformerFactory(wfclientset, 10*time.Minute)
+	wftmplInformer := informerFactory.Argoproj().V1alpha1().WorkflowTemplates()
+	ctx := context.Background()
+	go wftmplInformer.Informer().Run(ctx.Done())
+	if !cache.WaitForCacheSync(ctx.Done(), wftmplInformer.Informer().HasSynced) {
+		panic("Timed out waiting for caches to sync")
+	}
+	myBool := true
+	return &WorkflowController{
+		Config: config.WorkflowControllerConfig{
+			ExecutorImage: "executor:latest",
+			DefautWorkflowSpec: &wfv1.WorkflowSpec{
+				HostNetwork: &myBool,
+			},
+		},
+		kubeclientset:  fake.NewSimpleClientset(),
+		wfclientset:    wfclientset,
+		completedPods:  make(chan string, 512),
+		wftmplInformer: wftmplInformer,
+		wfQueue:        workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
+		wfArchive:      sqldb.NullWorkflowArchive,
+	}
+}
+
+func newControllerWithComplexDefaults() *WorkflowController {
+	wfclientset := fakewfclientset.NewSimpleClientset()
+	informerFactory := wfextv.NewSharedInformerFactory(wfclientset, 10*time.Minute)
+	wftmplInformer := informerFactory.Argoproj().V1alpha1().WorkflowTemplates()
+	ctx := context.Background()
+	go wftmplInformer.Informer().Run(ctx.Done())
+	if !cache.WaitForCacheSync(ctx.Done(), wftmplInformer.Informer().HasSynced) {
+		panic("Timed out waiting for caches to sync")
+	}
+	myBool := true
+	var ten int32 = 10
+	var seven int32 = 10
+	return &WorkflowController{
+		Config: config.WorkflowControllerConfig{
+			ExecutorImage: "executor:latest",
+			DefautWorkflowSpec: &wfv1.WorkflowSpec{
+				HostNetwork:        &myBool,
+				Entrypoint:         "good_entrypoint",
+				ServiceAccountName: "my_service_account",
+				TTLStrategy: &wfv1.TTLStrategy{
+					SecondsAfterCompletion: &ten,
+					SecondsAfterSuccess:    &ten,
+					SecondsAfterFailure:    &ten,
+				},
+				TTLSecondsAfterFinished: &seven,
+			},
+		},
+		kubeclientset:  fake.NewSimpleClientset(),
+		wfclientset:    wfclientset,
+		completedPods:  make(chan string, 512),
+		wftmplInformer: wftmplInformer,
+		wfQueue:        workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
+		wfArchive:      sqldb.NullWorkflowArchive,
+	}
+}
+
 func unmarshalWF(yamlStr string) *wfv1.Workflow {
 	var wf wfv1.Workflow
 	err := yaml.Unmarshal([]byte(yamlStr), &wf)
@@ -98,3 +209,52 @@ func makePodsPhase(t *testing.T, phase apiv1.PodPhase, kubeclientset kubernetes.
 		}
 	}
 }
+
+func TestAddingWorkflowDefaultValueIfValueNotExist(t *testing.T) {
+	assert.Equal(t, "hello", "hello")
+	ans := true
+	controller := newController()
+	workflow := unmarshalWF(helloWorldWf)
+	err := controller.addingWorkflowDefaultValueIfValueNotExist(workflow)
+	assert.NoError(t, err)
+	assert.Equal(t, workflow, unmarshalWF(helloWorldWf))
+	controllerDefaults := newControllerWithDefaults()
+	defautWorkflowSpec := unmarshalWF(helloWorldWf)
+	err = controllerDefaults.addingWorkflowDefaultValueIfValueNotExist(defautWorkflowSpec)
+	assert.NoError(t, err)
+	assert.Equal(t, defautWorkflowSpec.Spec.HostNetwork, &ans)
+	assert.NotEqual(t, defautWorkflowSpec, unmarshalWF(helloWorldWf))
+	assert.Equal(t, *defautWorkflowSpec.Spec.HostNetwork, true)
+}
+
+func TestAddingWorkflowDefaultComplex(t *testing.T) {
+	assert.Equal(t, "hello", "hello")
+	controller := newControllerWithComplexDefaults()
+	workflow := unmarshalWF(testDefaultWf)
+	var ten int32 = 10
+	assert.Equal(t, workflow.Spec.Entrypoint, "whalesay")
+	assert.Nil(t, workflow.Spec.TTLStrategy)
+	err := controller.addingWorkflowDefaultValueIfValueNotExist(workflow)
+	assert.NoError(t, err)
+	assert.NotEqual(t, workflow, unmarshalWF(testDefaultWf))
+	assert.Equal(t, workflow.Spec.Entrypoint, "whalesay")
+	assert.Equal(t, workflow.Spec.ServiceAccountName, "whalesay")
+	assert.Equal(t, *workflow.Spec.TTLStrategy.SecondsAfterFailure, ten)
+}
+
+func TestAddingWorkflowDefaultComplexTwo(t *testing.T) {
+	assert.Equal(t, "hello", "hello")
+	controller := newControllerWithComplexDefaults()
+	workflow := unmarshalWF(testDefaultWfTTL)
+	var ten int32 = 10
+	var seven int32 = 7
+	var five int32 = 5
+	err := controller.addingWorkflowDefaultValueIfValueNotExist(workflow)
+	assert.NoError(t, err)
+	assert.NotEqual(t, workflow, unmarshalWF(testDefaultWfTTL))
+	assert.Equal(t, workflow.Spec.Entrypoint, "whalesay")
+	assert.Equal(t, workflow.Spec.ServiceAccountName, "whalesay")
+	assert.Equal(t, *workflow.Spec.TTLStrategy.SecondsAfterCompletion, five)
+	assert.Equal(t, *workflow.Spec.TTLStrategy.SecondsAfterFailure, ten)
+	assert.Equal(t, *workflow.Spec.TTLSecondsAfterFinished, seven)
+}
diff --git a/workflow/controller/workflowpod_test.go b/workflow/controller/workflowpod_test.go
index eba25c0e2..20bebe4ef 100644
--- a/workflow/controller/workflowpod_test.go
+++ b/workflow/controller/workflowpod_test.go
@@ -3,6 +3,7 @@ package controller
 import (
 	"encoding/json"
 	"fmt"
+	"path/filepath"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -82,6 +83,25 @@ script:
     ls -al
 `
 
+var scriptTemplateWithOptionalInputArtifactProvidedAndOverlappedPath = `
+name: script-with-input-artifact
+inputs:
+  artifacts:
+  - name: manifest
+    path: /manifest
+    optional: true
+    http:
+        url: https://raw.githubusercontent.com/argoproj/argo/stable/manifests/install.yaml
+script:
+  volumeMounts:
+  - mountPath: /manifest
+    name: my-mount
+  image: alpine:latest
+  command: [sh]
+  source: |
+    ls -al
+`
+
 var scriptTemplateWithOptionalInputArtifactNotProvided = `
 name: script-with-input-artifact
 inputs:
@@ -107,6 +127,24 @@ func TestScriptTemplateWithoutVolumeOptionalArtifact(t *testing.T) {
 		SubPathExpr:      "",
 	}
 
+	customVolumeMount := apiv1.VolumeMount{
+		Name:             "my-mount",
+		ReadOnly:         false,
+		MountPath:        "/manifest",
+		SubPath:          "",
+		MountPropagation: nil,
+		SubPathExpr:      "",
+	}
+
+	customVolumeMountForInit := apiv1.VolumeMount{
+		Name:             "my-mount",
+		ReadOnly:         false,
+		MountPath:        filepath.Join(common.ExecutorMainFilesystemDir, "/manifest"),
+		SubPath:          "",
+		MountPropagation: nil,
+		SubPathExpr:      "",
+	}
+
 	// Ensure that volume mount is added when artifact is provided
 	tmpl := unmarshalTemplate(scriptTemplateWithOptionalInputArtifactProvided)
 	woc := newWoc()
@@ -114,7 +152,24 @@ func TestScriptTemplateWithoutVolumeOptionalArtifact(t *testing.T) {
 	mainCtr.Args = append(mainCtr.Args, common.ExecutorScriptSourcePath)
 	pod, err := woc.createWorkflowPod(tmpl.Name, mainCtr, tmpl, true)
 	assert.NoError(t, err)
+	// Note: pod.Spec.Containers[0] is wait
 	assert.Contains(t, pod.Spec.Containers[1].VolumeMounts, volumeMount)
+	assert.NotContains(t, pod.Spec.Containers[1].VolumeMounts, customVolumeMount)
+	assert.NotContains(t, pod.Spec.InitContainers[0].VolumeMounts, customVolumeMountForInit)
+
+	// Ensure that volume mount is added to initContainer when artifact is provided
+	// and the volume is mounted manually in the template
+	tmpl = unmarshalTemplate(scriptTemplateWithOptionalInputArtifactProvidedAndOverlappedPath)
+	wf := unmarshalWF(helloWorldWf)
+	wf.Spec.Volumes = append(wf.Spec.Volumes, apiv1.Volume{Name: "my-mount"})
+	woc = newWoc(*wf)
+	mainCtr = tmpl.Script.Container
+	mainCtr.Args = append(mainCtr.Args, common.ExecutorScriptSourcePath)
+	pod, err = woc.createWorkflowPod(tmpl.Name, mainCtr, tmpl, true)
+	assert.NoError(t, err)
+	assert.NotContains(t, pod.Spec.Containers[1].VolumeMounts, volumeMount)
+	assert.Contains(t, pod.Spec.Containers[1].VolumeMounts, customVolumeMount)
+	assert.Contains(t, pod.Spec.InitContainers[0].VolumeMounts, customVolumeMountForInit)
 
 	// Ensure that volume mount is not created when artifact is not provided
 	tmpl = unmarshalTemplate(scriptTemplateWithOptionalInputArtifactNotProvided)