예제 #1
0
 def validate(self):
     """validate that all (required) fields are included for the Docker
     recipe. We minimimally just need a FROM image, and must ensure
     it's in a valid format. If anything is missing, we exit with error.
     """
     if self.recipe is None:
         bot.exit("Please provide a Recipe() to the writer first.")
예제 #2
0
    def convert(self, runscript="/bin/bash", force=False):
        """docker2singularity will return a Singularity build recipe based on
        a the loaded recipe object. It doesn't take any arguments as the
        recipe object contains the sections, and the calling function
        determines saving / output logic.
        """
        self.validate()

        # Write single recipe that includes all layer
        recipe = []

        # Number of layers
        num_layers = len(self.recipe)
        count = 0

        # Write each layer to new file
        for stage, parser in self.recipe.items():

            # Set the first and active stage
            self.stage = stage

            # From header is required
            if parser.fromHeader is None:
                bot.exit("Singularity recipe requires a from header.")

            recipe += ["\n\n\nBootstrap: docker"]
            recipe += ["From: %s" % parser.fromHeader]
            recipe += ["Stage: %s\n\n\n" % stage]

            # TODO: stopped here - bug with files being found
            # Add global files, and then layer files
            recipe += self._create_section("files")
            for layer, files in parser.layer_files.items():
                recipe += create_keyval_section(files, "files", layer)

            # Sections with key value pairs
            recipe += self._create_section("labels")
            recipe += self._create_section("install", "post")
            recipe += self._create_section("environ", "environment")

            # If we are at the last layer, write the runscript
            if count == num_layers - 1:
                runscript = self._create_runscript(runscript, force)

                # If a working directory was used, add it as a cd
                if parser.workdir is not None:
                    runscript = ["cd " + parser.workdir] + [runscript]

                # Finish the recipe, also add as startscript
                recipe += finish_section(runscript, "runscript")
                recipe += finish_section(runscript, "startscript")

                if parser.test is not None:
                    recipe += finish_section(parser.test, "test")
            count += 1

        # Clean up extra white spaces
        recipe = "\n".join(recipe).replace("\n\n", "\n").strip("\n")
        return recipe.rstrip()
예제 #3
0
def compress(self, image_path):
    '''compress will (properly) compress an image'''
    if os.path.exists(image_path):
        compressed_image = "%s.gz" % image_path
        os.system('gzip -c -6 %s > %s' % (image_path, compressed_image))
        return compressed_image

    bot.exit("Cannot find image %s" % image_path)
예제 #4
0
    def validate(self):
        '''validate that all (required) fields are included for the Docker
           recipe. We minimimally just need a FROM image, and must ensure
           it's in a valid format. If anything is missing, we exit with error.
        '''
        if self.recipe is None:
            bot.exit('Please provide a Recipe() to the writer first.')

        if self.recipe.fromHeader is None:
            bot.exit("Singularity recipe requires a from header.")
예제 #5
0
def decompress(self, image_path, quiet=True):
    """decompress will (properly) decompress an image"""

    if not os.path.exists(image_path):
        bot.exit("Cannot find image %s" % image_path)

    extracted_file = image_path.replace(".gz", "")
    cmd = ["gzip", "-d", "-f", image_path]
    self.run_command(cmd, quiet=quiet)  # exits if return code != 0
    return extracted_file
예제 #6
0
def decompress(self, image_path, quiet=True):
    '''decompress will (properly) decompress an image'''

    if not os.path.exists(image_path):
        bot.exit("Cannot find image %s" % image_path)

    extracted_file = image_path.replace('.gz', '')
    cmd = ['gzip', '-d', '-f', image_path]
    result = self.run_command(cmd, quiet=quiet)  # exits if return code != 0
    return extracted_file
예제 #7
0
def inspect(self, image=None, json=True, app=None, quiet=True):
    '''inspect will show labels, defile, runscript, and tests for an image
    
       Parameters
       ==========
       image: path of image to inspect
       json: print json instead of raw text (default True)
       quiet: Don't print result to the screen (default True)
       app: if defined, return help in context of an app

    '''
    check_install()

    # No image provided, default to use the client's loaded image
    if not image:
        image = self._get_uri()

    # If there still isn't an image, exit on error
    if not image:
        bot.exit('Please provide an image to inspect.')

    cmd = self._init_command('inspect')
    if app:
        cmd = cmd + ['--app', app]

    options = ['e', 'd', 'l', 'r', 'hf', 't']

    # After Singularity 3.0, helpfile was changed to H from

    if "version 3" in self.version():
        options = ['e', 'd', 'l', 'r', 'H', 't']

    for x in options:
        cmd.append('-%s' % x)

    if json:
        cmd.append('--json')

    cmd.append(image)
    result = run_command(cmd, quiet=quiet)

    if result['return_code'] == 0:
        result = jsonp.loads(result['message'][0])

        # Unify output to singularity 3 format
        if "data" in result:
            result = result['data']

        # Fix up labels
        result = parse_labels(result)

        if not quiet:
            print(jsonp.dumps(result, indent=4))

    return result
