Beispiel #1
0
    def download_atomically(self,
                            url,
                            file_name,
                            headers=None,
                            show_progress=False):
        '''download stream atomically will stream to a temporary file, and
        rename only upon successful completion. This is to ensure that
        errored downloads are not found as complete in the cache
        :param file_name: the file name to stream to
        :param url: the url to stream from
        :param headers: additional headers to add to the get (default None)
        '''
        try:
            tmp_file = "%s.%s" % (file_name,
                                  next(tempfile._get_candidate_names()))
            response = self.stream(url,
                                   file_name=tmp_file,
                                   headers=headers,
                                   show_progress=show_progress)
            os.rename(tmp_file, file_name)

        except Exception:

            download_folder = os.path.dirname(os.path.abspath(file_name))
            msg = "Error downloading %s. " % (url)
            msg += "Do you have permission to write to %s?" % (download_folder)
            bot.error(msg)
            try:
                os.remove(tmp_file)
            except Exception:
                pass
            sys.exit(1)

        return file_name
Beispiel #2
0
    def download_atomically(self,
                            url,
                            file_name,
                            headers=None,
                            show_progress=False):

        '''download stream atomically will stream to a temporary file, and
        rename only upon successful completion. This is to ensure that
        errored downloads are not found as complete in the cache
        :param file_name: the file name to stream to
        :param url: the url to stream from
        :param headers: additional headers to add to the get (default None)
        '''
        try:
            tmp_file = "%s.%s" % (file_name,
                                  next(tempfile._get_candidate_names()))
            response = self.stream(url,
                                   file_name=tmp_file,
                                   headers=headers,
                                   show_progress=show_progress)
            os.rename(tmp_file, file_name)

        except Exception:

            download_folder = os.path.dirname(os.path.abspath(file_name))
            msg = "Error downloading %s. " % (url)
            msg += "Do you have permission to write to %s?" % (download_folder)
            bot.error(msg)
            try:
                os.remove(tmp_file)
            except Exception:
                pass
            sys.exit(1)

        return file_name
Beispiel #3
0
    def run(self,func,tasks,func2=None):
        '''run will send a list of tasks, a tuple with arguments, through a function.
        the arguments should be ordered correctly.
        :param func: the function to run with multiprocessing.pool
        :param tasks: a list of tasks, each a tuple of arguments to process
        :param func2: filter function to run result from func through (optional)
        '''
 
        # Keep track of some progress for the user
        progress = 1
        total = len(tasks)

        # If two functions are run per task, double total jobs
        if func2 is not None:
            total = total * 2

        finished = []
        level1 = []
        results = []

        try:
            prefix = "[%s/%s]" %(progress,total)
            bot.show_progress(0,total,length=35,prefix=prefix)
            pool = multiprocessing.Pool(self.workers,init_worker)

            self.start()
            for task in tasks:
                result = pool.apply_async(multi_wrapper,
                                          multi_package(func,[task]))
                results.append(result)
                level1.append(result._job)

            while len(results) > 0:
                result = results.pop()
                result.wait()
                bot.show_progress(progress,total,length=35,prefix=prefix)
                progress+=1
                prefix = "[%s/%s]" %(progress,total)

                # Pass the result through a second function?
                if func2 is not None and result._job in level1:
                    result = pool.apply_async(multi_wrapper,
                                              multi_package(func2,[(result.get(),)]))
                    results.append(result)
                else:
                    finished.append(result.get())

            self.end()
            pool.close()
            pool.join()

        except (KeyboardInterrupt, SystemExit):
            bot.error("Keyboard interrupt detected, terminating workers!")
            pool.terminate()
            sys.exit(1)

        except Exception as e:
            bot.error(e)

        return finished
Beispiel #4
0
    def get_manifest(self,old_version=False):
        '''get_manifest should return an image manifest for a particular repo and tag. 
        The image details are extracted when the client is generated.
        :param old_version: return version 1 (for cmd/entrypoint), default False
        '''
        registry = self.registry
        if registry == None:
            registry = self.api_base
        registry = add_http(registry) # make sure we have a complete url

        base = "%s/%s/%s/%s/manifests" %(registry,self.api_version,self.namespace,self.repo_name)
        if self.version is not None:
            base = "%s/%s" %(base,self.version)
        else:
            base = "%s/%s" %(base,self.repo_tag)
        bot.verbose("Obtaining manifest: %s" %base)
    
        headers = self.headers
        if old_version == True:
            headers['Accept'] = 'application/json' 

        response = self.get(base,headers=self.headers)

        try:
            response = json.loads(response)

        except:
            # If the call fails, give the user a list of acceptable tags
            tags = self.get_tags()
            print("\n".join(tags))
            repo_uri = "%s/%s:%s" %(self.namespace,self.repo_name,self.repo_tag)
            bot.error("Error getting manifest for %s, exiting." %repo_uri)
            sys.exit(1)

        return response
Beispiel #5
0
def main():
    '''main is a wrapper for the client to hand the parser
    to the executable functions
    This makes it possible to set up a parser in test cases
    '''
    bot.debug("\n*** STARTING SINGULARITY PYTHON PULL ****")
    from defaults import LAYERFILE, DISABLE_CACHE, getenv

    # What image is the user asking for?
    container = getenv("SINGULARITY_CONTAINER", required=True)
    pull_folder = getenv("SINGULARITY_PULLFOLDER")

    image_uri = get_image_uri(container)
    container = remove_image_uri(container, quiet=True)

    if image_uri == "shub://":

        from shub.main import PULL
        manifest = PULL(image=container,
                        download_folder=pull_folder,
                        layerfile=LAYERFILE)

    else:
        bot.error("uri %s is not supported for pull. Exiting." % (image_uri))
        sys.exit(1)
