Skip to content

Commit 5ec768e

Browse files
authored
Merge pull request #3975 from boegel/apptainer
extend (experimental) support for generating container images with Apptainer
2 parents 2c9dc75 + f7654c4 commit 5ec768e

5 files changed

Lines changed: 212 additions & 2 deletions

File tree

.github/workflows/container_tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# documentation: https://help.github.com/en/articles/workflow-syntax-for-github-actions
2-
name: Tests for container support
2+
name: Tests for Singularity container support
33
on: [push, pull_request]
44

55
permissions:
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# documentation: https://help.github.com/en/articles/workflow-syntax-for-github-actions
2+
name: Tests for Apptainer container support
3+
on: [push, pull_request]
4+
5+
permissions:
6+
contents: read # to fetch code (actions/checkout)
7+
8+
concurrency:
9+
group: ${{format('{0}:{1}:{2}', github.repository, github.ref, github.workflow)}}
10+
cancel-in-progress: true
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-22.04
15+
strategy:
16+
matrix:
17+
python: [2.7, 3.7]
18+
apptainer: [1.0.0, 1.1.7]
19+
fail-fast: false
20+
steps:
21+
- uses: actions/checkout@v2
22+
23+
- name: set up Python
24+
uses: actions/setup-python@v2
25+
with:
26+
python-version: ${{matrix.python}}
27+
architecture: x64
28+
29+
- name: install OS & Python packages
30+
run: |
31+
# for building CentOS 7 container images
32+
sudo apt-get install rpm
33+
sudo apt-get install dnf
34+
# for modules tool
35+
sudo apt-get install lua5.2 liblua5.2-dev lua-filesystem lua-posix tcl tcl-dev
36+
# fix for lua-posix packaging issue, see https://bugs.launchpad.net/ubuntu/+source/lua-posix/+bug/1752082
37+
# needed for Ubuntu 18.04, but not for Ubuntu 20.04, so skipping symlinking if posix.so already exists
38+
if [ ! -e /usr/lib/x86_64-linux-gnu/lua/5.2/posix.so ] ; then
39+
sudo ln -s /usr/lib/x86_64-linux-gnu/lua/5.2/posix_c.so /usr/lib/x86_64-linux-gnu/lua/5.2/posix.so
40+
fi
41+
42+
- name: install Lmod
43+
run: |
44+
# avoid downloading modules tool sources into easybuild-framework dir
45+
cd $HOME
46+
export INSTALL_DEP=$GITHUB_WORKSPACE/easybuild/scripts/install_eb_dep.sh
47+
# install Lmod
48+
source $INSTALL_DEP Lmod-8.4.27 $HOME
49+
# changes in environment are not passed to other steps, so need to create files...
50+
echo $MOD_INIT > mod_init
51+
echo $PATH > path
52+
if [ ! -z $MODULESHOME ]; then echo $MODULESHOME > moduleshome; fi
53+
54+
- name: install Apptainer
55+
run: |
56+
curl -OL https://github.com/apptainer/apptainer/releases/download/v${{matrix.apptainer}}/apptainer_${{matrix.apptainer}}_amd64.deb
57+
sudo apt install ./apptainer*.deb
58+
# Apptainer provides both apptainer and singularity commands
59+
apptainer --version
60+
singularity --version
61+
62+
- name: install sources
63+
run: |
64+
# install from source distribution tarball, to test release as published on PyPI
65+
python setup.py sdist
66+
ls dist
67+
export PREFIX=/tmp/$USER/$GITHUB_SHA
68+
pip install --prefix $PREFIX dist/easybuild-framework*tar.gz
69+
pip install --prefix $PREFIX https://github.com/easybuilders/easybuild-easyblocks/archive/develop.tar.gz
70+
71+
- name: run test
72+
run: |
73+
# run tests *outside* of checked out easybuild-framework directory,
74+
# to ensure we're testing installed version (see previous step)
75+
cd $HOME
76+
# initialize environment for modules tool
77+
if [ -f $HOME/moduleshome ]; then export MODULESHOME=$(cat $HOME/moduleshome); fi
78+
source $(cat $HOME/mod_init); type module
79+
# make sure 'eb' is available via $PATH, and that $PYTHONPATH is set (some tests expect that);
80+
# also pick up changes to $PATH set by sourcing $MOD_INIT
81+
export PREFIX=/tmp/$USER/$GITHUB_SHA
82+
export PATH=$PREFIX/bin:$(cat $HOME/path)
83+
export PYTHONPATH=$PREFIX/lib/python${{matrix.python}}/site-packages:$PYTHONPATH
84+
eb --version
85+
# create $HOME/.rpmmacros, see also https://github.com/apptainer/singularity/issues/241
86+
echo '%_var /var' > $HOME/.rpmmacros
87+
echo '%_dbpath %{_var}/lib/rpm' >> $HOME/.rpmmacros
88+
# build CentOS 7 container image for bzip2 1.0.8 using EasyBuild;
89+
# see https://docs.easybuild.io/en/latest/Containers.html
90+
curl -OL https://raw.githubusercontent.com/easybuilders/easybuild-easyconfigs/develop/easybuild/easyconfigs/b/bzip2/bzip2-1.0.8.eb
91+
export EASYBUILD_CONTAINERPATH=$PWD
92+
export EASYBUILD_CONTAINER_CONFIG='bootstrap=docker,from=ghcr.io/easybuilders/centos-7.9-python3-amd64'
93+
export EASYBUILD_CONTAINER_TYPE='apptainer'
94+
eb bzip2-1.0.8.eb --containerize --experimental --container-build-image
95+
apptainer exec bzip2-1.0.8.sif command -v bzip2 | grep '/app/software/bzip2/1.0.8/bin/bzip2' || (echo "Path to bzip2 '$which_bzip2' is not correct" && exit 1)
96+
apptainer exec bzip2-1.0.8.sif bzip2 --help

