Exemplo n.º 1
0
 def __init__(self, localrepo):
     self.localrepo = localrepo  # LocalRepository object
     self._autoinstall = Config.conf['autoinstall']  # True / False
     self._tarball = Config.conf['tarball']  # URL or file
     self._installinfo = Config.conf['installinfo']  # URL or file
     self._tarball_release = Config.conf['tarball_release']
     self._installretry = Config.conf['installretry']
     self._install_json = dict()
     self.curl = GetURL()
Exemplo n.º 2
0
 def __init__(self, localrepo):
     self.index_url = Config.conf['dockerio_index_url']
     self.registry_url = Config.conf['dockerio_registry_url']
     self.v1_auth_header = ""
     self.v2_auth_header = ""
     self.v2_auth_token = ""
     self.localrepo = localrepo
     self.curl = GetURL()
     self.search_pause = True
     self.search_page = 0
     self.search_ended = False
Exemplo n.º 3
0
    def test_04_set_insecure(self, mock_gupycurl):
        """Test04 GetURL().set_insecure()."""
        mock_gupycurl.return_value = True
        geturl = GetURL()
        geturl.set_insecure()
        self.assertEqual(geturl.insecure, True)

        geturl = GetURL()
        geturl.set_insecure(False)
        self.assertEqual(geturl.insecure, False)
Exemplo n.º 4
0
    def test_08_get_status_code(self):
        """Test08 GetURL().get_status_code()."""
        sline = "HTTP-Version 400 Reason-Phrase"
        geturl = GetURL()
        status = geturl.get_status_code(sline)
        self.assertEqual(status, 400)

        sline = "HTTP-Version Reason-Phrase"
        geturl = GetURL()
        status = geturl.get_status_code(sline)
        self.assertEqual(status, 400)

        sline = ""
        geturl = GetURL()
        status = geturl.get_status_code(sline)
        self.assertEqual(status, 404)
Exemplo n.º 5
0
 def test_01_init(self, mock_msg, mock_gupycurl, mock_guexecurl,
                  mock_select):
     """Test01 GetURL() constructor."""
     mock_msg.level = 0
     mock_gupycurl.return_value = False
     mock_guexecurl.return_value = True
     geturl = GetURL()
     mock_select.assert_called()
     self.assertEqual(geturl.ctimeout, Config().conf['ctimeout'])
     self.assertEqual(geturl.insecure, Config().conf['http_insecure'])
     self.assertFalse(geturl.cache_support)
Exemplo n.º 6
0
    def test_02__select_implementation(self, mock_gupycurl, mock_guexecurl,
                                       mock_msg):
        """Test02 GetURL()._select_implementation()."""
        mock_msg.level = 0
        mock_gupycurl.return_value = True
        geturl = GetURL()
        geturl._select_implementation()
        # self.assertTrue(geturl.cache_support)

        mock_gupycurl.return_value = False
        geturl = GetURL()
        geturl._select_implementation()
        self.assertFalse(geturl.cache_support)

        mock_guexecurl.return_value = False
        with self.assertRaises(NameError):
            GetURL()
Exemplo n.º 7
0
    def test_06_get(self):
        """Test06 GetURL().get()."""
        geturl = GetURL()
        self.assertRaises(TypeError, geturl.get)

        geturl = GetURL()
        geturl._geturl = type('test', (object, ), {})()
        geturl._geturl.get = self._get
        self.assertEqual(geturl.get("http://host"), "http://host")
Exemplo n.º 8
0
    def test_07_post(self):
        """Test07 GetURL().post()."""
        geturl = GetURL()
        self.assertRaises(TypeError, geturl.post)
        self.assertRaises(TypeError, geturl.post, "http://host")

        geturl = GetURL()
        geturl._geturl = type('test', (object, ), {})()
        geturl._geturl.get = self._get
        status = geturl.post("http://host", {
            "DATA": 1,
        })
        self.assertEqual(status, "http://host")
Exemplo n.º 9
0
    def test_03_get_content_length(self):
        """Test03 GetURL().get_content_length()."""
        hdr = type('test', (object, ), {})()
        hdr.data = {
            "content-length": 10,
        }
        geturl = GetURL()
        self.assertEqual(geturl.get_content_length(hdr), 10)

        hdr = type('test', (object, ), {})()
        hdr.data = {
            "content-length": dict(),
        }
        geturl = GetURL()
        self.assertEqual(geturl.get_content_length(hdr), -1)
