diff --git a/.gitea/workflows/deploy-staging.yml b/.gitea/workflows/deploy-staging.yml index 3f185d75..d633a2a0 100644 --- a/.gitea/workflows/deploy-staging.yml +++ b/.gitea/workflows/deploy-staging.yml @@ -49,7 +49,6 @@ jobs: env: REQUESTED_SHA: ${{ gitea.event.workflow_run.head_sha || inputs.deploy_sha }} WORKFLOW_RUN_ID: ${{ gitea.event.workflow_run.id }} - WORKFLOW_RUN_ATTEMPT: ${{ gitea.event.workflow_run.run_attempt }} REQUESTED_ROLLBACK: ${{ inputs.allow_rollback || 'false' }} GITEA_EVENT_NAME: ${{ gitea.event_name }} run: | @@ -62,7 +61,6 @@ jobs: fi gate_run_id="${WORKFLOW_RUN_ID:-}" - gate_run_attempt="${WORKFLOW_RUN_ATTEMPT:-}" if [[ "$GITEA_EVENT_NAME" == workflow_dispatch ]]; then runs="$(curl --fail --silent --show-error \ --header "Authorization: token $GITEA_TOKEN" \ @@ -75,10 +73,8 @@ jobs: )] | sort_by(.id) | reverse | first ' <<<"$runs")" gate_run_id="$(jq -er '.id' <<<"$selected_run")" - gate_run_attempt="$(jq -er '.run_attempt // 0' <<<"$selected_run")" fi [[ "$gate_run_id" =~ ^[0-9]+$ ]] || { echo "no successful exact-SHA staging quality gate run found" >&2; exit 1; } - [[ "$gate_run_attempt" =~ ^[0-9]+$ ]] || { echo "invalid staging quality gate run attempt" >&2; exit 1; } staging_head="$(git ls-remote https://git.copse.top/root/Jyotisha.git refs/heads/staging | awk '{print $1}')" [[ "$staging_head" =~ ^[0-9a-f]{40}$ ]] @@ -89,7 +85,6 @@ jobs: { echo "sha=$REQUESTED_SHA" echo "gate_run_id=$gate_run_id" - echo "gate_run_attempt=$gate_run_attempt" echo "allow_rollback=$allow_rollback" } >>"$GITHUB_OUTPUT" @@ -148,15 +143,25 @@ jobs: - name: Download gate-produced image manifest env: GATE_RUN_ID: ${{ steps.revision.outputs.gate_run_id }} - GATE_RUN_ATTEMPT: ${{ steps.revision.outputs.gate_run_attempt }} DEPLOY_SHA: ${{ steps.revision.outputs.sha }} run: | set -euo pipefail - artifact_name="staging-image-manifest-$DEPLOY_SHA-$GATE_RUN_ATTEMPT" + artifact_prefix="staging-image-manifest-$DEPLOY_SHA-" artifacts="$(curl --fail --silent --show-error \ --header "Authorization: token $GITEA_TOKEN" \ - "$GITEA_API_URL/repos/$GITEA_REPOSITORY/actions/runs/$GATE_RUN_ID/artifacts?name=$artifact_name")" - artifact_id="$(jq -er --arg name "$artifact_name" '[.artifacts[] | select(.name == $name and .expired == false)] | first | .id' <<<"$artifacts")" + "$GITEA_API_URL/repos/$GITEA_REPOSITORY/actions/runs/$GATE_RUN_ID/artifacts?limit=100")" + selected_artifact="$(jq -cer --arg prefix "$artifact_prefix" ' + [(.artifacts // [])[] + | select(.expired == false and (.name | startswith($prefix))) + | . + {attempt: ((.name | ltrimstr($prefix)) | tonumber?)} + | select(.attempt != null and .attempt >= 1) + ] | sort_by(.attempt, .id) | reverse | first + ' <<<"$artifacts")" + artifact_name="$(jq -er '.name' <<<"$selected_artifact")" + artifact_id="$(jq -er '.id' <<<"$selected_artifact")" + artifact_attempt="${artifact_name#"$artifact_prefix"}" + [[ "$artifact_name" == "$artifact_prefix"* ]] + [[ "$artifact_attempt" =~ ^[1-9][0-9]*$ ]] [[ "$artifact_id" =~ ^[0-9]+$ ]] install -d -m 700 artifacts/staging-image curl --fail --silent --show-error --location \ diff --git a/.gitea/workflows/migrate-staging-database.yml b/.gitea/workflows/migrate-staging-database.yml index 32bb9f27..37de62f9 100644 --- a/.gitea/workflows/migrate-staging-database.yml +++ b/.gitea/workflows/migrate-staging-database.yml @@ -54,13 +54,10 @@ jobs: )] | sort_by(.id) | reverse | first ' <<<"$runs")" gate_run_id="$(jq -er '.id' <<<"$selected_run")" - gate_run_attempt="$(jq -er '.run_attempt // 0' <<<"$selected_run")" [[ "$gate_run_id" =~ ^[0-9]+$ ]] - [[ "$gate_run_attempt" =~ ^[0-9]+$ ]] { echo "sha=$DEPLOY_SHA" echo "gate_run_id=$gate_run_id" - echo "gate_run_attempt=$gate_run_attempt" } >>"$GITHUB_OUTPUT" - name: Checkout trusted main controller @@ -118,15 +115,25 @@ jobs: - name: Download gate-produced migration manifest env: GATE_RUN_ID: ${{ steps.revision.outputs.gate_run_id }} - GATE_RUN_ATTEMPT: ${{ steps.revision.outputs.gate_run_attempt }} DEPLOY_SHA: ${{ steps.revision.outputs.sha }} run: | set -euo pipefail - artifact_name="staging-image-manifest-$DEPLOY_SHA-$GATE_RUN_ATTEMPT" + artifact_prefix="staging-image-manifest-$DEPLOY_SHA-" artifacts="$(curl --fail --silent --show-error \ --header "Authorization: token $GITEA_TOKEN" \ - "$GITEA_API_URL/repos/$GITEA_REPOSITORY/actions/runs/$GATE_RUN_ID/artifacts?name=$artifact_name")" - artifact_id="$(jq -er --arg name "$artifact_name" '[.artifacts[] | select(.name == $name and .expired == false)] | first | .id' <<<"$artifacts")" + "$GITEA_API_URL/repos/$GITEA_REPOSITORY/actions/runs/$GATE_RUN_ID/artifacts?limit=100")" + selected_artifact="$(jq -cer --arg prefix "$artifact_prefix" ' + [(.artifacts // [])[] + | select(.expired == false and (.name | startswith($prefix))) + | . + {attempt: ((.name | ltrimstr($prefix)) | tonumber?)} + | select(.attempt != null and .attempt >= 1) + ] | sort_by(.attempt, .id) | reverse | first + ' <<<"$artifacts")" + artifact_name="$(jq -er '.name' <<<"$selected_artifact")" + artifact_id="$(jq -er '.id' <<<"$selected_artifact")" + artifact_attempt="${artifact_name#"$artifact_prefix"}" + [[ "$artifact_name" == "$artifact_prefix"* ]] + [[ "$artifact_attempt" =~ ^[1-9][0-9]*$ ]] [[ "$artifact_id" =~ ^[0-9]+$ ]] install -d -m 700 artifacts/staging-image curl --fail --silent --show-error --location \ diff --git a/frontend/tests/staging-backend-workflows.test.ts b/frontend/tests/staging-backend-workflows.test.ts index ce238791..c17df1ee 100644 --- a/frontend/tests/staging-backend-workflows.test.ts +++ b/frontend/tests/staging-backend-workflows.test.ts @@ -445,10 +445,8 @@ test("Gitea deploy and migration consume the exact successful gate artifact", () assert.match(workflow, /\.head_branch == "staging"/); assert.match(workflow, /\.event == "push"/); assert.match(workflow, /\.conclusion == "success"/); - assert.match(workflow, /\.run_attempt \/\/ 0/); - assert.match(workflow, /\[\[ "\$gate_run_attempt" =~ \^\[0-9\]\+\$ \]\]/); assert.match(workflow, /sort_by\(\.id\) \| reverse \| first/); - assert.match(workflow, /actions\/runs\/\$GATE_RUN_ID\/artifacts\?name=\$artifact_name/); + assert.match(workflow, /actions\/runs\/\$GATE_RUN_ID\/artifacts\?limit=100/); assert.match(workflow, /actions\/artifacts\/\$artifact_id\/zip/); assert.match(workflow, /node frontend\/scripts\/staging-image-manifest\.mjs/); assert.match(workflow, /name: Checkout trusted main controller/); @@ -460,6 +458,34 @@ test("Gitea deploy and migration consume the exact successful gate artifact", () } }); +test("Gitea staging mutations resolve the manifest from actual gate-run artifacts", () => { + for (const workflow of [read(giteaDeployWorkflow), read(giteaMigrationWorkflow)]) { + assert.match( + workflow, + /artifact_prefix="staging-image-manifest-\$DEPLOY_SHA-"/, + ); + assert.match(workflow, /\.expired == false/); + assert.match(workflow, /\.name \| startswith\(\$prefix\)/); + assert.match(workflow, /\.name \| ltrimstr\(\$prefix\)/); + assert.match(workflow, /tonumber\?/); + assert.match(workflow, /sort_by\(\.attempt, \.id\) \| reverse \| first/); + assert.match(workflow, /artifact_name="\$\(jq -er '\.name'/); + assert.match(workflow, /artifact_id="\$\(jq -er '\.id'/); + assert.match(workflow, /artifact_attempt="\$\{artifact_name#"\$artifact_prefix"\}"/); + assert.match(workflow, /\[\[ "\$artifact_name" == "\$artifact_prefix"\* \]\]/); + assert.match(workflow, /\[\[ "\$artifact_attempt" =~ \^\[1-9\]\[0-9\]\*\$ \]\]/); + assertOrder(workflow, [ + 'artifact_prefix="staging-image-manifest-$DEPLOY_SHA-"', + 'actions/runs/$GATE_RUN_ID/artifacts?limit=100', + 'sort_by(.attempt, .id) | reverse | first', + 'artifact_name="$(jq -er', + 'actions/artifacts/$artifact_id/zip', + ]); + assert.doesNotMatch(workflow, /run_attempt/i); + assert.doesNotMatch(workflow, /staging-image-manifest-\$DEPLOY_SHA-1/); + } +}); + test("Gitea deployment follows only a successful staging push gate and keeps rollback manual", () => { const workflow = read(giteaDeployWorkflow);