Skip to content

Commit

Permalink
Merge pull request #3975 from boegel/apptainer
Browse files Browse the repository at this point in the history
extend (experimental) support for generating container images with Apptainer
  • Loading branch information
branfosj authored Apr 9, 2023
2 parents 2c9dc75 + f7654c4 commit 5ec768e
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/container_tests.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# documentation: https://help.github.com/en/articles/workflow-syntax-for-github-actions
name: Tests for container support
name: Tests for Singularity container support
on: [push, pull_request]

permissions:
Expand Down
96 changes: 96 additions & 0 deletions .github/workflows/container_tests_apptainer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# documentation: https://help.github.com/en/articles/workflow-syntax-for-github-actions
name: Tests for Apptainer container support
on: [push, pull_request]

permissions:
contents: read # to fetch code (actions/checkout)

concurrency:
group: ${{format('{0}:{1}:{2}', github.repository, github.ref, github.workflow)}}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-22.04
strategy:
matrix:
python: [2.7, 3.7]
apptainer: [1.0.0, 1.1.7]
fail-fast: false
steps:
- uses: actions/checkout@v2

- name: set up Python
uses: actions/setup-python@v2
with:
python-version: ${{matrix.python}}
architecture: x64

- name: install OS & Python packages
run: |
# for building CentOS 7 container images
sudo apt-get install rpm
sudo apt-get install dnf
# for modules tool
sudo apt-get install lua5.2 liblua5.2-dev lua-filesystem lua-posix tcl tcl-dev
# fix for lua-posix packaging issue, see https://bugs.launchpad.net/ubuntu/+source/lua-posix/+bug/1752082
# needed for Ubuntu 18.04, but not for Ubuntu 20.04, so skipping symlinking if posix.so already exists
if [ ! -e /usr/lib/x86_64-linux-gnu/lua/5.2/posix.so ] ; then
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
fi
- name: install Lmod
run: |
# avoid downloading modules tool sources into easybuild-framework dir
cd $HOME
export INSTALL_DEP=$GITHUB_WORKSPACE/easybuild/scripts/install_eb_dep.sh
# install Lmod
source $INSTALL_DEP Lmod-8.4.27 $HOME
# changes in environment are not passed to other steps, so need to create files...
echo $MOD_INIT > mod_init
echo $PATH > path
if [ ! -z $MODULESHOME ]; then echo $MODULESHOME > moduleshome; fi
- name: install Apptainer
run: |
curl -OL https://github.com/apptainer/apptainer/releases/download/v${{matrix.apptainer}}/apptainer_${{matrix.apptainer}}_amd64.deb
sudo apt install ./apptainer*.deb
# Apptainer provides both apptainer and singularity commands
apptainer --version
singularity --version
- name: install sources
run: |
# install from source distribution tarball, to test release as published on PyPI
python setup.py sdist
ls dist
export PREFIX=/tmp/$USER/$GITHUB_SHA
pip install --prefix $PREFIX dist/easybuild-framework*tar.gz
pip install --prefix $PREFIX https://github.com/easybuilders/easybuild-easyblocks/archive/develop.tar.gz
- name: run test
run: |
# run tests *outside* of checked out easybuild-framework directory,
# to ensure we're testing installed version (see previous step)
cd $HOME
# initialize environment for modules tool
if [ -f $HOME/moduleshome ]; then export MODULESHOME=$(cat $HOME/moduleshome); fi
source $(cat $HOME/mod_init); type module
# make sure 'eb' is available via $PATH, and that $PYTHONPATH is set (some tests expect that);
# also pick up changes to $PATH set by sourcing $MOD_INIT
export PREFIX=/tmp/$USER/$GITHUB_SHA
export PATH=$PREFIX/bin:$(cat $HOME/path)
export PYTHONPATH=$PREFIX/lib/python${{matrix.python}}/site-packages:$PYTHONPATH
eb --version
# create $HOME/.rpmmacros, see also https://github.com/apptainer/singularity/issues/241
echo '%_var /var' > $HOME/.rpmmacros
echo '%_dbpath %{_var}/lib/rpm' >> $HOME/.rpmmacros
# build CentOS 7 container image for bzip2 1.0.8 using EasyBuild;
# see https://docs.easybuild.io/en/latest/Containers.html
curl -OL https://raw.githubusercontent.com/easybuilders/easybuild-easyconfigs/develop/easybuild/easyconfigs/b/bzip2/bzip2-1.0.8.eb
export EASYBUILD_CONTAINERPATH=$PWD
export EASYBUILD_CONTAINER_CONFIG='bootstrap=docker,from=ghcr.io/easybuilders/centos-7.9-python3-amd64'
export EASYBUILD_CONTAINER_TYPE='apptainer'
eb bzip2-1.0.8.eb --containerize --experimental --container-build-image
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)
apptainer exec bzip2-1.0.8.sif bzip2 --help
3 changes: 2 additions & 1 deletion easybuild/tools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,10 @@
CONT_IMAGE_FORMAT_SQUASHFS,
]