Exemplo n.º 10
0
class DockerIoAPI(object):
    """Class to encapsulate the access to the Docker Hub service
    Allows to search and download images from Docker Hub
    """
    def __init__(self, localrepo):
        self.index_url = Config.conf['dockerio_index_url']
        self.registry_url = Config.conf['dockerio_registry_url']
        self.v1_auth_header = ""
        self.v2_auth_header = ""
        self.v2_auth_token = ""
        self.localrepo = localrepo
        self.curl = GetURL()
        self.search_pause = True
        self.search_page = 0
        self.search_ended = False

    def set_proxy(self, http_proxy):
        """Select a socks http proxy for API access and file download"""
        self.curl.set_proxy(http_proxy)

    def set_registry(self, registry_url):
        """Change docker registry url"""
        self.registry_url = registry_url

    def set_index(self, index_url):
        """Change docker index url"""
        self.index_url = index_url

    def is_repo_name(self, imagerepo):
        """Check if name matches authorized characters for a docker repo"""
        if imagerepo and re.match("^[a-zA-Z0-9][a-zA-Z0-9-_./:]+$", imagerepo):
            return True
        return False

    def _get_url(self, *args, **kwargs):
        """Encapsulates the call to GetURL.get() so that authentication
        for v1 and v2 repositories can be treated differently.
        Example:
             _get_url(url, ctimeout=5, timeout=5, header=[]):
        """
        url = str(args[0])
        if "RETRY" not in kwargs:
            kwargs["RETRY"] = 3
        if "FOLLOW" not in kwargs:
            kwargs["FOLLOW"] = 3
        kwargs["RETRY"] -= 1
        (hdr, buf) = self.curl.get(*args, **kwargs)
        Msg().out("Info: header: %s" % (hdr.data), l=Msg.DBG)
        Msg().out("Info: buffer: %s" % (buf.getvalue()), l=Msg.DBG)
        status_code = self.curl.get_status_code(hdr.data["X-ND-HTTPSTATUS"])
        if status_code == 200:
            return (hdr, buf)
        if not kwargs["RETRY"]:
            hdr.data["X-ND-CURLSTATUS"] = 13  # Permission denied
            return (hdr, buf)
        auth_kwargs = kwargs.copy()
        if "location" not in hdr.data:
            kwargs["FOLLOW"] = 3
        if "location" in hdr.data and hdr.data['location']:
            if not kwargs["FOLLOW"]:
                hdr.data["X-ND-CURLSTATUS"] = 13
                return (hdr, buf)
            kwargs["FOLLOW"] -= 1
            args = [hdr.data['location']]
            if "header" in auth_kwargs:
                del auth_kwargs["header"]
        elif status_code == 401:
            if "www-authenticate" in hdr.data:
                www_authenticate = hdr.data["www-authenticate"]
                if not "realm" in www_authenticate:
                    return (hdr, buf)
                if 'error="insufficient_scope"' in www_authenticate:
                    return (hdr, buf)
                auth_header = ""
                if "/v2/" in url:
                    auth_header = self._get_v2_auth(www_authenticate,
                                                    kwargs["RETRY"])
                if "/v1/" in url:
                    auth_header = self._get_v1_auth(www_authenticate)
                auth_kwargs.update({"header": [auth_header]})
        (hdr, buf) = self._get_url(*args, **auth_kwargs)
        return (hdr, buf)

    def _get_file(self, url, filename, cache_mode):
        """Get a file and check its size. Optionally enable other
        capabilities such as caching to check if the
        file already exists locally and whether its size is the
        same to avoid downloaded it again.
        """
        match = re.search("/([^/:]+):(\\S+)$", filename)
        if match:
            layer_f_chksum = ChkSUM().hash(filename, match.group(1))
            if layer_f_chksum == match.group(2):
                return True  # is cached skip download
            cache_mode = 0
        if self.curl.cache_support and cache_mode:
            if cache_mode == 1:
                (hdr, dummy) = self._get_url(url, nobody=1)
            elif cache_mode == 3:
                (hdr, dummy) = self._get_url(url, sizeonly=True)
            remote_size = self.curl.get_content_length(hdr)
            if remote_size == FileUtil(filename).size():
                return True  # is cached skip download
        else:
            remote_size = -1
        resume = False
        if filename.endswith("layer"):
            resume = True
        (hdr, dummy) = self._get_url(url, ofile=filename, resume=resume)
        if self.curl.get_status_code(hdr.data["X-ND-HTTPSTATUS"]) != 200:
            return False
        if remote_size == -1:
            remote_size = self.curl.get_content_length(hdr)
        if (remote_size != FileUtil(filename).size()
                and hdr.data["X-ND-CURLSTATUS"]):
            Msg().err("Error: file size mismatch:", filename, remote_size,
                      FileUtil(filename).size())
            return False
        return True

    def _split_fields(self, buf):
        """Split  fields, used in the web authentication"""
        all_fields = dict()
        for field in buf.split(','):
            pair = field.split('=', 1)
            if len(pair) == 2:
                all_fields[pair[0]] = pair[1].strip('"')
        return all_fields

    def is_v1(self):
        """Check if registry is of type v1"""
        for prefix in ("/v1", "/v1/_ping"):
            (hdr, dummy) = self._get_url(self.index_url + prefix)
            try:
                if ("200" in hdr.data["X-ND-HTTPSTATUS"]
                        or "401" in hdr.data["X-ND-HTTPSTATUS"]):
                    return True
            except (KeyError, AttributeError, TypeError):
                pass
        return False

    def has_search_v1(self, url=None):
        """Check if registry has search capabilities in v1"""
        if url is None:
            url = self.index_url
        (hdr, dummy) = self._get_url(url + "/v1/search")
        try:
            if ("200" in hdr.data["X-ND-HTTPSTATUS"]
                    or "401" in hdr.data["X-ND-HTTPSTATUS"]):
                return True
        except (KeyError, AttributeError, TypeError):
            pass
        return False

    def get_v1_repo(self, imagerepo):
        """Get list of images in a repo from Docker Hub"""
        url = self.index_url + "/v1/repositories/" + imagerepo + "/images"
        Msg().out("Info: repo url", url, l=Msg.DBG)
        (hdr, buf) = self._get_url(url, header=["X-Docker-Token: true"])
        try:
            self.v1_auth_header = "Authorization: Token " + \
                hdr.data["x-docker-token"]
            return hdr.data, json.loads(buf.getvalue())
        except (IOError, OSError, AttributeError, ValueError, TypeError,
                KeyError):
            self.v1_auth_header = ""
            return hdr.data, []

    def _get_v1_auth(self, www_authenticate):
        """Authentication for v1 API"""
        if "Token" in www_authenticate:
            return self.v1_auth_header
        return ""

    def get_v1_image_tags(self, imagerepo, tags_only=False):
        """Get list of tags in a repo from Docker Hub"""
        (hdr_data, dummy) = self.get_v1_repo(imagerepo)
        try:
            endpoint = "http://" + hdr_data["x-docker-endpoints"]
        except KeyError:
            endpoint = self.index_url
        url = endpoint + "/v1/repositories/" + imagerepo + "/tags"
        Msg().out("Info: tags url", url, l=Msg.DBG)
        (dummy, buf) = self._get_url(url)
        tags = []
        try:
            if tags_only:
                for tag in json.loads(buf.getvalue()):
                    tags.append(tag["name"])
                return tags

            return json.loads(buf.getvalue())
        except (IOError, OSError, AttributeError, ValueError, TypeError):
            return []

    def get_v1_image_tag(self, endpoint, imagerepo, tag):
        """Get list of tags in a repo from Docker Hub"""
        url = endpoint + "/v1/repositories/" + imagerepo + "/tags/" + tag
        Msg().out("Info: tags url", url, l=Msg.DBG)
        (hdr, buf) = self._get_url(url)
        try:
            return (hdr.data, json.loads(buf.getvalue()))
        except (IOError, OSError, AttributeError, ValueError, TypeError):
            return (hdr.data, [])

    def get_v1_image_ancestry(self, endpoint, image_id):
        """Get the ancestry which is an ordered list of layers"""
        url = endpoint + "/v1/images/" + image_id + "/ancestry"
        Msg().out("Info: ancestry url", url, l=Msg.DBG)
        (hdr, buf) = self._get_url(url)
        try:
            return (hdr.data, json.loads(buf.getvalue()))
        except (IOError, OSError, AttributeError, ValueError, TypeError):
            return (hdr.data, [])

    def get_v1_image_json(self, endpoint, layer_id):
        """Get the JSON metadata for a specific layer"""
        url = endpoint + "/v1/images/" + layer_id + "/json"
        Msg().out("Info: json url", url, l=Msg.DBG)
        filename = self.localrepo.layersdir + '/' + layer_id + ".json"
        if self._get_file(url, filename, 0):
            self.localrepo.add_image_layer(filename)
            return True
        return False

    def get_v1_image_layer(self, endpoint, layer_id):
        """Get a specific layer data file (layer files are tarballs)"""
        url = endpoint + "/v1/images/" + layer_id + "/layer"
        Msg().out("Info: layer url", url, l=Msg.DBG)
        filename = self.localrepo.layersdir + '/' + layer_id + ".layer"
        if self._get_file(url, filename, 3):
            self.localrepo.add_image_layer(filename)
            return True
        return False

    def get_v1_layers_all(self, endpoint, layer_list):
        """Using a layer list download data and metadata files"""
        files = []
        if layer_list:
            for layer_id in reversed(layer_list):
                Msg().out("Info: downloading layer", layer_id, l=Msg.INF)
                filesize = self.get_v1_image_json(endpoint, layer_id)
                if not filesize:
                    return []
                files.append(layer_id + ".json")
                filesize = self.get_v1_image_layer(endpoint, layer_id)
                if not filesize:
                    return []
                files.append(layer_id + ".layer")
        return files

    def _get_v2_auth(self, www_authenticate, retry):
        """Authentication for v2 API"""
        auth_header = ""
        (bearer, auth_data) = www_authenticate.rsplit(' ', 1)
        if bearer == "Bearer":
            auth_fields = self._split_fields(auth_data)
            if "realm" in auth_fields:
                auth_url = auth_fields["realm"] + '?'
                for field in auth_fields:
                    if field != "realm":
                        auth_url += field + '=' + auth_fields[field] + '&'
                header = []
                if self.v2_auth_token:
                    header = ["Authorization: Basic %s" % (self.v2_auth_token)]
                (dummy, auth_buf) = self._get_url(auth_url,
                                                  header=header,
                                                  RETRY=retry)
                if sys.version_info[0] >= 3:
                    token_buf = auth_buf.getvalue().decode()
                else:
                    token_buf = auth_buf.getvalue()
                if token_buf and "token" in token_buf:
                    try:
                        auth_token = json.loads(token_buf)
                    except (IOError, OSError, AttributeError, ValueError,
                            TypeError):
                        return auth_header
                    auth_header = "Authorization: Bearer " + \
                        auth_token["token"]
                    self.v2_auth_header = auth_header
        # PR #126
        elif 'BASIC' in bearer or 'Basic' in bearer:
            auth_header = "Authorization: Basic %s" % (self.v2_auth_token)
            self.v2_auth_header = auth_header
        return auth_header

    def get_v2_login_token(self, username, password):
        """Get a login token from username and password"""
        if not (username and password):
            return ""
        try:
            self.v2_auth_token = \
                base64.b64encode(("%s:%s" % \
                    (username, password)).encode("utf-8")).decode("ascii")
        except (KeyError, AttributeError, TypeError, ValueError, NameError):
            self.v2_auth_token = ""
        return self.v2_auth_token

    def set_v2_login_token(self, v2_auth_token):
        """Load previously created login token"""
        self.v2_auth_token = v2_auth_token

    def is_v2(self):
        """Check if registry is of type v2"""
        (hdr, dummy) = self._get_url(self.registry_url + "/v2/")
        try:
            if ("200" in hdr.data["X-ND-HTTPSTATUS"]
                    or "401" in hdr.data["X-ND-HTTPSTATUS"]):
                return True
        except (KeyError, AttributeError, TypeError):
            pass
        return False

    def has_search_v2(self, url=None):
        """Check if registry has search capabilities in v2"""
        if url is None:
            url = self.registry_url
        (hdr, dummy) = self._get_url(url + "/v2/search/repositories")
        try:
            if ("200" in hdr.data["X-ND-HTTPSTATUS"]
                    or "401" in hdr.data["X-ND-HTTPSTATUS"]):
                return True
        except (KeyError, AttributeError, TypeError):
            pass
        return False

    def get_v2_image_tags(self, imagerepo, tags_only=False):
        """Get list of tags in a repo from Docker Hub"""
        url = self.registry_url + "/v2/" + imagerepo + "/tags/list"
        Msg().out("Info: tags url", url, l=Msg.DBG)
        (dummy, buf) = self._get_url(url)
        tags = []
        try:
            if tags_only:
                for tag in json.loads(buf.getvalue())["tags"]:
                    tags.append(tag)
                return tags

            return json.loads(buf.getvalue())
        except (IOError, OSError, AttributeError, ValueError, TypeError):
            return []

    def get_v2_image_manifest(self, imagerepo, tag):
        """Get the image manifest which contains JSON metadata
        that is common to all layers in this image tag
        """
        url = self.registry_url + "/v2/" + imagerepo + \
            "/manifests/" + tag
        Msg().out("Info: manifest url", url, l=Msg.DBG)
        (hdr, buf) = self._get_url(url)
        try:
            return (hdr.data, json.loads(buf.getvalue()))
        except (IOError, OSError, AttributeError, ValueError, TypeError):
            return (hdr.data, [])

    def get_v2_image_layer(self, imagerepo, layer_id):
        """Get one image layer data file (tarball)"""
        url = self.registry_url + "/v2/" + imagerepo + \
            "/blobs/" + layer_id
        Msg().out("Info: layer url", url, l=Msg.DBG)
        filename = self.localrepo.layersdir + '/' + layer_id
        if self._get_file(url, filename, 3):
            self.localrepo.add_image_layer(filename)
            return True
        return False

    def get_v2_layers_all(self, imagerepo, fslayers):
        """Get all layer data files belonging to a image tag"""
        files = []
        blob = ""
        if fslayers:
            for layer in reversed(fslayers):
                if "blobSum" in layer:
                    blob = layer["blobSum"]
                elif "digest" in layer:
                    blob = layer["digest"]
                Msg().out("Info: downloading layer", blob, l=Msg.INF)
                if not self.get_v2_image_layer(imagerepo, blob):
                    return []
                files.append(blob)
        return files

    def get_v2(self, imagerepo, tag):
        """Pull container with v2 API"""
        files = []
        (hdr_data, manifest) = self.get_v2_image_manifest(imagerepo, tag)
        status = self.curl.get_status_code(hdr_data["X-ND-HTTPSTATUS"])
        if status == 401:
            Msg().err("Error: manifest not found or not authorized")
            return []
        if status != 200:
            Msg().err("Error: pulling manifest:")
            return []
        try:
            if not (self.localrepo.setup_tag(tag)
                    and self.localrepo.set_version("v2")):
                Msg().err("Error: setting localrepo v2 tag and version")
                return []
            self.localrepo.save_json("manifest", manifest)
            Msg().out("Info: v2 layers: %s" % (imagerepo), l=Msg.DBG)
            if "fsLayers" in manifest:
                files = self.get_v2_layers_all(imagerepo, manifest["fsLayers"])
            elif "layers" in manifest:
                if "config" in manifest:
                    manifest["layers"].append(manifest["config"])
                files = self.get_v2_layers_all(imagerepo, manifest["layers"])
            else:
                Msg().err("Error: layers section missing in manifest")
        except (KeyError, AttributeError, IndexError, ValueError, TypeError):
            pass
        return files

    def _get_v1_id_from_tags(self, tags_obj, tag):
        """Get image id from array of tags"""
        if isinstance(tags_obj, dict):
            try:
                return tags_obj[tag]
            except KeyError:
                pass
        elif isinstance(tags_obj, list):
            try:
                for tag_dict in tags_obj:
                    if tag_dict["name"] == tag:
                        return tag_dict["layer"]
            except KeyError:
                pass
        return ""

    def _get_v1_id_from_images(self, images_array, short_id):
        """Get long image id from array of images using the short id"""
        try:
            for image_dict in images_array:
                if image_dict["id"][0:8] == short_id:
                    return image_dict["id"]
        except KeyError:
            pass
        return ""

    def get_v1(self, imagerepo, tag):
        """Pull container with v1 API"""
        Msg().out("Info: v1 image id: %s" % (imagerepo), l=Msg.DBG)
        (hdr_data, images_array) = self.get_v1_repo(imagerepo)
        status = self.curl.get_status_code(hdr_data["X-ND-HTTPSTATUS"])
        if status == 401 or not images_array:
            Msg().err("Error: image not found or not authorized")
            return []
        try:
            endpoint = "http://" + hdr_data["x-docker-endpoints"]
        except KeyError:
            endpoint = self.index_url
        (tags_array) = self.get_v1_image_tags(endpoint, imagerepo)
        image_id = self._get_v1_id_from_tags(tags_array, tag)
        if not image_id:
            Msg().err("Error: image tag not found")
            return []
        if len(image_id) <= 8:
            image_id = self._get_v1_id_from_images(images_array, image_id)
            if not image_id:
                Msg().err("Error: image id not found")
                return []
        if not (self.localrepo.setup_tag(tag)
                and self.localrepo.set_version("v1")):
            Msg().err("Error: setting localrepo v1 tag and version")
            return []
        Msg().out("Info: v1 ancestry", image_id, l=Msg.DBG)
        (dummy, ancestry) = self.get_v1_image_ancestry(endpoint, image_id)
        if not ancestry:
            Msg().err("Error: ancestry not found")
            return []
        self.localrepo.save_json("ancestry", ancestry)
        Msg().out("Info: v1 layers", image_id, l=Msg.DBG)
        files = self.get_v1_layers_all(endpoint, ancestry)
        return files

    def _parse_imagerepo(self, imagerepo):
        """Parse imagerepo to extract registry"""
        remoterepo = imagerepo
        registry = ""
        registry_url = ""
        index_url = ""
        components = imagerepo.split('/')
        if '.' in components[0] and len(components) >= 2:
            registry = components[0]
            del components[0]
        elif ('.' not in components[0] and components[0] != "library"
              and len(components) == 1):
            components.insert(0, "library")
        remoterepo = '/'.join(components)
        if registry:
            try:
                registry_url = Config.conf['docker_registries'][registry][0]
                index_url = Config.conf['docker_registries'][registry][1]
            except (KeyError, NameError, TypeError):
                registry_url = registry
                if "://" not in registry:
                    registry_url = "https://%s" % registry
                index_url = registry_url
            if registry_url:
                self.registry_url = registry_url
            if index_url:
                self.index_url = index_url
        return (imagerepo, remoterepo)

    def get(self, imagerepo, tag):
        """Pull a docker image from a v2 registry or v1 index"""
        Msg().out("Info: get imagerepo: %s tag: %s" % (imagerepo, tag),
                  l=Msg.DBG)
        (imagerepo, remoterepo) = self._parse_imagerepo(imagerepo)
        if self.localrepo.cd_imagerepo(imagerepo, tag):
            new_repo = False
        else:
            self.localrepo.setup_imagerepo(imagerepo)
            new_repo = True
        if self.is_v2():
            files = self.get_v2(remoterepo, tag)  # try v2
        else:
            files = self.get_v1(remoterepo, tag)  # try v1
        if new_repo and not files:
            self.localrepo.del_imagerepo(imagerepo, tag, False)
        return files

    def get_tags(self, imagerepo):
        """List tags from a v2 or v1 repositories"""
        Msg().out("Info: get tags", imagerepo, l=Msg.DBG)
        (dummy, remoterepo) = self._parse_imagerepo(imagerepo)
        if self.is_v2():
            return self.get_v2_image_tags(remoterepo, True)  # try v2
        return self.get_v1_image_tags(remoterepo, True)  # try v1

    def search_init(self, pause):
        """Setup new search"""
        self.search_pause = pause
        self.search_page = 0
        self.search_ended = False

    def search_get_page_v1(self, expression, url):
        """Get search results from Docker Hub using v1 API"""
        if expression:
            url = url + "/v1/search?q=%s" % expression
        else:
            url = url + "/v1/search?"
        url += "&page=%s" % str(self.search_page)
        (dummy, buf) = self._get_url(url)
        try:
            repo_list = json.loads(buf.getvalue())
            if repo_list["page"] == repo_list["num_pages"]:
                self.search_ended = True
            return repo_list
        except (IOError, OSError, AttributeError, ValueError, TypeError):
            self.search_ended = True
            return []

    def search_get_page_v2(self, expression, url, lines=22, official=None):
        """Search results from Docker Hub using v2 API"""
        if not expression:
            expression = '*'
        if expression and official is None:
            url = url + \
                    "/v2/search/repositories?query=%s" % (expression)
        elif expression and official is True:
            url = url + \
                    "/v2/search/repositories?query=%s&is_official=%s" \
                    % (expression, "true")
        elif expression and official is False:
            url = url + \
                    "/v2/search/repositories?query=%s&is_official=%s" \
                    % (expression, "false")
        else:
            return []
        url += "&page_size=%d" % (lines)
        if self.search_page != 1:
            url += "&page=%d" % (self.search_page)
        (dummy, buf) = self._get_url(url)
        try:
            repo_list = json.loads(buf.getvalue())
            if repo_list["count"] == self.search_page:
                self.search_ended = True
            return repo_list
        except (IOError, OSError, AttributeError, KeyError, ValueError,
                TypeError):
            self.search_ended = True
            return []

    def search_get_page(self, expression, lines=22):
        """Get search results from Docker Hub"""
        if self.search_ended:
            return []
        self.search_page += 1
        if self.has_search_v2(self.index_url):
            return self.search_get_page_v2(expression, self.index_url, lines)
        if self.has_search_v1():
            return self.search_get_page_v1(expression, self.index_url)
        if self.has_search_v2(self.registry_url):
            return self.search_get_page_v2(expression, self.registry_url,
                                           lines)
        if self.has_search_v1(self.registry_url):
            return self.search_get_page_v1(expression, self.registry_url)
        return []
