Пример #1
0
def get_config(manifest, spec="Entrypoint", delim=None):
    '''get_config returns a particular spec (default is Entrypoint)
    from a VERSION 1 manifest obtained with get_manifest.
    :param manifest: the manifest obtained from get_manifest
    :param spec: the key of the spec to return, default is "Entrypoint"
    :param delim: Given a list, the delim to use to join the entries.
                  Default is newline
    '''
    cmd = None
    if "history" in manifest:
        history = manifest['history']
        for entry in manifest['history']:
            if 'v1Compatibility' in entry:
                entry = json.loads(entry['v1Compatibility'])
                if "config" in entry:
                    if spec in entry["config"]:
                        if entry["config"][spec] is not None:
                            cmd = entry["config"][spec]
                            break

    # Standard is to include commands like ['/bin/sh']
    if isinstance(cmd, list):
        if delim is not None:
            cmd = delim.join(cmd)
    bot.verbose3("Found Docker command (%s) %s" % (spec, cmd))

    return cmd
Пример #2
0
def get_config(manifest, spec="Entrypoint", delim=None):
    '''get_config returns a particular spec (default is Entrypoint)
    from a VERSION 1 manifest obtained with get_manifest.
    :param manifest: the manifest obtained from get_manifest
    :param spec: the key of the spec to return, default is "Entrypoint"
    :param delim: Given a list, the delim to use to join the entries.
                  Default is newline
    '''
    cmd = None
    if "history" in manifest:
        history = manifest['history']
        for entry in manifest['history']:
            if 'v1Compatibility' in entry:
                entry = json.loads(entry['v1Compatibility'])
                if "config" in entry:
                    if spec in entry["config"]:
                        if entry["config"][spec] is not None:
                            cmd = entry["config"][spec]
                            break

    # Standard is to include commands like ['/bin/sh']
    if isinstance(cmd, list):
        if delim is not None:
            cmd = delim.join(cmd)
    bot.verbose3("Found Docker command (%s) %s" % (spec, cmd))

    return cmd
Пример #3
0
    def update_token(self, response=None, auth=None):
        '''update_token uses HTTP basic authentication to get a token for
        Docker registry API V2 operations. We get here if a 401 is
        returned for a request.
        https://docs.docker.com/registry/spec/auth/token/
        '''
        if self.token_url is None:

            if response is None:
                response = self.get_tags(return_response=True)

            if not isinstance(response, HTTPError):
                bot.verbose3('Response on obtaining token is None.')
                return None

            not_asking_auth = "Www-Authenticate" not in response.headers
            if response.code != 401 or not_asking_auth:
                bot.error("Authentication error, exiting.")
                sys.exit(1)

            challenge = response.headers["Www-Authenticate"]
            regexp = '^Bearer\s+realm="(.+)",service="(.+)",scope="(.+)",?'
            match = re.match(regexp, challenge)

            if not match:
                bot.error("Unrecognized authentication challenge, exiting.")
                sys.exit(1)

            realm = match.group(1)
            service = match.group(2)
            scope = match.group(3).split(',')[0]

            self.token_url = ("%s?service=%s&expires_in=9000&scope=%s" %
                              (realm, service, scope))

        headers = dict()

        # First priority comes to auth supplied directly to function
        if auth is not None:
            headers.update(auth)

        # Second priority is default if supplied at init
        elif self.auth is not None:
            headers.update(self.auth)

        response = self.get(self.token_url,
                            default_headers=False,
                            headers=headers)

        try:
            token = json.loads(response)["token"]
            token = {"Authorization": "Bearer %s" % token}
            self.token = token
            self.update_headers(token)

        except Exception:
            msg = "Error getting token for repository "
            msg += "%s/%s, exiting." % (self.namespace, self.repo_name)
            bot.error(msg)
            sys.exit(1)