Beispiel #6
0
    def get_tags(self, return_response=False):
        '''get_tags will return the tags for a repo using the
        Docker Version 2.0 Registry API
        '''
        registry = self.registry
        if registry is None:
            registry = self.api_base

        registry = add_http(registry)  # make sure we have a complete url

        base = "%s/%s/%s/tags/list" % (registry, self.api_version,
                                       self.repo_name)

        bot.verbose("Obtaining tags: %s" % base)

        # We use get_tags for a testing endpoint in update_token
        response = self.get(base, return_response=return_response)

        if return_response:
            return response

        try:
            response = json.loads(response)
            return response['tags']
        except Exception:
            bot.error("Error obtaining tags: %s" % base)
            sys.exit(1)
Beispiel #7
0
def main():
    '''main is a wrapper for the client to hand the parser
    to the executable functions
    This makes it possible to set up a parser in test cases
    '''
    bot.debug("\n*** STARTING SINGULARITY PYTHON PULL ****")
    from defaults import LAYERFILE, DISABLE_CACHE, getenv

    # What image is the user asking for?
    container = getenv("SINGULARITY_CONTAINER", required=True)
    pull_folder = getenv("SINGULARITY_PULLFOLDER")

    image_uri = get_image_uri(container)
    container = remove_image_uri(container, quiet=True)

    if image_uri == "shub://":

        from shub.main import PULL
        manifest = PULL(image=container,
                        download_folder=pull_folder,
                        layerfile=LAYERFILE)

    else:
        bot.error("uri %s is not supported for pull. Exiting." % (image_uri))
        sys.exit(1)
Beispiel #8
0
    def get_tags(self, return_response=False):
        '''get_tags will return the tags for a repo using the
        Docker Version 2.0 Registry API
        '''
        registry = self.registry
        if registry is None:
            registry = self.api_base

        registry = add_http(registry)  # make sure we have a complete url

        base = "%s/%s/%s/tags/list" % (registry,
                                       self.api_version,
                                       self.repo_name)

        bot.verbose("Obtaining tags: %s" % base)

        # We use get_tags for a testing endpoint in update_token
        response = self.get(base,
                            return_response=return_response)

        if return_response:
            return response

        try:
            response = json.loads(response)
            return response['tags']
        except Exception:
            bot.error("Error obtaining tags: %s" % base)
            sys.exit(1)
Beispiel #9
0
def read_digests(manifest):
    '''read_layers will return a list of layers from a manifest. The function is
    intended to work with both version 1 and 2 of the schema
    :param manifest: the manifest to read_layers from
    '''

    digests = []

    # https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-2.md#image-manifest
    if 'layers' in manifest:
        layer_key = 'layers'
        digest_key = 'digest'
        bot.debug('Image manifest version 2.2 found.')

    # https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-1.md#example-manifest
    elif 'fsLayers' in manifest:
        layer_key = 'fsLayers'
        digest_key = 'blobSum'
        bot.debug('Image manifest version 2.1 found.')

    else:
        bot.error(
            'Improperly formed manifest, layers or fsLayers must be present')
        sys.exit(1)

    for layer in manifest[layer_key]:
        if digest_key in layer:
            if layer[digest_key] not in digests:
                bot.debug("Adding digest %s" % layer[digest_key])
                digests.append(layer[digest_key])
    return digests
Beispiel #10
0
    def get_tags(self, return_response=False):
        '''get_tags will return the tags for a repo using the Docker Version 2.0 Registry API
        :param namespace: the namespace (eg, "library")
        :param repo_name: the name for the repo (eg, "ubuntu")
        :param registry: the docker registry to use (default will use index.docker.io)
        :param auth: authorization header (default None)
        '''
        registry = self.registry
        if registry == None:
            registry = self.api_base

        registry = add_http(registry)  # make sure we have a complete url

        base = "%s/%s/%s/%s/tags/list" % (registry, self.api_version,
                                          self.namespace, self.repo_name)
        bot.verbose("Obtaining tags: %s" % base)

        # We use get_tags for a testing endpoint in update_token
        response = self.get(base, return_response=return_response)

        if return_response:
            return response

        try:
            response = json.loads(response)
            return response['tags']
        except:
            bot.error("Error obtaining tags: %s" % base)
            sys.exit(1)
Beispiel #11
0
def read_digests(manifest):
    '''read_layers will return a list of layers from a manifest. The function is
    intended to work with both version 1 and 2 of the schema
    :param manifest: the manifest to read_layers from
    '''

    digests = []

    # https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-2.md#image-manifest
    if 'layers' in manifest:
        layer_key = 'layers'
        digest_key = 'digest'
        bot.debug('Image manifest version 2.2 found.')

    # https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-1.md#example-manifest
    elif 'fsLayers' in manifest:
        layer_key = 'fsLayers'
        digest_key = 'blobSum'
        bot.debug('Image manifest version 2.1 found.')

    else:
        bot.error('Improperly formed manifest, layers or fsLayers must be present')
        sys.exit(1)

    for layer in manifest[layer_key]:
        if digest_key in layer:
            if layer[digest_key] not in digests:
                bot.debug("Adding digest %s" %layer[digest_key])
                digests.append(layer[digest_key])
    return digests
Beispiel #12
0
    def get_tags(self,return_response=False):
        '''get_tags will return the tags for a repo using the Docker Version 2.0 Registry API
        :param namespace: the namespace (eg, "library")
        :param repo_name: the name for the repo (eg, "ubuntu")
        :param registry: the docker registry to use (default will use index.docker.io)
        :param auth: authorization header (default None)
        '''
        registry = self.registry
        if registry == None:
            registry = self.api_base
        
        registry = add_http(registry) # make sure we have a complete url

        base = "%s/%s/%s/%s/tags/list" %(registry,self.api_version,self.namespace,self.repo_name)
        bot.verbose("Obtaining tags: %s" %base)

        # We use get_tags for a testing endpoint in update_token
        response = self.get(base,
                            return_response=return_response)

        if return_response:
            return response

        try:
            response = json.loads(response)
            return response['tags']
        except:
            bot.error("Error obtaining tags: %s" %base)
            sys.exit(1)
