From 9635ff3d20c17a92a89cf82db8d3f877dd04e1c7 Mon Sep 17 00:00:00 2001 From: Massimiliano Culpo Date: Fri, 31 Jan 2020 02:19:55 +0100 Subject: [PATCH] `spack containerize` generates containers from envs (#14202) This PR adds a new command to Spack: ```console $ spack containerize -h usage: spack containerize [-h] [--config CONFIG] creates recipes to build images for different container runtimes optional arguments: -h, --help show this help message and exit --config CONFIG configuration for the container recipe that will be generated ``` which takes an environment with an additional `container` section: ```yaml spack: specs: - gromacs build_type=Release - mpich - fftw precision=float packages: all: target: [broadwell] container: # Select the format of the recipe e.g. docker, # singularity or anything else that is currently supported format: docker # Select from a valid list of images base: image: "ubuntu:18.04" spack: prerelease # Additional system packages that are needed at runtime os_packages: - libgomp1 ``` and turns it into a `Dockerfile` or a Singularity definition file, for instance: ```Dockerfile # Build stage with Spack pre-installed and ready to be used FROM spack/ubuntu-bionic:prerelease as builder # What we want to install and how we want to install it # is specified in a manifest file (spack.yaml) RUN mkdir /opt/spack-environment \ && (echo "spack:" \ && echo " specs:" \ && echo " - gromacs build_type=Release" \ && echo " - mpich" \ && echo " - fftw precision=float" \ && echo " packages:" \ && echo " all:" \ && echo " target:" \ && echo " - broadwell" \ && echo " config:" \ && echo " install_tree: /opt/software" \ && echo " concretization: together" \ && echo " view: /opt/view") > /opt/spack-environment/spack.yaml # Install the software, remove unecessary deps and strip executables RUN cd /opt/spack-environment && spack install && spack autoremove -y RUN find -L /opt/view/* -type f -exec readlink -f '{}' \; | \ xargs file -i | \ grep 'charset=binary' | \ grep 'x-executable\|x-archive\|x-sharedlib' | \ awk -F: '{print $1}' | xargs strip -s # Modifications to the environment that are necessary to run RUN cd /opt/spack-environment && \ spack env activate --sh -d . >> /etc/profile.d/z10_spack_environment.sh # Bare OS image to run the installed executables FROM ubuntu:18.04 COPY --from=builder /opt/spack-environment /opt/spack-environment COPY --from=builder /opt/software /opt/software COPY --from=builder /opt/view /opt/view COPY --from=builder /etc/profile.d/z10_spack_environment.sh /etc/profile.d/z10_spack_environment.sh RUN apt-get -yqq update && apt-get -yqq upgrade \ && apt-get -yqq install libgomp1 \ && rm -rf /var/lib/apt/lists/* ENTRYPOINT ["/bin/bash", "--rcfile", "/etc/profile", "-l"] ``` --- lib/spack/docs/containers.rst | 307 ++++++++++++++++++ lib/spack/docs/dockerhub_spack.png | Bin 0 -> 90235 bytes lib/spack/docs/environments.rst | 2 + lib/spack/docs/index.rst | 1 + lib/spack/spack/cmd/containerize.py | 25 ++ lib/spack/spack/container/__init__.py | 81 +++++ lib/spack/spack/container/images.json | 50 +++ lib/spack/spack/container/images.py | 72 ++++ lib/spack/spack/container/writers/__init__.py | 154 +++++++++ lib/spack/spack/container/writers/docker.py | 30 ++ .../spack/container/writers/singularity.py | 33 ++ lib/spack/spack/schema/container.py | 82 +++++ lib/spack/spack/schema/merged.py | 2 + lib/spack/spack/test/cmd/gc.py | 4 +- lib/spack/spack/test/cmd/test.py | 2 +- lib/spack/spack/test/container/cli.py | 16 + lib/spack/spack/test/container/conftest.py | 43 +++ lib/spack/spack/test/container/docker.py | 74 +++++ lib/spack/spack/test/container/images.py | 58 ++++ lib/spack/spack/test/container/schema.py | 16 + lib/spack/spack/test/container/singularity.py | 42 +++ share/spack/spack-completion.bash | 6 +- share/spack/templates/container/Dockerfile | 51 +++ .../spack/templates/container/singularity.def | 90 +++++ 24 files changed, 1238 insertions(+), 3 deletions(-) create mode 100644 lib/spack/docs/containers.rst create mode 100644 lib/spack/docs/dockerhub_spack.png create mode 100644 lib/spack/spack/cmd/containerize.py create mode 100644 lib/spack/spack/container/__init__.py create mode 100644 lib/spack/spack/container/images.json create mode 100644 lib/spack/spack/container/images.py create mode 100644 lib/spack/spack/container/writers/__init__.py create mode 100644 lib/spack/spack/container/writers/docker.py create mode 100644 lib/spack/spack/container/writers/singularity.py create mode 100644 lib/spack/spack/schema/container.py create mode 100644 lib/spack/spack/test/container/cli.py create mode 100644 lib/spack/spack/test/container/conftest.py create mode 100644 lib/spack/spack/test/container/docker.py create mode 100644 lib/spack/spack/test/container/images.py create mode 100644 lib/spack/spack/test/container/schema.py create mode 100644 lib/spack/spack/test/container/singularity.py create mode 100644 share/spack/templates/container/Dockerfile create mode 100644 share/spack/templates/container/singularity.def diff --git a/lib/spack/docs/containers.rst b/lib/spack/docs/containers.rst new file mode 100644 index 00000000000..bbb21a2e005 --- /dev/null +++ b/lib/spack/docs/containers.rst @@ -0,0 +1,307 @@ +.. Copyright 2013-2020 Lawrence Livermore National Security, LLC and other + Spack Project Developers. See the top-level COPYRIGHT file for details. + + SPDX-License-Identifier: (Apache-2.0 OR MIT) + +.. _containers: + +================ +Container Images +================ + +Spack can be an ideal tool to setup images for containers since all the +features discussed in :ref:`environments` can greatly help to manage +the installation of software during the image build process. Nonetheless, +building a production image from scratch still requires a lot of +boilerplate to: + +- Get Spack working within the image, possibly running as root +- Minimize the physical size of the software installed +- Properly update the system software in the base image + +To facilitate users with these tedious tasks, Spack provides a command +to automatically generate recipes for container images based on +Environments: + +.. code-block:: console + + $ ls + spack.yaml + + $ spack containerize + # Build stage with Spack pre-installed and ready to be used + FROM spack/centos7:latest as builder + + # What we want to install and how we want to install it + # is specified in a manifest file (spack.yaml) + RUN mkdir /opt/spack-environment \ + && (echo "spack:" \ + && echo " specs:" \ + && echo " - gromacs+mpi" \ + && echo " - mpich" \ + && echo " concretization: together" \ + && echo " config:" \ + && echo " install_tree: /opt/software" \ + && echo " view: /opt/view") > /opt/spack-environment/spack.yaml + + # Install the software, remove unecessary deps + RUN cd /opt/spack-environment && spack install && spack gc -y + + # Strip all the binaries + RUN find -L /opt/view/* -type f -exec readlink -f '{}' \; | \ + xargs file -i | \ + grep 'charset=binary' | \ + grep 'x-executable\|x-archive\|x-sharedlib' | \ + awk -F: '{print $1}' | xargs strip -s + + # Modifications to the environment that are necessary to run + RUN cd /opt/spack-environment && \ + spack env activate --sh -d . >> /etc/profile.d/z10_spack_environment.sh + + + # Bare OS image to run the installed executables + FROM centos:7 + + COPY --from=builder /opt/spack-environment /opt/spack-environment + COPY --from=builder /opt/software /opt/software + COPY --from=builder /opt/view /opt/view + COPY --from=builder /etc/profile.d/z10_spack_environment.sh /etc/profile.d/z10_spack_environment.sh + + RUN yum update -y && yum install -y epel-release && yum update -y \ + && yum install -y libgomp \ + && rm -rf /var/cache/yum && yum clean all + + RUN echo 'export PS1="\[$(tput bold)\]\[$(tput setaf 1)\][gromacs]\[$(tput setaf 2)\]\u\[$(tput sgr0)\]:\w $ \[$(tput sgr0)\]"' >> ~/.bashrc + + + LABEL "app"="gromacs" + LABEL "mpi"="mpich" + + ENTRYPOINT ["/bin/bash", "--rcfile", "/etc/profile", "-l"] + + +The bits that make this automation possible are discussed in details +below. All the images generated in this way will be based on +multi-stage builds with: + +- A fat ``build`` stage containing common build tools and Spack itself +- A minimal ``final`` stage containing only the software requested by the user + +----------------- +Spack Base Images +----------------- + +Docker images with Spack preinstalled and ready to be used are +built on `Docker Hub `_ +at every push to ``develop`` or to a release branch. The OS that +are currently supported are summarized in the table below: + +.. _containers-supported-os: + +.. list-table:: Supported operating systems + :header-rows: 1 + + * - Operating System + - Base Image + - Spack Image + * - Ubuntu 16.04 + - ``ubuntu:16.04`` + - ``spack/ubuntu-xenial`` + * - Ubuntu 18.04 + - ``ubuntu:16.04`` + - ``spack/ubuntu-bionic`` + * - CentOS 6 + - ``centos:6`` + - ``spack/centos6`` + * - CentOS 7 + - ``centos:7`` + - ``spack/centos7`` + +All the images are tagged with the corresponding release of Spack: + +.. image:: dockerhub_spack.png + +with the exception of the ``latest`` tag that points to the HEAD +of the ``develop`` branch. These images are available for anyone +to use and take care of all the repetitive tasks that are necessary +to setup Spack within a container. All the container recipes generated +automatically by Spack use them as base images for their ``build`` stage. + + +------------------------- +Environment Configuration +------------------------- + +Any Spack Environment can be used for the automatic generation of container +recipes. Sensible defaults are provided for things like the base image or the +version of Spack used in the image. If a finer tuning is needed it can be +obtained by adding the relevant metadata under the ``container`` attribute +of environments: + +.. code-block:: yaml + + spack: + specs: + - gromacs+mpi + - mpich + + container: + # Select the format of the recipe e.g. docker, + # singularity or anything else that is currently supported + format: docker + + # Select from a valid list of images + base: + image: "centos:7" + spack: develop + + # Whether or not to strip binaries + strip: true + + # Additional system packages that are needed at runtime + os_packages: + - libgomp + + # Extra instructions + extra_instructions: + final: | + RUN echo 'export PS1="\[$(tput bold)\]\[$(tput setaf 1)\][gromacs]\[$(tput setaf 2)\]\u\[$(tput sgr0)\]:\w $ \[$(tput sgr0)\]"' >> ~/.bashrc + + # Labels for the image + labels: + app: "gromacs" + mpi: "mpich" + +The tables below describe the configuration options that are currently supported: + +.. list-table:: General configuration options for the ``container`` section of ``spack.yaml`` + :header-rows: 1 + + * - Option Name + - Description + - Allowed Values + - Required + * - ``format`` + - The format of the recipe + - ``docker`` or ``singularity`` + - Yes + * - ``base:image`` + - Base image for ``final`` stage + - See :ref:`containers-supported-os` + - Yes + * - ``base:spack`` + - Version of Spack + - Valid tags for ``base:image`` + - Yes + * - ``strip`` + - Whether to strip binaries + - ``true`` (default) or ``false`` + - No + * - ``os_packages`` + - System packages to be installed + - Valid packages for the ``final`` OS + - No + * - ``extra_instructions:build`` + - Extra instructions (e.g. `RUN`, `COPY`, etc.) at the end of the ``build`` stage + - Anything understood by the current ``format`` + - No + * - ``extra_instructions:final`` + - Extra instructions (e.g. `RUN`, `COPY`, etc.) at the end of the ``final`` stage + - Anything understood by the current ``format`` + - No + * - ``labels`` + - Labels to tag the image + - Pairs of key-value strings + - No + +.. list-table:: Configuration options specific to Singularity + :header-rows: 1 + + * - Option Name + - Description + - Allowed Values + - Required + * - ``singularity:runscript`` + - Content of ``%runscript`` + - Any valid script + - No + * - ``singularity:startscript`` + - Content of ``%startscript`` + - Any valid script + - No + * - ``singularity:test`` + - Content of ``%test`` + - Any valid script + - No + * - ``singularity:help`` + - Description of the image + - Description string + - No + +Once the Environment is properly configured a recipe for a container +image can be printed to standard output by issuing the following +command from the directory where the ``spack.yaml`` resides: + +.. code-block:: console + + $ spack containerize + +The example ``spack.yaml`` above would produce for instance the +following ``Dockerfile``: + +.. code-block:: docker + + # Build stage with Spack pre-installed and ready to be used + FROM spack/centos7:latest as builder + + # What we want to install and how we want to install it + # is specified in a manifest file (spack.yaml) + RUN mkdir /opt/spack-environment \ + && (echo "spack:" \ + && echo " specs:" \ + && echo " - gromacs+mpi" \ + && echo " - mpich" \ + && echo " concretization: together" \ + && echo " config:" \ + && echo " install_tree: /opt/software" \ + && echo " view: /opt/view") > /opt/spack-environment/spack.yaml + + # Install the software, remove unecessary deps + RUN cd /opt/spack-environment && spack install && spack gc -y + + # Strip all the binaries + RUN find -L /opt/view/* -type f -exec readlink -f '{}' \; | \ + xargs file -i | \ + grep 'charset=binary' | \ + grep 'x-executable\|x-archive\|x-sharedlib' | \ + awk -F: '{print $1}' | xargs strip -s + + # Modifications to the environment that are necessary to run + RUN cd /opt/spack-environment && \ + spack env activate --sh -d . >> /etc/profile.d/z10_spack_environment.sh + + + # Bare OS image to run the installed executables + FROM centos:7 + + COPY --from=builder /opt/spack-environment /opt/spack-environment + COPY --from=builder /opt/software /opt/software + COPY --from=builder /opt/view /opt/view + COPY --from=builder /etc/profile.d/z10_spack_environment.sh /etc/profile.d/z10_spack_environment.sh + + RUN yum update -y && yum install -y epel-release && yum update -y \ + && yum install -y libgomp \ + && rm -rf /var/cache/yum && yum clean all + + RUN echo 'export PS1="\[$(tput bold)\]\[$(tput setaf 1)\][gromacs]\[$(tput setaf 2)\]\u\[$(tput sgr0)\]:\w $ \[$(tput sgr0)\]"' >> ~/.bashrc + + + LABEL "app"="gromacs" + LABEL "mpi"="mpich" + + ENTRYPOINT ["/bin/bash", "--rcfile", "/etc/profile", "-l"] + +.. note:: + Spack can also produce Singularity definition files to build the image. The + minimum version of Singularity required to build a SIF (Singularity Image Format) + from them is ``3.5.3``. \ No newline at end of file diff --git a/lib/spack/docs/dockerhub_spack.png b/lib/spack/docs/dockerhub_spack.png new file mode 100644 index 0000000000000000000000000000000000000000..44ff0ed7ed87652681e4d0bddd0c568d57fcc8c3 GIT binary patch literal 90235 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

*|zelEEH`gE@3+Dt^ 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 literal 0 HcmV?d00001 diff --git a/lib/spack/docs/environments.rst b/lib/spack/docs/environments.rst index 336d574bd7f..5ec1ec9032a 100644 --- a/lib/spack/docs/environments.rst +++ b/lib/spack/docs/environments.rst @@ -49,6 +49,8 @@ Spack uses a "manifest and lock" model similar to `Bundler gemfiles managers. The user input file is named ``spack.yaml`` and the lock file is named ``spack.lock`` +.. _environments-using: + ------------------ Using Environments ------------------ diff --git a/lib/spack/docs/index.rst b/lib/spack/docs/index.rst index 489c15645a6..8170f152a4e 100644 --- a/lib/spack/docs/index.rst +++ b/lib/spack/docs/index.rst @@ -66,6 +66,7 @@ or refer to the full manual below. config_yaml build_settings environments + containers mirrors module_file_support repositories diff --git a/lib/spack/spack/cmd/containerize.py b/lib/spack/spack/cmd/containerize.py new file mode 100644 index 00000000000..cc2c0015603 --- /dev/null +++ b/lib/spack/spack/cmd/containerize.py @@ -0,0 +1,25 @@ +# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import os +import os.path +import spack.container + +description = ("creates recipes to build images for different" + " container runtimes") +section = "container" +level = "long" + + +def containerize(parser, args): + config_dir = args.env_dir or os.getcwd() + config_file = os.path.abspath(os.path.join(config_dir, 'spack.yaml')) + if not os.path.exists(config_file): + msg = 'file not found: {0}' + raise ValueError(msg.format(config_file)) + + config = spack.container.validate(config_file) + + recipe = spack.container.recipe(config) + print(recipe) diff --git a/lib/spack/spack/container/__init__.py b/lib/spack/spack/container/__init__.py new file mode 100644 index 00000000000..fc3750355a5 --- /dev/null +++ b/lib/spack/spack/container/__init__.py @@ -0,0 +1,81 @@ +# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +"""Package that provides functions and classes to +generate container recipes from a Spack environment +""" +import warnings + +import spack.environment +import spack.schema.env as env +import spack.util.spack_yaml as syaml +from .writers import recipe + +__all__ = ['validate', 'recipe'] + + +def validate(configuration_file): + """Validate a Spack environment YAML file that is being used to generate a + recipe for a container. + + Since a few attributes of the configuration must have specific values for + the container recipe, this function returns a sanitized copy of the + configuration in the input file. If any modification is needed, a warning + will be issued. + + Args: + configuration_file (str): path to the Spack environment YAML file + + Returns: + A sanitized copy of the configuration stored in the input file + """ + import jsonschema + with open(configuration_file) as f: + config = syaml.load(f) + + # Ensure we have a "container" attribute with sensible defaults set + env_dict = spack.environment.config_dict(config) + env_dict.setdefault('container', { + 'format': 'docker', + 'base': {'image': 'ubuntu:18.04', 'spack': 'develop'} + }) + env_dict['container'].setdefault('format', 'docker') + env_dict['container'].setdefault( + 'base', {'image': 'ubuntu:18.04', 'spack': 'develop'} + ) + + # Remove attributes that are not needed / allowed in the + # container recipe + for subsection in ('cdash', 'gitlab_ci', 'modules'): + if subsection in env_dict: + msg = ('the subsection "{0}" in "{1}" is not used when generating' + ' container recipes and will be discarded') + warnings.warn(msg.format(subsection, configuration_file)) + env_dict.pop(subsection) + + # Set the default value of the concretization strategy to "together" and + # warn if the user explicitly set another value + env_dict.setdefault('concretization', 'together') + if env_dict['concretization'] != 'together': + msg = ('the "concretization" attribute of the environment is set ' + 'to "{0}" [the advised value is instead "together"]') + warnings.warn(msg.format(env_dict['concretization'])) + + # Check if the install tree was explicitly set to a custom value and warn + # that it will be overridden + environment_config = env_dict.get('config', {}) + if environment_config.get('install_tree', None): + msg = ('the "config:install_tree" attribute has been set explicitly ' + 'and will be overridden in the container image') + warnings.warn(msg) + + # Likewise for the view + environment_view = env_dict.get('view', None) + if environment_view: + msg = ('the "view" attribute has been set explicitly ' + 'and will be overridden in the container image') + warnings.warn(msg) + + jsonschema.validate(config, schema=env.schema) + return config diff --git a/lib/spack/spack/container/images.json b/lib/spack/spack/container/images.json new file mode 100644 index 00000000000..ecd911815d4 --- /dev/null +++ b/lib/spack/spack/container/images.json @@ -0,0 +1,50 @@ +{ + "ubuntu:18.04": { + "update": "apt-get -yqq update && apt-get -yqq upgrade", + "install": "apt-get -yqq install", + "clean": "rm -rf /var/lib/apt/lists/*", + "environment": [], + "build": "spack/ubuntu-bionic", + "build_tags": { + "develop": "latest", + "0.14": "0.14", + "0.14.0": "0.14.0" + } + }, + "ubuntu:16.04": { + "update": "apt-get -yqq update && apt-get -yqq upgrade", + "install": "apt-get -yqq install", + "clean": "rm -rf /var/lib/apt/lists/*", + "environment": [], + "build": "spack/ubuntu-xenial", + "build_tags": { + "develop": "latest", + "0.14": "0.14", + "0.14.0": "0.14.0" + } + }, + "centos:7": { + "update": "yum update -y && yum install -y epel-release && yum update -y", + "install": "yum install -y", + "clean": "rm -rf /var/cache/yum && yum clean all", + "environment": [], + "build": "spack/centos7", + "build_tags": { + "develop": "latest", + "0.14": "0.14", + "0.14.0": "0.14.0" + } + }, + "centos:6": { + "update": "yum update -y && yum install -y epel-release && yum update -y", + "install": "yum install -y", + "clean": "rm -rf /var/cache/yum && yum clean all", + "environment": [], + "build": "spack/centos6", + "build_tags": { + "develop": "latest", + "0.14": "0.14", + "0.14.0": "0.14.0" + } + } +} \ No newline at end of file diff --git a/lib/spack/spack/container/images.py b/lib/spack/spack/container/images.py new file mode 100644 index 00000000000..421fc244258 --- /dev/null +++ b/lib/spack/spack/container/images.py @@ -0,0 +1,72 @@ +# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +"""Manages the details on the images used in the build and the run stage.""" +import json +import os.path + +#: Global variable used to cache in memory the content of images.json +_data = None + + +def data(): + """Returns a dictionary with the static data on the images. + + The dictionary is read from a JSON file lazily the first time + this function is called. + """ + global _data + if not _data: + json_dir = os.path.abspath(os.path.dirname(__file__)) + json_file = os.path.join(json_dir, 'images.json') + with open(json_file) as f: + _data = json.load(f) + return _data + + +def build_info(image, spack_version): + """Returns the name of the build image and its tag. + + Args: + image (str): image to be used at run-time. Should be of the form + : e.g. "ubuntu:18.04" + spack_version (str): version of Spack that we want to use to build + + Returns: + A tuple with (image_name, image_tag) for the build image + """ + # Don't handle error here, as a wrong image should have been + # caught by the JSON schema + image_data = data()[image] + build_image = image_data['build'] + + # Try to check if we have a tag for this Spack version + try: + build_tag = image_data['build_tags'][spack_version] + except KeyError: + msg = ('the image "{0}" has no tag for Spack version "{1}" ' + '[valid versions are {2}]') + msg = msg.format(build_image, spack_version, + ', '.join(image_data['build_tags'].keys())) + raise ValueError(msg) + + return build_image, build_tag + + +def package_info(image): + """Returns the commands used to update system repositories, install + system packages and clean afterwards. + + Args: + image (str): image to be used at run-time. Should be of the form + : e.g. "ubuntu:18.04" + + Returns: + A tuple of (update, install, clean) commands. + """ + image_data = data()[image] + update = image_data['update'] + install = image_data['install'] + clean = image_data['clean'] + return update, install, clean diff --git a/lib/spack/spack/container/writers/__init__.py b/lib/spack/spack/container/writers/__init__.py new file mode 100644 index 00000000000..a1d2fa31020 --- /dev/null +++ b/lib/spack/spack/container/writers/__init__.py @@ -0,0 +1,154 @@ +# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +"""Writers for different kind of recipes and related +convenience functions. +""" +import collections +import copy + +import spack.environment +import spack.schema.env +import spack.tengine as tengine +import spack.util.spack_yaml as syaml + +from spack.container.images import build_info, package_info + +#: Caches all the writers that are currently supported +_writer_factory = {} + + +def writer(name): + """Decorator to register a factory for a recipe writer. + + Each factory should take a configuration dictionary and return a + properly configured writer that, when called, prints the + corresponding recipe. + """ + def _decorator(factory): + _writer_factory[name] = factory + return factory + return _decorator + + +def create(configuration): + """Returns a writer that conforms to the configuration passed as input. + + Args: + configuration: how to generate the current recipe + """ + name = spack.environment.config_dict(configuration)['container']['format'] + return _writer_factory[name](configuration) + + +def recipe(configuration): + """Returns a recipe that conforms to the configuration passed as input. + + Args: + configuration: how to generate the current recipe + """ + return create(configuration)() + + +class PathContext(tengine.Context): + """Generic context used to instantiate templates of recipes that + install software in a common location and make it available + directly via PATH. + """ + def __init__(self, config): + self.config = spack.environment.config_dict(config) + self.container_config = self.config['container'] + + @tengine.context_property + def run(self): + """Information related to the run image.""" + image = self.container_config['base']['image'] + Run = collections.namedtuple('Run', ['image']) + return Run(image=image) + + @tengine.context_property + def build(self): + """Information related to the build image.""" + + # Map the final image to the correct build image + run_image = self.container_config['base']['image'] + spack_version = self.container_config['base']['spack'] + image, tag = build_info(run_image, spack_version) + + Build = collections.namedtuple('Build', ['image', 'tag']) + return Build(image=image, tag=tag) + + @tengine.context_property + def strip(self): + """Whether or not to strip binaries in the image""" + return self.container_config.get('strip', True) + + @tengine.context_property + def paths(self): + """Important paths in the image""" + Paths = collections.namedtuple('Paths', [ + 'environment', 'store', 'view' + ]) + return Paths( + environment='/opt/spack-environment', + store='/opt/software', + view='/opt/view' + ) + + @tengine.context_property + def manifest(self): + """The spack.yaml file that should be used in the image""" + import jsonschema + # Copy in the part of spack.yaml prescribed in the configuration file + manifest = copy.deepcopy(self.config) + manifest.pop('container') + + # Ensure that a few paths are where they need to be + manifest.setdefault('config', syaml.syaml_dict()) + manifest['config']['install_tree'] = self.paths.store + manifest['view'] = self.paths.view + manifest = {'spack': manifest} + + # Validate the manifest file + jsonschema.validate(manifest, schema=spack.schema.env.schema) + + return syaml.dump(manifest, default_flow_style=False).strip() + + @tengine.context_property + def os_packages(self): + """Additional system packages that are needed at run-time.""" + package_list = self.container_config.get('os_packages', None) + if not package_list: + return package_list + + image = self.container_config['base']['image'] + update, install, clean = package_info(image) + Packages = collections.namedtuple( + 'Packages', ['update', 'install', 'list', 'clean'] + ) + return Packages(update=update, install=install, + list=package_list, clean=clean) + + @tengine.context_property + def extra_instructions(self): + Extras = collections.namedtuple('Extra', ['build', 'final']) + extras = self.container_config.get('extra_instructions', {}) + build, final = extras.get('build', None), extras.get('final', None) + return Extras(build=build, final=final) + + @tengine.context_property + def labels(self): + return self.container_config.get('labels', {}) + + def __call__(self): + """Returns the recipe as a string""" + env = tengine.make_environment() + t = env.get_template(self.template_name) + return t.render(**self.to_dict()) + + +# Import after function definition all the modules in this package, +# so that registration of writers will happen automatically +import spack.container.writers.singularity # noqa +import spack.container.writers.docker # noqa diff --git a/lib/spack/spack/container/writers/docker.py b/lib/spack/spack/container/writers/docker.py new file mode 100644 index 00000000000..557d22c8039 --- /dev/null +++ b/lib/spack/spack/container/writers/docker.py @@ -0,0 +1,30 @@ +# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import spack.tengine as tengine + +from . import writer, PathContext + + +@writer('docker') +class DockerContext(PathContext): + """Context used to instantiate a Dockerfile""" + #: Name of the template used for Dockerfiles + template_name = 'container/Dockerfile' + + @tengine.context_property + def manifest(self): + manifest_str = super(DockerContext, self).manifest + # Docker doesn't support HEREDOC so we need to resort to + # a horrible echo trick to have the manifest in the Dockerfile + echoed_lines = [] + for idx, line in enumerate(manifest_str.split('\n')): + if idx == 0: + echoed_lines.append('&& (echo "' + line + '" \\') + continue + echoed_lines.append('&& echo "' + line + '" \\') + + echoed_lines[-1] = echoed_lines[-1].replace(' \\', ')') + + return '\n'.join(echoed_lines) diff --git a/lib/spack/spack/container/writers/singularity.py b/lib/spack/spack/container/writers/singularity.py new file mode 100644 index 00000000000..32f29eb83d3 --- /dev/null +++ b/lib/spack/spack/container/writers/singularity.py @@ -0,0 +1,33 @@ +# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import spack.tengine as tengine +from . import writer, PathContext + + +@writer('singularity') +class SingularityContext(PathContext): + """Context used to instantiate a Singularity definition file""" + #: Name of the template used for Singularity definition files + template_name = 'container/singularity.def' + + @property + def singularity_config(self): + return self.container_config.get('singularity', {}) + + @tengine.context_property + def runscript(self): + return self.singularity_config.get('runscript', '') + + @tengine.context_property + def startscript(self): + return self.singularity_config.get('startscript', '') + + @tengine.context_property + def test(self): + return self.singularity_config.get('test', '') + + @tengine.context_property + def help(self): + return self.singularity_config.get('help', '') diff --git a/lib/spack/spack/schema/container.py b/lib/spack/spack/schema/container.py new file mode 100644 index 00000000000..cb1ed8d63ac --- /dev/null +++ b/lib/spack/spack/schema/container.py @@ -0,0 +1,82 @@ +# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +"""Schema for the 'container' subsection of Spack environments.""" + +#: Schema for the container attribute included in Spack environments +container_schema = { + 'type': 'object', + 'additionalProperties': False, + 'properties': { + # The recipe formats that are currently supported by the command + 'format': { + 'type': 'string', + 'enum': ['docker', 'singularity'] + }, + # Describes the base image to start from and the version + # of Spack to be used + 'base': { + 'type': 'object', + 'additionalProperties': False, + 'properties': { + 'image': { + 'type': 'string', + 'enum': ['ubuntu:18.04', + 'ubuntu:16.04', + 'centos:7', + 'centos:6'] + }, + 'spack': { + 'type': 'string', + 'enum': ['develop', '0.14', '0.14.0'] + } + }, + 'required': ['image', 'spack'] + }, + # Whether or not to strip installed binaries + 'strip': { + 'type': 'boolean', + 'default': True + }, + # Additional system packages that are needed at runtime + 'os_packages': { + 'type': 'array', + 'items': { + 'type': 'string' + } + }, + # Add labels to the image + 'labels': { + 'type': 'object', + }, + # Add a custom extra section at the bottom of a stage + 'extra_instructions': { + 'type': 'object', + 'additionalProperties': False, + 'properties': { + 'build': {'type': 'string'}, + 'final': {'type': 'string'} + } + }, + # Reserved for properties that are specific to each format + 'singularity': { + 'type': 'object', + 'additionalProperties': False, + 'default': {}, + 'properties': { + 'runscript': {'type': 'string'}, + 'startscript': {'type': 'string'}, + 'test': {'type': 'string'}, + 'help': {'type': 'string'} + } + }, + 'docker': { + 'type': 'object', + 'additionalProperties': False, + 'default': {}, + } + } +} + +properties = {'container': container_schema} diff --git a/lib/spack/spack/schema/merged.py b/lib/spack/spack/schema/merged.py index d56228c1160..e118acf2860 100644 --- a/lib/spack/spack/schema/merged.py +++ b/lib/spack/spack/schema/merged.py @@ -13,6 +13,7 @@ import spack.schema.cdash import spack.schema.compilers import spack.schema.config +import spack.schema.container import spack.schema.gitlab_ci import spack.schema.mirrors import spack.schema.modules @@ -26,6 +27,7 @@ spack.schema.cdash.properties, spack.schema.compilers.properties, spack.schema.config.properties, + spack.schema.container.properties, spack.schema.gitlab_ci.properties, spack.schema.mirrors.properties, spack.schema.modules.properties, diff --git a/lib/spack/spack/test/cmd/gc.py b/lib/spack/spack/test/cmd/gc.py index 76eb608cf29..22c85a1d780 100644 --- a/lib/spack/spack/test/cmd/gc.py +++ b/lib/spack/spack/test/cmd/gc.py @@ -30,7 +30,9 @@ def test_packages_are_removed(config, mutable_database, capsys): @pytest.mark.db -def test_gc_with_environment(config, mutable_database, capsys): +def test_gc_with_environment( + config, mutable_database, mutable_mock_env_path, capsys +): s = spack.spec.Spec('simple-inheritance') s.concretize() s.package.do_install(fake=True, explicit=True) diff --git a/lib/spack/spack/test/cmd/test.py b/lib/spack/spack/test/cmd/test.py index 3595f91953d..9a64209cfa6 100644 --- a/lib/spack/spack/test/cmd/test.py +++ b/lib/spack/spack/test/cmd/test.py @@ -1,4 +1,4 @@ -# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other +# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) diff --git a/lib/spack/spack/test/container/cli.py b/lib/spack/spack/test/container/cli.py new file mode 100644 index 00000000000..8e5403f072f --- /dev/null +++ b/lib/spack/spack/test/container/cli.py @@ -0,0 +1,16 @@ +# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import llnl.util.filesystem as fs +import spack.main + + +containerize = spack.main.SpackCommand('containerize') + + +def test_command(configuration_dir, capsys): + with capsys.disabled(): + with fs.working_dir(configuration_dir): + output = containerize() + assert 'FROM spack/ubuntu-bionic' in output diff --git a/lib/spack/spack/test/container/conftest.py b/lib/spack/spack/test/container/conftest.py new file mode 100644 index 00000000000..802b34c5f89 --- /dev/null +++ b/lib/spack/spack/test/container/conftest.py @@ -0,0 +1,43 @@ +# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import pytest + +import spack.util.spack_yaml as syaml + + +@pytest.fixture() +def minimal_configuration(): + return { + 'spack': { + 'specs': [ + 'gromacs', + 'mpich', + 'fftw precision=float' + ], + 'container': { + 'format': 'docker', + 'base': { + 'image': 'ubuntu:18.04', + 'spack': 'develop' + } + } + } + } + + +@pytest.fixture() +def config_dumper(tmpdir): + """Function that dumps an environment config in a temporary folder.""" + def dumper(configuration): + content = syaml.dump(configuration, default_flow_style=False) + config_file = tmpdir / 'spack.yaml' + config_file.write(content) + return str(tmpdir) + return dumper + + +@pytest.fixture() +def configuration_dir(minimal_configuration, config_dumper): + return config_dumper(minimal_configuration) diff --git a/lib/spack/spack/test/container/docker.py b/lib/spack/spack/test/container/docker.py new file mode 100644 index 00000000000..fbdc085828e --- /dev/null +++ b/lib/spack/spack/test/container/docker.py @@ -0,0 +1,74 @@ +# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +import spack.container.writers as writers + + +def test_manifest(minimal_configuration): + writer = writers.create(minimal_configuration) + manifest_str = writer.manifest + for line in manifest_str.split('\n'): + assert 'echo' in line + + +def test_build_and_run_images(minimal_configuration): + writer = writers.create(minimal_configuration) + + # Test the output of run property + run = writer.run + assert run.image == 'ubuntu:18.04' + + # Test the output of the build property + build = writer.build + assert build.image == 'spack/ubuntu-bionic' + assert build.tag == 'latest' + + +def test_packages(minimal_configuration): + # In this minimal configuration we don't have packages + writer = writers.create(minimal_configuration) + assert writer.os_packages is None + + # If we add them a list should be returned + pkgs = ['libgomp1'] + minimal_configuration['spack']['container']['os_packages'] = pkgs + writer = writers.create(minimal_configuration) + p = writer.os_packages + assert p.update + assert p.install + assert p.clean + assert p.list == pkgs + + +def test_ensure_render_works(minimal_configuration): + # Here we just want to ensure that nothing is raised + writer = writers.create(minimal_configuration) + writer() + + +def test_strip_is_set_from_config(minimal_configuration): + writer = writers.create(minimal_configuration) + assert writer.strip is True + + minimal_configuration['spack']['container']['strip'] = False + writer = writers.create(minimal_configuration) + assert writer.strip is False + + +def test_extra_instructions_is_set_from_config(minimal_configuration): + writer = writers.create(minimal_configuration) + assert writer.extra_instructions == (None, None) + + test_line = 'RUN echo Hello world!' + e = minimal_configuration['spack']['container'] + e['extra_instructions'] = {} + e['extra_instructions']['build'] = test_line + writer = writers.create(minimal_configuration) + assert writer.extra_instructions == (test_line, None) + + e['extra_instructions']['final'] = test_line + del e['extra_instructions']['build'] + writer = writers.create(minimal_configuration) + assert writer.extra_instructions == (None, test_line) diff --git a/lib/spack/spack/test/container/images.py b/lib/spack/spack/test/container/images.py new file mode 100644 index 00000000000..808676c39a9 --- /dev/null +++ b/lib/spack/spack/test/container/images.py @@ -0,0 +1,58 @@ +# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import os.path + +import pytest + +import spack.container + + +@pytest.mark.parametrize('image,spack_version,expected', [ + ('ubuntu:18.04', 'develop', ('spack/ubuntu-bionic', 'latest')), + ('ubuntu:18.04', '0.14.0', ('spack/ubuntu-bionic', '0.14.0')), +]) +def test_build_info(image, spack_version, expected): + output = spack.container.images.build_info(image, spack_version) + assert output == expected + + +@pytest.mark.parametrize('image,spack_version', [ + ('ubuntu:18.04', 'doesnotexist') +]) +def test_build_info_error(image, spack_version): + with pytest.raises(ValueError, match=r"has no tag for"): + spack.container.images.build_info(image, spack_version) + + +@pytest.mark.parametrize('image', [ + 'ubuntu:18.04' +]) +def test_package_info(image): + update, install, clean = spack.container.images.package_info(image) + assert update + assert install + assert clean + + +@pytest.mark.parametrize('extra_config,expected_msg', [ + ({'modules': {'enable': ['tcl']}}, 'the subsection "modules" in'), + ({'concretization': 'separately'}, 'the "concretization" attribute'), + ({'config': {'install_tree': '/some/dir'}}, + 'the "config:install_tree" attribute has been set'), + ({'view': '/some/dir'}, 'the "view" attribute has been set') +]) +def test_validate( + extra_config, expected_msg, minimal_configuration, config_dumper +): + minimal_configuration['spack'].update(extra_config) + spack_yaml_dir = config_dumper(minimal_configuration) + spack_yaml = os.path.join(spack_yaml_dir, 'spack.yaml') + + with pytest.warns(UserWarning) as w: + spack.container.validate(spack_yaml) + + # Tests are designed to raise only one warning + assert len(w) == 1 + assert expected_msg in str(w.pop().message) diff --git a/lib/spack/spack/test/container/schema.py b/lib/spack/spack/test/container/schema.py new file mode 100644 index 00000000000..3f33a3f9f7d --- /dev/null +++ b/lib/spack/spack/test/container/schema.py @@ -0,0 +1,16 @@ +# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +import spack.container +import spack.schema.container + + +def test_images_in_schema(): + properties = spack.schema.container.container_schema['properties'] + allowed_images = set( + properties['base']['properties']['image']['enum'] + ) + images_in_json = set(x for x in spack.container.images.data()) + assert images_in_json == allowed_images diff --git a/lib/spack/spack/test/container/singularity.py b/lib/spack/spack/test/container/singularity.py new file mode 100644 index 00000000000..445a119f6cb --- /dev/null +++ b/lib/spack/spack/test/container/singularity.py @@ -0,0 +1,42 @@ +# Copyright 2013-2020 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import pytest + +import spack.container.writers as writers + + +@pytest.fixture +def singularity_configuration(minimal_configuration): + minimal_configuration['spack']['container']['format'] = 'singularity' + return minimal_configuration + + +def test_ensure_render_works(singularity_configuration): + container_config = singularity_configuration['spack']['container'] + assert container_config['format'] == 'singularity' + # Here we just want to ensure that nothing is raised + writer = writers.create(singularity_configuration) + writer() + + +@pytest.mark.parametrize('properties,expected', [ + ({'runscript': '/opt/view/bin/h5ls'}, + {'runscript': '/opt/view/bin/h5ls', + 'startscript': '', + 'test': '', + 'help': ''}) +]) +def test_singularity_specific_properties( + properties, expected, singularity_configuration +): + # Set the property in the configuration + container_config = singularity_configuration['spack']['container'] + for name, value in properties.items(): + container_config.setdefault('singularity', {})[name] = value + + # Assert the properties return the expected values + writer = writers.create(singularity_configuration) + for name, value in expected.items(): + assert getattr(writer, name) == value diff --git a/share/spack/spack-completion.bash b/share/spack/spack-completion.bash index b408d0b2344..623e9fba738 100755 --- a/share/spack/spack-completion.bash +++ b/share/spack/spack-completion.bash @@ -313,7 +313,7 @@ _spack() { then SPACK_COMPREPLY="-h --help -H --all-help --color -C --config-scope -d --debug --timestamp --pdb -e --env -D --env-dir -E --no-env --use-env-repo -k --insecure -l --enable-locks -L --disable-locks -m --mock -p --profile --sorted-profile --lines -v --verbose --stacktrace -V --version --print-shell-vars" else - SPACK_COMPREPLY="activate add arch blame bootstrap build build-env buildcache cd checksum ci clean clone commands compiler compilers concretize config configure create deactivate debug dependencies dependents deprecate dev-build diy docs edit env extensions fetch find flake8 gc gpg graph help info install license list load location log-parse maintainers mirror module patch pkg providers pydoc python reindex remove rm repo resource restage setup spec stage test uninstall unload upload-s3 url verify versions view" + SPACK_COMPREPLY="activate add arch blame bootstrap build build-env buildcache cd checksum ci clean clone commands compiler compilers concretize config configure containerize create deactivate debug dependencies dependents deprecate dev-build diy docs edit env extensions fetch find flake8 gc gpg graph help info install license list load location log-parse maintainers mirror module patch pkg providers pydoc python reindex remove rm repo resource restage setup spec stage test uninstall unload upload-s3 url verify versions view" fi } @@ -628,6 +628,10 @@ _spack_configure() { fi } +_spack_containerize() { + SPACK_COMPREPLY="-h --help" +} + _spack_create() { if $list_options then diff --git a/share/spack/templates/container/Dockerfile b/share/spack/templates/container/Dockerfile new file mode 100644 index 00000000000..740f46e9ee9 --- /dev/null +++ b/share/spack/templates/container/Dockerfile @@ -0,0 +1,51 @@ +# Build stage with Spack pre-installed and ready to be used +FROM {{ build.image }}:{{ build.tag }} as builder + +# What we want to install and how we want to install it +# is specified in a manifest file (spack.yaml) +RUN mkdir {{ paths.environment }} \ +{{ manifest }} > {{ paths.environment }}/spack.yaml + +# Install the software, remove unecessary deps +RUN cd {{ paths.environment }} && spack install && spack gc -y +{% if strip %} + +# Strip all the binaries +RUN find -L {{ paths.view }}/* -type f -exec readlink -f '{}' \; | \ + xargs file -i | \ + grep 'charset=binary' | \ + grep 'x-executable\|x-archive\|x-sharedlib' | \ + awk -F: '{print $1}' | xargs strip -s +{% endif %} + +# Modifications to the environment that are necessary to run +RUN cd {{ paths.environment }} && \ + spack env activate --sh -d . >> /etc/profile.d/z10_spack_environment.sh + +{% if extra_instructions.build %} +{{ extra_instructions.build }} +{% endif %} + +# Bare OS image to run the installed executables +FROM {{ run.image }} + +COPY --from=builder {{ paths.environment }} {{ paths.environment }} +COPY --from=builder {{ paths.store }} {{ paths.store }} +COPY --from=builder {{ paths.view }} {{ paths.view }} +COPY --from=builder /etc/profile.d/z10_spack_environment.sh /etc/profile.d/z10_spack_environment.sh + +{% if os_packages %} +RUN {{ os_packages.update }} \ + && {{ os_packages.install }}{% for pkg in os_packages.list %} {{ pkg }}{% endfor %} \ + && {{ os_packages.clean }} +{% endif %} + +{% if extra_instructions.final %} +{{ extra_instructions.final }} +{% endif %} + +{% for label, value in labels.items() %} +LABEL "{{ label }}"="{{ value }}" +{% endfor %} + +ENTRYPOINT ["/bin/bash", "--rcfile", "/etc/profile", "-l"] diff --git a/share/spack/templates/container/singularity.def b/share/spack/templates/container/singularity.def new file mode 100644 index 00000000000..616e677f966 --- /dev/null +++ b/share/spack/templates/container/singularity.def @@ -0,0 +1,90 @@ +Bootstrap: docker +From: {{ build.image }}:{{ build.tag }} +Stage: build + +%post + # Create the manifest file for the installation in /opt/spack-environment + mkdir {{ paths.environment }} && cd {{ paths.environment }} + cat << EOF > spack.yaml +{{ manifest }} +EOF + + # Install all the required software + . /opt/spack/share/spack/setup-env.sh + spack install + spack gc -y + spack env activate --sh -d . >> {{ paths.environment }}/environment_modifications.sh +{% if strip %} + + # Strip the binaries to reduce the size of the image + find -L {{ paths.view }}/* -type f -exec readlink -f '{}' \; | \ + xargs file -i | \ + grep 'charset=binary' | \ + grep 'x-executable\|x-archive\|x-sharedlib' | \ + awk -F: '{print $1}' | xargs strip -s +{% endif %} +{% if extra_instructions.build %} +{{ extra_instructions.build }} +{% endif %} + + +{% if apps %} +{% for application, help_text in apps.items() %} + +%apprun {{ application }} + exec /opt/view/bin/{{ application }} "$@" + +%apphelp {{ application }} + {{help_text }} +{% endfor %} +{% endif %} + +Bootstrap: docker +From: {{ run.image }} +Stage: final + +%files from build + {{ paths.environment }} /opt + {{ paths.store }} /opt + {{ paths.view }} /opt + {{ paths.environment }}/environment_modifications.sh {{ paths.environment }}/environment_modifications.sh + +%post +{% if os_packages.list %} + # Update, install and cleanup of system packages + {{ os_packages.update }} + {{ os_packages.install }} {{ os_packages.list | join | replace('\n', ' ') }} + {{ os_packages.clean }} +{% endif %} + # Modify the environment without relying on sourcing shell specific files at startup + cat {{ paths.environment }}/environment_modifications.sh >> $SINGULARITY_ENVIRONMENT +{% if extra_instructions.final %} +{{ extra_instructions.final }} +{% endif %} + +{% if runscript %} +%runscript +{{ runscript }} +{% endif %} + +{% if startscript %} +%startscript +{{ startscript }} +{% endif %} + +{% if test %} +%test +{{ test }} +{% endif %} + +{% if help %} +%help +{{ help }} +{% endif %} + +{% if labels %} +%labels +{% for label, value in labels.items() %} + {{ label }} {{ value }} +{% endfor %} +{% endif %} \ No newline at end of file