def fetch_manifest_by_digest(namespace_name, repo_name, manifest_ref): repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown() manifest = registry_model.lookup_manifest_by_digest( repository_ref, manifest_ref) if manifest is None: raise ManifestUnknown() manifest_bytes, manifest_digest, manifest_media_type = _rewrite_schema_if_necessary( namespace_name, repo_name, '$digest', manifest) if manifest_digest is None: raise ManifestUnknown() track_and_log('pull_repo', repository_ref, manifest_digest=manifest_ref) metric_queue.repository_pull.Inc( labelvalues=[namespace_name, repo_name, 'v2', True]) return Response(manifest_bytes.as_unicode(), status=200, headers={ 'Content-Type': manifest_media_type, 'Docker-Content-Digest': manifest_digest, })
def start_blob_upload(namespace_name, repo_name): repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown() # Check for mounting of a blob from another repository. mount_blob_digest = request.args.get('mount', None) if mount_blob_digest is not None: response = _try_to_mount_blob(repository_ref, mount_blob_digest) if response is not None: return response # Begin the blob upload process. blob_uploader = create_blob_upload(repository_ref, storage, _upload_settings()) if blob_uploader is None: logger.debug('Could not create a blob upload for `%s/%s`', namespace_name, repo_name) raise InvalidRequest( message='Unable to start blob upload for unknown repository') # Check if the blob will be uploaded now or in followup calls. If the `digest` is given, then # the upload will occur as a monolithic chunk in this call. Otherwise, we return a redirect # for the client to upload the chunks as distinct operations. digest = request.args.get('digest', None) if digest is None: # Short-circuit because the user will send the blob data in another request. return Response( status=202, headers={ 'Docker-Upload-UUID': blob_uploader.blob_upload_id, 'Range': _render_range(0), 'Location': get_app_url() + url_for('v2.upload_chunk', repository='%s/%s' % (namespace_name, repo_name), upload_uuid=blob_uploader.blob_upload_id) }, ) # Upload the data sent and commit it to a blob. with complete_when_uploaded(blob_uploader): _upload_chunk(blob_uploader, digest) # Write the response to the client. return Response( status=201, headers={ 'Docker-Content-Digest': digest, 'Location': get_app_url() + url_for('v2.download_blob', repository='%s/%s' % (namespace_name, repo_name), digest=digest), }, )
def write_manifest_by_digest(namespace_name, repo_name, manifest_ref): parsed = _parse_manifest() if parsed.digest != manifest_ref: raise ManifestInvalid(detail={"message": "manifest digest mismatch"}) if parsed.schema_version != 2: return _write_manifest_and_log(namespace_name, repo_name, parsed.tag, parsed) # If the manifest is schema version 2, then this cannot be a normal tag-based push, as the # manifest does not contain the tag and this call was not given a tag name. Instead, we write the # manifest with a temporary tag, as it is being pushed as part of a call for a manifest list. repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown() expiration_sec = app.config["PUSH_TEMP_TAG_EXPIRATION_SEC"] manifest = registry_model.create_manifest_with_temp_tag( repository_ref, parsed, expiration_sec, storage ) if manifest is None: raise ManifestInvalid() return Response( "OK", status=202, headers={ "Docker-Content-Digest": manifest.digest, "Location": url_for( "v2.fetch_manifest_by_digest", repository="%s/%s" % (namespace_name, repo_name), manifest_ref=manifest.digest, ), }, )
def upload_chunk(namespace_name, repo_name, upload_uuid): repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown("repository not found") if app.config.get("FEATURE_QUOTA_MANAGEMENT", False): quota = namespacequota.verify_namespace_quota_during_upload(repository_ref) if quota["severity_level"] == "Reject": namespacequota.notify_organization_admins( repository_ref, "quota_error", {"severity": "Reject"} ) raise QuotaExceeded uploader = retrieve_blob_upload_manager( repository_ref, upload_uuid, storage, _upload_settings() ) if uploader is None: raise BlobUploadUnknown() # Upload the chunk for the blob. _upload_chunk(uploader) # Write the response to the client. return Response( status=202, headers={ "Location": _current_request_url(), "Range": _render_range(uploader.blob_upload.byte_count, with_bytes_prefix=False), "Docker-Upload-UUID": upload_uuid, }, )
def upload_chunk(namespace_name, repo_name, upload_uuid): repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown() uploader = retrieve_blob_upload_manager(repository_ref, upload_uuid, storage, _upload_settings()) if uploader is None: raise BlobUploadUnknown() # Upload the chunk for the blob. _upload_chunk(uploader) # Write the response to the client. return Response( status=204, headers={ "Location": _current_request_url(), "Range": _render_range(uploader.blob_upload.byte_count, with_bytes_prefix=False), "Docker-Upload-UUID": upload_uuid, }, )
def fetch_manifest_by_tagname(namespace_name, repo_name, manifest_ref, registry_model): try: repository_ref = registry_model.lookup_repository( namespace_name, repo_name, raise_on_error=True, manifest_ref=manifest_ref ) except RepositoryDoesNotExist as e: image_pulls.labels("v2", "tag", 404).inc() raise NameUnknown("repository not found") try: tag = registry_model.get_repo_tag(repository_ref, manifest_ref, raise_on_error=True) except TagDoesNotExist as e: if registry_model.has_expired_tag(repository_ref, manifest_ref): logger.debug( "Found expired tag %s for repository %s/%s", manifest_ref, namespace_name, repo_name ) msg = ( "Tag %s was deleted or has expired. To pull, revive via time machine" % manifest_ref ) image_pulls.labels("v2", "tag", 404).inc() raise TagExpired(msg) image_pulls.labels("v2", "tag", 404).inc() raise ManifestUnknown(str(e)) manifest = registry_model.get_manifest_for_tag(tag) if manifest is None: # Something went wrong. image_pulls.labels("v2", "tag", 400).inc() raise ManifestInvalid() try: manifest_bytes, manifest_digest, manifest_media_type = _rewrite_schema_if_necessary( namespace_name, repo_name, manifest_ref, manifest, registry_model ) except (ManifestException, ManifestDoesNotExist) as e: image_pulls.labels("v2", "tag", 404).inc() raise ManifestUnknown(str(e)) if manifest_bytes is None: image_pulls.labels("v2", "tag", 404).inc() raise ManifestUnknown() track_and_log( "pull_repo", repository_ref, analytics_name="pull_repo_100x", analytics_sample=0.01, tag=manifest_ref, ) image_pulls.labels("v2", "tag", 200).inc() return Response( manifest_bytes.as_unicode(), status=200, headers={ "Content-Type": manifest_media_type, "Docker-Content-Digest": manifest_digest, }, )
def monolithic_upload_or_last_chunk(namespace_name, repo_name, upload_uuid): # Ensure the digest is present before proceeding. digest = request.args.get("digest", None) if digest is None: raise BlobUploadInvalid(detail={"reason": "Missing digest arg on monolithic upload"}) # Find the upload. repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown() uploader = retrieve_blob_upload_manager( repository_ref, upload_uuid, storage, _upload_settings() ) if uploader is None: raise BlobUploadUnknown() # Upload the chunk for the blob and commit it once complete. with complete_when_uploaded(uploader): _upload_chunk(uploader, digest) # Write the response to the client. return Response( status=201, headers={ "Docker-Content-Digest": digest, "Location": get_app_url() + url_for( "v2.download_blob", repository="%s/%s" % (namespace_name, repo_name), digest=digest ), }, )
def _write_manifest( namespace_name, repo_name, tag_name, manifest_impl, registry_model=registry_model ): # Ensure that the repository exists. repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown("repository not found") # Create the manifest(s) and retarget the tag to point to it. try: manifest, tag = registry_model.create_manifest_and_retarget_tag( repository_ref, manifest_impl, tag_name, storage, raise_on_error=True ) except CreateManifestException as cme: raise ManifestInvalid(detail={"message": str(cme)}) except RetargetTagException as rte: raise ManifestInvalid(detail={"message": str(rte)}) if manifest is None: raise ManifestInvalid() if app.config.get("FEATURE_QUOTA_MANAGEMENT", False): quota = namespacequota.verify_namespace_quota_force_cache(repository_ref) if quota["severity_level"] == "Warning": namespacequota.notify_organization_admins(repository_ref, "quota_warning") elif quota["severity_level"] == "Reject": namespacequota.notify_organization_admins(repository_ref, "quota_error") raise QuotaExceeded() return repository_ref, manifest, tag
def delete_manifest_by_digest(namespace_name, repo_name, manifest_ref): """ Delete the manifest specified by the digest. Note: there is no equivalent method for deleting by tag name because it is forbidden by the spec. """ with db_disallow_replica_use(): repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown("repository not found") manifest = registry_model.lookup_manifest_by_digest(repository_ref, manifest_ref) if manifest is None: raise ManifestUnknown() tags = registry_model.delete_tags_for_manifest(manifest) if not tags: raise ManifestUnknown() for tag in tags: track_and_log("delete_tag", repository_ref, tag=tag.name, digest=manifest_ref) if app.config.get("FEATURE_QUOTA_MANAGEMENT", False): repository.force_cache_repo_size(repository_ref.id) return Response(status=202)
def fetch_manifest_by_digest(namespace_name, repo_name, manifest_ref, registry_model): try: repository_ref = registry_model.lookup_repository( namespace_name, repo_name, raise_on_error=True, manifest_ref=manifest_ref ) except RepositoryDoesNotExist as e: image_pulls.labels("v2", "manifest", 404).inc() raise NameUnknown("repository not found") try: manifest = registry_model.lookup_manifest_by_digest( repository_ref, manifest_ref, raise_on_error=True ) except ManifestDoesNotExist as e: image_pulls.labels("v2", "manifest", 404).inc() raise ManifestUnknown(str(e)) track_and_log("pull_repo", repository_ref, manifest_digest=manifest_ref) image_pulls.labels("v2", "manifest", 200).inc() return Response( manifest.internal_manifest_bytes.as_unicode(), status=200, headers={ "Content-Type": manifest.media_type, "Docker-Content-Digest": manifest.digest, }, )
def delete_manifest_by_digest(namespace_name, repo_name, manifest_ref): """ Delete the manifest specified by the digest. Note: there is no equivalent method for deleting by tag name because it is forbidden by the spec. """ with db_disallow_replica_use(): repository_ref = registry_model.lookup_repository( namespace_name, repo_name) if repository_ref is None: raise NameUnknown() manifest = registry_model.lookup_manifest_by_digest( repository_ref, manifest_ref) if manifest is None: raise ManifestUnknown() tags = registry_model.delete_tags_for_manifest(manifest) if not tags: raise ManifestUnknown() for tag in tags: track_and_log("delete_tag", repository_ref, tag=tag.name, digest=manifest_ref) return Response(status=202)
def fetch_manifest_by_digest(namespace_name, repo_name, manifest_ref): repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: image_pulls.labels("v2_1", "manifest", 404).inc() raise NameUnknown() manifest = registry_model.lookup_manifest_by_digest( repository_ref, manifest_ref) if manifest is None: image_pulls.labels("v2_1", "manifest", 404).inc() raise ManifestUnknown() manifest_bytes, manifest_digest, manifest_media_type = _rewrite_schema_if_necessary( namespace_name, repo_name, "$digest", manifest) if manifest_digest is None: image_pulls.labels("v2_1", "manifest", 404).inc() raise ManifestUnknown() track_and_log("pull_repo", repository_ref, manifest_digest=manifest_ref) image_pulls.labels("v2_1", "manifest", 200).inc() return Response( manifest_bytes.as_unicode(), status=200, headers={ "Content-Type": manifest_media_type, "Docker-Content-Digest": manifest_digest, }, )
def _write_manifest(namespace_name, repo_name, tag_name, manifest_impl): # NOTE: These extra checks are needed for schema version 1 because the manifests # contain the repo namespace, name and tag name. if manifest_impl.schema_version == 1: if (manifest_impl.namespace == "" and features.LIBRARY_SUPPORT and namespace_name == app.config["LIBRARY_NAMESPACE"]): pass elif manifest_impl.namespace != namespace_name: raise NameInvalid( message="namespace name does not match manifest", detail={ "namespace name `%s` does not match `%s` in manifest" % (namespace_name, manifest_impl.namespace) }, ) if manifest_impl.repo_name != repo_name: raise NameInvalid( message="repository name does not match manifest", detail={ "repository name `%s` does not match `%s` in manifest" % (repo_name, manifest_impl.repo_name) }, ) try: if not manifest_impl.layers: raise ManifestInvalid( detail={ "message": "manifest does not reference any layers" }) except ManifestException as me: raise ManifestInvalid(detail={"message": str(me)}) # Ensure that the repository exists. repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown() # Create the manifest(s) and retarget the tag to point to it. try: manifest, tag = registry_model.create_manifest_and_retarget_tag( repository_ref, manifest_impl, tag_name, storage, raise_on_error=True) except CreateManifestException as cme: raise ManifestInvalid(detail={"message": str(cme)}) except RetargetTagException as rte: raise ManifestInvalid(detail={"message": str(rte)}) if manifest is None: raise ManifestInvalid() return repository_ref, manifest, tag
def cancel_upload(namespace_name, repo_name, upload_uuid): repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown() uploader = retrieve_blob_upload_manager( repository_ref, upload_uuid, storage, _upload_settings() ) if uploader is None: raise BlobUploadUnknown() uploader.cancel_upload() return Response(status=204)
def list_all_tags(namespace_name, repo_name, start_id, limit, pagination_callback): repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown() # NOTE: We add 1 to the limit because that's how pagination_callback knows if there are # additional tags. tags = registry_model.lookup_cached_active_repository_tags(model_cache, repository_ref, start_id, limit + 1) response = jsonify({ 'name': '{0}/{1}'.format(namespace_name, repo_name), 'tags': [tag.name for tag in tags][0:limit], }) pagination_callback(tags, response) return response
def fetch_existing_upload(namespace_name, repo_name, upload_uuid): repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown() uploader = retrieve_blob_upload_manager(repository_ref, upload_uuid, storage, _upload_settings()) if uploader is None: raise BlobUploadUnknown() return Response( status=204, headers={ "Docker-Upload-UUID": upload_uuid, "Range": _render_range(uploader.blob_upload.byte_count + 1), # byte ranges are exclusive }, )
def fetch_manifest_by_tagname(namespace_name, repo_name, manifest_ref): repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown() tag = registry_model.get_repo_tag(repository_ref, manifest_ref) if tag is None: if registry_model.has_expired_tag(repository_ref, manifest_ref): logger.debug( "Found expired tag %s for repository %s/%s", manifest_ref, namespace_name, repo_name ) msg = ( "Tag %s was deleted or has expired. To pull, revive via time machine" % manifest_ref ) raise TagExpired(msg) raise ManifestUnknown() manifest = registry_model.get_manifest_for_tag(tag, backfill_if_necessary=True) if manifest is None: # Something went wrong. raise ManifestInvalid() manifest_bytes, manifest_digest, manifest_media_type = _rewrite_schema_if_necessary( namespace_name, repo_name, manifest_ref, manifest ) if manifest_bytes is None: raise ManifestUnknown() track_and_log( "pull_repo", repository_ref, analytics_name="pull_repo_100x", analytics_sample=0.01, tag=manifest_ref, ) metric_queue.repository_pull.Inc(labelvalues=[namespace_name, repo_name, "v2", True]) return Response( manifest_bytes.as_unicode(), status=200, headers={"Content-Type": manifest_media_type, "Docker-Content-Digest": manifest_digest,}, )
def monolithic_upload_or_last_chunk(namespace_name, repo_name, upload_uuid): # Ensure the digest is present before proceeding. digest = request.args.get("digest", None) if digest is None: raise BlobUploadInvalid(detail={"reason": "Missing digest arg on monolithic upload"}) # Find the upload. repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown("repository not found") if app.config.get("FEATURE_QUOTA_MANAGEMENT", False): quota = namespacequota.verify_namespace_quota_during_upload(repository_ref) if quota["severity_level"] == "Reject": namespacequota.notify_organization_admins( repository_ref, "quota_error", {"severity": "Reject"} ) raise QuotaExceeded uploader = retrieve_blob_upload_manager( repository_ref, upload_uuid, storage, _upload_settings() ) if uploader is None: raise BlobUploadUnknown() # Upload the chunk for the blob and commit it once complete. with complete_when_uploaded(uploader): _upload_chunk(uploader, digest) # Write the response to the client. return Response( status=201, headers={ "Docker-Content-Digest": digest, "Location": get_app_url() + url_for( "v2.download_blob", repository="%s/%s" % (namespace_name, repo_name), digest=digest ), }, )
def start_blob_upload(namespace_name, repo_name): repository_ref = registry_model.lookup_repository(namespace_name, repo_name) if repository_ref is None: raise NameUnknown("repository not found") if app.config.get("FEATURE_QUOTA_MANAGEMENT", False): quota = namespacequota.verify_namespace_quota(repository_ref) if quota["severity_level"] == "Reject": namespacequota.notify_organization_admins( repository_ref, "quota_error", {"severity": "Reject"} ) raise QuotaExceeded # Check for mounting of a blob from another repository. mount_blob_digest = request.args.get("mount", None) if mount_blob_digest is not None: response = _try_to_mount_blob(repository_ref, mount_blob_digest) if response is not None: return response # Begin the blob upload process. blob_uploader = create_blob_upload(repository_ref, storage, _upload_settings()) if blob_uploader is None: logger.debug("Could not create a blob upload for `%s/%s`", namespace_name, repo_name) raise InvalidRequest(message="Unable to start blob upload for unknown repository") # Check if the blob will be uploaded now or in followup calls. If the `digest` is given, then # the upload will occur as a monolithic chunk in this call. Otherwise, we return a redirect # for the client to upload the chunks as distinct operations. digest = request.args.get("digest", None) if digest is None: # Short-circuit because the user will send the blob data in another request. return Response( status=202, headers={ "Docker-Upload-UUID": blob_uploader.blob_upload_id, "Range": _render_range(0), "Location": get_app_url() + url_for( "v2.upload_chunk", repository="%s/%s" % (namespace_name, repo_name), upload_uuid=blob_uploader.blob_upload_id, ), }, ) # Upload the data sent and commit it to a blob. with complete_when_uploaded(blob_uploader): _upload_chunk(blob_uploader, digest) # Write the response to the client. return Response( status=201, headers={ "Docker-Content-Digest": digest, "Location": get_app_url() + url_for( "v2.download_blob", repository="%s/%s" % (namespace_name, repo_name), digest=digest ), }, )