Пример #4
0
    def update_token(self, response=None, auth=None):
        '''update_token uses HTTP basic authentication to get a token for
        Docker registry API V2 operations. We get here if a 401 is
        returned for a request.
        https://docs.docker.com/registry/spec/auth/token/
        '''
        if self.token_url is None:

            if response is None:
                response = self.get_tags(return_response=True)

            if not isinstance(response, HTTPError):
                bot.verbose3('Response on obtaining token is None.')
                return None

            not_asking_auth = "Www-Authenticate" not in response.headers
            if response.code != 401 or not_asking_auth:
                bot.error("Authentication error, exiting.")
                sys.exit(1)

            challenge = response.headers["Www-Authenticate"]
            regexp = '^Bearer\s+realm="(.+)",service="(.+)",scope="(.+)",?'
            match = re.match(regexp, challenge)

            if not match:
                bot.error("Unrecognized authentication challenge, exiting.")
                sys.exit(1)

            realm = match.group(1)
            service = match.group(2)
            scope = match.group(3).split(',')[0]

            self.token_url = ("%s?service=%s&expires_in=9000&scope=%s"
                              % (realm, service, scope))

        headers = dict()

        # First priority comes to auth supplied directly to function
        if auth is not None:
            headers.update(auth)

        # Second priority is default if supplied at init
        elif self.auth is not None:
            headers.update(self.auth)

        response = self.get(self.token_url,
                            default_headers=False,
                            headers=headers)

        try:
            token = json.loads(response)["token"]
            token = {"Authorization": "Bearer %s" % token}
            self.token = token
            self.update_headers(token)

        except Exception:
            bot.error("Error getting token for repository %s, exiting."
                      % self.repo_name)
            sys.exit(1)
Пример #5
0
def clean_up(files):
    '''clean up will delete a list of files, only if they exist
    '''
    if not isinstance(files, list):
        files = [files]

    for f in files:
        if os.path.exists(f):
            bot.verbose3("Cleaning up %s" % f)
            os.remove(f)
Пример #6
0
def clean_up(files):
    '''clean up will delete a list of files, only if they exist
    '''
    if not isinstance(files, list):
        files = [files]

    for f in files:
        if os.path.exists(f):
            bot.verbose3("Cleaning up %s" % f)
            os.remove(f)
Пример #7
0
def read_file(filename,mode="r",readlines=True):
    '''write_file will open a file, "filename" and write content, "content"
    and properly close the file
    '''
    bot.verbose3("Reading file %s with mode %s." %(filename,mode))
    with open(filename,mode) as filey:
        if readlines:
            content = filey.readlines()
        else:
            content = filey.read()
    return content
Пример #8
0
def read_file(filename, mode="r", readlines=True):
    '''read_file will open a file, "filename" and
    read content, "content" and properly close the file
    '''
    bot.verbose3("Reading file %s with mode %s." % (filename, mode))
    with open(filename, mode) as filey:
        if readlines:
            content = filey.readlines()
        else:
            content = filey.read()
    return content
Пример #9
0
def extract_env(manifest):
    '''extract the environment from the manifest, or return None. Used by
    functions env_extract_image, and env_extract_tar
    '''
    environ = get_config(manifest,'Env')
    if environ is not None:
        if isinstance(environ,list):
            environ = "\n".join(environ)
        environ = ["export %s" %x for x in environ.split('\n')]
        environ = "\n".join(environ)
        bot.verbose3("Found Docker container environment!")    
    return environ
Пример #10
0
def extract_env(manifest):
    '''extract the environment from the manifest, or return None.
    Used by functions env_extract_image, and env_extract_tar
    '''
    environ = get_config(manifest, 'Env')
    if environ is not None:
        if not isinstance(environ, list):
            environ = [environ]

        lines = []
        for line in environ:
            line = re.findall("(?P<var_name>.+?)=(?P<var_value>.+)", line)
            line = ['export %s="%s"' % (x[0], x[1]) for x in line]
            lines = lines + line

        environ = "\n".join(lines)
        bot.verbose3("Found Docker container environment!")
    return environ
