-
Notifications
You must be signed in to change notification settings - Fork 0
201 lines (194 loc) · 8.16 KB
/
Copy pathdeploy-dev.yml
File metadata and controls
201 lines (194 loc) · 8.16 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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
name: Deploy Dev
on:
workflow_dispatch:
push:
branches: [main]
permissions:
contents: read
# The verify-ci job reads the application CI workflow's runs for this exact
# source SHA (REL-01); it never mutates anything.
actions: read
concurrency:
group: deploy-dev
cancel-in-progress: false
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check community-base dependency source
# D0.1a: fail closed before anything installs if community-base is no
# longer the pinned tagged git release in pyproject.toml/uv.lock (a
# local path, editable, or branch source would ship unreviewed
# package code). Stdlib-only; runs with the runner's python3.
run: python3 scripts/check_community_base_source.py
- uses: astral-sh/setup-uv@v6
with:
version: "0.10.11"
enable-cache: true
- run: uv sync --locked
- name: Test the deployment contract
run: uv run --frozen python -m unittest core.tests.test_cmp_style_deployment
verify-ci:
# REL-01: the deployment-contract checks in `test` are not application
# verification. Automatic delivery waits for the CI workflow's aggregate
# `ci-gate` job (quality, Django, Playwright, screenshots, container) to
# conclude success for this exact source SHA, and fails closed on missing,
# pending, canceled, or unsuccessful verdicts. The decision logic is
# code-owned in deploy/ci_verdict.py and exercised by
# ci/tests/test_deploy_ci_gate.py.
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
# The full CI profile (including the hosted Playwright suite) currently
# fits inside one hour; this bounded reserve covers it while keeping a
# wedged verification from holding the serialized deploy queue forever.
timeout-minutes: 100
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
with:
version: "0.10.11"
enable-cache: true
- run: uv sync --locked
- name: Require a green CI verdict for this exact source
env:
GH_TOKEN: ${{ github.token }}
# The verdict module is stdlib-only, but it runs through the locked
# environment like every other Python entry point in this repository
# (REL-19): no release tooling on the runner's unpinned interpreter.
run: >-
uv run --frozen python -m deploy.ci_verdict require
--workflow ci.yml
--repository "$GITHUB_REPOSITORY"
--sha "$GITHUB_SHA"
--timeout-seconds 5400
--interval-seconds 60
publish:
needs: [test, verify-ci]
runs-on: ubuntu-24.04-arm
if: github.ref == 'refs/heads/main'
permissions:
contents: read
id-token: write
outputs:
image: ${{ steps.push.outputs.image }}
source_sha: ${{ steps.identity.outputs.source_sha }}
version: ${{ steps.identity.outputs.version }}
constructed_at: ${{ steps.identity.outputs.constructed_at }}
env:
AWS_REGION: eu-west-1
REPO_URI: 387546586013.dkr.ecr.eu-west-1.amazonaws.com/website-production
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::387546586013:role/website-production-github-publisher
aws-region: ${{ env.AWS_REGION }}
role-session-name: website-dev-publisher-${{ github.run_id }}
- id: identity
name: Construct the release identity once
run: |
set -euo pipefail
constructed_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
version="$(date -u +%Y%m%d-%H%M%S)-${GITHUB_SHA:0:7}"
echo "constructed_at=${constructed_at}" >> "$GITHUB_OUTPUT"
echo "source_sha=${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
echo "version=${version}" >> "$GITHUB_OUTPUT"
- run: aws ecr get-login-password --region "$AWS_REGION" | docker login --username AWS --password-stdin "${REPO_URI%%/*}"
- name: Build the ARM64 release image
env:
VERSION: ${{ steps.identity.outputs.version }}
CONSTRUCTED_AT: ${{ steps.identity.outputs.constructed_at }}
run: |
docker buildx build \
--platform linux/arm64 \
--provenance=false \
--label "org.opencontainers.image.version=$VERSION" \
--label "org.opencontainers.image.revision=$GITHUB_SHA" \
--label "org.opencontainers.image.created=$CONSTRUCTED_AT" \
--tag "${REPO_URI}:${VERSION}" \
--load .
- id: push
name: Publish and capture the immutable digest
env:
VERSION: ${{ steps.identity.outputs.version }}
run: |
set -euo pipefail
docker push "${REPO_URI}:${VERSION}"
image="$(docker image inspect --format '{{index .RepoDigests 0}}' "${REPO_URI}:${VERSION}")"
[[ "${image%@*}" == "$REPO_URI" ]]
[[ "${image##*@}" =~ ^sha256:[0-9a-f]{64}$ ]]
echo "image=${image}" >> "$GITHUB_OUTPUT"
deploy:
needs: publish
runs-on: ubuntu-latest
environment:
name: development
url: https://dev.datatalks.club
permissions:
contents: read
id-token: write
env:
ECS_CLUSTER_NAME: ${{ vars.ECS_CLUSTER_NAME }}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
with:
version: "0.10.11"
enable-cache: true
- run: uv sync --locked
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::387546586013:role/website-dev-github-deployer
aws-region: eu-west-1
role-session-name: website-dev-deployer-${{ github.run_id }}
- name: Deploy the exact published release
# `uv run` puts the locked project environment first on PATH, so every
# python3 call inside deploy_website.sh (REL-02's receipt capture and
# bounded recovery, the task-definition promotion) resolves to the
# pinned interpreter rather than the runner's (REL-19).
run: >-
uv run --frozen bash deploy/deploy_dev.sh
"${{ needs.publish.outputs.image }}"
"${{ needs.publish.outputs.version }}"
"${{ needs.publish.outputs.source_sha }}"
- name: Upload the redacted deploy recovery receipts
# REL-02: the orchestrator writes a redacted pre-mutation service
# snapshot to .tmp/deploy-receipts/ before its first update-service and
# appends the recovery outcome on a post-mutation failure. Uploading
# with always() is what keeps recovery evidence alive when the job is
# red. Only allowlisted identifiers/counts are in these files.
if: always()
uses: actions/upload-artifact@v4
with:
name: deploy-recovery-receipts-${{ needs.publish.outputs.source_sha }}
path: .tmp/deploy-receipts/
if-no-files-found: ignore
retention-days: 90
- name: Record the release proven in dev
# REL-07: the record binds the image to the run that proved it -- the
# dev run id and the construction timestamp -- so a production
# selection can be checked against the exact run it selected instead
# of trusting three image strings alone.
env:
IMAGE: ${{ needs.publish.outputs.image }}
VERSION: ${{ needs.publish.outputs.version }}
SOURCE_SHA: ${{ needs.publish.outputs.source_sha }}
CONSTRUCTED_AT: ${{ needs.publish.outputs.constructed_at }}
run: |
mkdir -p .tmp
jq -n \
--arg image "$IMAGE" \
--arg version "$VERSION" \
--arg source_sha "$SOURCE_SHA" \
--arg dev_run_id "${{ github.run_id }}" \
--arg constructed_at "$CONSTRUCTED_AT" \
'{image: $image, version: $version, source_sha: $source_sha,
dev_run_id: $dev_run_id, constructed_at: $constructed_at}' \
> .tmp/dev-release.json
- uses: actions/upload-artifact@v4
with:
name: dev-release-${{ needs.publish.outputs.source_sha }}
path: .tmp/dev-release.json
if-no-files-found: error
retention-days: 90