Exemplo n.º 11
0
    def test_02__select_implementation(self, mock_gupycurl, mock_guexecurl,
                                       mock_msg):
        """Test02 GetURL()._select_implementation()."""
        Config.conf['use_curl_executable'] = ""
        mock_msg.level = 0
        mock_gupycurl.return_value = True
        geturl = GetURL()
        geturl._select_implementation()
        self.assertTrue(geturl.cache_support)
        self.assertTrue(mock_gupycurl.called)

        mock_gupycurl.return_value = False
        geturl = GetURL()
        geturl._select_implementation()
        self.assertFalse(geturl.cache_support)

        mock_gupycurl.return_value = False
        mock_guexecurl.return_value = False
        with self.assertRaises(NameError) as nameerr:
            geturl = GetURL()
            geturl._select_implementation()
            self.assertEqual(nameerr.exception.code, 1)
Exemplo n.º 12
0
 def test_05_set_proxy(self, mock_gupycurl):
     """Test05 GetURL().set_proxy()."""
     mock_gupycurl.return_value = True
     geturl = GetURL()
     geturl.set_proxy("http://host")
     self.assertEqual(geturl.http_proxy, "http://host")
Exemplo n.º 13
0
class UdockerTools(object):
    """Download and setup of the udocker supporting tools
    Includes: proot and alternative python modules, these
    are downloaded to facilitate the installation by the
    end-user.
    """
    def __init__(self, localrepo):
        self.localrepo = localrepo  # LocalRepository object
        self._autoinstall = Config.conf['autoinstall']  # True / False
        self._tarball = Config.conf['tarball']  # URL or file
        self._installinfo = Config.conf['installinfo']  # URL or file
        self._tarball_release = Config.conf['tarball_release']
        self._installretry = Config.conf['installretry']
        self._install_json = dict()
        self.curl = GetURL()

    def _instructions(self):
        """
        Udocker installation instructions are available at:

          https://github.com/indigo-dc/udocker/tree/master/doc
          https://github.com/indigo-dc/udocker/tree/devel/doc

        Udocker requires additional tools to run. These are available
        in the udocker tarball. The tarballs are available at several
        locations. By default udocker will install from the locations
        defined in Config.tarball.

        To install from files or other URLs use these instructions:

        1) set UDOCKER_TARBALL to a remote URL or local filename:

          $ export UDOCKER_TARBALL=http://host/path
          or
          $ export UDOCKER_TARBALL=/tmp/filename

        2) run udocker with the install command or optionally using
           the option --force to overwrite the local installation:

          ./udocker install
          or
          ./udocker install --force

        3) once installed the binaries and containers will be placed
           by default under $HOME/.udocker

        """
        Msg().out(self._instructions.__doc__, l=Msg.ERR)
        Msg().out("udocker command line interface version:",
                  __version__,
                  "\nrequires udockertools tarball release :",
                  self._tarball_release,
                  l=Msg.ERR)

    def _version2int(self, version):
        """Convert version string to integer"""
        version_int = 0
        factor = 1000 * 1000
        for vitem in _str(version).strip().split('.'):
            try:
                version_int = version_int + (int(vitem) * factor)
            except (TypeError, ValueError):
                pass
            factor = factor / 1000
        return int(version_int)

    def _version_isok(self, version):
        """Is version >= than the minimum required tarball release"""
        if not (version and self._tarball_release):
            return False
        tarball_version_int = self._version2int(version)
        required_version_int = self._version2int(self._tarball_release)
        return tarball_version_int >= required_version_int

    def is_available(self):
        """Are the tools already installed"""
        version = \
            FileUtil(self.localrepo.libdir + "/VERSION").getdata('r').strip()
        return self._version_isok(version)

    def purge(self):
        """Remove existing files in bin, lib and doc"""
        for f_name in os.listdir(self.localrepo.bindir):
            f_path = self.localrepo.bindir + '/' + f_name
            FileUtil(f_path).register_prefix()
            FileUtil(f_path).remove(recursive=True)
        for f_name in os.listdir(self.localrepo.libdir):
            f_path = self.localrepo.libdir + '/' + f_name
            FileUtil(f_path).register_prefix()
            FileUtil(f_path).remove(recursive=True)
        for f_name in os.listdir(self.localrepo.docdir):
            f_path = self.localrepo.docdir + '/' + f_name
            FileUtil(f_path).register_prefix()
            FileUtil(f_path).remove(recursive=True)

    def _download(self, url):
        """Download a file """
        download_file = FileUtil("udockertools").mktmp()
        if Msg.level <= Msg.DEF:
            Msg().setlevel(Msg.NIL)
        (hdr, dummy) = self.curl.get(url, ofile=download_file, follow=True)
        if Msg.level == Msg.NIL:
            Msg().setlevel()
        try:
            if "200" in hdr.data["X-ND-HTTPSTATUS"]:
                return download_file
        except (KeyError, TypeError, AttributeError):
            pass
        FileUtil(download_file).remove()
        return ""

    def _get_file(self, url):
        """Get file from list of possible locations file or internet"""
        filename = ""
        if "://" in url:
            filename = self._download(url)
        elif os.path.exists(url):
            filename = os.path.realpath(url)
        if filename and os.path.isfile(filename):
            return filename
        return ""

    def _verify_version(self, tarball_file):
        """verify the tarball version"""
        if not (tarball_file and os.path.isfile(tarball_file)):
            return (False, "")

        tmpdir = FileUtil("VERSION").mktmpdir()
        if not tmpdir:
            return (False, "")

        try:
            tfile = tarfile.open(tarball_file, "r:gz")
            for tar_in in tfile.getmembers():
                if tar_in.name.startswith("udocker_dir/lib/VERSION"):
                    tar_in.name = os.path.basename(tar_in.name)
                    tfile.extract(tar_in, path=tmpdir)
            tfile.close()
        except tarfile.TarError:
            FileUtil(tmpdir).remove(recursive=True)
            return (False, "")
        tarball_version = FileUtil(tmpdir + "/VERSION").getdata('r').strip()
        status = self._version_isok(tarball_version)
        FileUtil(tmpdir).remove(recursive=True)
        return (status, tarball_version)

    def _install(self, tarball_file):
        """Install the tarball"""
        if not (tarball_file and os.path.isfile(tarball_file)):
            return False
        FileUtil(self.localrepo.topdir).chmod()
        self.localrepo.create_repo()

        try:
            tfile = tarfile.open(tarball_file, "r:gz")
            FileUtil(self.localrepo.bindir).rchmod()
            for tar_in in tfile.getmembers():
                if tar_in.name.startswith("udocker_dir/bin/"):
                    tar_in.name = os.path.basename(tar_in.name)
                    Msg().out("Info: extrating", tar_in.name, l=Msg.DBG)
                    tfile.extract(tar_in, self.localrepo.bindir)
            FileUtil(self.localrepo.bindir).rchmod(stat.S_IRUSR | stat.S_IWUSR
                                                   | stat.S_IXUSR)

            FileUtil(self.localrepo.libdir).rchmod()
            for tar_in in tfile.getmembers():
                if tar_in.name.startswith("udocker_dir/lib/"):
                    tar_in.name = os.path.basename(tar_in.name)
                    Msg().out("Info: extrating", tar_in.name, l=Msg.DBG)
                    tfile.extract(tar_in, self.localrepo.libdir)
            FileUtil(self.localrepo.libdir).rchmod()

            FileUtil(self.localrepo.docdir).rchmod()
            for tar_in in tfile.getmembers():
                if tar_in.name.startswith("udocker_dir/doc/"):
                    tar_in.name = os.path.basename(tar_in.name)
                    Msg().out("Info: extrating", tar_in.name, l=Msg.DBG)
                    tfile.extract(tar_in, self.localrepo.docdir)
            FileUtil(self.localrepo.docdir).rchmod()
            tfile.close()
        except tarfile.TarError:
            return False
        return True

    def _get_mirrors(self, mirrors):
        """Get shuffled list of tarball mirrors"""
        if is_genstr(mirrors):
            mirrors = mirrors.split(' ')
        try:
            random.shuffle(mirrors)
        except NameError:
            pass
        return mirrors

    def get_installinfo(self):
        """Get json containing installation info"""
        Msg().out("Info: searching for messages:", l=Msg.VER)
        for url in self._get_mirrors(self._installinfo):
            infofile = self._get_file(url)
            try:
                with open(infofile, 'r') as filep:
                    self._install_json = json.load(filep)
                for msg in self._install_json["messages"]:
                    Msg().out("Info:", msg)
            except (KeyError, AttributeError, ValueError, OSError, IOError):
                Msg().out("Info: no messages:", infofile, url, l=Msg.VER)
            return self._install_json

    def _install_logic(self, force=False):
        """Obtain random mirror, download, verify and install"""
        for url in self._get_mirrors(self._tarball):
            Msg().out("Info: install using:", url, l=Msg.VER)
            tarballfile = self._get_file(url)
            (status, version) = self._verify_version(tarballfile)
            if status:
                Msg().out("Info: installing udockertools", version)
                status = self._install(tarballfile)
            elif force:
                Msg().out("Info: forcing install of udockertools", version)
                status = self._install(tarballfile)
            else:
                Msg().err("Error: version is", version, "for", url, l=Msg.VER)
            if "://" in url and tarballfile:
                FileUtil(tarballfile).remove()
            if status:
                return True
        return False

    def install(self, force=False):
        """Get the udocker tools tarball and install the binaries"""
        if self.is_available() and not force:
            return True

        if not self._autoinstall and not force:
            Msg().out("Warning: installation missing and autoinstall disabled",
                      l=Msg.WAR)
            return None

        if not self._tarball:
            Msg().out("Info: UDOCKER_TARBALL not set, installation skipped",
                      l=Msg.VER)
            return True

        Msg().out("Info: udocker command line interface", __version__)
        Msg().out("Info: searching for udockertools",
                  self._tarball_release,
                  l=Msg.INF)
        retry = self._installretry
        while retry:
            if self._install_logic(force):
                self.get_installinfo()
                Msg().out("Info: installation of udockertools successful")
                return True

            retry = retry - 1
            Msg().err("Error: installation failure retrying ...", l=Msg.VER)

        self._instructions()
        self.get_installinfo()
        Msg().err("Error: installation of udockertools failed")
        return False