diff --git a/.github/scripts/release_images.sh b/.github/scripts/release_images.sh new file mode 100755 index 000000000..6a2666740 --- /dev/null +++ b/.github/scripts/release_images.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# Initialize array to store digest SHAs and all release versions +declare -a digests +declare -a all_release_versions + +# Fetch all docker image names +image_names=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "-GitHub-Api-Version: 2022-11-28" \ + -H "Authorization: Token ${GITHUB_TOKEN}" \ + --paginate "/orgs/boozallen/packages?package_type=container" | jq -r '.[] | select((.name | startswith("aissemble")) and (.name | endswith("-chart") | not)) | .name') + +# For each docker image, find all release versions, excluding 1.7.0 +for name in $image_names; do + release_versions=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "-GitHub-Api-Version: 2022-11-28" \ + -H "Authorization: Token ${GITHUB_TOKEN}" \ + --paginate "/orgs/boozallen/packages/container/${name}/versions" \ + | jq -r '.[] | .metadata.container.tags[] | select(test("^\\d+\\.\\d+\\.\\d+$"))' \ + | jq -R -s 'split("\n") | map(select(length > 0)) | map(select(. != "1.7.0"))') + + # Add release versions to all_release_versions array + all_release_versions+=("$release_versions") + + # Loop through all release versions + for version in $(echo "$release_versions" | jq -r '.[]'); do + echo "Processing release image ${name}:${version}" + + # Fetch the base manifest SHA + # Inspect command will output Name, MediaType, Digest in the first three lines + # so we can use a regex to pull out the SHA + manifest_base_sha=$(docker buildx imagetools inspect "ghcr.io/boozallen/${name}:${version}" | head -n 3 | sed -n 's/^Digest: *//p') + + echo "Manifest index SHA: ${manifest_base_sha}" + + # Add to digests array + digests+=("$manifest_base_sha") + + # Query the raw inpect output to get the nested manifest list SHAs + manifest_list_shas=$(docker buildx imagetools inspect --raw "ghcr.io/boozallen/${name}:${version}" | jq -r '.manifests[].digest' | paste -s -d ' ' -) + + echo "Manifest List SHAs: $manifest_list_shas" + + # Add to digests array + digests+=("$manifest_list_shas") + done +done + +# Pass the latest_release_version to GITHUB_OUTPUT so snapshot_images.sh can use it +latest_release_version=$(echo "$all_release_versions" | jq -r .[] | sort -uV -r | head -n 1) +echo "Latest release version is: ${latest_release_version}" +echo "latest-release-version=${latest_release_version}" >> "$GITHUB_OUTPUT" + +# Join digests into a single string separated by spaces +digests_string=$(echo "${digests[*]}") + +# Save the output to $GITHUB_OUTPUT +echo "multi-arch-digests=${digests_string}" >> "$GITHUB_OUTPUT" \ No newline at end of file diff --git a/.github/scripts/snapshot_images.sh b/.github/scripts/snapshot_images.sh new file mode 100755 index 000000000..6fd1654c9 --- /dev/null +++ b/.github/scripts/snapshot_images.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +# Initialize array to store digest SHAs +declare -a digests + +# Fetch all docker image names +image_names=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "-GitHub-Api-Version: 2022-11-28" \ + -H "Authorization: Token ${GITHUB_TOKEN}" \ + --paginate "/orgs/boozallen/packages?package_type=container" | jq -r '.[] | select((.name | startswith("aissemble")) and (.name | endswith("-chart") | not)) | .name') + +# For each docker image, find their snapshot versions by grabbing any tag that ends with "-SNAPSHOT" +for name in $image_names; do + all_snapshot_versions=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "-GitHub-Api-Version: 2022-11-28" \ + -H "Authorization: Token ${GITHUB_TOKEN}" \ + --paginate "/orgs/boozallen/packages/container/${name}/versions" \ + | jq -r '.[] | .metadata.container.tags[] | select(endswith("-SNAPSHOT"))'| jq -R -s 'split("\n")| map(select(length > 0))') + + # Find the latest snapshot version by sorting all snapshot versions, then selecting the top + latest_snapshot_version=$(echo "$all_snapshot_versions" | jq -r '.[] | select(endswith("-SNAPSHOT"))' | sort -r | head -n 1) + + echo "Processing snapshot image ${name}:${latest_snapshot_version}" + + # Fetch the base manifest SHA + # Inspect command will output Name, MediaType, Digest in the first three lines + # Use regex to pull out the SHA + latest_snapshot_manifest_base_sha=$(docker buildx imagetools inspect "ghcr.io/boozallen/${name}:${version}" | head -n 3 | sed -n 's/^Digest: *//p') + + echo "Manifest index SHA: ${latest_snapshot_manifest_base_sha}" + + # Add to digests array + digests+=("$latest_snapshot_manifest_base_sha") + + # Fetch the manifest list SHAs + latest_snapshot_manifest_list_shas=$(docker buildx imagetools inspect --raw "ghcr.io/boozallen/${name}:${latest_snapshot_version}" | jq -r '.manifests[].digest' | paste -s -d ' ' -) + + echo "Manifest List: $latest_snapshot_manifest_list_shas" + + # Add to digests array + digests+=("$latest_snapshot_manifest_list_shas") + + # Find if there are any patch versions available + # Extract the major, minor, and patch components of the latest release version using IFS (Internal Field Separator) + IFS='.' read -r major minor patch <<< "${LATEST_RELEASE_VERSION}" + + # Patch pattern uses the latest release version's major and minor components + patch_pattern="${major}\.${minor}\.[1-9]+[0-9]*-SNAPSHOT?" + + for version in $(echo "$all_snapshot_versions" | jq -r '.[]'); do + if [[ ${version} =~ ^${patch_pattern}$ ]]; then + echo "Patch version ${version} matches patch pattern ${patch_pattern}" + + # Add to array containing all matching patch versions + matching_patch_versions+=("$version") + fi + + # If matching patch versions array is not empty + if [ ${#matching_patch_versions[@]} -ne 0 ]; then + echo "Patch Versions array is not empty" + # Find the latest patch version + latest_patch_version=($(printf "%s\n" "${matching_patch_versions[@]}" | sort -V -r | head -n 1)) + + # Fetch the SHA + latest_patch_version_sha=$(docker buildx imagetools inspect --raw "ghcr.io/boozallen/${name}:${latest_patch_version}" | jq -r '.manifests[].digest' | paste -s -d ' ' -) + + # Add to the digests array + digests+=("$latest_patch_version_sha") + fi + done +done + +# Join digests into a single string separated by spaces +digests_string=$(echo "${digests[*]}") + +# Save the output to $GITHUB_OUTPUT +echo "latest-snapshot-digests=${digests_string}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/prune.yml b/.github/workflows/prune.yml index 103af6d48..b4cf3ed5b 100644 --- a/.github/workflows/prune.yml +++ b/.github/workflows/prune.yml @@ -1,4 +1,5 @@ -# Prunes old aissemble-* untagged containers +# Prunes old aissemble-* containers, excluding those with the following tags: +# any release versions, the latest snapshot version, and the latest patch version name: Prune ghcr.io @@ -15,14 +16,38 @@ jobs: runs-on: ubuntu-latest steps: - - name: Prune - uses: snok/container-retention-policy@v2 + # Required in order to access script files in this repository + - uses: actions/checkout@v4 + + # Prevents multi-platform release images from being pruned by identifying all manifest lists + - name: Fetch multi-platform package version SHAs + id: multi-arch-digests + env: + GITHUB_TOKEN: ${{ secrets.GHCR_IO_TOKEN }} + run: bash ${GITHUB_WORKSPACE}/.github/scripts/release_images.sh + + # Prevents the latest snapshot images from being pruned + - name: Fetch latest snapshot version SHAs + id: latest-snapshot-digests + env: + GITHUB_TOKEN: ${{ secrets.GHCR_IO_TOKEN }} + LATEST_RELEASE_VERSION: ${{ steps.multi-arch-digests.outputs.latest-release-version }} + run: bash ${GITHUB_WORKSPACE}/.github/scripts/snapshot_images.sh + + - name: Concatenate digests + id: concat-digests + run: | + skip-shas="${{ steps.multi-arch-digests.outputs.multi-arch-digests }} ${{ steps.latest-snapshot-digests.outputs.latest-snapshot-digests }}" + echo "skip-shas=$skip-shas" >> $GITHUB_OUTPUT + + - name: Prune old release versions + uses: snok/container-retention-policy@v3.0.0 with: - image-names: aissemble-* - cut-off: Two days ago UTC - account-type: org - org-name: boozallen - keep-at-least: 2 - untagged-only: true - dry-run: false + skip-shas: ${{ steps.concat-digests.outputs.skip-shas }} + image-names: "aissemble-* !aissemble-*-chart" + image-tags: "!1.7.0 !1.7.0-arm64 !1.7.0-amd64" + dry-run: true + cut-off: 2d + account: boozallen token: ${{ secrets.GHCR_IO_TOKEN }} +