Beispiel #13
0
    def get_layer(self,
                  image_id,
                  download_folder=None,
                  change_perms=False,
                  return_tmp=False):
        '''get_layer will download an image layer (.tar.gz)
        to a specified download folder.
        :param download_folder: if specified, download to folder.
                                Otherwise return response with raw data
        :param change_perms: change permissions additionally
                             (default False to support multiprocessing)
        :param return_tmp: If true, return the temporary file name (and
                             don't rename to the file's final name). Default
                             is False, should be True for multiprocessing
                             that requires extra permission changes
        '''
        registry = self.registry
        if registry is None:
            registry = self.api_base

        # make sure we have a complete url
        registry = add_http(registry)

        # The <name> variable is the namespace/repo_name
        base = "%s/%s/%s/%s/blobs/%s" % (registry, self.api_version,
                                         self.namespace, self.repo_name,
                                         image_id)
        bot.verbose("Downloading layers from %s" % base)

        if download_folder is None:
            download_folder = tempfile.mkdtemp()

        download_folder = "%s/%s.tar.gz" % (download_folder, image_id)

        # Update user what we are doing
        bot.debug("Downloading layer %s" % image_id)

        # Step 1: Download the layer atomically
        file_name = "%s.%s" % (download_folder,
                               next(tempfile._get_candidate_names()))
        tar_download = self.download_atomically(url=base, file_name=file_name)
        bot.debug('Download of raw file (pre permissions fix) is %s' %
                  tar_download)

        # Step 2: Fix Permissions?
        if change_perms:
            tar_download = change_tar_permissions(tar_download)

        if return_tmp is True:
            return tar_download

        try:
            os.rename(tar_download, download_folder)
        except Exception:
            msg = "Cannot untar layer %s," % tar_download
            msg += " was there a problem with download?"
            bot.error(msg)
            sys.exit(1)
        return download_folder
Beispiel #14
0
    def stream(self,url,file_name,data=None,headers=None,default_headers=True,
               show_progress=False):
        '''stream is a get that will stream to file_name
        :param data: a dictionary of key:value items to add to the data args variable
        :param url: the url to get
        :param show_progress: if True, show a progress bar with the bot
        :returns response: the requests response object, or stream        
        '''
        bot.debug("GET (stream) %s" %url)

        # If we use default headers, start with client's
        request_headers = dict()
        if default_headers and len(self.headers) > 0:
            request_headers = self.headers

        if headers is not None:
            request_headers.update(headers)

        request = self.prepare_request(headers=request_headers,
                                       data=data,
                                       url=url)

        response = self.submit_request(request)


        # Keep user updated with Progress Bar?
        if show_progress:
            content_size = None
            if 'Content-Length' in response.headers and response.code not in [400,401]:
                progress = 0
                content_size = int(response.headers['Content-Length'])
                bot.show_progress(progress,content_size,length=35)

        chunk_size = 1 << 20
        with open(file_name, 'wb') as filey:
            while True:
                chunk = response.read(chunk_size)
                if not chunk: 
                    break
                try:
                    filey.write(chunk)
                    if show_progress:
                        if content_size is not None:
                            progress+=chunk_size
                            bot.show_progress(iteration=progress,
                                              total=content_size,
                                              length=35,
                                              carriage_return=False)
                except Exception as error:
                    bot.error("Error writing to %s: %s exiting" %(file_name,error))
                    sys.exit(1)

            # Newline to finish download
            if show_progress:
                sys.stdout.write('\n')

        return file_name
Beispiel #15
0
 def load_image(self, image):
     self.image = parse_image_uri(image=image,
                                  uri='shub://',
                                  default_registry=SHUB_API_BASE,
                                  quiet=True)
     # parse_image_uri may return an empty namespace cause that's allowed
     # with docker://, but not with shub://
     if len(self.image["namespace"]) == 0:
         bot.error("Namespace cannot be empty for shub:// url!")
         sys.exit(1)
Beispiel #16
0
def main():
    '''this function will run a docker import, returning a list of layers
    and environmental variables and metadata to the metadata base
    '''

    container = getenv("SINGULARITY_CONTAINER", required=True)
    image_uri = get_image_uri(container, quiet=True)
    container = remove_image_uri(container)

    ########################################################################
    # Docker Image #########################################################
    ########################################################################

    if image_uri == "docker://":

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

        from sutils import basic_auth_header
        from defaults import LAYERFILE

        bot.debug("Docker layers and metadata will be written to: %s"
                  % (LAYERFILE))

        username = getenv("SINGULARITY_DOCKER_USERNAME")
        password = getenv("SINGULARITY_DOCKER_PASSWORD",
                          silent=True)

        auth = None
        if username is not None and password is not None:
            auth = basic_auth_header(username, password)

        from docker.main import IMPORT

        manifest = IMPORT(auth=auth,
                          image=container,
                          layerfile=LAYERFILE)

    ########################################################################
    # Singularity Hub ######################################################
    ########################################################################

    elif image_uri == "shub://":

        bot.debug("\n*** STARTING SINGULARITY HUB IMPORT PYTHON  ****")

        from defaults import LAYERFILE, LABELFILE
        from shub.main import IMPORT
        IMPORT(image=container,
               layerfile=LAYERFILE,
               labelfile=LABELFILE)

    else:
        bot.error("uri %s is not supported for import. Exiting."
                  % (image_uri))
        sys.exit(1)
