diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c806317f..6c9fe748 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,9 +125,8 @@ jobs: context: "./posit-bakery/test/resources/multiplatform/" dev-versions: include - clean: - name: Clean - if: github.ref == 'refs/heads/main' + with-macros-clean-caches: + name: Clean Caches (with-macros suite) permissions: contents: read packages: write @@ -141,6 +140,23 @@ jobs: context: "./posit-bakery/test/resources/with-macros/" remove-dangling-caches: true remove-caches-older-than: 14 + clean-temporary-images: false # TODO: flip to true if this build starts using the native workflow + + multiplatform-clean-caches: + name: Clean Caches (multiplatform suite) + permissions: + contents: read + packages: write + needs: + - bakery + - bakery-native + + uses: "./.github/workflows/clean.yml" + with: + version: ${{ github.head_ref || github.ref_name }} + context: "./posit-bakery/test/resources/multiplatform/" + remove-dangling-caches: true + remove-caches-older-than: 14 remove-dangling-temporary-images: false remove-temporary-images-older-than: 3 diff --git a/posit-bakery/posit_bakery/cli/clean.py b/posit-bakery/posit_bakery/cli/clean.py index 9f253209..2c0a04b7 100644 --- a/posit-bakery/posit_bakery/cli/clean.py +++ b/posit-bakery/posit_bakery/cli/clean.py @@ -77,11 +77,14 @@ def cache_registry( log.info(f"Cleaning cache registries in {registry}") - config.clean_caches( + errors = config.clean_caches( remove_untagged=untagged, remove_older_than=timedelta(days=older_than) if older_than else None, dry_run=dry_run, ) + if errors: + log.error(f"Completed with {len(errors)} errors encountered during cleanup.") + raise typer.Exit(code=1) @app.command() @@ -140,8 +143,11 @@ def temp_registry( log.info(f"Cleaning temporary registries in {registry}") - config.clean_temporary( + errors = config.clean_temporary( remove_untagged=untagged, remove_older_than=timedelta(days=older_than) if older_than else None, dry_run=dry_run, ) + if errors: + log.error(f"Completed with {len(errors)} errors encountered during cleanup.") + raise typer.Exit(code=1) diff --git a/posit-bakery/posit_bakery/config/config.py b/posit-bakery/posit_bakery/config/config.py index 6efaa807..9d7bc8ab 100644 --- a/posit-bakery/posit_bakery/config/config.py +++ b/posit-bakery/posit_bakery/config/config.py @@ -817,14 +817,19 @@ def clean_caches( """ target_caches = list(set([target.cache_name.split(":")[0] for target in self.targets])) + errors = [] for target_cache in target_caches: - ghcr.clean_temporary_artifacts( - ghcr_registry=target_cache, - remove_untagged=remove_untagged, - remove_older_than=remove_older_than, - dry_run=dry_run, + errors.extend( + ghcr.clean_temporary_artifacts( + ghcr_registry=target_cache, + remove_untagged=remove_untagged, + remove_older_than=remove_older_than, + dry_run=dry_run, + ) ) + return errors + def clean_temporary( self, remove_untagged: bool = True, @@ -839,10 +844,15 @@ def clean_temporary( """ target_caches = list(set([target.temp_name for target in self.targets])) + errors = [] for target_cache in target_caches: - ghcr.clean_temporary_artifacts( - ghcr_registry=target_cache, - remove_untagged=remove_untagged, - remove_older_than=remove_older_than, - dry_run=dry_run, + errors.extend( + ghcr.clean_temporary_artifacts( + ghcr_registry=target_cache, + remove_untagged=remove_untagged, + remove_older_than=remove_older_than, + dry_run=dry_run, + ) ) + + return errors diff --git a/posit-bakery/posit_bakery/registry_management/ghcr/api.py b/posit-bakery/posit_bakery/registry_management/ghcr/api.py index 64940ac2..6a9f1b83 100644 --- a/posit-bakery/posit_bakery/registry_management/ghcr/api.py +++ b/posit-bakery/posit_bakery/registry_management/ghcr/api.py @@ -3,7 +3,7 @@ import os from urllib.parse import quote -from github import Auth, Github +from github import Auth, Github, GithubException from posit_bakery.registry_management.ghcr.models import GHCRPackageVersions, GHCRPackageVersion @@ -68,5 +68,11 @@ def delete_package_version(self, version: GHCRPackageVersion): ) def delete_package_versions(self, versions: GHCRPackageVersions): + errors = [] for version in versions.versions: - self.delete_package_version(version) + try: + self.delete_package_version(version) + except GithubException as e: + logging.error(f"Failed to delete package version {version.html_url}: {e.message}") + errors.append((version.id, e.message)) + return errors diff --git a/posit-bakery/posit_bakery/registry_management/ghcr/clean.py b/posit-bakery/posit_bakery/registry_management/ghcr/clean.py index 7d6705ac..d058dae5 100644 --- a/posit-bakery/posit_bakery/registry_management/ghcr/clean.py +++ b/posit-bakery/posit_bakery/registry_management/ghcr/clean.py @@ -2,6 +2,8 @@ import re from datetime import timedelta +from github import GithubException + from posit_bakery.log import stdout_console from posit_bakery.registry_management.ghcr.api import GHCRClient from posit_bakery.registry_management.ghcr.models import GHCRPackageVersions @@ -15,7 +17,7 @@ def clean_temporary_artifacts( remove_untagged: bool = True, remove_older_than: timedelta | None = None, dry_run: bool = False, -): +) -> list[GithubException]: """Cleans up temporary caches and images that are not tagged or are older than a given timedelta.""" # Check that the registry matches the expected pattern. match = REGISTRY_PATTERN.match(ghcr_registry) @@ -28,7 +30,11 @@ def clean_temporary_artifacts( # Retrieve all package versions. client = GHCRClient(organization) - package_versions = client.get_package_versions(organization, package) + try: + package_versions = client.get_package_versions(organization, package) + except GithubException as e: + log.error(f"Failed to retrieve package versions for {ghcr_registry}: {e}") + return [e] # Filter package versions that should be deleted. versions_to_delete = [] @@ -48,10 +54,12 @@ def clean_temporary_artifacts( if dry_run: stdout_console.print_json(versions_to_delete.model_dump_json(indent=2)) else: - client.delete_package_versions(versions_to_delete) + return client.delete_package_versions(versions_to_delete) else: log.info(f"No artifacts to remove from {ghcr_registry}") + return [] + def clean_registry( image_registry: str, @@ -69,7 +77,11 @@ def clean_registry( # Retrieve all package versions. client = GHCRClient(organization) - package_versions = client.get_package_versions(organization, package) + try: + package_versions = client.get_package_versions(organization, package) + except GithubException as e: + log.error(f"Failed to retrieve package versions for {image_registry}: {e}") + return [e] # Filter package versions that should be deleted. versions_to_delete = [] @@ -90,6 +102,8 @@ def clean_registry( if dry_run: stdout_console.print_json(versions_to_delete.model_dump_json(indent=2)) else: - client.delete_package_versions(versions_to_delete) + return client.delete_package_versions(versions_to_delete) else: log.info(f"No versions to remove from {image_registry}") + + return []