예제 #8
0
    def _run_checks(self):
        """basic sanity checks for the file name (and others if needed) before
           attempting parsing.
        """
        if self.filename is not None:

            # Does the recipe provided exist?
            if not os.path.exists(self.filename):
                bot.exit("Cannot find %s, is the path correct?" % self.filename)

            # Ensure we carry fullpath
            self.filename = os.path.abspath(self.filename)
예제 #9
0
    def validate_stage(self, parser):
        """Given a recipe parser for a stage, ensure that the recipe is valid"""
        if parser.fromHeader is None:
            bot.exit("Dockerfile requires a fromHeader.")

            # Parse the provided name
            uri_regexes = [_reduced_uri, _default_uri, _docker_uri]

            for r in uri_regexes:
                match = r.match(parser.fromHeader)
                if match:
                    break

            if not match:
                bot.exit("FROM header %s not valid." % parser.fromHeader)
예제 #10
0
def pull(self, image=None, name=None, pull_folder='', ext="simg"):
    '''pull will pull a singularity hub or Docker image
        
       Parameters
       ==========
       image: the complete image uri. If not provided, the client loaded is used
       pull_folder: if not defined, pulls to $PWD (''). If defined, pulls to
                    user specified location instead.

       Docker and Singularity Hub Naming
       ---------------------------------
       name: a custom name to use, to override default
       ext: if no name specified, the default extension to use.

    '''
    self.check_install()
    cmd = self._init_command('pull')

    # No image provided, default to use the client's loaded image
    if image is None:
        image = self._get_uri()

    # If it's still None, no go!
    if image is None:
        bot.exit('You must provide an image uri, or use client.load() first.')

    # Singularity Only supports shub and Docker pull
    if not re.search('^(shub|docker)://', image):
        bot.exit("pull only valid for docker and shub. Use sregistry client.")

    # Did the user ask for a custom pull folder?
    if pull_folder:
        self.setenv('SINGULARITY_PULLFOLDER', pull_folder)

    # If we still don't have a custom name, base off of image uri.
    if name is None:
        name = self._get_filename(image, ext)

    cmd = cmd + ["--name", name]

    cmd.append(image)
    bot.info(' '.join(cmd))
    self._run_command(cmd, capture=False)
    final_image = os.path.join(pull_folder, name)

    if os.path.exists(final_image):
        bot.info(final_image)
    return final_image
예제 #11
0
    def get_container_id(self, container_id=None):
        """a helper function shared between functions that will return a
        container_id. First preference goes to a container_id provided by
        the user at runtime. Second preference goes to the container_id
        instantiated with the client.

        Parameters
        ==========
        container_id: image uri to parse (required)
        """

        # The user must provide a container_id, or have one with the client
        if container_id is None and self.container_id is None:
            bot.exit("You must provide a container_id.")

        # Choose whichever is not None, with preference for function provided
        container_id = container_id or self.container_id
        return container_id
예제 #12
0
def create(self, image_path, size=1024, sudo=False):
    '''create will create a a new image

        Parameters
        ==========
        image_path: full path to image
        size: image sizein MiB, default is 1024MiB
        filesystem: supported file systems ext3/ext4 (ext[2/3]: default ext3

    '''
    self.check_install()
    cmd = self.init_command('image.create')
    cmd = cmd + ['--size', str(size), image_path]

    output = self.run_command(cmd, sudo=sudo)
    self.println(output)

    if not os.path.exists(image_path):
        bot.exit("Could not create image %s" % image_path)

    return image_path
예제 #13
0
def export(self, image_path, tmptar=None):
    '''export will export an image, sudo must be used.

       Parameters
       ==========
   
       image_path: full path to image
       tmptar: if defined, use custom temporary path for tar export

    '''
    from spython.utils import check_install
    check_install()

    if 'version 3' in self.version():
        bot.exit('export is deprecated after Singularity 2.*')

    if tmptar is None:
        tmptar = "/%s/tmptar.tar" %(tempfile.mkdtemp())
    cmd = ['singularity', 'image.export', '-f', tmptar, image_path]

    self.run_command(cmd, sudo=False)
    return tmptar
예제 #14
0
    def write(self, output_file=None, force=False):
        '''convert a recipe to a specified format, and write to file, meaning
           we use the loaded recipe to write to an output file.
           If the output file is not specified, a temporary file is used.

           Parameters
           ==========
           output_file: the file to save to, not required (estimates default)
           force: if True, if file exists, over-write existing file

        '''
        if output_file is None:
            output_file = self._get_conversion_outfile()

        # Cut out early if file exists and we aren't overwriting
        if os.path.exists(output_file) and not force:
            bot.exit('%s exists, and force is False.' % output_file)

        # Do the conversion if function is provided by subclass
        if hasattr(self, 'convert'):
            converted = self.convert()
            bot.info('Saving to %s' % output_file)
            write_file(output_file, converted)