Пример #11
0
def extract_labels(manifest, labelfile=None, prefix=None):
    '''extract_labels will write a file of key value pairs including
    maintainer, and labels.
    :param manifest: the manifest to use
    :param labelfile: if defined, write to labelfile (json)
    :param prefix: an optional prefix to add to the names
    '''
    if prefix is None:
        prefix = ""

    labels = get_config(manifest, 'Labels')
    if labels is not None and len(labels) is not 0:
        bot.verbose3("Found Docker container labels!")
        if labelfile is not None:
            for key, value in labels.items():
                key = "%s%s" % (prefix, key)
                value = ADD(key, value, labelfile, force=True)
    return labels
Пример #12
0
def extract_env(manifest):
    '''extract the environment from the manifest, or return None.
    Used by functions env_extract_image, and env_extract_tar
    '''
    environ = get_config(manifest, 'Env')
    if environ is not None:
        if not isinstance(environ, list):
            environ = [environ]

        lines = []
        for line in environ:
            line = re.findall("(?P<var_name>.+?)=(?P<var_value>.+)", line)
            line = ['export %s="%s"' % (x[0], x[1]) for x in line]
            lines = lines + line

        environ = "\n".join(lines)
        bot.verbose3("Found Docker container environment!")
    return environ
Пример #13
0
def extract_labels(manifest, labelfile=None, prefix=None):
    '''extract_labels will write a file of key value pairs including
    maintainer, and labels.
    :param manifest: the manifest to use
    :param labelfile: if defined, write to labelfile (json)
    :param prefix: an optional prefix to add to the names
    '''
    if prefix is None:
        prefix = ""

    labels = get_config(manifest, 'Labels')
    if labels is not None and len(labels) is not 0:
        bot.verbose3("Found Docker container labels!")
        if labelfile is not None:
            for key, value in labels.items():
                key = "%s%s" % (prefix, key)
                value = ADD(key, value, labelfile, force=True)
    return labels
Пример #14
0
def extract_runscript(manifest, includecmd=False):
    '''create_runscript will write a bash script with default "ENTRYPOINT"
    into the base_dir. If includecmd is True, CMD is used instead. For both.
    if the result is found empty, the other is tried, and then a default used.
    :param manifest: the manifest to use to get the runscript
    :param includecmd: overwrite default command (ENTRYPOINT) default is False
    '''
    cmd = None

    # Does the user want to use the CMD instead of ENTRYPOINT?
    commands = ["Entrypoint", "Cmd"]
    if includecmd is True:
        commands.reverse()
    configs = get_configs(manifest, commands)

    # Look for non "None" command
    for command in commands:
        if configs[command] is not None:
            cmd = configs[command]
            break

    if cmd is not None:
        bot.verbose3("Adding Docker %s as Singularity runscript..." %
                     command.upper())

        # If the command is a list, join. (eg ['/usr/bin/python','hello.py']
        if not isinstance(cmd, list):
            cmd = [cmd]

        cmd = " ".join(['"%s"' % x for x in cmd])

        if not RUNSCRIPT_COMMAND_ASIS:
            cmd = 'exec %s "$@"' % cmd
        cmd = "#!/bin/sh\n\n%s\n" % cmd
        return cmd

    bot.debug("CMD and ENTRYPOINT not found, skipping runscript.")
    return cmd
Пример #15
0
def extract_runscript(manifest, includecmd=False):
    '''create_runscript will write a bash script with default "ENTRYPOINT"
    into the base_dir. If includecmd is True, CMD is used instead. For both.
    if the result is found empty, the other is tried, and then a default used.
    :param manifest: the manifest to use to get the runscript
    :param includecmd: overwrite default command (ENTRYPOINT) default is False
    '''
    cmd = None

    # Does the user want to use the CMD instead of ENTRYPOINT?
    commands = ["Entrypoint", "Cmd"]
    if includecmd is True:
        commands.reverse()
    configs = get_configs(manifest, commands)

    # Look for non "None" command
    for command in commands:
        if configs[command] is not None:
            cmd = configs[command]
            break

    if cmd is not None:
        bot.verbose3("Adding Docker %s as Singularity runscript..."
                     % command.upper())

        # If the command is a list, join. (eg ['/usr/bin/python','hello.py']
        if not isinstance(cmd, list):
            cmd = [cmd]

        cmd = " ".join(['"%s"' % x for x in cmd])

        if not RUNSCRIPT_COMMAND_ASIS:
            cmd = 'exec %s "$@"' % cmd
        cmd = "#!/bin/sh\n\n%s\n" % cmd
        return cmd

    bot.debug("CMD and ENTRYPOINT not found, skipping runscript.")
    return cmd
