Beispiel #1
0
    def pull(self,
             session,
             namespace,
             repo_name,
             tag_names,
             images,
             credentials=None,
             expected_failure=None,
             options=None):
        options = options or ProtocolOptions()
        auth = self._auth_for_credentials(credentials)
        tag_names = [tag_names] if isinstance(tag_names, str) else tag_names
        prefix = '/v1/repositories/%s/' % self.repo_name(namespace, repo_name)

        # Ping!
        self.ping(session)

        # GET /v1/repositories/{namespace}/{repository}/images
        headers = {'X-Docker-Token': 'true'}
        result = self.conduct(session,
                              'GET',
                              prefix + 'images',
                              auth=auth,
                              headers=headers,
                              expected_status=(200, expected_failure,
                                               V1ProtocolSteps.GET_IMAGES))
        if result.status_code != 200:
            return

        headers = {}
        if credentials is not None:
            headers['Authorization'] = 'token ' + result.headers[
                'www-authenticate']
        else:
            assert not 'www-authenticate' in result.headers

        # GET /v1/repositories/{namespace}/{repository}/tags
        image_ids = self.conduct(session,
                                 'GET',
                                 prefix + 'tags',
                                 headers=headers).json()

        for tag_name in tag_names:
            # GET /v1/repositories/{namespace}/{repository}/tags/<tag_name>
            image_id_data = self.conduct(
                session,
                'GET',
                prefix + 'tags/' + tag_name,
                headers=headers,
                expected_status=(200, expected_failure,
                                 V1ProtocolSteps.GET_TAG))

            if tag_name not in image_ids:
                assert expected_failure == Failures.UNKNOWN_TAG
                return None

            tag_image_id = image_ids[tag_name]
            assert image_id_data.json() == tag_image_id

            # Retrieve the ancestry of the tagged image.
            image_prefix = '/v1/images/%s/' % tag_image_id
            ancestors = self.conduct(session,
                                     'GET',
                                     image_prefix + 'ancestry',
                                     headers=headers).json()

            assert len(ancestors) == len(images)
            for index, image_id in enumerate(reversed(ancestors)):
                # /v1/images/{imageID}/{ancestry, json, layer}
                image_prefix = '/v1/images/%s/' % image_id
                self.conduct(session,
                             'GET',
                             image_prefix + 'ancestry',
                             headers=headers)

                result = self.conduct(session,
                                      'GET',
                                      image_prefix + 'json',
                                      headers=headers)
                assert result.json()['id'] == image_id

                # Ensure we can HEAD the image layer.
                self.conduct(session,
                             'HEAD',
                             image_prefix + 'layer',
                             headers=headers)

                # And retrieve the layer data.
                result = self.conduct(
                    session,
                    'GET',
                    image_prefix + 'layer',
                    headers=headers,
                    expected_status=(200, expected_failure,
                                     V1ProtocolSteps.GET_LAYER),
                    options=options)
                if result.status_code == 200:
                    assert result.content == images[index].bytes

        return PullResult(manifests=None, image_ids=image_ids)
Beispiel #2
0
    def pull(
        self,
        session,
        namespace,
        repo_name,
        tag_names,
        images,
        credentials=None,
        expected_failure=None,
        options=None,
    ):
        options = options or ProtocolOptions()
        auth = self._auth_for_credentials(credentials)
        tag_names = [tag_names] if isinstance(tag_names, str) else tag_names
        prefix = "/v1/repositories/%s/" % self.repo_name(namespace, repo_name)

        # Ping!
        self.ping(session)

        # GET /v1/repositories/{namespace}/{repository}/images
        headers = {"X-Docker-Token": "true"}
        result = self.conduct(
            session,
            "GET",
            prefix + "images",
            auth=auth,
            headers=headers,
            expected_status=(200, expected_failure,
                             V1ProtocolSteps.GET_IMAGES),
        )
        if result.status_code != 200:
            return

        headers = {}
        if credentials is not None:
            headers["Authorization"] = "token " + result.headers[
                "www-authenticate"]
        else:
            assert not "www-authenticate" in result.headers

        # GET /v1/repositories/{namespace}/{repository}/tags
        image_ids = self.conduct(session,
                                 "GET",
                                 prefix + "tags",
                                 headers=headers).json()

        for tag_name in tag_names:
            # GET /v1/repositories/{namespace}/{repository}/tags/<tag_name>
            image_id_data = self.conduct(
                session,
                "GET",
                prefix + "tags/" + tag_name,
                headers=headers,
                expected_status=(200, expected_failure,
                                 V1ProtocolSteps.GET_TAG),
            )

            if tag_name not in image_ids:
                assert expected_failure == Failures.UNKNOWN_TAG
                return None

            tag_image_id = image_ids[tag_name]
            assert image_id_data.json() == tag_image_id

            # Retrieve the ancestry of the tagged image.
            image_prefix = "/v1/images/%s/" % tag_image_id
            ancestors = self.conduct(session,
                                     "GET",
                                     image_prefix + "ancestry",
                                     headers=headers).json()

            assert len(ancestors) == len(images)
            for index, image_id in enumerate(reversed(ancestors)):
                # /v1/images/{imageID}/{ancestry, json, layer}
                image_prefix = "/v1/images/%s/" % image_id
                self.conduct(session,
                             "GET",
                             image_prefix + "ancestry",
                             headers=headers)

                result = self.conduct(session,
                                      "GET",
                                      image_prefix + "json",
                                      headers=headers)
                assert result.json()["id"] == image_id

                # Ensure we can HEAD the image layer.
                self.conduct(session,
                             "HEAD",
                             image_prefix + "layer",
                             headers=headers)

                # And retrieve the layer data.
                result = self.conduct(
                    session,
                    "GET",
                    image_prefix + "layer",
                    headers=headers,
                    expected_status=(200, expected_failure,
                                     V1ProtocolSteps.GET_LAYER),
                    options=options,
                )
                if result.status_code == 200:
                    assert result.content == images[index].bytes

        return PullResult(manifests=None, image_ids=image_ids)
