def push(self, session, namespace, repo_name, tag_names, images, credentials=None, expected_failure=None, options=None): auth = self._auth_for_credentials(credentials) tag_names = [tag_names] if isinstance(tag_names, str) else tag_names # Ping! self.ping(session) # PUT /v1/repositories/{namespace}/{repository}/ result = self.conduct(session, 'PUT', '/v1/repositories/%s/' % self.repo_name(namespace, repo_name), expected_status=(201, expected_failure, V1ProtocolSteps.PUT_IMAGES), json_data={}, auth=auth) if result.status_code != 201: return headers = {} headers[ 'Authorization'] = 'token ' + result.headers['www-authenticate'] for image in images: assert image.urls is None # PUT /v1/images/{imageID}/json image_json_data = {'id': image.id} if image.size is not None: image_json_data['Size'] = image.size if image.parent_id is not None: image_json_data['parent'] = image.parent_id if image.config is not None: image_json_data['config'] = image.config if image.created is not None: image_json_data['created'] = image.created image_json = json.dumps(image_json_data) response = self.conduct( session, 'PUT', '/v1/images/%s/json' % image.id, data=image_json, headers=headers, expected_status=(200, expected_failure, V1ProtocolSteps.PUT_IMAGE_JSON)) if response.status_code != 200: return # PUT /v1/images/{imageID}/checksum (old style) old_checksum = compute_tarsum(StringIO(image.bytes), image_json) checksum_headers = {'X-Docker-Checksum': old_checksum} checksum_headers.update(headers) self.conduct(session, 'PUT', '/v1/images/%s/checksum' % image.id, headers=checksum_headers) # PUT /v1/images/{imageID}/layer self.conduct(session, 'PUT', '/v1/images/%s/layer' % image.id, data=StringIO(image.bytes), headers=headers) # PUT /v1/images/{imageID}/checksum (new style) checksum = compute_simple(StringIO(image.bytes), image_json) checksum_headers = {'X-Docker-Checksum-Payload': checksum} checksum_headers.update(headers) self.conduct(session, 'PUT', '/v1/images/%s/checksum' % image.id, headers=checksum_headers) # PUT /v1/repositories/{namespace}/{repository}/tags/latest for tag_name in tag_names: self.conduct(session, 'PUT', '/v1/repositories/%s/tags/%s' % (self.repo_name(namespace, repo_name), tag_name), data='"%s"' % images[-1].id, headers=headers, expected_status=(200, expected_failure, V1ProtocolSteps.PUT_TAG)) # PUT /v1/repositories/{namespace}/{repository}/images self.conduct(session, 'PUT', '/v1/repositories/%s/images' % self.repo_name(namespace, repo_name), expected_status=204, headers=headers) return PushResult(manifests=None, headers=headers)
def push( self, session, namespace, repo_name, tag_names, images, credentials=None, expected_failure=None, options=None, ): auth = self._auth_for_credentials(credentials) tag_names = [tag_names] if isinstance(tag_names, str) else tag_names # Ping! self.ping(session) # PUT /v1/repositories/{namespace}/{repository}/ result = self.conduct( session, "PUT", "/v1/repositories/%s/" % self.repo_name(namespace, repo_name), expected_status=(201, expected_failure, V1ProtocolSteps.PUT_IMAGES), json_data={}, auth=auth, ) if result.status_code != 201: return headers = {} headers[ "Authorization"] = "token " + result.headers["www-authenticate"] for image in images: assert image.urls is None # PUT /v1/images/{imageID}/json image_json_data = {"id": image.id} if image.size is not None: image_json_data["Size"] = image.size if image.parent_id is not None: image_json_data["parent"] = image.parent_id if image.config is not None: image_json_data["config"] = image.config if image.created is not None: image_json_data["created"] = image.created image_json = json.dumps(image_json_data) response = self.conduct( session, "PUT", "/v1/images/%s/json" % image.id, data=image_json, headers=headers, expected_status=(200, expected_failure, V1ProtocolSteps.PUT_IMAGE_JSON), ) if response.status_code != 200: return # PUT /v1/images/{imageID}/checksum (old style) old_checksum = compute_tarsum(BytesIO(image.bytes), image_json) checksum_headers = {"X-Docker-Checksum": old_checksum} checksum_headers.update(headers) self.conduct(session, "PUT", "/v1/images/%s/checksum" % image.id, headers=checksum_headers) # PUT /v1/images/{imageID}/layer self.conduct( session, "PUT", "/v1/images/%s/layer" % image.id, data=BytesIO(image.bytes), headers=headers, ) # PUT /v1/images/{imageID}/checksum (new style) checksum = compute_simple(BytesIO(image.bytes), image_json) checksum_headers = {"X-Docker-Checksum-Payload": checksum} checksum_headers.update(headers) self.conduct(session, "PUT", "/v1/images/%s/checksum" % image.id, headers=checksum_headers) # PUT /v1/repositories/{namespace}/{repository}/tags/latest for tag_name in tag_names: self.conduct( session, "PUT", "/v1/repositories/%s/tags/%s" % (self.repo_name(namespace, repo_name), tag_name), data='"%s"' % images[-1].id, headers=headers, expected_status=(200, expected_failure, V1ProtocolSteps.PUT_TAG), ) # PUT /v1/repositories/{namespace}/{repository}/images self.conduct( session, "PUT", "/v1/repositories/%s/images" % self.repo_name(namespace, repo_name), expected_status=204, headers=headers, ) return PushResult(manifests=None, headers=headers)
def put_image_layer(namespace, repository, image_id): logger.debug("Checking repo permissions") permission = ModifyRepositoryPermission(namespace, repository) if not permission.can(): abort(403) repository_ref = registry_model.lookup_repository(namespace, repository, kind_filter="image") if repository_ref is None: abort(403) logger.debug("Checking for image in manifest builder") builder = lookup_manifest_builder(repository_ref, session.get("manifest_builder"), store, docker_v2_signing_key) if builder is None: abort(400) layer = builder.lookup_layer(image_id) if layer is None: abort(404) logger.debug("Storing layer data") input_stream = request.stream if request.headers.get("transfer-encoding") == "chunked": # Careful, might work only with WSGI servers supporting chunked # encoding (Gunicorn) input_stream = request.environ["wsgi.input"] expiration_sec = app.config["PUSH_TEMP_TAG_EXPIRATION_SEC"] settings = BlobUploadSettings( maximum_blob_size=app.config["MAXIMUM_LAYER_SIZE"], committed_blob_expiration=expiration_sec, ) extra_handlers = [] # Add a handler that copies the data into a temp file. This is used to calculate the tarsum, # which is only needed for older versions of Docker. requires_tarsum = bool(builder.get_layer_checksums(layer)) if requires_tarsum: tmp, tmp_hndlr = store.temp_store_handler() extra_handlers.append(tmp_hndlr) # Add a handler which computes the simple Docker V1 checksum. h, sum_hndlr = checksums.simple_checksum_handler(layer.v1_metadata_string) extra_handlers.append(sum_hndlr) uploaded_blob = None try: with upload_blob(repository_ref, store, settings, extra_blob_stream_handlers=extra_handlers) as manager: manager.upload_chunk(app.config, input_stream) uploaded_blob = manager.commit_to_blob(app.config) except BlobUploadException: logger.exception("Exception when writing image data") abort(520, "Image %(image_id)s could not be written. Please try again.", image_id=image_id) # Compute the final checksum csums = [] csums.append("sha256:{0}".format(h.hexdigest())) try: if requires_tarsum: tmp.seek(0) csums.append( checksums.compute_tarsum(tmp, layer.v1_metadata_string)) tmp.close() except (IOError, checksums.TarError) as exc: logger.debug("put_image_layer: Error when computing tarsum %s", exc) # If there was already a precomputed checksum, validate against it now. if builder.get_layer_checksums(layer): checksum = builder.get_layer_checksums(layer)[0] if not builder.validate_layer_checksum(layer, checksum): logger.debug( "put_image_checksum: Wrong checksum. Given: %s and expected: %s", checksum, builder.get_layer_checksums(layer), ) abort( 400, "Checksum mismatch for image: %(image_id)s", issue="checksum-mismatch", image_id=image_id, ) # Assign the blob to the layer in the manifest. if not builder.assign_layer_blob(layer, uploaded_blob, csums): abort(500, "Something went wrong") # Send a job to the work queue to replicate the image layer. # TODO: move this into a better place. queue_storage_replication(namespace, uploaded_blob) return make_response("true", 200)