-
Notifications
You must be signed in to change notification settings - Fork 0
137 lines (124 loc) · 5.39 KB
/
Copy pathpromote.yml
File metadata and controls
137 lines (124 loc) · 5.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
name: Promote (test / prod)
# Promotion across the security boundary:
# - Manual workflow_dispatch -> TEST: build the canonical image from the chosen ref into TEST's
# own Artifact Registry and deploy it to test. (test doubles as UAT.) This is the "one
# rebuild into test" — dev's image is never promoted, since dev is outside the boundary.
# - Push a v* tag -> PROD: deploy the exact image TEST already validated, by digest,
# referencing TEST's registry directly (no rebuild, no copy). Gated by the `prod`
# Environment's required-reviewer rule. prod's deployer SA + Cloud Run service agent must
# have read on TEST's Artifact Registry. The tag must equal "v" + pyproject.toml's version
# (checked below): no rebuild happens here, so the version is whatever test baked in.
#
# For the prod path, test's registry coordinates come from repo-level Variables (test's project
# isn't in prod's Environment): TEST_PROJECT_ID (required), TEST_REGION / TEST_AR_REPO (optional).
# The image for the tagged commit must already exist in test (i.e. it was promoted to test first).
# See dev/deployment.md.
on:
workflow_dispatch:
inputs:
ref:
description: "Git ref (branch / tag / SHA) to build into test"
type: string
required: true
default: main
target:
description: "Which service(s) to deploy"
type: choice
options: [rest, mcp, both]
default: both
push:
tags: ["v*"]
permissions:
contents: read
jobs:
# ---------- TEST path (manual dispatch): build canonical image into test, then deploy ----------
build-test:
if: ${{ github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
environment: test
outputs:
build_label: ${{ steps.vars.outputs.sha }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}
- name: Resolve short SHA
id: vars
run: echo "sha=$(git rev-parse --short=7 HEAD)" >> "$GITHUB_OUTPUT"
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v3
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Set up gcloud
uses: google-github-actions/setup-gcloud@v3
with:
project_id: ${{ secrets.GCP_PROJECT_ID }}
- name: Build & push canonical image (Cloud Build)
env:
PROJECT: ${{ secrets.GCP_PROJECT_ID }}
REGION: ${{ vars.REGION || 'us-central1' }}
AR_REPO: ${{ vars.AR_REPO || 'idc' }}
SHA: ${{ steps.vars.outputs.sha }}
run: |
set -euo pipefail
IMAGE="${REGION}-docker.pkg.dev/${PROJECT}/${AR_REPO}/idc-api-v3:${SHA}"
gcloud builds submit --config dev/cloudbuild.yaml --substitutions _IMAGE="$IMAGE"
deploy-test:
if: ${{ github.event_name == 'workflow_dispatch' }}
needs: build-test
uses: ./.github/workflows/deploy.yml
with:
environment: test
build_label: ${{ needs.build-test.outputs.build_label }}
target: ${{ inputs.target }}
secrets: inherit
# ---------- PROD path (v* tag): deploy test's digest to prod, no rebuild ----------
resolve-prod:
if: ${{ github.event_name == 'push' }}
runs-on: ubuntu-latest
outputs:
build_label: ${{ steps.r.outputs.sha }}
source_image: ${{ steps.r.outputs.image }}
steps:
- uses: actions/checkout@v7
# The tag names the release, but the version the running server reports comes from
# pyproject.toml, baked into the image at build time. Prod redeploys test's existing digest
# and never rebuilds, so a tag that disagrees with the packaged version would deploy fine
# and quietly serve the wrong version at /v3/version. Fail here, before the reviewer gate.
- name: Verify the tag matches the packaged version
env:
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
VERSION=$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')
if [ "${TAG#v}" != "$VERSION" ]; then
echo "::error::Tag '${TAG}' does not match pyproject.toml version '${VERSION}'. The version is baked in when test builds the image, so bump pyproject.toml, promote that commit to test, then tag it. See CONTRIBUTING.md -> Releasing."
exit 1
fi
echo "Tag ${TAG} matches packaged version ${VERSION}."
- id: r
env:
TEST_PROJECT: ${{ vars.TEST_PROJECT_ID }}
TEST_REGION: ${{ vars.TEST_REGION || vars.REGION || 'us-central1' }}
TEST_AR_REPO: ${{ vars.TEST_AR_REPO || vars.AR_REPO || 'idc' }}
run: |
set -euo pipefail
if [ -z "${TEST_PROJECT:-}" ]; then
echo "::error::Repo variable TEST_PROJECT_ID is not set — prod deploys test's image and needs to know test's project."
exit 1
fi
SHA=${GITHUB_SHA:0:7}
{
echo "sha=$SHA"
echo "image=${TEST_REGION}-docker.pkg.dev/${TEST_PROJECT}/${TEST_AR_REPO}/idc-api-v3:${SHA}"
} >> "$GITHUB_OUTPUT"
deploy-prod:
if: ${{ github.event_name == 'push' }}
needs: resolve-prod
uses: ./.github/workflows/deploy.yml
with:
environment: prod
build_label: ${{ needs.resolve-prod.outputs.build_label }}
source_image: ${{ needs.resolve-prod.outputs.source_image }}
target: both
secrets: inherit