Skip to content

Commit

Permalink
api: Add tag deletion endpoint for v2 api (PROJQUAY-7599) (quay#3128)
Browse files Browse the repository at this point in the history
* api: Add ability to delete tags via v2 call (PROJQUAY-7599)
The deletion of tags was previously not supported by the Docker v2 API. Current versions of both the OCI spec and Docker v2 API provide this ability, hence adding it to Quay as well. See [OCI spec](https://github.com/opencontainers/distribution-spec/blob/main/spec.md) for more details.

* Fix test call

* Add missing argument to test

* Add security tests

* Enable conformance tests

* Switch to v1.1.0 instead of release candidate for conformance tests

* Revert changes to conformance tests
  • Loading branch information
ibazulic authored Aug 21, 2024
1 parent 3c19150 commit 475cba8
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 0 deletions.
32 changes: 32 additions & 0 deletions endpoints/v2/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,38 @@ def delete_manifest_by_digest(namespace_name, repo_name, manifest_ref):
return Response(status=202)


@v2_bp.route(MANIFEST_TAGNAME_ROUTE, methods=["DELETE"])
@disallow_for_account_recovery_mode
@parse_repository_name()
@process_registry_jwt_auth(scopes=["pull", "push"])
@log_unauthorized_delete
@require_repo_write(allow_for_superuser=True, disallow_for_restricted_users=True)
@anon_protect
@check_readonly
@check_pushes_disabled
def delete_manifest_by_tag(namespace_name, repo_name, manifest_ref):
"""
Deletes the manifest specified by the tag.
"""
with db_disallow_replica_use():
repository_ref = registry_model.lookup_repository(
namespace_name, repo_name, model_cache=model_cache
)
if repository_ref is None:
raise NameUnknown("repository not found")

tag = registry_model.get_repo_tag(repository_ref, manifest_ref)
if tag is None:
raise ManifestUnknown()

deleted_tag = registry_model.delete_tag(model_cache, repository_ref, manifest_ref)
if not deleted_tag:
raise ManifestUnknown()

track_and_log("delete_tag", repository_ref, tag=deleted_tag.name, digest=manifest_ref)
return Response(status=202)


def _write_manifest_and_log(namespace_name, repo_name, tag_name, manifest_impl):
_validate_schema1_manifest(namespace_name, repo_name, manifest_impl)
with db_disallow_replica_use():
Expand Down
40 changes: 40 additions & 0 deletions test/registry/registry_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3103,3 +3103,43 @@ def test_conftest_policy_push(manifest_protocol, openpolicyagent_policy, liveser
credentials=credentials,
expected_failure=None,
)


def test_attempt_pull_by_tag_reference_for_deleted_tag(
manifest_protocol, basic_images, liveserver_session
):
"""Test: Delete a tag by specifying the reference in a v2 call"""
credentials = ("devtable", "password")

# Push the new repository
result = manifest_protocol.push(
liveserver_session, "devtable", "newrepo", "latest", basic_images, credentials=credentials
)
digests = [str(manifest.digest) for manifest in list(result.manifests.values())]
assert len(digests) == 1

# Ensure we can pull by digest
manifest_protocol.pull(
liveserver_session, "devtable", "newrepo", digests[0], basic_images, credentials=credentials
)

# Ensure we can pull by tag
manifest_protocol.pull(
liveserver_session, "devtable", "newrepo", "latest", basic_images, credentials=credentials
)

# Delete the tag by reference
manifest_protocol.delete(
liveserver_session, "devtable", "newrepo", "latest", credentials=credentials
)

# Attempt to pull from the repository by tag to verify it's not accessible
manifest_protocol.pull(
liveserver_session,
"devtable",
"newrepo",
"latest",
basic_images,
credentials=credentials,
expected_failure=Failures.UNKNOWN_TAG,
)
13 changes: 13 additions & 0 deletions test/specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,19 @@ def build_v2_index_specs():
IndexV2TestSpec(
"v2.write_manifest_by_digest", "PUT", ANOTHER_ORG_REPO, manifest_ref=FAKE_DIGEST
).request_status(401, 401, 401, 401, 400),
# v2.delete_manifest_by_tag
IndexV2TestSpec(
"v2.delete_manifest_by_tag", "DELETE", PUBLIC_REPO, manifest_ref=FAKE_MANIFEST
).request_status(401, 401, 401, 401, 401),
IndexV2TestSpec(
"v2.delete_manifest_by_tag", "DELETE", PRIVATE_REPO, manifest_ref=FAKE_MANIFEST
).request_status(401, 401, 401, 401, 404),
IndexV2TestSpec(
"v2.delete_manifest_by_tag", "DELETE", ORG_REPO, manifest_ref=FAKE_MANIFEST
).request_status(401, 401, 401, 401, 404),
IndexV2TestSpec(
"v2.delete_manifest_by_tag", "DELETE", ANOTHER_ORG_REPO, manifest_ref=FAKE_MANIFEST
).request_status(401, 401, 401, 401, 404),
# v2.delete_manifest_by_digest
IndexV2TestSpec(
"v2.delete_manifest_by_digest", "DELETE", PUBLIC_REPO, manifest_ref=FAKE_DIGEST
Expand Down

0 comments on commit 475cba8

Please sign in to comment.