Skip to content

Commit

Permalink
Add support for Apptainer container engine (#3345)
Browse files Browse the repository at this point in the history
Signed-off-by: tbugfinder <tbugfinder@online.ms>
Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
  • Loading branch information
pditommaso committed Nov 3, 2022
1 parent c2a2899 commit 29f9897
Show file tree
Hide file tree
Showing 13 changed files with 539 additions and 34 deletions.
1 change: 1 addition & 0 deletions modules/nextflow/src/main/groovy/nextflow/Session.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,7 @@ class Session implements ISession {
getContainerConfig0('shifter', engines)
getContainerConfig0('udocker', engines)
getContainerConfig0('singularity', engines)
getContainerConfig0('apptainer', engines)
getContainerConfig0('charliecloud', engines)

def enabled = engines.findAll { it.enabled?.toString() == 'true' }
Expand Down
3 changes: 3 additions & 0 deletions modules/nextflow/src/main/groovy/nextflow/cli/CmdRun.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ class CmdRun extends CmdBase implements HubOptions {
@Parameter(names = '-with-singularity', description = 'Enable process execution in a Singularity container')
def withSingularity

@Parameter(names = '-with-apptainer', description = 'Enable process execution in a Apptainer container')
def withApptainer

@Parameter(names = '-with-podman', description = 'Enable process execution in a Podman container')
def withPodman

Expand Down
4 changes: 4 additions & 0 deletions modules/nextflow/src/main/groovy/nextflow/cli/Launcher.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ class Launcher {
normalized << '-'
}

else if( current == '-with-apptainer' && (i==args.size() || args[i].startsWith('-'))) {
normalized << '-'
}

else if( current == '-with-charliecloud' && (i==args.size() || args[i].startsWith('-'))) {
normalized << '-'
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2020-2022, Seqera Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package nextflow.container

import groovy.transform.CompileStatic

/**
* Implements a builder for Apptainer containerisation
*
* see https://apptainer.org
*
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
*/
@CompileStatic
class ApptainerBuilder extends SingularityBuilder {

ApptainerBuilder(String name) {
super(name)
}

@Override
protected String getBinaryName() { 'apptainer' }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2020-2022, Seqera Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package nextflow.container

import groovy.transform.CompileStatic

/**
* Handle caching of remote Apptainer images
*
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
*/
@CompileStatic
class ApptainerCache extends SingularityCache {

/** only for testing */
protected ApptainerCache() {}

ApptainerCache(ContainerConfig config, Map<String,String> env=null) {
super(config, env)
}

@Override
protected String getBinaryName() { 'apptainer' }
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ abstract class ContainerBuilder<V extends ContainerBuilder> {
return new PodmanBuilder(containerImage)
if( engine == 'singularity' )
return new SingularityBuilder(containerImage)
if( engine == 'apptainer' )
return new ApptainerBuilder(containerImage)
if( engine == 'udocker' )
return new UdockerBuilder(containerImage)
if( engine == 'shifter' )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,19 @@ class ContainerHandler {
if( !config.isEnabled() || !normalizedImageName )
return normalizedImageName
final requiresCaching = normalizedImageName =~ IMAGE_URL_PREFIX

final result = requiresCaching ? createSingularityCache(this.config, normalizedImageName) : normalizedImageName
return Escape.path(result)
}
if( engine == 'apptainer' ) {
final normalizedImageName = normalizeApptainerImageName(imageName)
if( !config.isEnabled() || !normalizedImageName )
return normalizedImageName
final requiresCaching = normalizedImageName =~ IMAGE_URL_PREFIX

final result = requiresCaching ? createApptainerCache(this.config, normalizedImageName) : normalizedImageName
return Escape.path(result)
}
if( engine == 'charliecloud' ) {
// if the imagename starts with '/' it's an absolute path
// otherwise we assume it's in a remote registry and pull it from there
Expand All @@ -97,6 +106,11 @@ class ContainerHandler {
new SingularityCache(new ContainerConfig(config)) .getCachePathFor(imageName) .toString()
}

@PackageScope
String createApptainerCache(Map config, String imageName) {
new ApptainerCache(new ContainerConfig(config)) .getCachePathFor(imageName) .toString()
}

@PackageScope
String createCharliecloudCache(Map config, String imageName) {
new CharliecloudCache(new ContainerConfig(config)) .getCachePathFor(imageName) .toString()
Expand Down Expand Up @@ -222,4 +236,40 @@ class ContainerHandler {
return "docker://${img}"
}

/**
* Normalize Apptainer image name resolving the absolute path or
* adding `docker://` prefix when required
*
* @param imageName The container image name
* @return Image name in Apptainer canonical format
*/
@PackageScope
String normalizeApptainerImageName(String img) {
if( !img )
return null

// when starts with `/` it's an absolute image file path, just return it
if( img.startsWith("/") )
return img

// when starts with `file://` it's an image file path, resolve it against the current path
if (img.startsWith("file://")) {
return baseDir.resolve(img.substring(7)).toString()
}

// check if matches a protocol scheme such as `docker://xxx`
if( img =~ IMAGE_URL_PREFIX ) {
return img
}

// if it's the path of an existing image file return it
def imagePath = baseDir.resolve(img)
if( imagePath.exists() ) {
return imagePath.toString()
}

// in all other case it's supposed to be the name of an image in the docker hub
// prefix it with the `docker://` pseudo protocol used by apptainer to download it
return "docker://${img}"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
*/

package nextflow.container

import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j

/**
* Implements a builder for Singularity containerisation
*
Expand All @@ -35,6 +37,8 @@ class SingularityBuilder extends ContainerBuilder<SingularityBuilder> {
this.image = name
}

protected String getBinaryName() { 'singularity' }

@Override
SingularityBuilder params(Map params) {

Expand All @@ -59,6 +63,7 @@ class SingularityBuilder extends ContainerBuilder<SingularityBuilder> {
return this
}

@Override
SingularityBuilder addRunOptions(String str) {
runOptions.add(str)
return this
Expand All @@ -71,7 +76,7 @@ class SingularityBuilder extends ContainerBuilder<SingularityBuilder> {

appendEnv(result)

result << 'singularity '
result << getBinaryName() << ' '

if( engineOptions )
result << engineOptions.join(' ') << ' '
Expand Down Expand Up @@ -107,11 +112,12 @@ class SingularityBuilder extends ContainerBuilder<SingularityBuilder> {
}

protected String prefixEnv(String key) {
if( key.startsWith('SINGULARITY_') )
final PREFIX = getBinaryName().toUpperCase()
if( key.startsWith(PREFIX+'_') )
return key
if( key.startsWith('SINGULARITYENV_') )
if( key.startsWith(PREFIX+'ENV_') )
return key
return "SINGULARITYENV_$key"
return PREFIX+'ENV_'+key
}

@Override
Expand Down

0 comments on commit 29f9897

Please sign in to comment.