Beispiel #1
0
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,
                    })
Beispiel #2
0
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),
        },
    )
Beispiel #3
0
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,
            ),
        },
    )
Beispiel #4
0
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,
        },
    )
Beispiel #5
0
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,
        },
    )
Beispiel #6
0
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,
        },
    )
Beispiel #7
0
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
            ),
        },
    )
Beispiel #8
0
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
Beispiel #9
0
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)
Beispiel #10
0
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,
        },
    )
Beispiel #11
0
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)
Beispiel #12
0
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,
        },
    )
Beispiel #13
0
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
Beispiel #14
0
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)
Beispiel #15
0
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
Beispiel #16
0
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
        },
    )
Beispiel #17
0
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,},
    )
Beispiel #18
0
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
            ),
        },
    )
Beispiel #19
0
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
            ),
        },
    )