diff --git a/config/registry_image_pruner/image_pruner/prune_images.py b/config/registry_image_pruner/image_pruner/prune_images.py index 741dda8..c2b4da9 100644 --- a/config/registry_image_pruner/image_pruner/prune_images.py +++ b/config/registry_image_pruner/image_pruner/prune_images.py @@ -81,6 +81,7 @@ def delete_image_tag(quay_token: str, namespace: str, name: str, tag: str) -> No def remove_tags(tags: List[Dict[str, Any]], quay_token: str, namespace: str, name: str, dry_run: bool = False) -> None: image_digests = [image["manifest_digest"] for image in tags] + tags_map = {tag_info["name"]: tag_info for tag_info in tags} tag_regex = re.compile(r"^sha256-([0-9a-f]+)(\.sbom|\.att|\.src|\.sig)$") for tag in tags: # attestation or sbom image @@ -91,8 +92,22 @@ def remove_tags(tags: List[Dict[str, Any]], quay_token: str, namespace: str, nam else: LOGGER.info("Removing tag %s from %s/%s", tag["name"], namespace, name) delete_image_tag(quay_token, namespace, name, tag["name"]) + elif tag["name"].endswith(".src"): + to_delete = False + + binary_tag = tag["name"].removesuffix(".src") + if binary_tag not in tags_map: + to_delete = True + else: + manifest_digest = tags_map[binary_tag]["manifest_digest"] + new_src_tag = f"{manifest_digest.replace(':', '-')}.src" + to_delete = new_src_tag in tags_map + + if to_delete: + LOGGER.info("Removing deprecated tag %s", tag["name"]) + delete_image_tag(quay_token, namespace, name, tag["name"]) else: - LOGGER.debug("%s is not an tag with suffix .att or .sbom", tag["name"]) + LOGGER.debug("%s is not in a known type to be deleted.", tag["name"]) def process_repositories(repos: List[ImageRepo], quay_token: str, dry_run: bool = False) -> None: diff --git a/config/registry_image_pruner/image_pruner/test_prune_images.py b/config/registry_image_pruner/image_pruner/test_prune_images.py index efe2008..71cdb9c 100644 --- a/config/registry_image_pruner/image_pruner/test_prune_images.py +++ b/config/registry_image_pruner/image_pruner/test_prune_images.py @@ -142,6 +142,23 @@ def test_remove_orphan_tags_with_expected_suffixes(self, urlopen): "name": "sha256-071c766795a0.src", "manifest_digest": "sha256:0ab207f62413", }, + + # single deprecated source image tag remains + {"name": "123abcd.src", "manifest_digest": "sha256:1234566"}, + + # old image has only the deprecated source image, which should not be removed + {"name": "donotdelete", "manifest_digest": "sha256:1234567"}, + {"name": "donotdelete.src", "manifest_digest": "sha256:1234568"}, + + # the binary image was deleted before. Now, these should be deleted. + {"name": "build-100.src", "manifest_digest": "sha256:1345678"}, + {"name": "sha256-4567890.src", "manifest_digest": "sha256:1345678"}, + + # existent image has a source image tagged with a deprecated tag as well. + # That should be removed. + {"name": "1a2b3c4df", "manifest_digest": "sha256:1237890"}, + {"name": "1a2b3c4df.src", "manifest_digest": "sha256:2345678"}, + {"name": "sha256-1237890.src", "manifest_digest": "sha256:2345678"}, ], }).encode() get_repo_rv.__enter__.return_value = response @@ -163,12 +180,15 @@ def test_remove_orphan_tags_with_expected_suffixes(self, urlopen): delete_tag_rv, delete_tag_rv, delete_tag_rv, + + delete_tag_rv, + delete_tag_rv, + delete_tag_rv, + delete_tag_rv, ] main() - self.assertEqual(8, urlopen.call_count) - def _assert_deletion_request(request: Request, tag: str) -> None: expected_url_path = f"{QUAY_API_URL}/repository/sample/hello-image/tag/{tag}" self.assertEqual(expected_url_path, request.get_full_url()) @@ -178,8 +198,9 @@ def _assert_deletion_request(request: Request, tag: str) -> None: tags_to_remove = ( "sha256-03fabe17d4c5.sbom", "sha256-03fabe17d4c5.att", "sha256-03fabe17d4c5.src", "sha256-071c766795a0.sbom", "sha256-071c766795a0.att", "sha256-071c766795a0.src", + "123abcd.src", "build-100.src", "sha256-4567890.src", "1a2b3c4df.src", ) - test_pairs = zip(tags_to_remove, urlopen.mock_calls[-6:]) + test_pairs = zip(tags_to_remove, urlopen.mock_calls[-10:]) for tag, call in test_pairs: _assert_deletion_request(call.args[0], tag)