예제 #15
0
def create(self, image_path, size=1024, sudo=False, singularity_options=None):
    """create will create a a new image

    Parameters
    ==========
    image_path: full path to image
    size: image sizein MiB, default is 1024MiB
    filesystem: supported file systems ext3/ext4 (ext[2/3]: default ext3
    singularity_options: a list of options to provide to the singularity client
    """
    from spython.utils import check_install

    check_install()

    cmd = self.init_command("image.create", singularity_options)
    cmd = cmd + ["--size", str(size), image_path]

    output = self.run_command(cmd, sudo=sudo)
    self.println(output)

    if not os.path.exists(image_path):
        bot.exit("Could not create image %s" % image_path)

    return image_path
예제 #16
0
    def validate(self):
        """validate that all (required) fields are included for the Docker
           recipe. We minimimally just need a FROM image, and must ensure
           it's in a valid format. If anything is missing, we exit with error.
        """
        if self.recipe is None:
            bot.exit("Please provide a Recipe() to the writer first.")

        if self.recipe.fromHeader is None:
            bot.exit("Dockerfile requires a fromHeader.")

            # Parse the provided name
            uri_regexes = [_reduced_uri, _default_uri, _docker_uri]

            for r in uri_regexes:
                match = r.match(self.recipe.fromHeader)
                if match:
                    break

            if not match:
                bot.exit("FROM header %s not valid." % self.recipe.fromHeader)
예제 #17
0
def start(self,
          image=None,
          name=None,
          args=None,
          sudo=False,
          options=None,
          capture=False):
    """start an instance. This is done by default when an instance is created.

       Parameters
       ==========
       image: optionally, an image uri (if called as a command from Client)
       name: a name for the instance
       sudo: if the user wants to run the command with sudo
       capture: capture output, default is False. With True likely to hang.
       args: arguments to provide to the instance (supported Singularity 3.1+)
       options: a list of tuples, each an option to give to the start command
                [("--bind", "/tmp"),...]

       USAGE: 
       singularity [...] instance.start [...] <container path> <instance name>

    """
    from spython.utils import run_command, check_install

    check_install()

    # If name provided, over write robot (default)
    if name is not None:
        self.name = name

    # If an image isn't provided, we have an initialized instance
    if image is None:

        # Not having this means it was called as a command, without an image
        if not hasattr(self, "_image"):
            bot.exit("Please provide an image, or create an Instance first.")

        image = self._image

    # Derive subgroup command based on singularity version
    subgroup = "instance.start"
    if "version 3" in self.version():
        subgroup = ["instance", "start"]

    cmd = self._init_command(subgroup)

    # Add options, if they are provided
    if not isinstance(options, list):
        options = [] if options is None else options.split(" ")

    # Assemble the command!
    cmd = cmd + options + [image, self.name]

    # If arguments are provided
    if args is not None:
        if not isinstance(args, list):
            args = [args]
        cmd = cmd + args

    # Save the options and cmd, if the user wants to see them later
    self.options = options
    self.args = args
    self.cmd = cmd

    output = run_command(cmd, sudo=sudo, quiet=True, capture=capture)

    if output["return_code"] == 0:
        self._update_metadata()

    else:
        message = "%s : return code %s" % (output["message"],
                                           output["return_code"])
        bot.error(message)

    return self
예제 #18
0
def execute(
    self,
    image=None,
    command=None,
    app=None,
    writable=False,
    contain=False,
    bind=None,
    stream=False,
    nv=False,
    return_result=False,
    options=None,
    singularity_options=None,
    sudo=False,
    sudo_options=None,
    quiet=True,
    environ=None,
):
    """execute: send a command to a container

    Parameters
    ==========

    image: full path to singularity image
    command: command to send to container
    app: if not None, execute a command in context of an app
    writable: This option makes the file system accessible as read/write
    contain: This option disables the automatic sharing of writable
             filesystems on your host
    options: an optional list of options to provide to execute.
    singularity_options: a list of options to provide to the singularity client
    bind: list or single string of bind paths.
         This option allows you to map directories on your host system to
         directories within your container using bind mounts
    nv: if True, load Nvidia Drivers in runtime (default False)
    return_result: if True, return entire json object with return code
                   and message result not (default)
    quiet: Do not print verbose output.
    environ: extra environment to add.
    """
    from spython.utils import check_install

    check_install()

    cmd = self._init_command("exec", singularity_options)

    # nv option leverages any GPU cards
    if nv:
        cmd += ["--nv"]

    # If the image is given as a list, it's probably the command
    if isinstance(image, list):
        command = image
        image = None

    if command is not None:

        # No image provided, default to use the client's loaded image
        if image is None:
            image = self._get_uri()
            self.quiet = True

        # If an instance is provided, grab it's name
        if isinstance(image, self.instance):
            image = image.get_uri()

        # If image is still None, not defined by user or previously with client
        if image is None:
            bot.exit("Please load or provide an image.")

        # Does the user want to use bind paths option?
        if bind is not None:
            cmd += self._generate_bind_list(bind)

        # Does the user want to run an app?
        if app is not None:
            cmd = cmd + ["--app", app]

        if writable:
            cmd.append("--writable")

        # Add additional options
        if options is not None:
            cmd = cmd + options

        if not isinstance(command, list):
            command = command.split(" ")

        cmd = cmd + [image] + command

        # Does the user want to see the command printed?
        if not (quiet or self.quiet):
            bot.info(" ".join(cmd))

        if not stream:
            return self._run_command(
                cmd,
                sudo=sudo,
                sudo_options=sudo_options,
                return_result=return_result,
                quiet=quiet,
                environ=environ,
            )
        return stream_command(cmd, sudo=sudo, sudo_options=sudo_options)

    bot.exit("Please include a command (list) to execute.")