Пример #16
0
def extract_metadata_tar(manifest,
                         image_name,
                         include_env=True,
                         include_labels=True,
                         runscript=None):

    '''extract_metadata_tar will write a tarfile with the environment,
    labels, and runscript. include_env and include_labels should be booleans,
    and runscript should be None or a string to write to the runscript.
    '''
    tar_file = None
    files = []
    if include_env or include_labels:

        # Extract and add environment
        if include_env:
            environ = extract_env(manifest)
            if environ not in [None, ""]:
                bot.verbose3('Adding Docker environment to metadata tar')
                template = get_template('tarinfo')
                template['name'] = './%s/env/%s-%s.sh' % (METADATA_FOLDER_NAME,
                                                          DOCKER_NUMBER,
                                                          DOCKER_PREFIX)
                template['content'] = environ
                files.append(template)

        # Extract and add labels
        if include_labels:
            labels = extract_labels(manifest)
            if labels is not None:
                if isinstance(labels, dict):
                    labels = print_json(labels)
                bot.verbose3('Adding Docker labels to metadata tar')
                template = get_template('tarinfo')
                template['name'] = "./%s/labels.json" % METADATA_FOLDER_NAME
                template['content'] = labels
                files.append(template)

        if runscript is not None:
            bot.verbose3('Adding Docker runscript to metadata tar')
            template = get_template('tarinfo')
            template['name'] = "./%s/runscript" % METADATA_FOLDER_NAME
            template['content'] = runscript
            files.append(template)

    if len(files) > 0:
        output_folder = get_cache(subfolder="metadata", quiet=True)
        tar_file = create_tar(files, output_folder)
    else:
        bot.warning("No metadata will be included.")
    return tar_file
Пример #17
0
def extract_metadata_tar(manifest,
                         image_name,
                         include_env=True,
                         include_labels=True,
                         runscript=None):
    '''extract_metadata_tar will write a tarfile with the environment,
    labels, and runscript. include_env and include_labels should be booleans,
    and runscript should be None or a string to write to the runscript.
    '''
    tar_file = None
    files = []
    if include_env or include_labels:

        # Extract and add environment
        if include_env:
            environ = extract_env(manifest)
            if environ not in [None, ""]:
                bot.verbose3('Adding Docker environment to metadata tar')
                template = get_template('tarinfo')
                template['name'] = './%s/env/%s-%s.sh' % (
                    METADATA_FOLDER_NAME, DOCKER_NUMBER, DOCKER_PREFIX)
                template['content'] = environ
                files.append(template)

        # Extract and add labels
        if include_labels:
            labels = extract_labels(manifest)
            if labels is not None:
                if isinstance(labels, dict):
                    labels = print_json(labels)
                bot.verbose3('Adding Docker labels to metadata tar')
                template = get_template('tarinfo')
                template['name'] = "./%s/labels.json" % METADATA_FOLDER_NAME
                template['content'] = labels
                files.append(template)

        if runscript is not None:
            bot.verbose3('Adding Docker runscript to metadata tar')
            template = get_template('tarinfo')
            template['name'] = "./%s/runscript" % METADATA_FOLDER_NAME
            template['content'] = runscript
            files.append(template)

    if len(files) > 0:
        output_folder = get_cache(subfolder="metadata", quiet=True)
        tar_file = create_tar(files, output_folder)
    else:
        bot.warning("No metadata will be included.")
    return tar_file
