Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions .github/scripts/cleanup-ghcr-ci-images.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/env bash
set -euo pipefail

if ! [[ "${STALE_HOURS}" =~ ^[0-9]+$ ]]; then
echo "stale_hours must be a positive integer, got: ${STALE_HOURS}" >&2
exit 1
fi

if [ "${STALE_HOURS}" -lt 1 ]; then
echo "stale_hours must be at least 1, got: ${STALE_HOURS}" >&2
exit 1
fi

owner="${GITHUB_REPOSITORY_OWNER,,}"
repo="${GITHUB_REPOSITORY#*/}"
repo="${repo,,}"

date_utc() {
if command -v gdate >/dev/null 2>&1; then
gdate -u -d "$1" '+%Y-%m-%dT%H:%M:%SZ'
return
fi

date -u -d "$1" '+%Y-%m-%dT%H:%M:%SZ'
}

cutoff="$(date_utc "${STALE_HOURS} hours ago")"

# GitHub's package version API exposes updated_at, not a separate last-pulled timestamp.
# These are the docker-cpu images published by .github/workflows/ci.yaml.
images=(
"nmp-api"
"nmp-core"
"nmp-cpu-tasks"
)

echo "Scanning ghcr.io/${owner}/${repo} image versions last updated before ${cutoff}"
echo "dry_run=${DRY_RUN}"

total_deleted=0
total_candidates=0

url_encode() {
jq -nr --arg value "$1" '$value|@uri'
}