예제 #19
0
def build(self,
          recipe=None,
          image=None,
          isolated=False,
          sandbox=False,
          writable=False,
          build_folder=None,
          robot_name=False,
          ext='sif',
          sudo=True,
          stream=False,
          force=False,
          options=None,
          quiet=False,
          return_result=False):
    '''build a singularity image, optionally for an isolated build
       (requires sudo). If you specify to stream, expect the image name
       and an iterator to be returned.
       
       image, builder = Client.build(...)

       Parameters
       ==========

       recipe: the path to the recipe file (or source to build from). If not
                  defined, we look for "Singularity" file in $PWD
       image: the image to build (if None, will use arbitary name
       isolated: if True, run build with --isolated flag
       sandbox: if True, create a writable sandbox
       writable: if True, use writable ext3 (sandbox takes preference)
       build_folder: where the container should be built.
       ext: the image extension to use.
       robot_name: boolean, default False. if you don't give your image a 
                   name (with "image") then a fun robot name will be generated
                   instead. Highly recommended :) 
       sudo: give sudo to the command (or not) default is True for build
       options: for all other options, specify them in this list.   
       quiet: quiet verbose printing from the client.
       return_result: if True, return complete error code / message dictionary
    '''
    from spython.utils import check_install
    check_install()

    cmd = self._init_command('build')

    # If no extra options
    if not options:
        options = []

    if 'version 3' in self.version():
        ext = 'sif'

    # Force the build if the image / sandbox exists
    if force:
        cmd.append('--force')

    # No image provided, default to use the client's loaded image
    if recipe is None:
        recipe = self._get_uri()

    # If it's still None, try default build recipe
    if recipe is None:
        recipe = 'Singularity'

        if not os.path.exists(recipe):
            bot.exit('Cannot find %s, exiting.' % image)

    if image is None:
        if re.search('(docker|shub)://', recipe) and not robot_name:
            image = self._get_filename(recipe, ext)
        else:
            image = "%s.%s" % (self.RobotNamer.generate(), ext)

    # Does the user want a custom build folder?
    if build_folder is not None:
        if not os.path.exists(build_folder):
            bot.exit('%s does not exist!' % build_folder)
        image = os.path.join(build_folder, image)

    # The user wants to run an isolated build
    if isolated:
        cmd.append('--isolated')

    if sandbox:
        cmd.append('--sandbox')

    elif writable:
        cmd.append('--writable')

    cmd = cmd + options + [image, recipe]

    if not stream:
        self._run_command(cmd,
                          sudo=sudo,
                          quiet=quiet,
                          return_result=return_result,
                          capture=False)

    else:
        # Here we return the expected image, and an iterator!
        # The caller must iterate over
        return image, stream_command(cmd, sudo=sudo)

    if os.path.exists(image):
        return image
예제 #20
0
def build(self,
          recipe=None,
          image=None,
          isolated=False,
          sandbox=False,
          writable=False,
          build_folder=None,
          robot_name=False,
          ext='simg',
          sudo=True,
          stream=False):
    '''build a singularity image, optionally for an isolated build
       (requires sudo). If you specify to stream, expect the image name
       and an iterator to be returned.
       
       image, builder = Client.build(...)

       Parameters
       ==========

       recipe: the path to the recipe file (or source to build from). If not
                  defined, we look for "Singularity" file in $PWD
       image: the image to build (if None, will use arbitary name
       isolated: if True, run build with --isolated flag
       sandbox: if True, create a writable sandbox
       writable: if True, use writable ext3 (sandbox takes preference)
       build_folder: where the container should be built.
       ext: the image extension to use.
       robot_name: boolean, default False. if you don't give your image a 
                   name (with "image") then a fun robot name will be generated
                   instead. Highly recommended :) 
       sudo: give sudo to the command (or not) default is True for build
    
    '''
    self.check_install()
    cmd = self._init_command('build')

    # No image provided, default to use the client's loaded image
    if recipe is None:
        recipe = self._get_uri()

    # If it's still None, try default build recipe
    if recipe is None:
        recipe = 'Singularity'

        if not os.path.exists(recipe):
            bot.exit('Cannot find %s, exiting.' % image)

    if image is None:
        if re.search('(docker|shub)://', recipe) and robot_name is False:
            image = self._get_filename(recipe, ext)
        else:
            image = "%s.%s" % (self.RobotNamer.generate(), ext)

    # Does the user want a custom build folder?
    if build_folder is not None:
        if not os.path.exists(build_folder):
            bot.exit('%s does not exist!' % build_folder)

        image = "%s/%s" % (build_folder, image)

    if isolated is True:
        cmd.append('--isolated')

    if sandbox is True:
        cmd.append('--sandbox')
    elif sandbox is True:
        cmd.append('--writable')

    cmd = cmd + [image, recipe]

    if stream is False:
        output = self._run_command(cmd, sudo=sudo, capture=False)
    else:
        # Here we return the expected image, and an iterator!
        # The caller must iterate over
        return image, stream_command(cmd, sudo=sudo)

    if os.path.exists(image):
        return image
