Compare commits

...

6 Commits

Author SHA1 Message Date
Jesse_Chen fa65e76c77 Resolve staging manifest from actual artifacts
Staging Backend Quality Gate / validate (pull_request) Successful in 12m2s
Staging Backend Quality Gate / publish (pull_request) Has been skipped
2026-08-06 03:53:19 +08:00
jesse cdd0985d22 Merge pull request 'Fix Docker artifact action inputs' (#10) from codex/fix-vendored-artifact-inputs-20260805 into main
Staging Backend Quality Gate / validate (push) Successful in 14m33s
Staging Backend Quality Gate / publish (push) Successful in 3m32s
2026-08-06 03:03:35 +08:00
Jesse_Chen 35fa0f0c78 Fix artifact inputs in Docker uploader
Staging Backend Quality Gate / validate (pull_request) Successful in 11m18s
Staging Backend Quality Gate / publish (pull_request) Has been skipped
2026-08-06 02:50:40 +08:00
jesse 2f7654e260 Merge pull request 'Run vendored artifact uploader directly' (#9) from codex/run-vendored-artifact-uploader-20260805 into main
Staging Backend Quality Gate / validate (push) Successful in 11m6s
Staging Backend Quality Gate / publish (push) Failing after 4m53s
2026-08-06 02:30:22 +08:00
Jesse_Chen fdd0dcdd3b Run vendored artifact uploader directly
Staging Backend Quality Gate / validate (pull_request) Successful in 14m17s
Staging Backend Quality Gate / publish (pull_request) Has been skipped
2026-08-06 02:14:34 +08:00
jesse 8842e834f3 Merge pull request 'Fix local artifact action inputs' (#8) from codex/fix-local-artifact-inputs-20260805 into main
Staging Backend Quality Gate / validate (push) Successful in 14m16s
Staging Backend Quality Gate / publish (push) Failing after 2m38s
2026-08-06 01:53:29 +08:00
4 changed files with 92 additions and 42 deletions
+23 -16
View File
@@ -269,22 +269,29 @@ jobs:
artifacts/staging-images/manifest.env "$GITEA_SHA" "$IMAGE_REPOSITORY" >/dev/null
- name: Upload immutable staging image manifest
uses: ./.gitea/actions/upload-artifact
# act_runner 0.6.1 resolves local Node actions but does not export their
# `with` values as INPUT_* variables. Keep `with` for newer runners and
# provide the same variables explicitly for the current staging runner.
env:
INPUT_NAME: staging-image-manifest-${{ gitea.sha }}-${{ gitea.run_attempt }}
INPUT_PATH: artifacts/staging-images/manifest.env
'INPUT_IF-NO-FILES-FOUND': error
'INPUT_RETENTION-DAYS': '30'
'INPUT_COMPRESSION-LEVEL': '6'
INPUT_OVERWRITE: 'false'
with:
name: staging-image-manifest-${{ gitea.sha }}-${{ gitea.run_attempt }}
path: artifacts/staging-images/manifest.env
if-no-files-found: error
retention-days: 30
run: |
set -euo pipefail
test -n "${ACTIONS_RUNTIME_TOKEN:-}"
test -n "${ACTIONS_RESULTS_URL:-}"
test -n "${GITHUB_RUN_ID:-}"
test -n "${GITHUB_REPOSITORY:-}"
workdir="$(pwd -P)"
docker run --rm \
--user "$(id -u):$(id -g)" \
--volume "$workdir:$workdir" \
--workdir "$workdir" \
--env HOME=/tmp \
--env "INPUT_NAME=staging-image-manifest-$GITEA_SHA-$GITEA_RUN_ATTEMPT" \
--env INPUT_PATH=artifacts/staging-images/manifest.env \
--env INPUT_OVERWRITE=false \
--env ACTIONS_RUNTIME_TOKEN \
--env ACTIONS_RESULTS_URL \
--env GITHUB_RUN_ID \
--env GITHUB_REPOSITORY \
--env "GITHUB_SHA=$GITEA_SHA" \
--env "GITHUB_WORKSPACE=$workdir" \
node:22-bookworm-slim \
node -e 'process.env["INPUT_IF-NO-FILES-FOUND"]="error"; process.env["INPUT_RETENTION-DAYS"]="30"; process.env["INPUT_COMPRESSION-LEVEL"]="6"; require("./.gitea/actions/upload-artifact/dist/index.js")'
- name: Logout ACR registry
if: always()
+14 -9
View File
@@ -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 \
+14 -7
View File
@@ -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 \
@@ -164,8 +164,8 @@ test("Gitea quality gate validates before publishing an immutable ACR manifest",
assert.equal((workflow.match(/timeout 180 docker pull "\$NODE_TOOL_SOURCE_IMAGE"/g) ?? []).length, 2);
assert.equal((workflow.match(/docker tag "\$NODE_TOOL_SOURCE_IMAGE" "\$NODE_TOOL_IMAGE"/g) ?? []).length, 2);
assert.equal((workflow.match(/cat > "\$tool_dir\/node" <<'EOF'/g) ?? []).length, 1);
assert.equal((workflow.match(/--volume "\$workdir:\$workdir"/g) ?? []).length, 1);
assert.equal((workflow.match(/--workdir "\$workdir"/g) ?? []).length, 1);
assert.equal((workflow.match(/--volume "\$workdir:\$workdir"/g) ?? []).length, 2);
assert.equal((workflow.match(/--workdir "\$workdir"/g) ?? []).length, 2);
assert.equal((workflow.match(/ln -s node "\$tool_dir\/npm"/g) ?? []).length, 1);
assert.equal((workflow.match(/>> "\$GITHUB_PATH"/g) ?? []).length, 2);
assert.match(workflow, /node:22-bookworm-slim "\$\{0##\*\/\}" "\$@"/);
@@ -209,14 +209,19 @@ test("Gitea quality gate validates before publishing an immutable ACR manifest",
assert.match(workflow, /get\("os"\)=="linux"/);
assert.match(workflow, /get\("architecture"\)=="amd64"/);
assert.match(workflow, /node frontend\/scripts\/staging-image-manifest\.mjs/);
assert.match(workflow, /uses: \.\/\.gitea\/actions\/upload-artifact/);
assert.match(workflow, /INPUT_PATH: artifacts\/staging-images\/manifest\.env/);
assert.match(workflow, /['"]INPUT_IF-NO-FILES-FOUND['"]: error/);
assert.match(workflow, /INPUT_OVERWRITE: ['"]false['"]/);
assert.match(workflow, /require\("\.\/\.gitea\/actions\/upload-artifact\/dist\/index\.js"\)/);
assert.match(workflow, /--env INPUT_PATH=artifacts\/staging-images\/manifest\.env/);
assert.match(workflow, /process\.env\["INPUT_IF-NO-FILES-FOUND"\]="error"/);
assert.match(workflow, /process\.env\["INPUT_RETENTION-DAYS"\]="30"/);
assert.match(workflow, /process\.env\["INPUT_COMPRESSION-LEVEL"\]="6"/);
assert.doesNotMatch(workflow, /--env ['"]INPUT_(?:IF-NO-FILES-FOUND|RETENTION-DAYS|COMPRESSION-LEVEL)=/);
assert.match(workflow, /--env ACTIONS_RUNTIME_TOKEN/);
assert.match(workflow, /--env ACTIONS_RESULTS_URL/);
assert.match(workflow, /--env ['"]GITHUB_WORKSPACE=\$workdir['"]/);
assert.match(read(giteaUploadArtifactAction), /main: ['"]dist\/index\.js['"]/);
assert.match(read(giteaUploadArtifactAction), /81f940d004763f986ba3582c007fd842dd5cb0d7/);
assert.ok(existsSync(fileURLToPath(giteaUploadArtifactEntrypoint)));
assert.match(workflow, /name: staging-image-manifest-\$\{\{ gitea\.sha \}\}-\$\{\{ gitea\.run_attempt \}\}/);
assert.match(workflow, /INPUT_NAME=staging-image-manifest-\$GITEA_SHA-\$GITEA_RUN_ATTEMPT/);
assertOrder(workflow, [
"docker login",
"Record immutable linux-amd64 image manifest",
@@ -440,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/);
@@ -455,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);