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)
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)
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)