예제 #21
0
def inspect(self,
            image=None,
            json=True,
            app=None,
            quiet=True,
            singularity_options=None):
    """inspect will show labels, defile, runscript, and tests for an image

    Parameters
    ==========
    image: path of image to inspect
    json: print json instead of raw text (default True)
    quiet: Don't print result to the screen (default True)
    app: if defined, return help in context of an app
    singularity_options: a list of options to provide to the singularity client

    """
    check_install()

    # No image provided, default to use the client's loaded image
    if not image:
        image = self._get_uri()

    # If there still isn't an image, exit on error
    if not image:
        bot.exit("Please provide an image to inspect.")

    cmd = self._init_command("inspect", singularity_options)
    if app:
        cmd = cmd + ["--app", app]

    options = ["e", "d", "l", "r", "hf", "t"]

    # After Singularity 3.0, helpfile was changed to H from

    if "version 3" in self.version():
        options = ["e", "d", "l", "r", "H", "t"]

    for x in options:
        cmd.append("-%s" % x)

    if json:
        cmd.append("--json")

    cmd.append(image)
    result = run_command(cmd, quiet=quiet)

    if result["return_code"] == 0:
        result = jsonp.loads(result["message"][0])

        # Unify output to singularity 3 format
        if "data" in result:
            result = result["data"]

        # Fix up labels
        result = parse_labels(result)

        if not quiet:
            print(jsonp.dumps(result, indent=4))

    return result
예제 #22
0
def list_instances(
    self,
    name=None,
    return_json=False,
    quiet=False,
    sudo=False,
    sudo_options=None,
    singularity_options=None,
):
    """list instances. For Singularity, this is provided as a command sub
    group.

    singularity instance list

    Return codes provided are different from standard linux:
    see https://github.com/singularityware/singularity/issues/1706
    Since we expect json output, we don't support older versions of Singularity.

    Parameters
    ==========
    return_json: return a json list of instances instead of objects (False)
    name: if defined, return the list for just one instance (used to ged pid)
    singularity_options: a list of options to provide to the singularity client

    Return Code  --   Reason
    0 -- Instances Found
    1 -- No Instances, libexecdir value not found, functions file not found
    255 -- Couldn't get UID

    """
    from spython.utils import check_install

    check_install()

    subgroup = ["instance", "list", "--json"]

    if "version 3" not in self.version():
        bot.exit("This version of Singularity Python does not support < 3.0.")

    cmd = self._init_command(subgroup, singularity_options)

    # If the user has provided a name, we want to see a particular instance
    if name is not None:
        cmd.append(name)

    # Does the user want to see the command printed?
    if not (quiet or self.quiet):
        bot.info(" ".join(cmd))

    output = run_command(cmd, quiet=True, sudo=sudo, sudo_options=sudo_options)
    instances = []

    # Success, we have instances

    if output["return_code"] == 0:

        instances = json.loads(output["message"][0]).get("instances", {})

        # Does the user want instance objects instead?
        listing = []

        if not return_json:
            for i in instances:

                # If the user has provided a name, only add instance matches
                if name is not None:
                    if name != i["instance"]:
                        continue

                # Otherwise, add instances to the listing
                new_instance = self.instance(
                    pid=i.get("pid"),
                    ip_address=i.get("ip"),
                    name=i.get("instance") or i.get("daemon_name"),
                    log_err_path=i.get("logErrPath"),
                    log_out_path=i.get("logOutPath"),
                    image=i.get("img") or i.get("container_image"),
                    start=False,
                )

                listing.append(new_instance)
            instances = listing

    # Couldn't get UID

    elif output["return_code"] == 255:
        bot.error("Couldn't get UID")

    # Return code of 0
    else:
        bot.info("No instances found.")

    # If we are given a name, return just one
    if name is not None and instances and len(instances) == 1:
        instances = instances[0]

    return instances
예제 #23
0
 def _check_install(self):
     """ensure that singularity is installed, and exit if not.
     """
     if check_install() is not True:
         bot.exit("Cannot find Singularity! Is it installed?")