find_stale_versions() {
local endpoint="$1"
local jq_filter
jq_filter="
.[] |
select(.updated_at != null and .updated_at < \"${cutoff}\") |
select(((.metadata.container.tags // []) | index(\"latest\")) | not) |
[.id, .name, .updated_at, ((.metadata.container.tags // []) | join(\",\"))] |
@tsv
"

gh api --paginate "${endpoint}/versions?per_page=100" --jq "${jq_filter}"
}

for image in "${images[@]}"; do
package_name="${repo}/${image}"
encoded_package_name="$(url_encode "${package_name}")"
endpoint="/orgs/${owner}/packages/container/${encoded_package_name}"

echo "::group::${package_name}"

api_error="$(mktemp)"
if ! stale_versions="$(find_stale_versions "${endpoint}" 2>"${api_error}")"; then
if grep -q "HTTP 404" "${api_error}"; then
echo "Package ${package_name} was not found; skipping."
echo "::endgroup::"
continue
fi

cat "${api_error}" >&2
exit 1
fi

if [ -z "${stale_versions}" ]; then
echo "No stale package versions found."
echo "::endgroup::"
continue
fi

while IFS=$'\t' read -r version_id digest updated_at tags; do
if [ -z "${version_id}" ]; then
continue
fi

total_candidates=$((total_candidates + 1))
tag_summary="${tags:-<untagged>}"
echo "Candidate ${package_name}@${digest} updated_at=${updated_at} tags=${tag_summary}"

if [ "${DRY_RUN}" = "true" ]; then
continue
fi

gh api -X DELETE "${endpoint}/versions/${version_id}" >/dev/null
total_deleted=$((total_deleted + 1))
echo "Deleted package version ${version_id}"
done <<< "${stale_versions}"

echo "::endgroup::"
done

echo "Stale candidates: ${total_candidates}"
echo "Deleted versions: ${total_deleted}"
11 changes: 10 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ jobs:
printf 'CI_COMMIT_SHA=%s\n' "$source_sha"
printf 'FASTEMBED_CACHE_CONTEXT=%s\n' "$fastembed_cache_dir"
printf 'FASTEMBED_CACHE_DIR=%s\n' "$fastembed_cache_dir"
printf 'DOCKER_BAKE_ALLOW_FS_READ=%s\n' "$fastembed_cache_dir"
printf 'FASTEMBED_MODEL_REPO=%s\n' "qdrant/all-MiniLM-L6-v2-onnx"
printf 'FASTEMBED_MODEL_REVISION=%s\n' "main"
printf 'PUBLISH_IMAGES=%s\n' "$publish_images"
Expand Down Expand Up @@ -1159,7 +1160,15 @@ jobs:
coverage-comment:
name: Post coverage comment
needs: [python-unit-test, python-integration-test]
if: always() && github.event_name == 'pull_request'
if: >
always() &&
github.event_name == 'pull_request' &&
(
needs.python-unit-test.result == 'success' ||
needs.python-unit-test.result == 'failure' ||
needs.python-integration-test.result == 'success' ||
needs.python-integration-test.result == 'failure'
)
runs-on: ubuntu-latest
permissions:
pull-requests: write
Expand Down
45 changes: 45 additions & 0 deletions .github/workflows/ghcr-cleanup.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Cleanup GHCR CI Images

on:
schedule:
- cron: "43 4 * * *"
workflow_dispatch:
inputs:
dry_run:
description: Log stale GHCR package versions without deleting them.
required: false
type: boolean
default: false
stale_hours:
description: Delete versions last updated more than this many hours ago.
required: false
type: number
default: 24

permissions: {}

concurrency:
group: ghcr-cleanup-${{ github.repository }}
cancel-in-progress: false

jobs:
cleanup:
name: Delete stale GHCR CI image versions
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
DRY_RUN: ${{ inputs.dry_run || 'false' }}
STALE_HOURS: ${{ inputs.stale_hours || '24' }}
steps:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

- name: Delete stale package versions
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: bash .github/scripts/cleanup-ghcr-ci-images.sh
8 changes: 5 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ DOCKER_BAKE_FILE ?= docker-bake.hcl
DOCKER_TARGET ?= $(if $(TARGET),$(TARGET),docker-cpu)
DOCKER_PLATFORMS ?= $(BUILD_ARCH)
DOCKER_PLATFORM_SET = $(if $(DOCKER_PLATFORMS),--set "*.platform=$(DOCKER_PLATFORMS)",)
DOCKER_BAKE_ALLOW_FS_READ ?=
DOCKER_BAKE_ALLOW_FS_READ_FLAG = $(if $(DOCKER_BAKE_ALLOW_FS_READ), --allow=fs.read=$(DOCKER_BAKE_ALLOW_FS_READ),)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

.PHONY: docker-list-targets
docker-list-targets: ## List Docker bake targets
Expand All @@ -44,15 +46,15 @@ docker-print: ## Print Docker bake graph for TARGET, default docker-cpu

.PHONY: docker-build
docker-build: ## Build Docker bake TARGET without pushing, default docker-cpu
docker buildx bake -f $(DOCKER_BAKE_FILE) $(DOCKER_TARGET)
docker buildx bake$(DOCKER_BAKE_ALLOW_FS_READ_FLAG) -f $(DOCKER_BAKE_FILE) $(DOCKER_TARGET)

.PHONY: docker-load
docker-load: ## Build and load single-platform Docker bake TARGET, default docker-cpu
docker buildx bake -f $(DOCKER_BAKE_FILE) $(DOCKER_TARGET) $(DOCKER_PLATFORM_SET) --load
docker buildx bake$(DOCKER_BAKE_ALLOW_FS_READ_FLAG) -f $(DOCKER_BAKE_FILE) $(DOCKER_TARGET) $(DOCKER_PLATFORM_SET) --load

.PHONY: docker-push
docker-push: ## Build and push Docker bake TARGET, default docker-cpu
docker buildx bake -f $(DOCKER_BAKE_FILE) $(DOCKER_TARGET) --push
docker buildx bake$(DOCKER_BAKE_ALLOW_FS_READ_FLAG) -f $(DOCKER_BAKE_FILE) $(DOCKER_TARGET) --push

.PHONY: refresh-openapi
refresh-openapi: ## Generate the OpenAPI specification
Expand Down
Loading