diff --git a/docs/config.rst b/docs/config.rst index e7af2d76b0..fcbb8016c6 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -396,7 +396,7 @@ serviceAccount Defines the Kubernetes `service account name /``. workDir Defines the path where the workflow temporary data is stored. This must be a path in a shared K8s persistent volume (default:``/work``). projectDir Defines the path where Nextflow projects are downloaded. This must be a path in a shared K8s persistent volume (default: ``/projects``). -pod Allows the definition of one or more pod configuration options such as environment variables, config maps, secrets, etc. It allows the same settings as the :ref:`process-pod` process directive. +podOptions Defines one or more pod configuration options such as environment variables, config maps, secrets, etc. It allows the same options as the :ref:`process-podOptions` process directive. pullPolicy Defines the strategy to be used to pull the container image e.g. ``pullPolicy: 'Always'``. runAsUser Defines the user ID to be used to run the containers. Shortcut for the ``securityContext`` option. securityContext Defines the `security context `_ for all pods. diff --git a/docs/kubernetes.rst b/docs/kubernetes.rst index ff8366673c..df88b539ba 100644 --- a/docs/kubernetes.rst +++ b/docs/kubernetes.rst @@ -118,13 +118,13 @@ with the directory in the volume to be mounted (default: ``/``). one specified in your Nextflow configuration file. Note also that the ``run`` command does not support the ``-v`` option. -.. tip:: It is also possible to mount multiple volumes using the ``pod`` directive, setting such as ``k8s.pod = [ [volumeClaim: "other-pvc", mountPath: "/other" ]]`` +.. tip:: It is also possible to mount multiple volumes using the ``podOptions`` directive, setting such as ``k8s.podOptions = [ [volumeClaim: "other-pvc", mountPath: "/other" ]]`` -Pod settings -============ +Pod options +=========== -The process :ref:`process-pod` directive allows the definition of pods specific settings, such as environment variables, -secrets and config maps when using the :ref:`k8s-executor` executor. See the :ref:`process-pod` directive for more details. +The process :ref:`process-podOptions` directive allows the definition of pod-specific options, such as environment variables, +secrets and config maps when using the :ref:`k8s-executor` executor. See the :ref:`process-podOptions` directive for more details. Limitation ========== diff --git a/docs/process.rst b/docs/process.rst index f1b0666f3c..08c95b7492 100644 --- a/docs/process.rst +++ b/docs/process.rst @@ -1352,7 +1352,7 @@ The directives are: * `memory`_ * `module`_ * `penv`_ -* `pod`_ +* `podOptions`_ * `publishDir`_ * `queue`_ * `scratch`_ @@ -1981,18 +1981,18 @@ cluster documentation or contact your admin to learn more about this. See also: `cpus`_, `memory`_, `time`_ -.. _process-pod: +.. _process-podOptions: -pod ---- +podOptions +---------- -The ``pod`` directive allows the definition of pods specific settings, such as environment variables, secrets +The ``podOptions`` directive allows the definition of pod-specific options, such as environment variables, secrets and config maps when using the :ref:`k8s-executor` executor. For example:: process your_task { - pod env: 'FOO', value: 'bar' + podOptions env: 'FOO', value: 'bar' ''' echo $FOO @@ -2001,7 +2001,7 @@ For example:: The above snippet defines an environment variable named ``FOO`` which value is ``bar``. -The ``pod`` directive allows the definition of the following options: +The ``podOptions`` directive supports the following options: ================================================= ================================================= ``label: , value: `` Defines a pod label with key ``K`` and value ``V``. @@ -2023,17 +2023,20 @@ The ``pod`` directive allows the definition of the following options: ``priorityClassName: `` Specifies the `priority class name `_ for pods. ================================================= ================================================= -When defined in the Nextflow configuration file, a pod setting can be defined using the canonical +When defined in the Nextflow configuration file, a pod option can be defined using the canonical associative array syntax. For example:: process { - pod = [env: 'FOO', value: 'bar'] + podOptions = [env: 'FOO', value: 'bar'] } -When more than one setting needs to be provides they must be enclosed in a list definition as shown below:: +When more than one option needs to be provided they must be enclosed in a list as shown below:: process { - pod = [ [env: 'FOO', value: 'bar'], [secret: 'my-secret/key1', mountPath: '/etc/file.txt'] ] + podOptions = [ + [env: 'FOO', value: 'bar'], + [secret: 'my-secret/key1', mountPath: '/etc/file.txt'] + ] } diff --git a/modules/nextflow/src/main/groovy/nextflow/k8s/K8sConfig.groovy b/modules/nextflow/src/main/groovy/nextflow/k8s/K8sConfig.groovy index b342ea1813..109885910c 100644 --- a/modules/nextflow/src/main/groovy/nextflow/k8s/K8sConfig.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/k8s/K8sConfig.groovy @@ -47,7 +47,16 @@ class K8sConfig implements Map { K8sConfig(Map config) { target = config ?: Collections.emptyMap() - this.podOptions = createPodOptions(target.pod) + // -- backward compatibility + if( target.pod ) { + log.warn "Process directive 'pod' has been renamed to 'podOptions'" + target.put('podOptions', target.pod) + } + + // -- initialize pod options + this.podOptions = createPodOptions(target.podOptions) + + // -- add pod volume claim if( getStorageClaimName() ) { final name = getStorageClaimName() final mount = getStorageMountPath() diff --git a/modules/nextflow/src/main/groovy/nextflow/k8s/K8sDriverLauncher.groovy b/modules/nextflow/src/main/groovy/nextflow/k8s/K8sDriverLauncher.groovy index 62209ea73e..ced35c3e85 100644 --- a/modules/nextflow/src/main/groovy/nextflow/k8s/K8sDriverLauncher.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/k8s/K8sDriverLauncher.groovy @@ -279,17 +279,22 @@ class K8sDriverLauncher { } def config = loadConfig(pipelineName) - - // normalize pod entries def k8s = config.k8s - if( !k8s.isSet('pod') ) - k8s.pod = [] - else if( k8s.pod instanceof Map ) { - k8s.pod = [ k8s.pod ] + // -- backward compatibility + if( k8s.pod ) { + log.warn "Config setting k8s.pod has been renamed to k8s.podOptions" + k8s.put('podOptions', k8s.pod) + k8s.remove('pod') } - else if( !(k8s.pod instanceof List) ) - throw new IllegalArgumentException("Illegal k8s.pod configuratun value: ${k8s.pod}") + + // -- normalize pod options + if( !k8s.podOptions ) + k8s.podOptions = [] + else if( k8s.podOptions instanceof Map ) + k8s.podOptions = [ k8s.podOptions ] + else if( !(k8s.podOptions instanceof List) ) + throw new IllegalArgumentException("Illegal k8s.podOptions configuratun value: ${k8s.podOptions}") // -- use the volume claims specified in the command line // to populate the pod config @@ -303,7 +308,7 @@ class K8sDriverLauncher { k8s.storageMountPath = path } else { - k8s.pod.add( [volumeClaim: name, mountPath: path] ) + k8s.podOptions.add( [volumeClaim: name, mountPath: path] ) } } @@ -318,7 +323,7 @@ class K8sDriverLauncher { k8s.storageMountPath = path } else if( !cmd.volMounts ) { - k8s.pod.add( [volumeClaim: name, mountPath: path] ) + k8s.podOptions.add( [volumeClaim: name, mountPath: path] ) } } // remove it @@ -339,8 +344,8 @@ class K8sDriverLauncher { k8s.workDir = config.workDir // -- some cleanup - if( !k8s.pod ) - k8s.remove('pod') + if( !k8s.podOptions ) + k8s.remove('podOptions') if( !k8s.storageClaimName ) k8s.remove('storageClaimName') diff --git a/modules/nextflow/src/main/groovy/nextflow/processor/TaskConfig.groovy b/modules/nextflow/src/main/groovy/nextflow/processor/TaskConfig.groovy index c7006f776f..c0f953cdff 100644 --- a/modules/nextflow/src/main/groovy/nextflow/processor/TaskConfig.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/processor/TaskConfig.groovy @@ -380,7 +380,7 @@ class TaskConfig extends LazyMap implements Cloneable { } PodOptions getPodOptions() { - new PodOptions((List)get('pod')) + new PodOptions((List)get('podOptions')) } AcceleratorResource getAccelerator() { diff --git a/modules/nextflow/src/main/groovy/nextflow/script/ProcessConfig.groovy b/modules/nextflow/src/main/groovy/nextflow/script/ProcessConfig.groovy index f7e4081cd1..4d0bf9c54d 100644 --- a/modules/nextflow/src/main/groovy/nextflow/script/ProcessConfig.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/script/ProcessConfig.groovy @@ -60,9 +60,8 @@ class ProcessConfig implements Map, Cloneable { 'errorStrategy', 'executor', 'ext', - 'machineType', - 'queue', 'label', + 'machineType', 'maxErrors', 'maxForks', 'maxRetries', @@ -70,7 +69,9 @@ class ProcessConfig implements Map, Cloneable { 'module', 'penv', 'pod', + 'podOptions', 'publishDir', + 'queue', 'scratch', 'shell', 'storeDir', @@ -93,7 +94,13 @@ class ProcessConfig implements Map, Cloneable { * Names of directives that can be used more than once in the process definition */ @PackageScope - static final List repeatableDirectives = ['label','module','pod','publishDir'] + static final List repeatableDirectives = [ + 'label', + 'module', + 'pod', + 'podOptions', + 'publishDir' + ] /** * Default directives values @@ -825,22 +832,22 @@ class ProcessConfig implements Map, Cloneable { } /** - * Allow use to specify K8s `pod` options + * Allow the user to specify K8s pod options * * @param entry * A map object representing pod config options * @return * The {@link ProcessConfig} instance itself */ - ProcessConfig pod( Map entry ) { + ProcessConfig podOptions( Map entry ) { if( !entry ) return this - def allOptions = (List)configProperties.get('pod') + def allOptions = (List)configProperties.get('podOptions') if( !allOptions ) { allOptions = new ConfigList() - configProperties.put('pod', allOptions) + configProperties.put('podOptions', allOptions) } allOptions.add(entry) diff --git a/modules/nextflow/src/test/groovy/nextflow/k8s/K8sConfigTest.groovy b/modules/nextflow/src/test/groovy/nextflow/k8s/K8sConfigTest.groovy index c49b593b5a..75cfc131e6 100644 --- a/modules/nextflow/src/test/groovy/nextflow/k8s/K8sConfigTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/k8s/K8sConfigTest.groovy @@ -89,17 +89,20 @@ class K8sConfigTest extends Specification { when: cfg = new K8sConfig([ - storageClaimName: 'pvc-2', - storageMountPath: '/data', - pod: [ [volumeClaim:'foo', mountPath: '/here'], - [volumeClaim: 'bar', mountPath: '/there']] ]) + storageClaimName: 'pvc-2', + storageMountPath: '/data', + podOptions: [ + [volumeClaim:'foo', mountPath: '/here'], + [volumeClaim: 'bar', mountPath: '/there'] + ] + ]) then: cfg.getStorageClaimName() == 'pvc-2' cfg.getStorageMountPath() == '/data' cfg.getPodOptions().getVolumeClaims() == [ - new PodVolumeClaim('pvc-2', '/data'), - new PodVolumeClaim('foo', '/here'), - new PodVolumeClaim('bar', '/there') + new PodVolumeClaim('pvc-2', '/data'), + new PodVolumeClaim('foo', '/here'), + new PodVolumeClaim('bar', '/there') ] as Set @@ -185,7 +188,7 @@ class K8sConfigTest extends Specification { when: - opts = new K8sConfig(pod: [ [pullPolicy: 'Always'], [env: 'HELLO', value: 'WORLD'] ]).getPodOptions() + opts = new K8sConfig(podOptions: [ [pullPolicy: 'Always'], [env: 'HELLO', value: 'WORLD'] ]).getPodOptions() then: opts.getImagePullPolicy() == 'Always' opts.getEnvVars() == [ PodEnv.value('HELLO','WORLD') ] as Set @@ -285,20 +288,20 @@ class K8sConfigTest extends Specification { def 'should create k8s config with one volume claim' () { when: - def cfg = new K8sConfig( pod: [runAsUser: 1000] ) + def cfg = new K8sConfig( podOptions: [runAsUser: 1000] ) then: cfg.getPodOptions().getSecurityContext() == new PodSecurityContext(1000) cfg.getPodOptions().getVolumeClaims().size() == 0 when: - cfg = new K8sConfig( pod: [volumeClaim: 'nf-0001', mountPath: '/workspace'] ) + cfg = new K8sConfig( podOptions: [volumeClaim: 'nf-0001', mountPath: '/workspace'] ) then: cfg.getPodOptions().getSecurityContext() == null cfg.getPodOptions().getVolumeClaims() == [new PodVolumeClaim('nf-0001', '/workspace')] as Set when: - cfg = new K8sConfig( pod: [ + cfg = new K8sConfig( podOptions: [ [runAsUser: 1000], [volumeClaim: 'nf-0001', mountPath: '/workspace'], [volumeClaim: 'nf-0002', mountPath: '/data', subPath: '/home'] diff --git a/modules/nextflow/src/test/groovy/nextflow/k8s/K8sDriverLauncherTest.groovy b/modules/nextflow/src/test/groovy/nextflow/k8s/K8sDriverLauncherTest.groovy index 2bd06f48cf..42ac5c7de8 100644 --- a/modules/nextflow/src/test/groovy/nextflow/k8s/K8sDriverLauncherTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/k8s/K8sDriverLauncherTest.groovy @@ -136,17 +136,17 @@ class K8sDriverLauncherTest extends Specification { def 'should create launcher spec' () { given: - def pod = Mock(PodOptions) - pod.getVolumeClaims() >> [ new PodVolumeClaim('pvc-1', '/mnt/path/data') ] - pod.getMountConfigMaps() >> [ new PodMountConfig('cfg-2', '/mnt/path/cfg') ] - pod.getAutomountServiceAccountToken() >> true + def podOptions = Mock(PodOptions) + podOptions.getVolumeClaims() >> [ new PodVolumeClaim('pvc-1', '/mnt/path/data') ] + podOptions.getMountConfigMaps() >> [ new PodMountConfig('cfg-2', '/mnt/path/cfg') ] + podOptions.getAutomountServiceAccountToken() >> true def k8s = Mock(K8sConfig) k8s.getNextflowImageName() >> 'the-image' k8s.getLaunchDir() >> '/the/user/dir' k8s.getWorkDir() >> '/the/work/dir' k8s.getProjectDir() >> '/the/project/dir' - k8s.getPodOptions() >> pod + k8s.getPodOptions() >> podOptions def driver = Spy(K8sDriverLauncher) driver.runName = 'foo-boo' @@ -161,7 +161,11 @@ class K8sDriverLauncherTest extends Specification { spec == [ apiVersion: 'v1', kind: 'Pod', - metadata: [name:'foo-boo', namespace:'foo', labels:[app:'nextflow', runName:'foo-boo']], + metadata: [ + name:'foo-boo', + namespace:'foo', + labels: [app:'nextflow', runName:'foo-boo'] + ], spec: [ restartPolicy: 'Never', containers: [ @@ -194,16 +198,16 @@ class K8sDriverLauncherTest extends Specification { def 'should use user provided pod image' () { given: - def pod = Mock(PodOptions) - pod.getVolumeClaims() >> [ new PodVolumeClaim('pvc-1', '/mnt/path/data') ] - pod.getMountConfigMaps() >> [ new PodMountConfig('cfg-2', '/mnt/path/cfg') ] - pod.getAutomountServiceAccountToken() >> true + def podOptions = Mock(PodOptions) + podOptions.getVolumeClaims() >> [ new PodVolumeClaim('pvc-1', '/mnt/path/data') ] + podOptions.getMountConfigMaps() >> [ new PodMountConfig('cfg-2', '/mnt/path/cfg') ] + podOptions.getAutomountServiceAccountToken() >> true def k8s = Mock(K8sConfig) k8s.getLaunchDir() >> '/the/user/dir' k8s.getWorkDir() >> '/the/work/dir' k8s.getProjectDir() >> '/the/project/dir' - k8s.getPodOptions() >> pod + k8s.getPodOptions() >> podOptions def driver = Spy(K8sDriverLauncher) driver.runName = 'foo-boo' @@ -219,7 +223,11 @@ class K8sDriverLauncherTest extends Specification { spec == [ apiVersion: 'v1', kind: 'Pod', - metadata: [name:'foo-boo', namespace:'foo', labels:[app:'nextflow', runName:'foo-boo']], + metadata: [ + name:'foo-boo', + namespace:'foo', + labels: [app:'nextflow', runName:'foo-boo'] + ], spec: [ restartPolicy: 'Never', containers: [ @@ -311,7 +319,7 @@ class K8sDriverLauncherTest extends Specification { then: 1 * driver.loadConfig(NAME) >> CFG_EMPTY config.process.executor == 'k8s' - config.k8s.pod == null + config.k8s.podOptions == null config.k8s.storageMountPath == null config.k8s.storageClaimName == null @@ -336,16 +344,15 @@ class K8sDriverLauncherTest extends Specification { config.process.executor == 'k8s' config.k8s.storageClaimName == 'pvc-1' config.k8s.storageMountPath == '/this' - config.k8s.pod == [ [volumeClaim: 'pvc-2', mountPath: '/that'] ] + config.k8s.podOptions == [ [volumeClaim: 'pvc-2', mountPath: '/that'] ] and: new K8sConfig(config.k8s).getStorageClaimName() == 'pvc-1' new K8sConfig(config.k8s).getStorageMountPath() == '/this' new K8sConfig(config.k8s).getPodOptions() == new PodOptions([ - [volumeClaim:'pvc-1', mountPath: '/this'], - [volumeClaim:'pvc-2', mountPath: '/that'] + [volumeClaim:'pvc-1', mountPath: '/this'], + [volumeClaim:'pvc-2', mountPath: '/that'] ]) - when: driver.cmd = new CmdKubeRun(volMounts: ['xyz:/this'] ) config = driver.makeConfig(NAME).toMap() @@ -354,16 +361,15 @@ class K8sDriverLauncherTest extends Specification { config.process.executor == 'k8s' config.k8s.storageClaimName == 'xyz' config.k8s.storageMountPath == '/this' - config.k8s.pod == null + config.k8s.podOptions == null and: and: new K8sConfig(config.k8s).getStorageClaimName() == 'xyz' new K8sConfig(config.k8s).getStorageMountPath() == '/this' new K8sConfig(config.k8s).getPodOptions() == new PodOptions([ - [volumeClaim:'xyz', mountPath: '/this'] + [volumeClaim:'xyz', mountPath: '/this'] ]) - when: driver.cmd = new CmdKubeRun(volMounts: ['xyz', 'bar:/mnt/bar'] ) config = driver.makeConfig(NAME).toMap() @@ -372,13 +378,13 @@ class K8sDriverLauncherTest extends Specification { config.process.executor == 'k8s' config.k8s.storageClaimName == 'xyz' config.k8s.storageMountPath == null - config.k8s.pod == [ [volumeClaim: 'bar', mountPath: '/mnt/bar'] ] + config.k8s.podOptions == [ [volumeClaim: 'bar', mountPath: '/mnt/bar'] ] and: new K8sConfig(config.k8s).getStorageClaimName() == 'xyz' new K8sConfig(config.k8s).getStorageMountPath() == '/workspace' new K8sConfig(config.k8s).getPodOptions() == new PodOptions([ - [volumeClaim:'xyz', mountPath: '/workspace'], - [volumeClaim:'bar', mountPath: '/mnt/bar'] + [volumeClaim:'xyz', mountPath: '/workspace'], + [volumeClaim:'bar', mountPath: '/mnt/bar'] ]) } @@ -417,8 +423,7 @@ class K8sDriverLauncherTest extends Specification { config.process.executor == 'k8s' config.k8s.storageClaimName == 'pvc-1' config.k8s.storageMountPath == '/this' - config.k8s.pod == [ [volumeClaim: 'pvc-2', mountPath: '/that'] ] - + config.k8s.podOptions == [ [volumeClaim: 'pvc-2', mountPath: '/that'] ] when: driver.cmd = new CmdKubeRun(volMounts: ['xyz:/this'] ) @@ -428,12 +433,12 @@ class K8sDriverLauncherTest extends Specification { config.process.executor == 'k8s' config.k8s.storageClaimName == 'xyz' config.k8s.storageMountPath == '/this' - config.k8s.pod == null + config.k8s.podOptions == null and: new K8sConfig(config.k8s).getStorageClaimName() == 'xyz' new K8sConfig(config.k8s).getStorageMountPath() == '/this' new K8sConfig(config.k8s).getPodOptions() == new PodOptions([ - [volumeClaim:'xyz', mountPath: '/this'] + [volumeClaim:'xyz', mountPath: '/this'] ]) } diff --git a/modules/nextflow/src/test/groovy/nextflow/k8s/K8sTaskHandlerTest.groovy b/modules/nextflow/src/test/groovy/nextflow/k8s/K8sTaskHandlerTest.groovy index b8dcb94050..3b22ffbe99 100644 --- a/modules/nextflow/src/test/groovy/nextflow/k8s/K8sTaskHandlerTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/k8s/K8sTaskHandlerTest.groovy @@ -620,11 +620,13 @@ class K8sTaskHandlerTest extends Specification { proc.getName() >> 'hello-proc' exec.getSession() >> sess sess.getUniqueId() >> uuid - exec.getK8sConfig() >> [pod: [ + exec.getK8sConfig() >> [ + podOptions: [ [label: 'foo', value: 'bar'], [label: 'app', value: 'nextflow'], [label: 'x', value: 'hello_world'] - ]] + ] + ] labels.app == 'nextflow' labels.foo == 'bar' diff --git a/modules/nextflow/src/test/groovy/nextflow/processor/TaskConfigTest.groovy b/modules/nextflow/src/test/groovy/nextflow/processor/TaskConfigTest.groovy index af9d41876a..5d2adf7ed6 100644 --- a/modules/nextflow/src/test/groovy/nextflow/processor/TaskConfigTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/processor/TaskConfigTest.groovy @@ -536,17 +536,19 @@ class TaskConfigTest extends Specification { when: def process = new ProcessConfig(script) - process.pod secret: 'foo', mountPath: '/this' - process.pod secret: 'bar', env: 'BAR_XXX' + process.podOptions secret: 'foo', mountPath: '/this' + process.podOptions secret: 'bar', env: 'BAR_XXX' then: - process.get('pod') == [ - [secret: 'foo', mountPath: '/this'], - [secret: 'bar', env: 'BAR_XXX'] ] + process.get('podOptions') == [ + [secret: 'foo', mountPath: '/this'], + [secret: 'bar', env: 'BAR_XXX'] + ] process.createTaskConfig().getPodOptions() == new PodOptions([ - [secret: 'foo', mountPath: '/this'], - [secret: 'bar', env: 'BAR_XXX'] ]) + [secret: 'foo', mountPath: '/this'], + [secret: 'bar', env: 'BAR_XXX'] + ]) } diff --git a/modules/nextflow/src/test/groovy/nextflow/script/ProcessConfigTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/ProcessConfigTest.groovy index e10639173e..de14416aa7 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/ProcessConfigTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/ProcessConfigTest.groovy @@ -544,27 +544,27 @@ class ProcessConfigTest extends Specification { } - def 'should apply pod configs' () { + def 'should apply pod options' () { when: def process = new ProcessConfig([:]) - process.applyConfigDefaults( pod: [secret: 'foo', mountPath: '/there'] ) + process.applyConfigDefaults( podOptions: [secret: 'foo', mountPath: '/there'] ) then: - process.pod == [ - [secret: 'foo', mountPath: '/there'] + process.podOptions == [ + [secret: 'foo', mountPath: '/there'] ] when: process = new ProcessConfig([:]) - process.applyConfigDefaults( pod: [ - [secret: 'foo', mountPath: '/here'], - [secret: 'bar', mountPath: '/there'] + process.applyConfigDefaults( podOptions: [ + [secret: 'foo', mountPath: '/here'], + [secret: 'bar', mountPath: '/there'] ] ) then: - process.pod == [ - [secret: 'foo', mountPath: '/here'], - [secret: 'bar', mountPath: '/there'] + process.podOptions == [ + [secret: 'foo', mountPath: '/here'], + [secret: 'bar', mountPath: '/there'] ] }