Beispiel #17
0
def create_folders(path):
    '''create_folders attempts to get the same functionality as mkdir -p
    :param path: the path to create.
    '''
    try:
        os.makedirs(path)
    except OSError as e:
        if e.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else:
            bot.error("Error creating path %s, exiting." % path)
            sys.exit(1)
Beispiel #18
0
def create_folders(path):
    '''create_folders attempts to get the same functionality as mkdir -p
    :param path: the path to create.
    '''
    try:
        os.makedirs(path)
    except OSError as e:
        if e.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else:
            bot.error("Error creating path %s, exiting." %path)
            sys.exit(1)
Beispiel #19
0
 def check_errors(self, response, exit=True):
     '''take a response with errors key,
     iterate through errors in expected format and exit upon completion
     '''
     if "errors" in response:
         for error in response['errors']:
             bot.error("%s: %s" % (error['code'], error['message']))
             if error['code'] == "UNAUTHORIZED":
                 msg = "Check existence, naming, and permissions"
                 bot.error(msg)
         if exit:
             sys.exit(1)
     return response
Beispiel #20
0
    def update_manifests(self):
        '''update manifests ensures that each of a version1 and version2
        manifest are present
        '''
        if self.repo_name is None and self.namespace is None:
            bot.error("Insufficient metadata to get manifest.")
            sys.exit(1)

        # Get full image manifest, using version 2.0 of Docker Registry API
        if self.manifest is None:
            self.manifest = self.get_manifest()
        if self.manifestv1 is None:
            self.manifestv1 = self.get_manifest(old_version=True)
Beispiel #21
0
def main():
    '''this function will run a docker import, returning a list of layers 
    and environmental variables and metadata to the metadata base
    '''

    container = getenv("SINGULARITY_CONTAINER", required=True)
    image_uri = get_image_uri(container, quiet=True)
    container = remove_image_uri(container)

    ##############################################################################
    # Docker Image ###############################################################
    ##############################################################################

    if image_uri == "docker://":

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

        from sutils import basic_auth_header
        from defaults import LAYERFILE

        bot.debug(
            "Docker layers and (env,labels,runscript) will be written to: %s" %
            LAYERFILE)
        username = getenv("SINGULARITY_DOCKER_USERNAME")
        password = getenv("SINGULARITY_DOCKER_PASSWORD", silent=True)

        auth = None
        if username is not None and password is not None:
            auth = basic_auth_header(username, password)

        from docker.main import IMPORT

        manifest = IMPORT(auth=auth, image=container, layerfile=LAYERFILE)

    ##############################################################################
    # Singularity Hub ############################################################
    ##############################################################################

    elif image_uri == "shub://":

        bot.debug("\n*** STARTING SINGULARITY HUB IMPORT PYTHON  ****")

        from defaults import LAYERFILE, LABELFILE
        from shub.main import IMPORT
        IMPORT(image=container, layerfile=LAYERFILE, labelfile=LABELFILE)

    else:
        bot.error(
            "uri %s is not a currently supported uri for import. Exiting." %
            image_uri)
        sys.exit(1)
Beispiel #22
0
 def check_errors(self, response, exit=True):
     '''take a response with errors key,
     iterate through errors in expected format and exit upon completion
     '''
     if "errors" in response:
         for error in response['errors']:
             bot.error("%s: %s" % (error['code'],
                                   error['message']))
             if error['code'] == "UNAUTHORIZED":
                 msg = "Check existence, naming, and permissions"
                 bot.error(msg)
         if exit:
             sys.exit(1)
     return response
Beispiel #23
0
    def get_manifest(self, old_version=False, version=None):
        '''get_manifest should return an image manifest
        for a particular repo and tag.  The image details
        are extracted when the client is generated.
        :param old_version: return version 1
                            (for cmd/entrypoint), default False
        '''
        registry = self.registry
        if registry is None:
            registry = self.api_base

        # make sure we have a complete url
        registry = add_http(registry)

        base = "%s/%s/%s/%s/manifests" % (registry, self.api_version,
                                          self.namespace, self.repo_name)

        # First priority given to calling function
        if version is not None:
            base = "%s/%s" % (base, version)

        elif self.version is not None:
            base = "%s/%s" % (base, self.version)

        else:
            base = "%s/%s" % (base, self.repo_tag)
        bot.verbose("Obtaining manifest: %s" % base)

        headers = self.headers.copy()
        if old_version is True:
            headers['Accept'] = 'application/json'

        response = self.get(base, headers=headers)

        try:
            response = json.loads(response)

        except Exception:

            # If the call fails, give the user a list of acceptable tags
            tags = self.get_tags()
            print("\n".join(tags))
            repo_uri = "%s/%s:%s" % (self.namespace, self.repo_name,
                                     self.repo_tag)

            bot.error("Error getting manifest for %s, exiting." % repo_uri)
            sys.exit(1)

        # If we have errors, don't continue
        return self.check_errors(response)
Beispiel #24
0
def main():

    parser = get_parser()

    try:
        (args, options) = parser.parse_args()
    except:
        sys.exit(0)

    if args.key is not None and args.file is not None:

        value = GET(key=args.key, jsonfile=args.file)
    else:
        bot.error("--key and --file must be defined for GET. Exiting")
        sys.exit(1)
Beispiel #25
0
def main():

    parser = get_parser()

    try:
        (args, options) = parser.parse_args()
    except Exception:
        sys.exit(0)

    if args.file is not None:

        dump = DUMP(args.file)

    else:
        bot.error("--file must be defined for DUMP. Exiting")
        sys.exit(1)