Beispiel #3
0
    def pull(
        self,
        session,
        namespace,
        repo_name,
        tag_names,
        images,
        credentials=None,
        expected_failure=None,
        options=None,
    ):
        options = options or ProtocolOptions()
        scopes = options.scopes or ["repository:%s:pull" % self.repo_name(namespace, repo_name)]
        tag_names = [tag_names] if isinstance(tag_names, str) else tag_names

        # Ping!
        self.ping(session)

        # Perform auth and retrieve a token.
        token, _ = self.auth(
            session,
            credentials,
            namespace,
            repo_name,
            scopes=scopes,
            expected_failure=expected_failure,
        )
        if token is None and not options.attempt_pull_without_token:
            return None

        headers = {}
        if token:
            headers = {
                "Authorization": "Bearer " + token,
            }

        if self.schema == "oci":
            headers["Accept"] = ",".join(
                options.accept_mimetypes
                if options.accept_mimetypes is not None
                else OCI_CONTENT_TYPES
            )
        elif self.schema == "schema2":
            headers["Accept"] = ",".join(
                options.accept_mimetypes
                if options.accept_mimetypes is not None
                else DOCKER_SCHEMA2_CONTENT_TYPES
            )

        manifests = {}
        image_ids = {}
        for tag_name in tag_names:
            # Retrieve the manifest for the tag or digest.
            response = self.conduct(
                session,
                "GET",
                "/v2/%s/manifests/%s" % (self.repo_name(namespace, repo_name), tag_name),
                expected_status=(200, expected_failure, V2ProtocolSteps.GET_MANIFEST),
                headers=headers,
            )
            if response.status_code == 401:
                assert "WWW-Authenticate" in response.headers

            response.encoding = "utf-8"
            if expected_failure is not None:
                return None

            # Ensure the manifest returned by us is valid.
            ct = response.headers["Content-Type"]
            if self.schema == "schema1":
                assert ct in DOCKER_SCHEMA1_CONTENT_TYPES

            if options.require_matching_manifest_type:
                if self.schema == "schema1":
                    assert ct in DOCKER_SCHEMA1_CONTENT_TYPES

                if self.schema == "schema2":
                    assert ct in DOCKER_SCHEMA2_CONTENT_TYPES

                if self.schema == "oci":
                    assert ct in OCI_CONTENT_TYPES

            manifest = parse_manifest_from_bytes(Bytes.for_string_or_unicode(response.text), ct)
            manifests[tag_name] = manifest

            if manifest.schema_version == 1:
                image_ids[tag_name] = manifest.leaf_layer_v1_image_id

            # Verify the blobs.
            layer_index = 0
            empty_count = 0
            blob_digests = list(manifest.blob_digests)
            for image in images:
                if manifest.schema_version == 2 and image.is_empty:
                    empty_count += 1
                    continue

                # If the layer is remote, then we expect the blob to *not* exist in the system.
                blob_digest = blob_digests[layer_index]
                expected_status = 404 if image.urls else 200
                result = self.conduct(
                    session,
                    "GET",
                    "/v2/%s/blobs/%s" % (self.repo_name(namespace, repo_name), blob_digest),
                    expected_status=(expected_status, expected_failure, V2ProtocolSteps.GET_BLOB),
                    headers=headers,
                    options=options,
                )

                if expected_status == 200:
                    assert result.content == image.bytes

                layer_index += 1

            assert (len(blob_digests) + empty_count) >= len(
                images
            )  # OCI/Schema 2 has 1 extra for config

        return PullResult(manifests=manifests, image_ids=image_ids)