diff --git a/docs/primitives.md b/docs/primitives.md index c61a253c..747ca7c5 100644 --- a/docs/primitives.md +++ b/docs/primitives.md @@ -199,6 +199,39 @@ __Examples__ environment(variables={'PATH': '/usr/local/bin:$PATH'}) ``` +# arg +```python +arg(self, **kwargs) +``` + +The `arg` primitive sets the corresponding environment +variables during the build time of a docker container. +Singularity and "bash" containers does not have a strict version of the +ARG keyword found on Dockerfiles but is possible to simulate +the behavior of this keyword as a build time parameter for the +Singularity and bash containers using environment variables. + +- __variables__: A dictionary of key / value pairs. The default is an +empty dictionary. + +__Examples__ + +```python +arg(variables={'HTTP_PROXY':'proxy.example.com', 'NO_PROXY':'example.com'}) +``` + +```bash + SINGULARITYENV_HTTP_PROXY="proxy.example.com" \ + SINGULARITYENV_NO_PROXY="example.com \ + singularity build image.sif recipe.def" +``` + +```bash + HTTP_PROXY="proxy.example.com" \ + NO_PROXY="example.com \ + recipe.sh +``` + # label ```python label(self, **kwargs) diff --git a/hpccm/primitives/__init__.py b/hpccm/primitives/__init__.py index 92addf38..0c5c3d55 100644 --- a/hpccm/primitives/__init__.py +++ b/hpccm/primitives/__init__.py @@ -14,7 +14,7 @@ from __future__ import absolute_import -__all__ = ['baseimage', 'blob', 'comment', 'copy', 'environment', 'label', +__all__ = ['arg', 'baseimage', 'blob', 'comment', 'copy', 'environment', 'label', 'raw', 'runscript', 'shell', 'user', 'workdir'] from hpccm.primitives.baseimage import baseimage @@ -28,3 +28,4 @@ from hpccm.primitives.shell import shell from hpccm.primitives.user import user from hpccm.primitives.workdir import workdir +from hpccm.primitives.arg import arg diff --git a/hpccm/primitives/arg.py b/hpccm/primitives/arg.py new file mode 100644 index 00000000..5237c344 --- /dev/null +++ b/hpccm/primitives/arg.py @@ -0,0 +1,93 @@ +# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. +# +# 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. + +# pylint: disable=invalid-name, too-few-public-methods + +"""Arg primitive""" + +from __future__ import absolute_import +from __future__ import unicode_literals +from __future__ import print_function + +import logging # pylint: disable=unused-import + +import hpccm.config + +from hpccm.common import container_type + +class arg(object): + """The `arg` primitive sets the corresponding environment + variables during the build time of a docker container. + Singularity and "bash" containers does not have a strict version of the + ARG keyword found on Dockerfiles but is possible to simulate + the behavior of this keyword as a build time parameter for the + Singularity and bash containers using environment variables. + + # Parameters + + variables: A dictionary of key / value pairs. The default is an + empty dictionary. + + # Examples + + ```python + arg(variables={'HTTP_PROXY':'proxy.example.com', 'NO_PROXY':'example.com'}) + + ```bash + SINGULARITYENV_HTTP_PROXY="proxy.example.com" \ + SINGULARITYENV_NO_PROXY="example.com \ + singularity build image.sif recipe.def" + ``` + + ```bash + HTTP_PROXY="proxy.example.com" \ + NO_PROXY="example.com \ + recipe.sh" + ``` + + """ + def __init__(self, **kwargs): + """Initialize primitive""" + self.__variables = kwargs.get('variables', {}) + + def __str__(self): + """String representation of the primitive""" + if self.__variables: + string = "" + num_vars = len(self.__variables) + variables = self.__variables + if hpccm.config.g_ctype == container_type.SINGULARITY: + if num_vars > 0: + string += "%post" + "\n" + for count, (key, val) in enumerate(sorted(variables.items())): + eol = "" if count == num_vars - 1 else "\n" + string += ' {0}=${{{0}:-"{1}"}}'.format(key, val) + eol + return string + elif hpccm.config.g_ctype == container_type.BASH: + for count, (key, val) in enumerate(sorted(variables.items())): + eol = "" if count == num_vars - 1 else "\n" + string += '{0}=${{{0}:-"{1}"}}'.format(key, val) + eol + return string + elif hpccm.config.g_ctype == container_type.DOCKER: + for count, (key, val) in enumerate(sorted(variables.items())): + eol = "" if count == num_vars - 1 else "\n" + if val == "": + string += 'ARG {0}'.format(key) + eol + else: + string += 'ARG {0}={1}'.format(key, val) + eol + return string + else: + raise RuntimeError('Unknown container type') + else: + return '' diff --git a/test/test_arg.py b/test/test_arg.py new file mode 100644 index 00000000..c9306362 --- /dev/null +++ b/test/test_arg.py @@ -0,0 +1,137 @@ +# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. +# +# 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. + +# pylint: disable=invalid-name, too-few-public-methods, bad-continuation + +"""Test cases for the arg module""" + +from __future__ import unicode_literals +from __future__ import print_function + +import logging # pylint: disable=unused-import +import unittest + +from helpers import bash, docker, invalid_ctype, singularity + +from hpccm.primitives.arg import arg + +class Test_arg(unittest.TestCase): + def setUp(self): + """Disable logging output messages""" + logging.disable(logging.ERROR) + + @docker + def test_empty(self): + """No arg specified""" + e = arg() + self.assertEqual(str(e), '') + + @invalid_ctype + def test_invalid_ctype(self): + """Invalid container type specified""" + e = arg(variables={'A': 'B'}) + with self.assertRaises(RuntimeError): + str(e) + + @docker + def test_single_docker(self): + """Single arg variable specified""" + e = arg(variables={'A': 'B'}) + self.assertEqual(str(e), 'ARG A=B') + + @docker + def test_single_docker_nodefault(self): + """Single arg variable specified (no default value)""" + e = arg(variables={'A': ''}) + self.assertEqual(str(e), 'ARG A') + + @singularity + def test_single_singularity(self): + """Single arg variable specified""" + e = arg(variables={'A': 'B'}) + self.assertEqual(str(e), '%post\n A=${A:-"B"}') + + @singularity + def test_single_singularity_nodefault(self): + """Single arg variable specified""" + e = arg(variables={'A': ''}) + self.assertEqual(str(e), '%post\n A=${A:-""}') + + @bash + def test_single_bash(self): + """Single arg variable specified""" + e = arg(variables={'A': 'B'}) + self.assertEqual(str(e), 'A=${A:-"B"}') + + @bash + def test_single_bash_nodefault(self): + """Single arg variable specified""" + e = arg(variables={'A': ''}) + self.assertEqual(str(e), 'A=${A:-""}') + + @docker + def test_multiple_docker(self): + """Multiple arg variables specified""" + e = arg(variables={'ONE': 1, 'TWO': 2, 'THREE': 3}) + self.assertEqual(str(e), +'''ARG ONE=1 +ARG THREE=3 +ARG TWO=2''') + + @docker + def test_multiple_docker_nodefault(self): + """Multiple arg variables specified (no default value)""" + e = arg(variables={'ONE': '', 'TWO': '', 'THREE': ''}) + self.assertEqual(str(e), +'''ARG ONE +ARG THREE +ARG TWO''') + + @singularity + def test_multiple_singularity(self): + """Multiple arg variables specified""" + e = arg(variables={'ONE': 1, 'TWO': 2, 'THREE': 3}) + self.assertEqual(str(e), +'''%post + ONE=${ONE:-"1"} + THREE=${THREE:-"3"} + TWO=${TWO:-"2"}''') + + @singularity + def test_multiple_singularity_nodefault(self): + """Multiple arg variables specified""" + e = arg(variables={'ONE':"", 'TWO':"", 'THREE':""}) + self.assertEqual(str(e), +'''%post + ONE=${ONE:-""} + THREE=${THREE:-""} + TWO=${TWO:-""}''') + + @bash + def test_multiple_bash(self): + """Multiple arg variables specified""" + e = arg(variables={'ONE': 1, 'TWO': 2, 'THREE': 3}) + self.assertEqual(str(e), +'''ONE=${ONE:-"1"} +THREE=${THREE:-"3"} +TWO=${TWO:-"2"}''') + + @bash + def test_multiple_bash_nodefault(self): + """Multiple arg variables specified""" + e = arg(variables={'ONE': "", 'TWO': "", 'THREE': ""}) + self.assertEqual(str(e), +'''ONE=${ONE:-""} +THREE=${THREE:-""} +TWO=${TWO:-""}''') \ No newline at end of file