Beispiel #26
0
def run_command(cmd):
    '''run_command uses subprocess to send a command to the terminal.
    :param cmd: the command to send, should be a list for subprocess
    '''
    try:
        bot.verbose2("Running command %s with subprocess" % " ".join(cmd))
        process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    except OSError as error:
        bot.error("Error with subprocess: %s, returning None" % error)
        return None

    output = process.communicate()[0]
    if process.returncode != 0:
        return None

    return output
Beispiel #27
0
def DUMP(jsonfile):
    '''DUMP will return the entire layfile as text, key value pairs
    :param jsonfile_path: the path to the jsonfile
    '''
    bot.debug("Reading %s to prepare dump to STDOUT" %jsonfile)
    if not os.path.exists(jsonfile):
        bot.error("Cannot find %s, exiting." %jsonfile)
        sys.exit(1)
    
    contents = read_json(jsonfile)
    dump = ""
    for key,value in contents.items():
        dump = '%s%s:"%s"\n' %(dump,key,value)
    dump = dump.strip('\n')
    print(dump)
    return dump
Beispiel #28
0
def main():

    parser = get_parser()

    try:
        (args, options) = parser.parse_args()
    except:
        sys.exit(0)

    if args.file is not None:

        dump = DUMP(args.file)

    else:
        bot.error("--file must be defined for DUMP. Exiting")
        sys.exit(1)
Beispiel #29
0
def run_command(cmd):
    '''run_command uses subprocess to send a command to the terminal.
    :param cmd: the command to send, should be a list for subprocess
    '''
    try:
        bot.verbose2("Running command %s with subprocess" %" ".join(cmd))
        process = subprocess.Popen(cmd,stdout=subprocess.PIPE)
    except OSError as error:
        bot.error("Error with subprocess: %s, returning None" %error)
        return None

    output = process.communicate()[0]
    if process.returncode != 0:
        return None

    return output
Beispiel #30
0
    def get_images(self, manifest=None):
        '''get_images will return a list of layers from a manifest.
        The function is intended to work with both version
        1 and 2 of the schema. All layers (including redundant)
        are returned.

        For version 1 manifests: extraction is reversed

        :param manifest: the manifest to read_layers from
        '''
        if manifest is None:
            self.update_manifests()
            manifest = self.manifest

        digests = []
        layer_key = 'layers'
        digest_key = 'digest'

        # Docker manifest-v2-2.md#image-manifest
        if 'layers' in manifest:
            bot.debug('Image manifest version 2.2 found.')

        # Docker manifest-v2-1.md#example-manifest  # noqa
        elif 'fsLayers' in manifest:
            layer_key = 'fsLayers'
            digest_key = 'blobSum'
            bot.debug('Image manifest version 2.1 found.')

        else:
            msg = "Improperly formed manifest, "
            msg += "layers, manifests, or fsLayers must be present"
            bot.error(msg)
            sys.exit(1)

        for layer in manifest[layer_key]:
            if digest_key in layer:
                bot.debug("Adding digest %s" % layer[digest_key])
                digests.append(layer[digest_key])

        # Reverse layer order for manifest version 1.0
        if self.reverseLayers is True:
            message = 'v%s manifest, reversing layers' % self.schemaVersion
            bot.debug(message)
            digests.reverse()

        return digests
Beispiel #31
0
    def get_images(self, manifest=None):
        '''get_images will return a list of layers from a manifest.
        The function is intended to work with both version
        1 and 2 of the schema. All layers (including redundant)
        are returned.

        For version 1 manifests: extraction is reversed

        :param manifest: the manifest to read_layers from
        '''
        if manifest is None:
            self.update_manifests()
            manifest = self.manifest

        digests = []
        layer_key = 'layers'
        digest_key = 'digest'

        # Docker manifest-v2-2.md#image-manifest
        if 'layers' in manifest:
            bot.debug('Image manifest version 2.2 found.')

        # Docker manifest-v2-1.md#example-manifest  # noqa
        elif 'fsLayers' in manifest:
            layer_key = 'fsLayers'
            digest_key = 'blobSum'
            bot.debug('Image manifest version 2.1 found.')

        else:
            msg = "Improperly formed manifest, "
            msg += "layers, manifests, or fsLayers must be present"
            bot.error(msg)
            sys.exit(1)

        for layer in manifest[layer_key]:
            if digest_key in layer:
                bot.debug("Adding digest %s" % layer[digest_key])
                digests.append(layer[digest_key])

        # Reverse layer order for manifest version 1.0
        if self.reverseLayers is True:
            message = 'v%s manifest, reversing layers' % self.schemaVersion
            bot.debug(message)
            digests.reverse()

        return digests
Beispiel #32
0
    def get_layer(self,image_id,download_folder=None,change_perms=False,return_tmp=False):
        '''get_layer will download an image layer (.tar.gz) to a specified download folder.
        :param download_folder: if specified, download to folder.
        Otherwise return response with raw data (not recommended)
        :param change_perms: change permissions additionally (default False to support 
        multiprocessing)
        :param return_tmp: If true, return the temporary file name (and don't rename to the file's final
        name). Default is False, should be True for multiprocessing that requires extra permission changes
        '''
        registry = self.registry
        if registry == None:
            registry = self.api_base
        registry = add_http(registry) # make sure we have a complete url

        # The <name> variable is the namespace/repo_name
        base = "%s/%s/%s/%s/blobs/%s" %(registry,self.api_version,self.namespace,self.repo_name,image_id)
        bot.verbose("Downloading layers from %s" %base)
    
        if download_folder is None:        
            download_folder = tempfile.mkdtemp()

        download_folder = "%s/%s.tar.gz" %(download_folder,image_id)

        # Update user what we are doing
        bot.debug("Downloading layer %s" %image_id)

        # Step 1: Download the layer atomically
        file_name = "%s.%s" %(download_folder,next(tempfile._get_candidate_names()))
        tar_download = self.download_atomically(url=base,
                                                file_name=file_name)
        bot.debug('Download of raw file (pre permissions fix) is %s' %tar_download)

        # Step 2: Fix Permissions?
        if change_perms:
            tar_download = change_tar_permissions(tar_download)

        if return_tmp is True:
            return tar_download

        try:
            os.rename(tar_download,download_folder)
        except:
            bot.error("Cannot untar layer %s, was there a problem with download?" %tar_download)
            sys.exit(1)
        return download_folder
