Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add/process.dockerfile #3916

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -2369,6 +2369,26 @@ This directive is only used by certain executors. Refer to the {ref}`executor-pa

See also: [cpus](#cpus), [memory](#memory), [queue](#queue) and [Dynamic computing resources](#dynamic-computing-resources).

(process-waveDockerfile)=

### waveDockerfile

The `waveDockerfile` directive allows you to execute the process script in a [Docker](http://docker.io) container, through the specification of the file path for the corresponding Dockerfile recipe.

This directive can only be used in conjunction with the {ref}`wave-page` service, which requires a Docker daemon to be running in machine where the pipeline is executed.

For example:

```groovy
process runThisInWaveContainer {
waveDockerfile 'Dockerfile'

"""
<your holy Dockerfile recipe here>
"""
}
```

### Dynamic directives

A directive can be assigned *dynamically*, during the process execution, so that its actual value can be evaluated based on the process inputs.
Expand Down
6 changes: 4 additions & 2 deletions docs/wave.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,15 @@ tower {

Wave can build and provision container images on-demand for your Nextflow pipelines.

To enable this feature, add the Dockerfile of the container to be built in the {ref}`module directory <dsl2-module-directory>` where the pipeline process is defined. When Wave is enabled, it automatically uses the Dockerfile to build the required container, upload to the registry, and it uses the container to carry out the tasks defined in the module.
To enable this feature, add the Dockerfile of the container to be built in the {ref}`module directory <dsl2-module-directory>` where the pipeline process is defined. In alternative, if you want to specify a custom file path for your Dockerfile, you can do so by means of the process {ref}`process-waveDockefile` directive.

When Wave is enabled, it automatically uses the provided Dockerfile to build the required container, upload to the registry, and it uses the container to carry out the tasks defined in the module.

:::{tip}
Make sure the process does not declare a `container` directive, otherwise it will take precedence over the Dockerfile definition.
:::

If a process uses a `container` directive and you still want to build the container using the Dockerfile provided in the module directory, add the following setting to the pipeline config file:
If a process uses a `container` directive and you still want to build the container using the provided Dockerfile, add the following setting to the pipeline config file:

```groovy
wave.strategy = ['dockerfile','container']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class TaskBean implements Serializable, Cloneable {

Path spackEnv

Path dockerfile

List<String> moduleNames

Path workDir
Expand Down Expand Up @@ -126,6 +128,7 @@ class TaskBean implements Serializable, Cloneable {

this.condaEnv = task.getCondaEnv()
this.spackEnv = task.getSpackEnv()
this.dockerfile = task.getDockerfile()
this.moduleNames = task.config.getModule()
this.shell = task.config.getShell() ?: BashWrapperBuilder.BASH
this.script = task.getScript()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,14 @@ class TaskConfig extends LazyMap implements Cloneable {
return get('container')
}

Path getDockerfile() {
path = get('waveDockerfile')
if( !path )
return null

return (path as Path)
}

Architecture getArchitecture() {
final value = get('arch')
if( value instanceof CharSequence )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,10 @@ class TaskRun implements Cloneable {
cache.getCachePathFor(config.spack as String, arch)
}

Path getDockerfile() {
return config.getDockerfile()
}

protected ContainerInfo containerInfo() {
cache0.computeIfAbsent('containerInfo', (it)-> containerInfo0())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class ProcessConfig implements Map<String,Object>, Cloneable {
'storeDir',
'tag',
'time',
'waveDockerfile',
// input-output qualifiers
'file',
'set',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,9 +342,10 @@ class WaveClient {
attrs.container = containerImage
attrs.conda = task.config.conda as String
attrs.spack = task.config.spack as String
if( bundle!=null && bundle.dockerfile ) {
if( task.config.getDockerfile() != null )
attrs.dockerfile = task.config.getDockerfile().text
else if( bundle!=null && bundle.dockerfile )
attrs.dockerfile = bundle.dockerfile.text
}

// validate request attributes
if( config().strategy() )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,26 @@ class WaveClientTest extends Specification {
!req.containerConfig.layers
}

def 'should create request object with dockerfile in process specific location' () {
given:
def folder = Files.createTempDirectory('test')
and:
def DOCKERFILE = folder.resolve('Dockerfile'); DOCKERFILE.text = 'from foo:latest'
and:
def session = Mock(Session) { getConfig() >> [:]}
def wave = new WaveClient(session)

when:
def req = wave.makeRequest(new WaveAssets(null, null, null, null, DOCKERFILE.text, null, null))
then:
!req.containerImage
new String(req.containerFile.decodeBase64()) == DOCKERFILE.text
!req.containerConfig.layers

cleanup:
folder?.deleteDir()
}

def 'should create request object with build and cache repos' () {
given:
def session = Mock(Session) { getConfig() >> [wave:[build:[repository:'some/repo',cacheRepository:'some/cache']]]}
Expand Down Expand Up @@ -775,6 +795,35 @@ CMD [ "/bin/bash" ]
folder?.deleteDir()
}

def 'should create asset with dockerfile in process specific location' () {
given:
def folder = Files.createTempDirectory('test')
def dockerfile = folder.resolve('Dockerfile'); dockerfile.text = 'from foo:latest'
and:
def session = Mock(Session) { getConfig() >> [:]}
def task = Mock(TaskRun) {getConfig() >> [waveDockerfile:dockerfile.toString()] }
and:
def client = new WaveClient(session)

when:
def assets = client.resolveAssets(task, null)
then:
assets.dockerFileContent == '''\
from foo:latest'''.stripIndent()
and:
assets.dockerFileContent == dockerfile.text
and:
!assets.moduleResources
!assets.containerImage
!assets.containerConfig
!assets.spackFile
!assets.condaFile
!assets.projectResources

cleanup:
folder?.deleteDir()
}

def 'should create asset with conda recipe' () {
given:
def session = Mock(Session) { getConfig() >> [:]}
Expand Down