예제 #24
0
def _run(
    self,
    bundle,
    container_id=None,
    empty_process=False,
    log_path=None,
    pid_file=None,
    sync_socket=None,
    command="run",
    log_format="kubernetes",
    singularity_options=None,
):
    """_run is the base function for run and create, the only difference
    between the two being that run does not have an option for sync_socket.

    Equivalent command line example:
       singularity oci create [create options...] <container_ID>

    Parameters
    ==========
    bundle: the full path to the bundle folder
    container_id: an optional container_id. If not provided, use same
                  container_id used to generate OciImage instance
    empty_process: run container without executing container process (for
                   example, for a pod container waiting for signals). This
                   is a specific use case for tools like Kubernetes
    log_path: the path to store the log.
    pid_file: specify the pid file path to use
    sync_socket: the path to the unix socket for state synchronization.
    command: the command (run or create) to use (default is run)
    log_format: defaults to kubernetes. Can also be "basic" or "json"
    singularity_options: a list of options to provide to the singularity client

    """
    container_id = self.get_container_id(container_id)

    # singularity oci create
    cmd = self._init_command(command, singularity_options)

    # Check that the bundle exists
    if not os.path.exists(bundle):
        bot.exit("Bundle not found at %s" % bundle)

    # Add the bundle
    cmd = cmd + ["--bundle", bundle]

    # Additional Logging Files
    cmd = cmd + ["--log-format", log_format]

    if log_path is not None:
        cmd = cmd + ["--log-path", log_path]
    if pid_file is not None:
        cmd = cmd + ["--pid-file", pid_file]
    if sync_socket is not None:
        cmd = cmd + ["--sync-socket", sync_socket]
    if empty_process:
        cmd.append("--empty-process")

    # Finally, add the container_id
    cmd.append(container_id)

    # Generate the instance
    self._send_command(cmd, sudo=True)

    # Get the status to report to the user!
    return self.state(container_id, sudo=True, sync_socket=sync_socket)
예제 #25
0
def main(args, options, parser):
    """This function serves as a wrapper around the DockerParser,
    SingularityParser, DockerWriter, and SingularityParser converters.
    We can either save to file if args.outfile is defined, or print
    to the console if not.
    """
    # We need something to work with
    if not args.files:
        parser.print_help()
        sys.exit(1)

    # Get the user specified input and output files
    outfile = None
    if len(args.files) > 1:
        outfile = args.files[1]

    # First try to get writer and parser, if not defined will return None
    writer = get_writer(args.writer)
    parser = get_parser(args.parser)

    # If the user wants to auto-detect the type
    if args.parser == "auto":
        if "dockerfile" in args.files[0].lower():
            parser = get_parser("docker")
        elif "singularity" in args.files[0].lower():
            parser = get_parser("singularity")

    # If the parser still isn't defined, no go.
    if parser is None:
        bot.exit(
            "Please provide a Dockerfile or Singularity recipe, or define the --parser type."
        )

    # If the writer needs auto-detect
    if args.writer == "auto":
        if parser.name == "docker":
            writer = get_writer("singularity")
        else:
            writer = get_writer("docker")

    # If the writer still isn't defined, no go
    if writer is None:
        bot.exit("Please define the --writer type.")

    # Initialize the chosen parser
    recipeParser = parser(args.files[0])

    # By default, discover entrypoint / cmd from Dockerfile
    entrypoint = "/bin/bash"
    force = False

    if args.entrypoint is not None:
        entrypoint = args.entrypoint

        # This is only done if the user intended to print json here
        recipeParser.entrypoint = args.entrypoint
        recipeParser.cmd = None
        force = True

    if args.json:

        if outfile is not None:
            if not os.path.exists(outfile):
                if force:
                    write_json(outfile, recipeParser.recipe.json())
                else:
                    bot.exit("%s exists, set --force to overwrite." % outfile)
        else:
            print(json.dumps(recipeParser.recipe.json(), indent=4))

    else:

        # Do the conversion
        recipeWriter = writer(recipeParser.recipe)
        result = recipeWriter.convert(runscript=entrypoint, force=force)

        # If the user specifies an output file, save to it
        if outfile is not None:
            write_file(outfile, result)

        # Otherwise, convert and print to screen
        else:
            print(result)