easybuild/tools/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,10 @@
8484
CONT_IMAGE_FORMAT_SQUASHFS,
8585
]
8686

87+
CONT_TYPE_APPTAINER = 'apptainer'
8788
CONT_TYPE_DOCKER = 'docker'
8889
CONT_TYPE_SINGULARITY = 'singularity'
89-
CONT_TYPES = [CONT_TYPE_DOCKER, CONT_TYPE_SINGULARITY]
90+
CONT_TYPES = [CONT_TYPE_APPTAINER, CONT_TYPE_DOCKER, CONT_TYPE_SINGULARITY]
9091
DEFAULT_CONT_TYPE = CONT_TYPE_SINGULARITY
9192

9293
DEFAULT_BRANCH = 'develop'
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Copyright 2022-2023 Ghent University
2+
#
3+
# This file is part of EasyBuild,
4+
# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
5+
# with support of Ghent University (http://ugent.be/hpc),
6+
# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
7+
# Flemish Research Foundation (FWO) (http://www.fwo.be/en)
8+
# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
9+
#
10+
# https://github.com/easybuilders/easybuild
11+
#
12+
# EasyBuild is free software: you can redistribute it and/or modify
13+
# it under the terms of the GNU General Public License as published by
14+
# the Free Software Foundation v2.
15+
#
16+
# EasyBuild is distributed in the hope that it will be useful,
17+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
# GNU General Public License for more details.
20+
#
21+
# You should have received a copy of the GNU General Public License
22+
# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>.
23+
#
24+
"""
25+
Support for generating Apptainer container recipes and creating container images
26+
27+
:author: Kenneth Hoste (HPC-UGent)
28+
"""
29+
import os
30+
import re
31+
32+
from easybuild.tools.build_log import EasyBuildError, print_msg
33+
from easybuild.tools.containers.singularity import SingularityContainer
34+
from easybuild.tools.config import CONT_IMAGE_FORMAT_EXT3, CONT_IMAGE_FORMAT_SANDBOX
35+
from easybuild.tools.config import CONT_IMAGE_FORMAT_SIF, CONT_IMAGE_FORMAT_SQUASHFS
36+
from easybuild.tools.config import build_option, container_path
37+
from easybuild.tools.filetools import remove_file, which
38+
from easybuild.tools.run import run_cmd
39+
40+
41+
class ApptainerContainer(SingularityContainer):
42+
43+
TOOLS = {'apptainer': '1.0', 'sudo': None}
44+
45+
RECIPE_FILE_NAME = 'Apptainer'
46+
47+
@staticmethod
48+
def apptainer_version():
49+
"""Get Apptainer version."""
50+
version_cmd = "apptainer --version"
51+
out, ec = run_cmd(version_cmd, simple=False, trace=False, force_in_dry_run=True)
52+
if ec:
53+
raise EasyBuildError("Error running '%s': %s for tool {1} with output: {2}" % (version_cmd, out))
54+
55+
res = re.search(r"\d+\.\d+(\.\d+)?", out.strip())
56+
if not res:
57+
raise EasyBuildError("Error parsing Apptainer version: %s" % out)
58+
59+
return res.group(0)
60+
61+
def build_image(self, recipe_path):
62+
"""Build container image by calling out to 'sudo apptainer build'."""
63+
64+
cont_path = container_path()
65+
def_file = os.path.basename(recipe_path)
66+
67+
# use --imagename if specified, otherwise derive based on filename of recipe
68+
img_name = self.img_name
69+
if img_name is None:
70+
# definition file Apptainer.<app>-<version, container name <app>-<version>.<img|simg>
71+
img_name = def_file.split('.', 1)[1]
72+
73+
cmd_opts = ''
74+
75+
image_format = self.image_format
76+
77+
# singularity image format (default for Apptainer)
78+
if image_format in [None, CONT_IMAGE_FORMAT_SQUASHFS, CONT_IMAGE_FORMAT_SIF]:
79+
img_path = os.path.join(cont_path, img_name + '.sif')
80+
81+
# ext3 image format, creating as writable container
82+
elif image_format == CONT_IMAGE_FORMAT_EXT3:
83+
raise EasyBuildError("ext3 image format is not supported with Apptainer")
84+
85+
# sandbox image format, creates as a directory but acts like a container
86+
elif image_format == CONT_IMAGE_FORMAT_SANDBOX:
87+
img_path = os.path.join(cont_path, img_name)
88+
cmd_opts = '--sandbox'
89+
90+
else:
91+
raise EasyBuildError("Unknown container image format specified for Apptainer: %s" % image_format)
92+
93+
if os.path.exists(img_path):
94+
if build_option('force'):
95+
print_msg("WARNING: overwriting existing container image at %s due to --force" % img_path)
96+
remove_file(img_path)
97+
else:
98+
raise EasyBuildError("Container image already exists at %s, not overwriting it without --force",
99+
img_path)
100+
101+
# resolve full path to 'apptainer' binary, since it may not be available via $PATH under sudo...
102+
apptainer = which('apptainer')
103+
cmd_env = ''
104+
105+
apptainer_tmpdir = self.tmpdir
106+
if apptainer_tmpdir:
107+
cmd_env += 'APPTAINER_TMPDIR=%s' % apptainer_tmpdir
108+
109+
cmd = ' '.join(['sudo', cmd_env, apptainer, 'build', cmd_opts, img_path, recipe_path])
110+
print_msg("Running '%s', you may need to enter your 'sudo' password..." % cmd)
111+
run_cmd(cmd, stream_output=True)
112+
print_msg("Apptainer image created at %s" % img_path, log=self.log)

easybuild/tools/containers/common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from easybuild.base import fancylogger
3535
from easybuild.tools.build_log import EasyBuildError
3636
from easybuild.tools.config import build_option
37+
from easybuild.tools.containers.apptainer import ApptainerContainer # noqa
3738
from easybuild.tools.containers.docker import DockerContainer # noqa
3839
from easybuild.tools.containers.singularity import SingularityContainer # noqa
3940

0 commit comments

Comments
 (0)