Пример #18
0
def IMPORT(image, auth=None, layerfile=None):
    '''IMPORT is the main script that will obtain docker layers,
    runscript information (either entrypoint or cmd), and environment
    and return a list of tarballs to extract into the image
    :param auth: if needed, an authentication header (default None)
    :param layerfile: The file to write layers to extract into
    '''
    bot.debug("Starting Docker IMPORT, includes env, runscript, and metadata.")
    bot.verbose("Docker image: %s" % image)

    # Does the user want to override default of using ENTRYPOINT?
    if INCLUDE_CMD:
        bot.verbose2("Specified Docker CMD as %runscript.")
    else:
        bot.verbose2("Specified Docker ENTRYPOINT as %runscript.")

    # Input Parsing ----------------------------
    # Parse image name, repo name, and namespace
    client = DockerApiConnection(image=image, auth=auth)

    docker_image_uri = "Docker image path: %s" % client.assemble_uri("/")
    bot.info(docker_image_uri)

    # IMAGE METADATA -------------------------------------------
    # Use Docker Registry API (version 2.0) to get images ids, manifest

    images = client.get_images()

    #  DOWNLOAD LAYERS -------------------------------------------
    # Each is a .tar.gz file, obtained from registry with curl

    # Get the cache (or temporary one) for docker
    cache_base = get_cache(subfolder="docker")
    download_client = MultiProcess()

    # Generate a queue of tasks to run with MultiProcess
    layers = []
    tasks = []
    for ii in range(len(images)):
        image_id = images[ii]
        targz = "%s/%s.tar.gz" % (cache_base, image_id)
        if not os.path.exists(targz):
            tasks.append((client, image_id, cache_base))
        layers.append(targz)

    # Does the user want to change permissions of tar?
    func2 = None
    if PLUGIN_FIXPERMS:
        func2 = change_permissions

    if len(tasks) > 0:
        download_layers = download_client.run(func=download_layer,
                                              func2=func2,
                                              tasks=tasks)

    # Get Docker runscript
    runscript = extract_runscript(manifest=client.manifestv1,
                                  includecmd=INCLUDE_CMD)

    # Add the environment export
    tar_file = extract_metadata_tar(client.manifestv1,
                                    client.assemble_uri(),
                                    runscript=runscript)

    bot.verbose2('Tar file with Docker env and labels: %s' % tar_file)

    # Write all layers to the layerfile
    if layerfile is not None:
        bot.verbose3("Writing Docker layers files to %s" % layerfile)
        write_file(layerfile, "\n".join(layers), mode="w")
        if tar_file is not None:
            write_file(layerfile, "\n%s" % tar_file, mode="a")

    # Return additions dictionary
    additions = {"layers": layers,
                 "image": image,
                 "manifest": client.manifest,
                 "manifestv1": client.manifestv1,
                 "cache_base": cache_base,
                 "metadata": tar_file}

    bot.debug("*** FINISHING DOCKER IMPORT PYTHON PORTION ****\n")

    return additions
