chore: import upstream snapshot with attribution
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,334 @@
|
||||
name: Publish Docker image
|
||||
|
||||
# SECURITY: Only invoked as a reusable workflow (`workflow_call`) from
|
||||
# release.yml after the release security gate AND the `release` environment
|
||||
# approval pass. We intentionally do NOT support workflow_dispatch — that
|
||||
# would bypass gates. We also intentionally do NOT support
|
||||
# repository_dispatch any more: with the atomicity refactor, this workflow
|
||||
# runs as a job inside release.yml's run so its result is visible to
|
||||
# downstream jobs (create-release, cleanup-on-rejection) — a property that
|
||||
# repository_dispatch fanout broke.
|
||||
#
|
||||
# This workflow is a thin RETAG step in the build-once-promote pipeline.
|
||||
# The actual build happens in prerelease-docker.yml; the multi-arch manifest
|
||||
# is signed and attested there. Here we only:
|
||||
# - Verify the source manifest digest matches what prerelease produced
|
||||
# - Retag the prerelease manifest to release tags (:1.6.9, :1.6, :latest)
|
||||
# - Verify the digest is preserved (defends against imagetools re-encoding)
|
||||
# - Re-run Trivy against the digest (catches CVE-database updates between
|
||||
# prerelease build and release promote)
|
||||
# - Verify cosign signature transitivity from the original digest
|
||||
# - Clean up the prerelease tags
|
||||
#
|
||||
# To re-publish, trigger a new release through release.yml.
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Release tag, e.g. 'v1.6.9' (with leading 'v')"
|
||||
type: string
|
||||
required: true
|
||||
source_tag:
|
||||
description: "Prerelease manifest tag to retag, e.g. 'prerelease-v1.6.9-abc1234'"
|
||||
type: string
|
||||
required: true
|
||||
expected_digest:
|
||||
description: "sha256:... digest of the prerelease manifest, captured by prerelease-docker.yml. Used to verify retag preserves the digest end-to-end."
|
||||
type: string
|
||||
required: true
|
||||
secrets:
|
||||
DOCKER_USERNAME:
|
||||
required: true
|
||||
description: "Docker Hub username (env-scoped to `release`)"
|
||||
DOCKER_PASSWORD:
|
||||
required: true
|
||||
description: "Docker Hub PAT with Read+Write+Delete scopes (env-scoped to `release`)"
|
||||
|
||||
permissions: {} # Minimal top-level for OSSF Scorecard Token-Permissions
|
||||
|
||||
# NOTE: no workflow-level `concurrency:` block. As a reusable workflow
|
||||
# called from release.yml, this workflow runs as part of the caller's run,
|
||||
# and release.yml's caller-level concurrency (keyed on github.workflow +
|
||||
# github.ref) already serialises release runs for the same tag. Adding a
|
||||
# callee-level block would be a no-op for the documented threat model
|
||||
# (two simultaneous releases for the same ref) and would not be reachable
|
||||
# anyway because there is no longer any standalone invocation path.
|
||||
|
||||
jobs:
|
||||
promote:
|
||||
name: Retag prerelease manifest as release
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
permissions:
|
||||
# cosign verify is read-only against public Rekor/Fulcio — does not
|
||||
# mint a GitHub OIDC token, so id-token: write is not required here.
|
||||
# Signing (which does need id-token: write) happens in prerelease-docker.yml.
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Check out the repo at the triggering commit
|
||||
# Pin the checkout to the EXACT commit that the prerelease was
|
||||
# built/scanned from, so .trivyignore (and any other repo-state-
|
||||
# dependent file the promote step reads) matches that commit.
|
||||
# We use github.sha, NOT inputs.tag — the v* git tag is created
|
||||
# by create-release LATER in this run (after publish-docker
|
||||
# completes), so it does not exist yet when this checkout runs
|
||||
# on a push-to-main trigger. github.sha is the triggering commit
|
||||
# for every event type (push to main, tag push, workflow_dispatch)
|
||||
# and is the same SHA the build/prerelease-docker jobs used.
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||
with:
|
||||
# Pin to cosign v2.x to match the version that signed the artifact
|
||||
# in prerelease-docker.yml. Mismatched versions across sign/verify
|
||||
# work today but new-bundle-format (cosign v3 default) would only
|
||||
# produce/consume on v3.
|
||||
cosign-release: 'v2.6.3'
|
||||
|
||||
- name: Determine release tags
|
||||
id: version
|
||||
env:
|
||||
DISPATCH_TAG: ${{ inputs.tag }}
|
||||
SOURCE_TAG: ${{ inputs.source_tag }}
|
||||
EXPECTED_DIGEST: ${{ inputs.expected_digest }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "$DISPATCH_TAG" || -z "$SOURCE_TAG" || -z "$EXPECTED_DIGEST" ]]; then
|
||||
echo "::error::Missing required workflow_call input. Got tag='${DISPATCH_TAG}' source_tag='${SOURCE_TAG}' expected_digest='${EXPECTED_DIGEST}'"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$EXPECTED_DIGEST" != sha256:* ]]; then
|
||||
echo "::error::expected_digest must be of the form sha256:... — got '${EXPECTED_DIGEST}'"
|
||||
exit 1
|
||||
fi
|
||||
VERSION="${DISPATCH_TAG#v}"
|
||||
MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1,2)
|
||||
# Group writes into one redirect (shellcheck SC2129).
|
||||
{
|
||||
echo "tag=${DISPATCH_TAG}"
|
||||
echo "version=${VERSION}"
|
||||
echo "major_minor=${MAJOR_MINOR}"
|
||||
echo "source_tag=${SOURCE_TAG}"
|
||||
echo "expected_digest=${EXPECTED_DIGEST}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Verify source digest matches expected
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
SOURCE_TAG: ${{ steps.version.outputs.source_tag }}
|
||||
EXPECTED_DIGEST: ${{ steps.version.outputs.expected_digest }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Defends against the prerelease tag being swapped between
|
||||
# prerelease-docker's signing and this promote step.
|
||||
SOURCE="${DOCKER_USERNAME}/local-deep-research:${SOURCE_TAG}"
|
||||
ACTUAL=$(docker buildx imagetools inspect "$SOURCE" --format '{{json .Manifest.Digest}}' | tr -d '"')
|
||||
if [[ "$ACTUAL" != "$EXPECTED_DIGEST" ]]; then
|
||||
echo "::error::Source digest mismatch — possible tag tampering between prerelease and promote"
|
||||
echo " expected: $EXPECTED_DIGEST"
|
||||
echo " actual: $ACTUAL"
|
||||
exit 1
|
||||
fi
|
||||
echo "Source digest verified: $ACTUAL"
|
||||
|
||||
- name: Promote (retag) prerelease manifest to release tags
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
SOURCE_TAG: ${{ steps.version.outputs.source_tag }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
MAJOR_MINOR: ${{ steps.version.outputs.major_minor }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SOURCE="${DOCKER_USERNAME}/local-deep-research:${SOURCE_TAG}"
|
||||
# Single imagetools create with multiple -t — registry-side
|
||||
# metadata-only operation, takes seconds, preserves digest.
|
||||
docker buildx imagetools create \
|
||||
-t "${DOCKER_USERNAME}/local-deep-research:${VERSION}" \
|
||||
-t "${DOCKER_USERNAME}/local-deep-research:${MAJOR_MINOR}" \
|
||||
-t "${DOCKER_USERNAME}/local-deep-research:latest" \
|
||||
"$SOURCE"
|
||||
echo "Promoted ${SOURCE} to :${VERSION}, :${MAJOR_MINOR}, :latest"
|
||||
|
||||
- name: Verify promoted tags share the source digest
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
MAJOR_MINOR: ${{ steps.version.outputs.major_minor }}
|
||||
EXPECTED_DIGEST: ${{ steps.version.outputs.expected_digest }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Defends against `imagetools create` re-encoding the manifest.
|
||||
# If digests diverge, signatures and attestations (keyed by the
|
||||
# original digest) won't be discoverable from the new tags.
|
||||
for TAG in "${VERSION}" "${MAJOR_MINOR}" "latest"; do
|
||||
REF="${DOCKER_USERNAME}/local-deep-research:${TAG}"
|
||||
ACTUAL=$(docker buildx imagetools inspect "$REF" --format '{{json .Manifest.Digest}}' | tr -d '"')
|
||||
if [[ "$ACTUAL" != "$EXPECTED_DIGEST" ]]; then
|
||||
echo "::error::Digest mismatch on ${TAG} — imagetools create may have re-encoded the manifest"
|
||||
echo " expected: $EXPECTED_DIGEST"
|
||||
echo " actual: $ACTUAL"
|
||||
exit 1
|
||||
fi
|
||||
echo "${TAG} -> ${ACTUAL} ✓"
|
||||
done
|
||||
|
||||
# Catches CVE-database updates that landed between the prerelease
|
||||
# build and this promote step. Use the SHA-pinned action wrapper
|
||||
# (same pin as prerelease-docker.yml's security-scan) with an
|
||||
# explicit binary version pin — the prior `apt-get install -y trivy`
|
||||
# approach was unpinned and exposed the release path to the Trivy
|
||||
# apt-repo supply chain. The pinned action downloads the v0.69.2
|
||||
# binary from GitHub releases by exact tag, which is the same
|
||||
# binary the prerelease scan validated, keeping the two scans
|
||||
# consistent.
|
||||
#
|
||||
# Unlike prerelease-docker.yml's security-scan (which scans a
|
||||
# locally-loaded image, no registry pull), this step scans by
|
||||
# registry digest — Trivy must pull the manifest + layers from
|
||||
# Docker Hub. TRIVY_USERNAME/TRIVY_PASSWORD is the action's
|
||||
# documented auth path; the `docker/login-action` above also
|
||||
# writes ~/.docker/config.json which Trivy reads as a fallback,
|
||||
# but the explicit env vars are more reliable (Trivy has
|
||||
# documented docker.io credential-helper quirks — aquasecurity/
|
||||
# trivy#432, aquasecurity/trivy#8385) and the image we scan IS on
|
||||
# Docker Hub so this is the path most likely to keep working.
|
||||
- name: Re-scan release digest with Trivy
|
||||
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
|
||||
env:
|
||||
TRIVY_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
TRIVY_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
with:
|
||||
image-ref: ${{ secrets.DOCKER_USERNAME }}/local-deep-research@${{ steps.version.outputs.expected_digest }}
|
||||
severity: 'CRITICAL,HIGH'
|
||||
ignore-unfixed: true
|
||||
trivyignores: '.trivyignore'
|
||||
exit-code: '1'
|
||||
version: 'v0.69.2'
|
||||
|
||||
- name: Verify cosign signature on promoted digest
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
EXPECTED_DIGEST: ${{ steps.version.outputs.expected_digest }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# The cert was issued to the prerelease-docker.yml workflow when
|
||||
# signing happened there, so the identity regex must match that
|
||||
# workflow's path. Fulcio's SAN is built from job_workflow_ref,
|
||||
# which for reusable workflows is the CALLEE.
|
||||
#
|
||||
# Verify by IMMUTABLE digest (not the :VERSION tag) so this step
|
||||
# is invariant under any retag race between the verify-promoted-
|
||||
# tags step above and this one. Trivy's re-scan above also uses
|
||||
# @${EXPECTED_DIGEST}; keeping cosign on the same reference is
|
||||
# consistent and avoids a tag-resolution TOCTOU window.
|
||||
IMAGE_REF="${DOCKER_USERNAME}/local-deep-research@${EXPECTED_DIGEST}"
|
||||
echo "Verifying signature for: $IMAGE_REF"
|
||||
cosign verify \
|
||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
||||
--certificate-identity-regexp "^https://github.com/${REPO}/\.github/workflows/prerelease-docker\.yml@refs/(heads|tags)/" \
|
||||
--certificate-github-workflow-repository "${REPO}" \
|
||||
"$IMAGE_REF"
|
||||
echo "Signature transitivity verified ✓"
|
||||
|
||||
- name: Clean up prerelease tags
|
||||
continue-on-error: true
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
set +e # Best-effort: never fail the workflow
|
||||
# Scope deletion to prereleases of THIS version only, so concurrent
|
||||
# prereleases for other versions (and any unrelated prerelease-* tags)
|
||||
# are left untouched.
|
||||
PREFIX="prerelease-v${RELEASE_VERSION}-"
|
||||
echo "Cleaning up ${PREFIX}* tags from Docker Hub..."
|
||||
|
||||
# Authenticate with Docker Hub API (password-based JWT)
|
||||
TOKEN=$(curl -s -X POST "https://hub.docker.com/v2/users/login/" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"username\": \"${DOCKER_USERNAME}\", \"password\": \"${DOCKER_PASSWORD}\"}" | jq -r '.token')
|
||||
|
||||
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
|
||||
echo "WARNING: Failed to authenticate with Docker Hub API. Skipping cleanup."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
REPO="${DOCKER_USERNAME}/local-deep-research"
|
||||
PAGE=1
|
||||
DELETED=0
|
||||
|
||||
while true; do
|
||||
RESPONSE=$(curl -s -H "Authorization: JWT ${TOKEN}" \
|
||||
"https://hub.docker.com/v2/repositories/${REPO}/tags/?page=${PAGE}&page_size=100")
|
||||
|
||||
RESULTS=$(echo "$RESPONSE" | jq -r '.results[]?.name // empty')
|
||||
if [ -z "$RESULTS" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
while IFS= read -r tag; do
|
||||
if [[ "$tag" == "${PREFIX}"* ]]; then
|
||||
echo "Deleting tag: ${tag}"
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
|
||||
-H "Authorization: JWT ${TOKEN}" \
|
||||
"https://hub.docker.com/v2/repositories/${REPO}/tags/${tag}/")
|
||||
if [ "$HTTP_CODE" -eq 204 ] || [ "$HTTP_CODE" -eq 200 ]; then
|
||||
echo " Deleted: ${tag}"
|
||||
DELETED=$((DELETED + 1))
|
||||
else
|
||||
echo " WARNING: Failed to delete ${tag} (HTTP ${HTTP_CODE})"
|
||||
fi
|
||||
fi
|
||||
done <<< "$RESULTS"
|
||||
|
||||
# Check if there are more pages
|
||||
NEXT=$(echo "$RESPONSE" | jq -r '.next // empty')
|
||||
if [ -z "$NEXT" ]; then
|
||||
break
|
||||
fi
|
||||
PAGE=$((PAGE + 1))
|
||||
done
|
||||
|
||||
echo "Prerelease tag cleanup complete. Deleted ${DELETED} tag(s) matching ${PREFIX}*."
|
||||
|
||||
- name: Summary
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
MAJOR_MINOR: ${{ steps.version.outputs.major_minor }}
|
||||
EXPECTED_DIGEST: ${{ steps.version.outputs.expected_digest }}
|
||||
run: |
|
||||
{
|
||||
echo "## Docker Release Promoted"
|
||||
echo ""
|
||||
echo "**Digest:** \`${EXPECTED_DIGEST}\`"
|
||||
echo ""
|
||||
echo "**Tags:** \`${VERSION}\`, \`${MAJOR_MINOR}\`, \`latest\` — all share the same digest as the prerelease manifest, so cosign signatures, SBOM, and SLSA provenance from the prerelease step are transitively valid."
|
||||
echo ""
|
||||
echo '```'
|
||||
echo "docker pull ${DOCKER_USERNAME}/local-deep-research:${VERSION}"
|
||||
echo "docker pull ${DOCKER_USERNAME}/local-deep-research:latest"
|
||||
echo '```'
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
Reference in New Issue
Block a user