CONT_TYPE_APPTAINER = 'apptainer'
CONT_TYPE_DOCKER = 'docker'
CONT_TYPE_SINGULARITY = 'singularity'
CONT_TYPES = [CONT_TYPE_DOCKER, CONT_TYPE_SINGULARITY]
CONT_TYPES = [CONT_TYPE_APPTAINER, CONT_TYPE_DOCKER, CONT_TYPE_SINGULARITY]
DEFAULT_CONT_TYPE = CONT_TYPE_SINGULARITY

DEFAULT_BRANCH = 'develop'
Expand Down
112 changes: 112 additions & 0 deletions easybuild/tools/containers/apptainer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Copyright 2022-2023 Ghent University
#
# This file is part of EasyBuild,
# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
# with support of Ghent University (http://ugent.be/hpc),
# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
# Flemish Research Foundation (FWO) (http://www.fwo.be/en)
# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
#
# https://github.com/easybuilders/easybuild
#
# EasyBuild is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation v2.
#
# EasyBuild is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>.
#
"""
Support for generating Apptainer container recipes and creating container images
:author: Kenneth Hoste (HPC-UGent)
"""
import os
import re

from easybuild.tools.build_log import EasyBuildError, print_msg
from easybuild.tools.containers.singularity import SingularityContainer
from easybuild.tools.config import CONT_IMAGE_FORMAT_EXT3, CONT_IMAGE_FORMAT_SANDBOX
from easybuild.tools.config import CONT_IMAGE_FORMAT_SIF, CONT_IMAGE_FORMAT_SQUASHFS
from easybuild.tools.config import build_option, container_path
from easybuild.tools.filetools import remove_file, which
from easybuild.tools.run import run_cmd


class ApptainerContainer(SingularityContainer):

TOOLS = {'apptainer': '1.0', 'sudo': None}

RECIPE_FILE_NAME = 'Apptainer'

@staticmethod
def apptainer_version():
"""Get Apptainer version."""
version_cmd = "apptainer --version"
out, ec = run_cmd(version_cmd, simple=False, trace=False, force_in_dry_run=True)
if ec:
raise EasyBuildError("Error running '%s': %s for tool {1} with output: {2}" % (version_cmd, out))

res = re.search(r"\d+\.\d+(\.\d+)?", out.strip())
if not res:
raise EasyBuildError("Error parsing Apptainer version: %s" % out)

return res.group(0)

def build_image(self, recipe_path):
"""Build container image by calling out to 'sudo apptainer build'."""

cont_path = container_path()
def_file = os.path.basename(recipe_path)

# use --imagename if specified, otherwise derive based on filename of recipe
img_name = self.img_name
if img_name is None:
# definition file Apptainer.<app>-<version, container name <app>-<version>.<img|simg>
img_name = def_file.split('.', 1)[1]

cmd_opts = ''

image_format = self.image_format

# singularity image format (default for Apptainer)
if image_format in [None, CONT_IMAGE_FORMAT_SQUASHFS, CONT_IMAGE_FORMAT_SIF]:
img_path = os.path.join(cont_path, img_name + '.sif')

# ext3 image format, creating as writable container
elif image_format == CONT_IMAGE_FORMAT_EXT3:
raise EasyBuildError("ext3 image format is not supported with Apptainer")

# sandbox image format, creates as a directory but acts like a container
elif image_format == CONT_IMAGE_FORMAT_SANDBOX:
img_path = os.path.join(cont_path, img_name)
cmd_opts = '--sandbox'

else:
raise EasyBuildError("Unknown container image format specified for Apptainer: %s" % image_format)

if os.path.exists(img_path):
if build_option('force'):
print_msg("WARNING: overwriting existing container image at %s due to --force" % img_path)
remove_file(img_path)
else:
raise EasyBuildError("Container image already exists at %s, not overwriting it without --force",
img_path)

# resolve full path to 'apptainer' binary, since it may not be available via $PATH under sudo...
apptainer = which('apptainer')
cmd_env = ''

apptainer_tmpdir = self.tmpdir
if apptainer_tmpdir:
cmd_env += 'APPTAINER_TMPDIR=%s' % apptainer_tmpdir

cmd = ' '.join(['sudo', cmd_env, apptainer, 'build', cmd_opts, img_path, recipe_path])
print_msg("Running '%s', you may need to enter your 'sudo' password..." % cmd)
run_cmd(cmd, stream_output=True)
print_msg("Apptainer image created at %s" % img_path, log=self.log)
1 change: 1 addition & 0 deletions easybuild/tools/containers/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from easybuild.base import fancylogger
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.config import build_option
from easybuild.tools.containers.apptainer import ApptainerContainer # noqa
from easybuild.tools.containers.docker import DockerContainer # noqa
from easybuild.tools.containers.singularity import SingularityContainer # noqa

Expand Down

0 comments on commit 5ec768e

Please sign in to comment.