Пример #19
0
def IMPORT(image, auth=None, layerfile=None):
    '''IMPORT is the main script that will obtain docker layers,
    runscript information (either entrypoint or cmd), and environment
    and return a list of tarballs to extract into the image
    :param auth: if needed, an authentication header (default None)
    :param layerfile: The file to write layers to extract into
    '''
    bot.debug("Starting Docker IMPORT, includes env, runscript, and metadata.")
    bot.verbose("Docker image: %s" % image)

    # Does the user want to override default of using ENTRYPOINT?
    if INCLUDE_CMD:
        bot.verbose2("Specified Docker CMD as %runscript.")
    else:
        bot.verbose2("Specified Docker ENTRYPOINT as %runscript.")

    # Input Parsing ----------------------------
    # Parse image name, repo name, and namespace
    client = DockerApiConnection(image=image, auth=auth)

    docker_image_uri = "Docker image path: %s" % client.assemble_uri("/")
    bot.info(docker_image_uri)

    # IMAGE METADATA -------------------------------------------
    # Use Docker Registry API (version 2.0) to get images ids, manifest

    images = client.get_images()

    #  DOWNLOAD LAYERS -------------------------------------------
    # Each is a .tar.gz file, obtained from registry with curl

    # Get the cache (or temporary one) for docker
    cache_base = get_cache(subfolder="docker")
    download_client = MultiProcess()

    # Generate a queue of tasks to run with MultiProcess
    layers = []
    tasks = []
    for ii in range(len(images)):
        image_id = images[ii]
        targz = "%s/%s.tar.gz" % (cache_base, image_id)
        if not os.path.exists(targz):
            tasks.append((client, image_id, cache_base))
        layers.append(targz)

    # Does the user want to change permissions of tar?
    func2 = None
    if PLUGIN_FIXPERMS:
        func2 = change_permissions

    if len(tasks) > 0:
        download_layers = download_client.run(func=download_layer,
                                              func2=func2,
                                              tasks=tasks)

    # Get Docker runscript
    runscript = extract_runscript(manifest=client.manifestv1,
                                  includecmd=INCLUDE_CMD)

    # Add the environment export
    tar_file = extract_metadata_tar(client.manifestv1,
                                    client.assemble_uri(),
                                    runscript=runscript)

    bot.verbose2('Tar file with Docker env and labels: %s' % tar_file)

    # Write all layers to the layerfile
    if layerfile is not None:
        bot.verbose3("Writing Docker layers files to %s" % layerfile)
        write_file(layerfile, "\n".join(layers), mode="w")
        if tar_file is not None:
            write_file(layerfile, "\n%s" % tar_file, mode="a")

    # Return additions dictionary
    additions = {
        "layers": layers,
        "image": image,
        "manifest": client.manifest,
        "manifestv1": client.manifestv1,
        "cache_base": cache_base,
        "metadata": tar_file
    }

    bot.debug("*** FINISHING DOCKER IMPORT PYTHON PORTION ****\n")

    return additions
Пример #20
0
    def update_token(self, response=None, auth=None, expires_in=300):
        '''update_token uses HTTP basic authentication to get a token for
        Docker registry API V2 operations. We get here if a 401 is
        returned for a request.
        https://docs.docker.com/registry/spec/auth/token/
        '''

        if self.tokenExpires and self.tokenExpires > (time.time() - 5):
            bot.debug("Not renewing token - does not expire within 5s")
            return

        if self.token_url is None:

            if response is None:
                response = self.get_tags(return_response=True)

            if not isinstance(response, HTTPError):
                bot.verbose3('Response on obtaining token is None.')
                return None

            not_asking_auth = "Www-Authenticate" not in response.headers
            if response.code != 401 or not_asking_auth:
                bot.error("Authentication error, exiting.")
                sys.exit(1)

            challenge = response.headers["Www-Authenticate"]

            self.token_url = self.get_token_url(challenge,
                                                expires_in=expires_in)

        headers = dict()

        # First priority comes to auth supplied directly to function
        if auth is not None:
            headers.update(auth)

        # Second priority is default if supplied at init
        elif self.auth is not None:
            headers.update(self.auth)

        bot.debug("Requesting token from docker registry API")

        response = self.get(self.token_url,
                            default_headers=False,
                            headers=headers,
                            updating_token=True)

        try:
            data = json.loads(response)
            if 'access_token' in data:
                access_token = data["access_token"]
            else:
                access_token = data["token"]
            token = {"Authorization": "Bearer %s" % access_token}
            self.token = token

            # Default expiry from API is 60s
            expires_in = 60
            if 'expires_in' in data:
                expires_in = data['expires_in']
            # issued_at and expires_in are optional, so use the completion of
            # token exchange (now)
            self.tokenExpires = time.time() + expires_in
            bot.debug("Received token valid for %d - expiring at %d" %
                      (expires_in, self.tokenExpires))

            self.update_headers(token)

        except Exception as e:
            bot.error(
                "Error getting token for repository %s, please check your credentials.\n%s\n"  # noqa
                % (self.repo_name, str(e)))

            sys.exit(1)