From d3cc70737db9008323654cdffe99509270bca34b Mon Sep 17 00:00:00 2001 From: linmeng <819991304@qq.com> Date: Thu, 30 Jul 2026 10:49:29 +0800 Subject: [PATCH] fix: select staging runtime image digest Resolve the linux amd64 descriptor from multi-entry ACR manifests so staging migration and deployment pin the runtime image correctly. --- .gitea/workflows/deploy-staging.yml | 4 ++-- .gitea/workflows/migrate-staging-database.yml | 2 +- frontend/tests/staging-backend-workflows.test.ts | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/deploy-staging.yml b/.gitea/workflows/deploy-staging.yml index e233155c..445ec571 100644 --- a/.gitea/workflows/deploy-staging.yml +++ b/.gitea/workflows/deploy-staging.yml @@ -63,8 +63,8 @@ jobs: if [[ "$ALLOW_ROLLBACK" != true ]]; then [[ "$DEPLOY_SHA" == "$staging_sha" ]] || { echo "forward redeploy must use staging head" >&2; exit 1; }; fi git cat-file -e "${DEPLOY_SHA}^{commit}" printf '%s' "$REGISTRY_PASSWORD" | docker login "$REGISTRY_HOST" --username "$REGISTRY_USERNAME" --password-stdin - api_digest="$(docker manifest inspect "${IMAGE_REPOSITORY}:api-${DEPLOY_SHA}" --verbose | python3 -c 'import json,sys; d=json.load(sys.stdin); d=d[0] if isinstance(d,list) and len(d)==1 else d; print(d.get("Descriptor", d).get("digest", "") if isinstance(d,dict) else "")')" - web_digest="$(docker manifest inspect "${IMAGE_REPOSITORY}:web-${DEPLOY_SHA}" --verbose | python3 -c 'import json,sys; d=json.load(sys.stdin); d=d[0] if isinstance(d,list) and len(d)==1 else d; print(d.get("Descriptor", d).get("digest", "") if isinstance(d,dict) else "")')" + api_digest="$(docker manifest inspect "${IMAGE_REPOSITORY}:api-${DEPLOY_SHA}" --verbose | python3 -c 'import json,sys; d=json.load(sys.stdin); xs=d if isinstance(d,list) else [d]; xs=[x for x in xs if isinstance(x,dict) and isinstance(x.get("Descriptor",x),dict)]; x=next((x for x in xs if x.get("Descriptor",x).get("platform",{}).get("os")=="linux" and x.get("Descriptor",x).get("platform",{}).get("architecture")=="amd64"),None); print(x.get("Descriptor",x).get("digest","") if x else "")')" + web_digest="$(docker manifest inspect "${IMAGE_REPOSITORY}:web-${DEPLOY_SHA}" --verbose | python3 -c 'import json,sys; d=json.load(sys.stdin); xs=d if isinstance(d,list) else [d]; xs=[x for x in xs if isinstance(x,dict) and isinstance(x.get("Descriptor",x),dict)]; x=next((x for x in xs if x.get("Descriptor",x).get("platform",{}).get("os")=="linux" and x.get("Descriptor",x).get("platform",{}).get("architecture")=="amd64"),None); print(x.get("Descriptor",x).get("digest","") if x else "")')" [[ "$api_digest" =~ ^sha256:[0-9a-f]{64}$ && "$web_digest" =~ ^sha256:[0-9a-f]{64}$ ]] || exit 1 printf 'git_sha=%s\napi_digest=%s\nweb_digest=%s\n' "$DEPLOY_SHA" "$api_digest" "$web_digest" > "${RUNNER_TEMP}/manifest.env" node frontend/scripts/staging-image-manifest.mjs "${RUNNER_TEMP}/manifest.env" "$DEPLOY_SHA" "$IMAGE_REPOSITORY" >> "$GITHUB_OUTPUT" diff --git a/.gitea/workflows/migrate-staging-database.yml b/.gitea/workflows/migrate-staging-database.yml index 427f59ba..0c281fc3 100644 --- a/.gitea/workflows/migrate-staging-database.yml +++ b/.gitea/workflows/migrate-staging-database.yml @@ -53,7 +53,7 @@ jobs: [[ "$(git ls-remote origin refs/heads/staging | awk '{print $1}')" == "$DEPLOY_SHA" ]] || { echo "migration requires current staging head" >&2; exit 1; } git cat-file -e "${DEPLOY_SHA}^{commit}" printf '%s' "$REGISTRY_PASSWORD" | docker login "$REGISTRY_HOST" --username "$REGISTRY_USERNAME" --password-stdin - web_digest="$(docker manifest inspect "${IMAGE_REPOSITORY}:web-${DEPLOY_SHA}" --verbose | python3 -c 'import json,sys; d=json.load(sys.stdin); d=d[0] if isinstance(d,list) and len(d)==1 else d; print(d.get("Descriptor", d).get("digest", "") if isinstance(d,dict) else "")')" + web_digest="$(docker manifest inspect "${IMAGE_REPOSITORY}:web-${DEPLOY_SHA}" --verbose | python3 -c 'import json,sys; d=json.load(sys.stdin); xs=d if isinstance(d,list) else [d]; xs=[x for x in xs if isinstance(x,dict) and isinstance(x.get("Descriptor",x),dict)]; x=next((x for x in xs if x.get("Descriptor",x).get("platform",{}).get("os")=="linux" and x.get("Descriptor",x).get("platform",{}).get("architecture")=="amd64"),None); print(x.get("Descriptor",x).get("digest","") if x else "")')" [[ "$web_digest" =~ ^sha256:[0-9a-f]{64}$ ]] || exit 1 echo "web_image=${IMAGE_REPOSITORY}@${web_digest}" >> "$GITHUB_OUTPUT" echo "deploy_sha=$DEPLOY_SHA" >> "$GITHUB_OUTPUT" diff --git a/frontend/tests/staging-backend-workflows.test.ts b/frontend/tests/staging-backend-workflows.test.ts index 39f1e93f..80fda8f5 100644 --- a/frontend/tests/staging-backend-workflows.test.ts +++ b/frontend/tests/staging-backend-workflows.test.ts @@ -212,10 +212,11 @@ test("staging owns its deployment control plane independently from main", () => } }); -test("Gitea staging digest resolution accepts single-platform manifest arrays", () => { +test("Gitea staging digest resolution selects linux amd64 from manifest arrays", () => { for (const workflow of [read(giteaDeployWorkflow), read(giteaMigrationWorkflow)]) { - assert.match(workflow, /d=d\[0\] if isinstance\(d,list\) and len\(d\)==1 else d/); - assert.match(workflow, /if isinstance\(d,dict\) else ""/); + assert.match(workflow, /xs=d if isinstance\(d,list\) else \[d\]/); + assert.match(workflow, /get\("os"\)=="linux"/); + assert.match(workflow, /get\("architecture"\)=="amd64"/); } });