예제 #26
0
def run(
    self,
    image=None,
    args=None,
    app=None,
    sudo=False,
    writable=False,
    contain=False,
    bind=None,
    stream=False,
    nv=False,
    options=None,
    return_result=False,
):
    """
        run will run the container, with or withour arguments (which
        should be provided in a list)
    
        Parameters
        ==========
        image: full path to singularity image
        args: args to include with the run 
        app: if not None, execute a command in context of an app
        writable: This option makes the file system accessible as read/write
        options: an optional list of options to provide to run.
        contain: This option disables the automatic sharing of writable
                 filesystems on your host
        bind: list or single string of bind paths.
              This option allows you to map directories on your host system to
              directories within your container using bind mounts
        stream: if True, return <generator> for the user to run
        nv: if True, load Nvidia Drivers in runtime (default False)
        return_result: if True, return entire json object with return code
             and message result (default is False)

    """
    from spython.utils import check_install

    check_install()

    cmd = self._init_command("run")

    # nv option leverages any GPU cards
    if nv:
        cmd += ["--nv"]

    # No image provided, default to use the client's loaded image
    if image is None:
        image = self._get_uri()

    # If an instance is provided, grab it's name
    if isinstance(image, self.instance):
        image = image.get_uri()

    # If image is still None, not defined by user or previously with client
    if image is None:
        bot.exit("Please load or provide an image.")

    # Does the user want to use bind paths option?
    if bind is not None:
        cmd += self._generate_bind_list(bind)

    # Does the user want to run an app?
    if app is not None:
        cmd = cmd + ["--app", app]

    # Does the user want writable?
    if writable:
        cmd.append("--writable")

    # Add options
    if options is not None:
        cmd = cmd + options

    cmd = cmd + [image]

    if args is not None:
        if not isinstance(args, list):
            args = args.split(" ")
        cmd = cmd + args

    if not stream:
        result = self._run_command(cmd, sudo=sudo, return_result=return_result)
    else:
        return stream_command(cmd, sudo=sudo)

    # If the user wants the raw result object
    if return_result:
        return result

    # Otherwise, we parse the result if it was successful
    if result:
        result = result.strip("\n")

        try:
            result = json.loads(result)
        except:
            pass
        return result
예제 #27
0
def pull(self,
         image=None,
         name=None,
         pull_folder='',
         ext=None,
         force=False,
         capture=False,
         stream=False,
         quiet=False):
    '''pull will pull a singularity hub or Docker image
        
       Parameters
       ==========
       image: the complete image uri. If not provided, the client loaded is used
       pull_folder: if not defined, pulls to $PWD (''). If defined, pulls to
                    user specified location instead.

       Docker and Singularity Hub Naming
       ---------------------------------
       name: a custom name to use, to override default
       ext: if no name specified, the default extension to use.

    '''
    from spython.utils import check_install
    check_install()

    cmd = self._init_command('pull')

    # Quiet is honored if set by the client, or user
    quiet = quiet or self.quiet

    if not ext:
        ext = 'sif' if 'version 3' in self.version() else 'simg'

    # No image provided, default to use the client's loaded image
    if image is None:
        image = self._get_uri()

    # If it's still None, no go!
    if image is None:
        bot.exit('You must provide an image uri, or use client.load() first.')

    # Singularity Only supports shub and Docker pull
    if not re.search('^(shub|docker)://', image):
        bot.exit("pull only valid for docker and shub. Use sregistry client.")

    # If we still don't have a custom name, base off of image uri.
    if name is None:
        name = self._get_filename(image, ext)

    if pull_folder:
        final_image = os.path.join(pull_folder, os.path.basename(name))

        # Regression Singularity 3.* onward, PULLFOLDER not honored
        # https://github.com/sylabs/singularity/issues/2788
        if 'version 3' in self.version():
            name = final_image
            pull_folder = None  # Don't use pull_folder
    else:
        final_image = name

    cmd = cmd + ["--name", name]

    if force:
        cmd = cmd + ["--force"]

    cmd.append(image)

    if not quiet:
        bot.info(' '.join(cmd))

    with ScopedEnvVar('SINGULARITY_PULLFOLDER', pull_folder):
        # Option 1: Streaming we just run to show user
        if not stream:
            self._run_command(cmd, capture=capture, quiet=quiet)

        # Option 3: A custom name we can predict (not commit/hash) and can also show
        else:
            return final_image, stream_command(cmd, sudo=False)

    if os.path.exists(final_image) and not quiet:
        bot.info(final_image)
    return final_image
