Esempio n. 1
0
    def _copy(self, lines):
        """parse_add will copy multiple files from one location to another.
        This likely will need tweaking, as the files might need to be
        mounted from some location before adding to the image.
        The add command is done for an entire directory. It is also
        possible to have more than one file copied to a destination:
        https://docs.docker.com/engine/reference/builder/#copy
        e.g.: <src> <src> <dest>/
        """
        lines = self._setup("COPY", lines)

        for line in lines:

            # Take into account multistage builds
            layer = None
            if line.startswith("--from"):
                layer = line.strip("--from").split(" ")[0].lstrip("=")
                if layer not in self.recipe:
                    bot.warning(
                        "COPY requested from layer %s, but layer not previously defined."
                        % layer)
                    continue

                # Remove the --from from the line
                line = " ".join([l for l in line.split(" ")[1:] if l])

            values = line.split(" ")
            topath = values.pop()
            for frompath in values:
                self._add_files(frompath, topath, layer)
Esempio n. 2
0
    def _add_files(self, source, dest):
        '''add files is the underlying function called to add files to the
           list, whether originally called from the functions to parse archives,
           or https. We make sure that any local references are changed to
           actual file locations before adding to the files list.
     
           Parameters
           ==========
           source: the source
           dest: the destiation
        '''

        # Create data structure to iterate over

        paths = {'source': source, 'dest': dest}

        for pathtype, path in paths.items():
            if path == ".":
                paths[pathtype] = os.getcwd()

            # Warning if doesn't exist
            if not os.path.exists(path):
                bot.warning("%s doesn't exist, ensure exists for build" % path)

        # The pair is added to the files as a list
        self.files.append([paths['source'], paths['dest']])
Esempio n. 3
0
def _logs(self, print_logs=False, ext="out"):
    """A shared function to print log files. The only differing element is
    the extension (err or out)
    """
    from spython.utils import check_install

    check_install()

    # Formulate the path of the logs
    hostname = platform.node()
    logpath = os.path.join(
        get_userhome(),
        ".singularity",
        "instances",
        "logs",
        hostname,
        get_username(),
        "%s.%s" % (self.name, ext),
    )

    if os.path.exists(logpath):
        with open(logpath, "r") as filey:
            logs = filey.read()
        if print_logs is True:
            print(logs)
    else:
        bot.warning("No log files have been produced.")
    return logs
Esempio n. 4
0
    def _add_files(self, source, dest, layer=None):
        """add files is the underlying function called to add files to the
        list, whether originally called from the functions to parse archives,
        or https. We make sure that any local references are changed to
        actual file locations before adding to the files list.

        Parameters
        ==========
        source: the source
        dest: the destiation
        """

        # Warn the user Singularity doesn't support expansion
        if "*" in source:
            bot.warning(
                "Singularity doesn't support expansion, * found in %s" %
                source)

        # Warning if file/folder (src) doesn't exist
        if not os.path.exists(source) and layer is None:
            bot.warning("%s doesn't exist, ensure exists for build" % source)

        # The pair is added to the files as a list
        if not layer:
            self.recipe[self.active_layer].files.append([source, dest])

        # Unless the file is to be copied from a particular layer
        else:
            if layer not in self.recipe[self.active_layer].layer_files:
                self.recipe[self.active_layer].layer_files[layer] = []
            self.recipe[self.active_layer].layer_files[layer].append(
                [source, dest])
Esempio n. 5
0
    def _arg(self, line):
        """singularity doesn't have support for ARG, so instead will issue
        a warning to the console for the user to export the variable
        with SINGULARITY prefixed at build.

        Parameters
        ==========
        line: the line from the recipe file to parse for ARG

        """
        line = self._setup("ARG", line)

        # Args are treated like envars, so we add them to install
        environ = self.parse_env([x for x in line if "=" in x])
        self.recipe[self.active_layer].install += environ

        # Try to extract arguments from the line
        for arg in line:

            # An undefined arg cannot be used
            if "=" not in arg:
                bot.warning(
                    "ARG is not supported for Singularity, and must be defined with "
                    "a default to be parsed. Skipping %s" % arg)
                continue

            arg, value = arg.split("=", 1)
            arg = arg.strip()
            value = value.strip()
            bot.debug("Updating ARG %s to %s" % (arg, value))
            self.args[arg] = value
