ce7e040e98
Build and Test Quality Gate / Contract Web typecheck and build (pull_request) Successful in 7m28s
Build and Test Quality Gate / Python tests and Contract API migration (pull_request) Failing after 26m12s
Security Quality Gate / npm audit, Bandit, pip-audit, detect-secrets (pull_request) Successful in 6m33s
110 lines
3.8 KiB
YAML
110 lines
3.8 KiB
YAML
name: Provider Promotion Canary
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
mode:
|
|
description: "Read-only verification mode"
|
|
required: true
|
|
type: choice
|
|
options:
|
|
- canary
|
|
- revocation
|
|
- kill-switch
|
|
node_a:
|
|
description: "First production AI Platform HTTPS base URL"
|
|
required: true
|
|
node_b:
|
|
description: "Second production AI Platform HTTPS base URL"
|
|
required: true
|
|
expected_release_id:
|
|
description: "Expected immutable release id for canary"
|
|
required: false
|
|
expected_receipt_id:
|
|
description: "Expected signed receipt id for canary"
|
|
required: false
|
|
expected_key_id:
|
|
description: "Expected receipt signing key id for canary"
|
|
required: false
|
|
expected_image_digest:
|
|
description: "Expected immutable AI Platform image digest for canary"
|
|
required: false
|
|
propagation_deadline_seconds:
|
|
description: "Maximum revocation or kill-switch propagation SLA"
|
|
required: false
|
|
default: "300"
|
|
max_observation_age_seconds:
|
|
description: "Maximum accepted age of each no-cache readiness response"
|
|
required: false
|
|
default: "120"
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: provider-promotion-control
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
verify:
|
|
runs-on: [self-hosted, yuqei-smoke]
|
|
environment: production-provider-promotion-control
|
|
timeout-minutes: 20
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
clean: true
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.12"
|
|
- name: Verify every production node without writes
|
|
shell: bash
|
|
env:
|
|
SERVICE_KEY: ${{ secrets.AI_PLATFORM_PRODUCTION_SERVICE_KEY }}
|
|
MODE: ${{ inputs.mode }}
|
|
NODE_A: ${{ inputs.node_a }}
|
|
NODE_B: ${{ inputs.node_b }}
|
|
EXPECTED_RELEASE_ID: ${{ inputs.expected_release_id }}
|
|
EXPECTED_RECEIPT_ID: ${{ inputs.expected_receipt_id }}
|
|
EXPECTED_KEY_ID: ${{ inputs.expected_key_id }}
|
|
EXPECTED_IMAGE_DIGEST: ${{ inputs.expected_image_digest }}
|
|
PROPAGATION_DEADLINE_SECONDS: ${{ inputs.propagation_deadline_seconds }}
|
|
MAX_OBSERVATION_AGE_SECONDS: ${{ inputs.max_observation_age_seconds }}
|
|
run: |
|
|
set -euo pipefail
|
|
umask 077
|
|
test -n "$SERVICE_KEY"
|
|
mkdir -p .promotion-control-private promotion-control-output
|
|
printf '%s' "$SERVICE_KEY" > .promotion-control-private/service-key
|
|
args=(
|
|
--mode "$MODE"
|
|
--node "$NODE_A"
|
|
--node "$NODE_B"
|
|
--service-key-file .promotion-control-private/service-key
|
|
--max-observation-age-seconds "$MAX_OBSERVATION_AGE_SECONDS"
|
|
)
|
|
if [ "$MODE" = "canary" ]; then
|
|
args+=(
|
|
--expected-release-id "$EXPECTED_RELEASE_ID"
|
|
--expected-receipt-id "$EXPECTED_RECEIPT_ID"
|
|
--expected-key-id "$EXPECTED_KEY_ID"
|
|
--expected-image-digest "$EXPECTED_IMAGE_DIGEST"
|
|
)
|
|
else
|
|
args+=(--deadline-seconds "$PROPAGATION_DEADLINE_SECONDS")
|
|
fi
|
|
python yuqei-infra/scripts/verify-provider-promotion-nodes.py "${args[@]}" \
|
|
| tee promotion-control-output/provider-promotion-control-summary.json
|
|
- name: Upload read-only verification summary
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: provider-promotion-control-summary
|
|
if-no-files-found: warn
|
|
retention-days: 30
|
|
path: promotion-control-output/provider-promotion-control-summary.json
|
|
- name: Remove service credential
|
|
if: always()
|
|
shell: bash
|
|
run: rm -f .promotion-control-private/service-key
|