예제 #28
0
def pull(self,
         image=None,
         name=None,
         pull_folder='',
         ext="simg",
         force=False,
         capture=False,
         name_by_commit=False,
         name_by_hash=False,
         stream=False):
    '''pull will pull a singularity hub or Docker image
        
       Parameters
       ==========
       image: the complete image uri. If not provided, the client loaded is used
       pull_folder: if not defined, pulls to $PWD (''). If defined, pulls to
                    user specified location instead.

       Docker and Singularity Hub Naming
       ---------------------------------
       name: a custom name to use, to override default
       ext: if no name specified, the default extension to use.

    '''
    from spython.utils import check_install
    check_install()

    cmd = self._init_command('pull')

    # No image provided, default to use the client's loaded image
    if image is None:
        image = self._get_uri()

    # If it's still None, no go!
    if image is None:
        bot.exit('You must provide an image uri, or use client.load() first.')

    # Singularity Only supports shub and Docker pull
    if not re.search('^(shub|docker)://', image):
        bot.exit("pull only valid for docker and shub. Use sregistry client.")

    # Did the user ask for a custom pull folder?
    if pull_folder:
        self.setenv('SINGULARITY_PULLFOLDER', pull_folder)

    # If we still don't have a custom name, base off of image uri.
    # Determine how to tell client to name the image, preference to hash

    if name_by_hash is True:
        cmd.append('--hash')

    elif name_by_commit is True:
        cmd.append('--commit')

    elif name is None:
        name = self._get_filename(image, ext)

    # Only add name if we aren't naming by hash or commit
    if not name_by_commit and not name_by_hash:
        cmd = cmd + ["--name", name]

    if force is True:
        cmd = cmd + ["--force"]

    cmd.append(image)
    bot.info(' '.join(cmd))

    # If name is still None, make empty string
    if name is None:
        name = ''

    final_image = os.path.join(pull_folder, name)

    # Option 1: For hash or commit, need return value to get final_image
    if name_by_commit or name_by_hash:

        # Set pull to temporary location
        tmp_folder = tempfile.mkdtemp()
        self.setenv('SINGULARITY_PULLFOLDER', tmp_folder)
        self._run_command(cmd, capture=capture)

        try:
            tmp_image = os.path.join(tmp_folder, os.listdir(tmp_folder)[0])
            final_image = os.path.join(pull_folder,
                                       os.path.basename(tmp_image))
            shutil.move(tmp_image, final_image)
            shutil.rmtree(tmp_folder)

        except:
            bot.error('Issue pulling image with commit or hash, try without?')

    # Option 2: Streaming we just run to show user
    elif stream is False:
        self._run_command(cmd, capture=capture)

    # Option 3: A custom name we can predict (not commit/hash) and can also show
    else:
        return final_image, stream_command(cmd, sudo=False)

    if os.path.exists(final_image):
        bot.info(final_image)
    return final_image
예제 #29
0
def build(
    self,
    recipe=None,
    image=None,
    isolated=False,
    sandbox=False,
    writable=False,
    build_folder=None,
    robot_name=False,
    ext="sif",
    sudo=True,
    stream=False,
    force=False,
    options=None,
    quiet=False,
    return_result=False,
    sudo_options=None,
    singularity_options=None,
):
    """build a singularity image, optionally for an isolated build
    (requires sudo). If you specify to stream, expect the image name
    and an iterator to be returned.

    image, builder = Client.build(...)

    Parameters
    ==========

    recipe: the path to the recipe file (or source to build from). If not
               defined, we look for "Singularity" file in $PWD
    image: the image to build (if None, will use arbitary name
    isolated: if True, run build with --isolated flag
    sandbox: if True, create a writable sandbox
    writable: if True, use writable ext3 (sandbox takes preference)
    build_folder: where the container should be built.
    ext: the image extension to use.
    robot_name: boolean, default False. if you don't give your image a
                name (with "image") then a fun robot name will be generated
                instead. Highly recommended :)
    sudo: give sudo to the command (or not) default is True for build
    sudo_options: options to pass to sudo (e.g. --preserve-env=SINGULARITY_CACHEDIR,SINGULARITY_TMPDIR)
    options: for all other options, specify them in this list.
    singularity_options: a list of options to provide to the singularity client
    quiet: quiet verbose printing from the client.
    return_result: if True, return complete error code / message dictionary
    """
    from spython.utils import check_install

    check_install()

    cmd = self._init_command("build", singularity_options)

    # If no extra options
    options = options or []

    if "version 3" in self.version():
        ext = "sif"

    # Force the build if the image / sandbox exists
    if force:
        cmd.append("--force")

    # No image provided, default to use the client's loaded image
    if recipe is None:
        recipe = self._get_uri()

    # If it's still None, try default build recipe
    if recipe is None:
        recipe = "Singularity"

        if not os.path.exists(recipe):
            bot.exit("Cannot find %s, exiting." % image)

    if image is None:
        if re.search("(docker|shub|library)://", recipe) and not robot_name:
            image = self._get_filename(recipe, ext)
        else:
            image = "%s.%s" % (self.RobotNamer.generate(), ext)

    # Does the user want a custom build folder?
    if build_folder is not None:
        if not os.path.exists(build_folder):
            bot.exit("%s does not exist!" % build_folder)
        image = os.path.join(build_folder, image)

    # The user wants to run an isolated build
    if isolated:
        cmd.append("--isolated")

    if sandbox:
        cmd.append("--sandbox")

    elif writable:
        cmd.append("--writable")

    cmd = cmd + options + [image, recipe]

    # Does the user want to see the command printed?
    if not (quiet or self.quiet):
        bot.info(" ".join(cmd))

    if not stream:
        self._run_command(
            cmd,
            sudo=sudo,
            sudo_options=sudo_options,
            quiet=quiet,
            return_result=return_result,
            capture=False,
        )

    else:
        # Here we return the expected image, and an iterator!
        # The caller must iterate over
        return image, stream_command(cmd, sudo=sudo, sudo_options=sudo_options)

    if os.path.exists(image):
        return image