Esempio n. 6
0
def export(
    self,
    image_path,
    pipe=False,
    output_file=None,
    command=None,
    sudo=False,
    singularity_options=None,
):

    """export will export an image, sudo must be used. If we have Singularity
    versions after 3, export is replaced with building into a sandbox.

    Parameters
    ==========
    image_path: full path to image
    pipe: export to pipe and not file (default, False)
    singularity_options: a list of options to provide to the singularity client
    output_file: if pipe=False, export tar to this file. If not specified,
    will generate temporary directory.
    """
    from spython.utils import check_install

    check_install()

    if "version 3" in self.version() or "2.6" in self.version():

        # If export is deprecated, we run a build
        bot.warning(
            "Export is not supported for Singularity 3.x. Building to sandbox instead."
        )

        if output_file is None:
            basename, _ = os.path.splitext(image_path)
            output_file = self._get_filename(basename, "sandbox", pwd=False)

        return self.build(
            recipe=image_path,
            image=output_file,
            sandbox=True,
            force=True,
            sudo=sudo,
            singularity_options=singularity_options,
        )

    # If not version 3, run deprecated command
    elif "2.5" in self.version():
        return self._export(
            image_path=image_path,
            pipe=pipe,
            output_file=output_file,
            command=command,
            singularity_options=singularity_options,
        )

    bot.warning("Unsupported version of Singularity, %s" % self.version())
Esempio n. 7
0
    def version(self):
        '''return the version of singularity
        '''

        if not check_install():
            bot.warning("Singularity version not found, so it's likely not installed.")
        else:
            cmd = ['singularity','--version']
            version = self._run_command(cmd).strip('\n')
            bot.debug("Singularity %s being used." % version)  
            return version
Esempio n. 8
0
    def _from(self, line):
        ''' get the FROM container image name from a FROM line!

           Parameters
           ==========
           line: the line from the recipe file to parse for FROM

        '''
        self.fromHeader = self._setup('FROM', line)
        if "scratch" in self.fromHeader:
            bot.warning('scratch is no longer available on Docker Hub.')
        bot.debug('FROM %s' % self.fromHeader)
Esempio n. 9
0
    def _arg(self, line):
        """singularity doesn't have support for ARG, so instead will issue
           a warning to the console for the user to export the variable
           with SINGULARITY prefixed at build.
 
           Parameters
           ==========
           line: the line from the recipe file to parse for ARG
   
        """
        line = self._setup("ARG", line)
        bot.warning("ARG is not supported for Singularity! To get %s" % line[0])
        bot.warning("in the container, on host export SINGULARITY_%s" % line[0])
Esempio n. 10
0
    def _from(self, line):
        ''' get the FROM container image name from a FROM line!

           Parameters
           ==========
           line: the line from the recipe file to parse for FROM

        '''
        fromHeader = self._setup('FROM', line)

        # Singularity does not support AS level
        self.fromHeader = re.sub("AS .+", "", fromHeader[0], flags=re.I)

        if "scratch" in self.fromHeader:
            bot.warning('scratch is no longer available on Docker Hub.')
        bot.debug('FROM %s' % self.fromHeader)
Esempio n. 11
0
    def _from(self, line):
        """ get the FROM container image name from a FROM line!

           Parameters
           ==========
           line: the line from the recipe file to parse for FROM
           recipe: the recipe object to populate.
        """
        fromHeader = self._setup("FROM", line)

        # Singularity does not support AS level
        self.recipe.fromHeader = re.sub("AS .+", "", fromHeader[0], flags=re.I)

        if "scratch" in self.recipe.fromHeader:
            bot.warning("scratch is no longer available on Docker Hub.")
        bot.debug("FROM %s" % self.recipe.fromHeader)
Esempio n. 12
0
def export(self,
           image_path,
           pipe=False,
           output_file=None,
           command=None,
           sudo=False):
    '''export will export an image, sudo must be used. If we have Singularity
       versions after 3, export is replaced with building into a sandbox.

       Parameters
       ==========
       image_path: full path to image
       pipe: export to pipe and not file (default, False)
       output_file: if pipe=False, export tar to this file. If not specified, 
       will generate temporary directory.
    '''
    from spython.utils import check_install
    check_install()

    if 'version 3' in self.version() or '2.6' in self.version():

        # If export is deprecated, we run a build
        bot.warning(
            'Export is not supported for Singularity 3.x. Building to sandbox instead.'
        )

        if output_file is None:
            basename, _ = os.path.splitext(image_path)
            output_file = self._get_filename(basename, 'sandbox', pwd=False)

        return self.build(recipe=image_path,
                          image=output_file,
                          sandbox=True,
                          force=True,
                          sudo=sudo)

    # If not version 3, run deprecated command
    elif '2.5' in self.version():
        return self._export(image_path=image_path,
                            pipe=pipe,
                            output_file=output_file,
                            command=command)

    bot.warning('Unsupported version of Singularity, %s' % self.version())
