hpccm.Stage

[docs] module hpccm.Stage

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# 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

"""Container stage"""


import logging # pylint: disable=unused-import

import hpccm.config

from hpccm.common import container_type
from hpccm.primitives.baseimage import baseimage

class Stage(object):
    """Class for container stages.

    Docker may have one or more stages,
       Singularity will always have a single stage.

    Args:
        name: Name to use when refering to the stage (Docker specific).
            The default is an empty string.
        separator: Separator to insert between stages.  The default is
            '\\n\\n'.
    """

    def __init__(self, **kwargs):

        self.__layers = []
        self.name = kwargs.get('name', '')
        self.__separator = kwargs.get('separator', '\n\n')

    def __iadd__(self, layer):
        """Add the layer to the stage.  Allows "+=" syntax."""

        # The name of the stage should reflect the name the user
        # provided in the baseimage primitive (via the _as parameter).
        # This violates the encapsulation of the baseimage primitive.
        if layer.__class__.__name__ == 'baseimage' and not self.name:
          self.name = layer._baseimage__as

        if isinstance(layer, list):
            self.__layers.extend(layer)
        else:
            self.__layers.append(layer)
        return self

    def __len__(self):
        """Return number of layers"""
        return len(self.__layers)

    def __str__(self):
        """String representation of the stage"""
        return self.__separator.join(str(x) for x in self.__layers)

    def baseimage(self, image, _distro=''):
        """Insert the baseimage as the first layer

        Args:
            image (string): The image identifier to use as the base image.
                The value is passed to the `baseimage` primitive.
            _distro: The underlying Linux distribution of the base image.
                The value is passed to the `baseimage` primitive.
        """
        if image:
            self.__layers.insert(0, baseimage(image=image, _as=self.name,
                                              _distro=_distro))

    def runtime(self, _from=None, exclude=[]):
        """Generate the set of instructions to install the runtime specific
        components from a previous stage.

        This method invokes the runtime() method for every layer in
        the stage.  If a layer does not have a runtime() method, then
        it is skipped.

        Args:
            _from: The name of the stage from which to copy the runtime.
                The default is `0`.
            exclude: List of building blocks to exclude when generating
                the runtime. The default is an empty list.

        Examples:
            ```python
            Stage0 += baseimage(image='nvidia/cuda:9.0-devel')
            Stage0 += gnu()
            Stage0 += boost()
            Stage0 += ofed()
            Stage0 += openmpi()
            ...
            Stage1 += baseimage(image='nvidia/cuda:9.0-base')
            Stage1 += Stage0.runtime(exclude=['boost'])
            ```

        """

        # If the name of the stage is not explicitly specified, use
        # the name of the Stage if available, otherwise 0 (Docker's
        # default)
        if not _from and self.name:
            _from = self.name
        elif not _from:
            if hpccm.config.g_ctype == container_type.SINGULARITY:
                logging.warning('Multi-stage Singularity containers require a named first stage')
            _from = '0'

        instructions = []
        for layer in self.__layers:
            runtime = getattr(layer, 'runtime', None)
            if callable(runtime) and layer.__class__.__name__ not in exclude:
                inst = layer.runtime(_from=_from)
                if inst:
                    instructions.append(inst)

        return self.__separator.join(instructions)