Beispiel #33
0
def get_fullpath(file_path,required=True):
    '''get_fullpath checks if a file exists, and returns the
    full path to it if it does. If required is true, an error is triggered.
    :param file_path: the path to check
    :param required: is the file required? If True, will exit with error
    '''
    file_path = os.path.abspath(file_path)
    if os.path.exists(file_path):
        return file_path

    # If file is required, we exit
    if required == True:
        bot.error("Cannot find file %s, exiting." %file_path)
        sys.exit(1)

    # If file isn't required and doesn't exist, return None
    bot.warning("Cannot find file %s" %file_path)
    return None
Beispiel #34
0
def GET(key,jsonfile):
    '''GET will return a key from the jsonfile, if it exists. If it doesn't, returns None.
    '''
    key = format_keyname(key)
    bot.debug("GET %s from %s" %(key,jsonfile))
    if not os.path.exists(jsonfile):
        bot.error("Cannot find %s, exiting." %jsonfile)
        sys.exit(1)
    
    contents = read_json(jsonfile)
    if key in contents:
        value = contents[key]
        print(value)
        bot.debug('%s is %s' %(key,value))
    else:
        bot.error("%s is not defined in file. Exiting" %key)
        sys.exit(1)
    return value
Beispiel #35
0
def get_fullpath(file_path, required=True):
    '''get_fullpath checks if a file exists, and returns the
    full path to it if it does. If required is true, an error is triggered.
    :param file_path: the path to check
    :param required: is the file required? If True, will exit with error
    '''
    file_path = os.path.abspath(file_path)
    if os.path.exists(file_path):
        return file_path

    # If file is required, we exit
    if required is True:
        bot.error("Cannot find file %s, exiting." % file_path)
        sys.exit(1)

    # If file isn't required and doesn't exist, return None
    bot.warning("Cannot find file %s" % file_path)
    return None
Beispiel #36
0
def main():

    parser = get_parser()
    
    try:
        (args,options) = parser.parse_args()
    except:
        sys.exit(0)
    
    if args.key is not None and args.file is not None and args.value is not None:

       value = ADD(key=args.key,
                   value=args.value,
                   jsonfile=args.file,
                   force=args.force)

    else:
        bot.error("--key and --file and --value must be defined for ADD. Exiting")
        sys.exit(1)
Beispiel #37
0
    def get_images(self):
        '''get_images is a wrapper for get_manifest, but it additionally parses the repo_name and tag's
        images and returns the complete ids
        :param repo_name: the name of the repo, eg "ubuntu"
        :param namespace: the namespace for the image, default is "library"
        :param repo_tag: the repo tag, default is "latest"
        :param registry: the docker registry url, default will use index.docker.io
        '''

        # Get full image manifest, using version 2.0 of Docker Registry API
        if self.manifest is None:
            if self.repo_name is not None and self.namespace is not None:
                self.manifest = self.get_manifest()

            else:
                bot.error("No namespace and repo name OR manifest provided, exiting.")
                sys.exit(1)

        digests = read_digests(self.manifest)
        return digests
Beispiel #38
0
    def update_manifests(self):
        '''update manifests ensures that each of a version1 and version2
        manifest are present
        '''
        bot.debug('Updating manifests.')

        if self.repo_name is None:
            bot.error("Insufficient metadata to get manifest.")
            sys.exit(1)

        # Get full image manifest, using version 2.0 of Docker Registry API
        if self.manifest is None:
            bot.debug('MANIFEST (Primary): not found, making initial call.')
            self.manifest = self.get_manifest()
            # This is the primary manifest schema version, determines if we
            # need to reverse layers
            self.schemaVersion = self.manifest['schemaVersion']
            if self.schemaVersion == 1:
                self.reverseLayers = True

        if self.manifestv1 is None:
            bot.debug('MANIFEST (Metadata): not found, making initial call.')
            self.manifestv1 = self.get_manifest(old_version=True)

        # https://docs.docker.com/registry/spec/manifest-v2-2/#manifest-list
        if "manifests" in self.manifest:
            for entry in self.manifest['manifests']:
                if entry['platform']['architecture'] == DOCKER_ARCHITECTURE:
                    if entry['platform']['os'] == DOCKER_OS:
                        digest = entry['digest']
                        bot.debug('Image manifest version 2.2 list found.')
                        bot.debug('Obtaining architecture: %s, OS: %s'
                                  % (DOCKER_ARCHITECTURE, DOCKER_OS))

                        # Obtain specific os, architecture
                        self.manifest = self.get_manifest(version=digest)
                        break

        # If we didn't get a new manifest, fall back to version 1
        if "manifests" in self.manifest:
            self.manifest = self.manifestv1