Esempio n. 13
0
    def get_hash(self, image=None):
        """return an md5 hash of the file based on a criteria level. This
        is intended to give the file a reasonable version. This only is
        useful for actual image files.

        Parameters
        ==========
        image: the image path to get hash for (first priority). Second
               priority is image path saved with image object, if exists.

        """
        hasher = hashlib.md5()
        image = image or self.image

        if os.path.exists(image):
            with open(image, "rb") as f:
                for chunk in iter(lambda: f.read(4096), b""):
                    hasher.update(chunk)
                return hasher.hexdigest()

        bot.warning("%s does not exist." % image)
Esempio n. 14
0
    def _load_section(self, lines, section, layer=None):
        """read in a section to a list, and stop when we hit the next section"""
        members = []

        while True:

            if not lines:
                break
            next_line = lines[0]

            # We have a start of another bootstrap
            if re.search("bootstrap:", next_line, re.IGNORECASE):
                break

            # The end of a section
            if next_line.strip().startswith("%"):
                break

            # Still in current section!
            else:
                new_member = lines.pop(0).strip()
                if new_member not in ["", None]:
                    members.append(new_member)

        # Add the list to the config
        if members and section is not None:

            # Get the correct parsing function
            parser = self._get_mapping(section)

            # Parse it, if appropriate
            if not parser:
                bot.warning("%s is an unrecognized section, skipping." %
                            section)
            else:
                if section == "files":
                    parser(members, layer)
                else:
                    parser(members)
Esempio n. 15
0
    def _from(self, line):
        """get the FROM container image name from a FROM line. If we have
        already seen a FROM statement, this is indicative of adding
        another image (multistage build).

        Parameters
        ==========
        line: the line from the recipe file to parse for FROM
        recipe: the recipe object to populate.
        """
        fromHeader = self._setup("FROM", line)

        # Do we have a multistge build to update the active layer?
        self._multistage(fromHeader[0])

        # Now extract the from header, make args replacements
        self.recipe[self.active_layer].fromHeader = self._replace_from_dict(
            re.sub("AS .+", "", fromHeader[0], flags=re.I), self.args)

        if "scratch" in self.recipe[self.active_layer].fromHeader:
            bot.warning("scratch is no longer available on Docker Hub.")
        bot.debug("FROM %s" % self.recipe[self.active_layer].fromHeader)
Esempio n. 16
0
    def _run(self, lines):
        """_parse the runscript to be the Docker CMD. If we have one line,
        call it directly. If not, write the entrypoint into a script.

        Parameters
        ==========
        lines: the line from the recipe file to parse for CMD

        """
        lines = [x for x in lines if x not in ["", None]]

        # Default runscript is first index
        runscript = lines[0]

        # Multiple line runscript needs multiple lines written to script
        if len(lines) > 1:

            bot.warning("More than one line detected for runscript!")
            bot.warning("These will be echoed into a single script to call.")
            self._write_script("/entrypoint.sh", lines)
            runscript = "/bin/bash /entrypoint.sh"

        self.recipe[self.active_layer].cmd = runscript
Esempio n. 17
0
    def _add_files(self, source, dest):
        '''add files is the underlying function called to add files to the
           list, whether originally called from the functions to parse archives,
           or https. We make sure that any local references are changed to
           actual file locations before adding to the files list.
     
           Parameters
           ==========
           source: the source
           dest: the destiation
        '''

        # Warn the user Singularity doesn't support expansion
        if '*' in source:
            bot.warning(
                "Singularity doesn't support expansion, * found in %s" %
                source)

        # Warning if file/folder (src) doesn't exist
        if not os.path.exists(source):
            bot.warning("%s doesn't exist, ensure exists for build" % source)

        # The pair is added to the files as a list
        self.recipe.files.append([source, dest])
Esempio n. 18
0
    def _setup(self, lines):
        """setup required adding content from the host to the rootfs,
        so we try to capture with with ADD.
        """
        bot.warning("SETUP is error prone, please check output.")

        for line in lines:

            # For all lines, replace rootfs with actual root /
            line = re.sub("[$]{?SINGULARITY_ROOTFS}?", "",
                          "$SINGULARITY_ROOTFS")

            # If we have nothing left, don't continue
            if line in ["", None]:
                continue

            # If the line starts with copy or move, assume is file from host
            if re.search("(^cp|^mv)", line):
                line = re.sub("(^cp|^mv)", "", line)
                self.files.append(line)

            # If it's a general command, add to install routine
            else:
                self.install.append(line)