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

extend (experimental) support for generating container images with Apptainer #3975

Merged
merged 7 commits into from
Apr 9, 2023
Merged
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