Beispiel #39
0
    def update_manifests(self):
        '''update manifests ensures that each of a version1 and version2
        manifest are present
        '''
        bot.debug('Updating manifests.')

        if self.repo_name is None:
            bot.error("Insufficient metadata to get manifest.")
            sys.exit(1)

        # Get full image manifest, using version 2.0 of Docker Registry API
        if self.manifest is None:
            bot.debug('MANIFEST (Primary): not found, making initial call.')
            self.manifest = self.get_manifest()
            # This is the primary manifest schema version, determines if we
            # need to reverse layers
            self.schemaVersion = self.manifest['schemaVersion']
            if self.schemaVersion == 1:
                self.reverseLayers = True

        if self.manifestv1 is None:
            bot.debug('MANIFEST (Metadata): not found, making initial call.')
            self.manifestv1 = self.get_manifest(old_version=True)

        # https://docs.docker.com/registry/spec/manifest-v2-2/#manifest-list
        if "manifests" in self.manifest:
            for entry in self.manifest['manifests']:
                if entry['platform']['architecture'] == DOCKER_ARCHITECTURE:
                    if entry['platform']['os'] == DOCKER_OS:
                        digest = entry['digest']
                        bot.debug('Image manifest version 2.2 list found.')
                        bot.debug('Obtaining architecture: %s, OS: %s' %
                                  (DOCKER_ARCHITECTURE, DOCKER_OS))

                        # Obtain specific os, architecture
                        self.manifest = self.get_manifest(version=digest)
                        break

        # If we didn't get a new manifest, fall back to version 1
        if "manifests" in self.manifest:
            self.manifest = self.manifestv1
Beispiel #40
0
def run_command(cmd, env=None, quiet=False):
    '''run_command uses subprocess to send a command to the terminal.
    :param cmd: the command to send, should be a list for subprocess
    :param env: an optional environment to include, a dictionary of key/values
    '''
    try:
        if quiet is False:
            bot.verbose2("Running command %s with subprocess" % " ".join(cmd))
        if env is None:
            process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
        else:
            process = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=env)
    except OSError as error:
        bot.error("Error with subprocess: %s, returning None" % error)
        return None

    output = process.communicate()[0]
    if process.returncode != 0:
        return None

    return output
Beispiel #41
0
def main():

    parser = get_parser()

    try:
        (args, options) = parser.parse_args()
    except Exception:
        sys.exit(0)

    if args.key is not None and args.file is not None:
        if args.value is not None:

            value = ADD(key=args.key,
                        value=args.value,
                        jsonfile=args.file,
                        force=args.force,
                        quiet=args.quiet)

    else:
        bot.error("--key and --file and --value must be defined for ADD.")
        sys.exit(1)
Beispiel #42
0
def run_command(cmd, env=None, quiet=False):
    '''run_command uses subprocess to send a command to the terminal.
    :param cmd: the command to send, should be a list for subprocess
    :param env: an optional environment to include, a dictionary of key/values
    '''
    try:
        if quiet is False:
            bot.verbose2("Running command %s with subprocess" % " ".join(cmd))
        if env is None:
            process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
        else:
            process = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=env)
    except OSError as error:
        bot.error("Error with subprocess: %s, returning None" % error)
        return None

    output = process.communicate()[0]
    if process.returncode != 0:
        return None

    return output
Beispiel #43
0
def getenv(variable_key,required=False,default=None,silent=False):
    '''getenv will attempt to get an environment variable. If the variable
    is not found, None is returned.
    :param variable_key: the variable name
    :param required: exit with error if not found
    :param silent: Do not print debugging information for variable
    '''
    variable = os.environ.get(variable_key, default)
    if variable == None and required:
        bot.error("Cannot find environment variable %s, exiting." %variable_key)
        sys.exit(1)

    if silent:
        bot.verbose2("%s found" %variable_key)
    else:
        if variable is not None:
            bot.verbose2("%s found as %s" %(variable_key,variable))
        else:
            bot.verbose2("%s not defined (None)" %variable_key)

    return variable 
Beispiel #44
0
def DELETE(key,jsonfile):
    '''DELETE will remove a key from a json file
    '''
    key = format_keyname(key)
    bot.debug("DELETE %s from %s" %(key,jsonfile))
    if not os.path.exists(jsonfile):
        bot.error("Cannot find %s, exiting." %jsonfile)
        sys.exit(1)
    
    contents = read_json(jsonfile)
    if key in contents:
        del contents[key]
        if len(contents) > 0:
            write_json(contents,jsonfile)
        else:
            bot.debug('%s is empty, deleting.' %jsonfile)
            os.remove(jsonfile)
        return True
    else:    
        bot.debug('Warning, %s not found in %s' %(key,jsonfile))
        return False
Beispiel #45
0
def main():
    '''this function will run the main size functions and call shub clients 
    '''

    container = getenv("SINGULARITY_CONTAINER",required=True)
    image_uri = get_image_uri(container)    
    container = remove_image_uri(container)
    from defaults import LAYERFILE

    ##############################################################################
    # Singularity Hub ############################################################
    ##############################################################################

    if image_uri == "shub://":

        bot.debug("\n*** STARTING SINGULARITY HUB SIZE PYTHON  ****")    
        from shub.main import SIZE
        SIZE(image=container,
             contentfile=LAYERFILE)

    elif image_uri == "docker://":

        from sutils import basic_auth_header
        from docker.main import SIZE

        bot.debug("Docker sizes will be written to: %s" %LAYERFILE)
        username = getenv("SINGULARITY_DOCKER_USERNAME") 
        password = getenv("SINGULARITY_DOCKER_PASSWORD",silent=True)

        auth = None
        if username is not None and password is not None:
            auth = basic_auth_header(username, password)

        SIZE(image=container,
             auth=auth,
             contentfile=LAYERFILE)

    else:
        bot.error("uri %s is not a currently supported uri for size. Exiting." %image_uri)
        sys.exit(1)
Beispiel #46
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)
Beispiel #47
0
    def get_manifest(self):
        '''get_image will return a json object with image metadata
        based on a unique id.

        Parameters
        ==========
        :param image: the image name, either an id
                      or a repo name, tag, etc.

        Returns
        =======
        manifest: a json manifest from the registry
        '''
        # make sure we have a complete url
        registry = add_http(self.image['registry'])
        base = "%s/api/container/%s/%s:%s" % (registry,
                                              self.image['namespace'],
                                              self.image['repo_name'],
                                              self.image['repo_tag'])
        # ------------------------------------------------------
        # If we need to authenticate, will do it here
        # ------------------------------------------------------

        # If the Hub returns 404, the image name is likely wrong
        response = self.get(base, return_response=True)
        if response.code == 404:
            msg = "Cannot find image."
            msg += " Is your capitalization correct?"
            bot.error(msg)
            sys.exit(1)

        try:
            response = response.read().decode('utf-8')
            response = json.loads(response)

        except Exception:
            print("Error getting image manifest using url %s" % base)
            sys.exit(1)

        return response
Beispiel #48
0
    def get_manifest(self):
        '''get_image will return a json object with image metadata
        based on a unique id.

        Parameters
        ==========
        :param image: the image name, either an id
                      or a repo name, tag, etc.

        Returns
        =======
        manifest: a json manifest from the registry
        '''
        # make sure we have a complete url
        registry = add_http(self.image['registry'])
        base = "%s/api/container/%s/%s:%s" % (registry,
                                              self.image['namespace'],
                                              self.image['repo_name'],
                                              self.image['repo_tag'])
        # ------------------------------------------------------
        # If we need to authenticate, will do it here
        # ------------------------------------------------------

        # If the Hub returns 404, the image name is likely wrong
        response = self.get(base, return_response=True)
        if response.code == 404:
            msg = "Cannot find image."
            msg += " Is your capitalization correct?"
            bot.error(msg)
            sys.exit(1)

        try:
            response = response.read().decode('utf-8')
            response = json.loads(response)

        except Exception:
            print("Error getting image manifest using url %s" % base)
            sys.exit(1)

        return response
Beispiel #49
0
def verify_layer(targz):
    '''check that a downloaded layer's sha256 checksum is OK
       correct checksum is in the filename:
           sha256:7d460157dea423c1e16c544fecad995439e12dd50c8db4a8e134fa245cd1846e.tar.gz
    '''

    targz_basename = os.path.basename(targz)

    bot.debug("Verifying checksum for layer: %s" % targz_basename)

    if targz_basename[:6] != 'sha256':
        bot.warning(
            "Unknown hash function for layer (%s) - will not checksum" %
            targz_basename[:5])
        return True

    expected = targz_basename[7:71]

    sha256 = hashlib.sha256()

    try:
        with open(targz, 'rb') as f:
            for block in iter(lambda: f.read(1048576), b''):
                sha256.update(block)
    except Exception as e:
        bot.error("Error computing checksum for layer (%s) - %s" %
                  (targz_basename, str(e)))
        return False

    computed = sha256.hexdigest()

    bot.debug("Computed checksum %s, expected checksum %s" %
              (computed, expected))

    if computed != expected:
        bot.error("Downloaded layer %s does not match checksum" %
                  targz_basename)
        return False

    return True
Beispiel #50
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)
Beispiel #51
0
def ADD(key,value,jsonfile,force=False):
    '''ADD will write or update a key in a json file
    '''
    key = format_keyname(key)
    bot.debug("Adding label: '%s' = '%s'" %(key, value))
    bot.debug("ADD %s from %s" %(key,jsonfile))
    if os.path.exists(jsonfile):    
        contents = read_json(jsonfile)
        if key in contents:
            bot.debug('Warning, %s is already set. Overwrite is set to %s' %(key,force))
            if force == True:
                contents[key] = value
            else:
                bot.error('%s found in %s and overwrite set to %s.' %(key,jsonfile,force))
                sys.exit(1)
        else:
            contents[key] = value
    else:
        contents = {key:value}
    bot.debug('%s is %s' %(key,value))
    write_json(contents,jsonfile)
    return value
Beispiel #52
0
    def get_images(self):
        '''get_images is a wrapper for get_manifest, but it additionally parses the repo_name and tag's
        images and returns the complete ids
        :param repo_name: the name of the repo, eg "ubuntu"
        :param namespace: the namespace for the image, default is "library"
        :param repo_tag: the repo tag, default is "latest"
        :param registry: the docker registry url, default will use index.docker.io
        '''

        # Get full image manifest, using version 2.0 of Docker Registry API
        if self.manifest is None:
            if self.repo_name is not None and self.namespace is not None:
                self.manifest = self.get_manifest()

            else:
                bot.error(
                    "No namespace and repo name OR manifest provided, exiting."
                )
                sys.exit(1)

        digests = read_digests(self.manifest)
        return digests
Beispiel #53
0
def getenv(variable_key, required=False, default=None, silent=False):
    '''getenv will attempt to get an environment variable. If the
    variable is not found, None is returned.
    :param variable_key: the variable name
    :param required: exit with error if not found
    :param silent: Do not print debugging information for variable
    '''
    variable = os.environ.get(variable_key, default)
    if variable is None and required:
        bot.error("Cannot find environment variable %s, exiting."
                  % (variable_key))
        sys.exit(1)

    if silent:
        bot.verbose2("%s found" % (variable_key))
    else:
        if variable is not None:
            bot.verbose2("%s found as %s" % (variable_key, variable))
        else:
            bot.verbose2("%s not defined (None)" % (variable_key))

    return variable
def main():
    '''parse configuration options and produce
       configuration output file
    '''
    bot.info("\n*** STARTING PYTHON CONFIGURATION HELPER ****")
    parser = get_parser()

    try:
        (args, options) = parser.parse_args()
    except Exception:
        bot.error("Input args to %s improperly set, exiting."
                  % os.path.abspath(__file__))
        parser.print_help()
        sys.exit(1)

    # Check for required args
    [check_required(parser, arg) for arg in [args.defaults,
                                             args.infile,
                                             args.outfile]]

    # Run the configuration
    configure(args)