-
Notifications
You must be signed in to change notification settings - Fork 9
283 lines (271 loc) · 12.5 KB
/
Copy pathcontinuous-deployment.yml
File metadata and controls
283 lines (271 loc) · 12.5 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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# Workflow for building Next.js site and downloading DocumentDB packages, then deploying to GitHub Pages
name: Deploy Next.js site and DocumentDB packages to Pages
on:
# Runs on pushes targeting the default branch
push:
branches:
- main
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: pages
cancel-in-progress: false
jobs:
# Build job
build:
name: Build Next.js static site
# Sets permissions of the GITHUB_TOKEN to allow reading of repository content
permissions:
contents: read
runs-on: ubuntu-22.04
# Without an explicit timeout a stalled step runs against GitHub's 6-hour
# default before failing, which for `pages` concurrency means blocking
# every deployment queued behind it.
timeout-minutes: 30
steps:
- name: Checkout source
uses: actions/checkout@v7
- name: Resolve optional build features
# The static site is always built. Mirroring the release packages and
# signing them are separate opt-outs so that a fork with no secrets can
# still run this workflow end to end.
id: features
env:
# The `secrets` context is not readable from a step-level `if:`, so
# the presence of the signing key has to be resolved into a step
# output first. Binding the secret to this one step also keeps it out
# of every other step's environment.
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
# A fork has neither the signing key nor a reason to spend several
# minutes mirroring release assets, so package generation defaults
# off outside this repository. `BUILD_PACKAGES` overrides it either
# way - but note that turning it off upstream publishes a site with
# no /deb, /rpm and no keyring, which breaks `apt-get update` for
# everyone already pointed at the repository.
BUILD_PACKAGES: ${{ vars.BUILD_PACKAGES }}
IS_UPSTREAM: ${{ github.repository == 'documentdb/documentdb.github.io' }}
run: |
set -euo pipefail
requested=$(printf '%s' "$BUILD_PACKAGES" | tr '[:upper:]' '[:lower:]')
case "$requested" in
true|false) packages="$requested" ;;
'') packages="$IS_UPSTREAM" ;;
*)
echo "::error::BUILD_PACKAGES must be 'true' or 'false' (got '$BUILD_PACKAGES')"
exit 1
;;
esac
if [ "$packages" = 'true' ] && [ -n "$GPG_PRIVATE_KEY" ]; then
sign=true
else
sign=false
fi
echo "packages=$packages" >> "$GITHUB_OUTPUT"
echo "sign=$sign" >> "$GITHUB_OUTPUT"
{
echo "### Build configuration"
echo ""
echo "| Feature | Enabled | Controlled by |"
echo "| --- | --- | --- |"
echo "| Static site | true | always built |"
echo "| Package repositories | $packages | \`BUILD_PACKAGES\` variable |"
echo "| Package signing | $sign | \`GPG_PRIVATE_KEY\` secret |"
} >> "$GITHUB_STEP_SUMMARY"
if [ "$packages" = 'true' ] && [ "$sign" != 'true' ]; then
echo "::warning::GPG_PRIVATE_KEY is not set - the package repositories will be published unsigned."
fi
- name: Install packaging tools
if: steps.features.outputs.packages == 'true'
run: |
until sudo apt-get update; do sleep 1; done
sudo apt-get install -y createrepo-c dpkg-dev dpkg-sig gnupg2 python3
- name: Setup GPG
id: import_gpg
if: steps.features.outputs.sign == 'true'
# Deliberately no `continue-on-error`: a key that is configured but
# cannot be imported has to fail the run. Swallowing that error
# republishes the site with an unsigned repository in place of a signed
# one, which breaks `apt-get update` for every client pinned with
# `signed-by`. Signing is optional; silently losing it is not.
uses: crazy-max/ghaction-import-gpg@v7
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
- name: Configure package build
if: steps.features.outputs.packages == 'true'
env:
SIGN: ${{ steps.features.outputs.sign }}
FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
KEY_ID: ${{ steps.import_gpg.outputs.keyid }}
KEY_NAME: ${{ steps.import_gpg.outputs.name }}
KEY_EMAIL: ${{ steps.import_gpg.outputs.email }}
# Configure which DocumentDB release to mirror. Both can be
# overridden by repository variables.
DOCUMENTDB_VERSION: ${{ vars.DOCUMENTDB_VERSION || 'latest' }}
MULTI_VERSION: ${{ vars.MULTI_VERSION || 'true' }}
run: |
set -euo pipefail
if [ "$SIGN" = 'true' ]; then
if [ -z "$FINGERPRINT" ]; then
echo "::error::The GPG key imported without a fingerprint; refusing to publish an unsigned repository."
exit 1
fi
echo "GPG_FINGERPRINT=$FINGERPRINT" >> "$GITHUB_ENV"
echo "GPG key loaded successfully"
echo " Fingerprint: $FINGERPRINT"
echo " Key ID: $KEY_ID"
echo " User ID: $KEY_NAME <$KEY_EMAIL>"
else
echo "No GPG key configured - packages will not be signed."
echo "To enable signing, add GPG_PRIVATE_KEY to the repository secrets."
fi
echo "DOCUMENTDB_VERSION=$DOCUMENTDB_VERSION" >> "$GITHUB_ENV"
echo "MULTI_VERSION=$MULTI_VERSION" >> "$GITHUB_ENV"
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3
bundler-cache: true
- name: Restore cache
uses: actions/cache@v6
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
- name: Install dependencies
run: npm ci
- name: Build with Next.js
# This repository is the organization Pages site served at the root of
# the custom domain (documentdb.io), so the build must NOT set
# NEXT_BASE_PATH. Setting it to the repository name (the usual trick
# for project pages) prefixes every internal link and asset URL with
# /documentdb.github.io/, which GitHub Pages then 301-redirects back
# to the root on every request and leaves the prefixed URL visible in
# the address bar after client-side navigation.
env:
JEKYLL_BASE_PATH: /blogs
run: npm run build
- name: Verify exported documentation pages
# A partially failed content compile must never reach production as a
# docs-less site. compile-content fails the build on clone/copy errors;
# this is the independent belt-and-braces check on the final artifact.
run: |
set -euo pipefail
for page in out/index.html out/docs/index.html out/docs/getting-started/index.html out/docs/reference/index.html; do
if [ ! -f "$page" ]; then
echo "Missing expected page: $page"
exit 1
fi
done
reference_count=$(find out/docs/reference -name index.html | wc -l)
echo "Reference pages exported: $reference_count"
# The docs repo currently holds ~240 reference entries; well under
# half of that means the compile silently lost content.
if [ "$reference_count" -lt 100 ]; then
echo "Only $reference_count reference pages exported - documentation content looks incomplete."
exit 1
fi
- name: Download DocumentDB packages from latest release
if: steps.features.outputs.packages == 'true'
run: .github/scripts/download_packages.sh
- name: Verify generated package components
if: steps.features.outputs.packages == 'true'
env:
SIGN: ${{ steps.features.outputs.sign }}
run: |
set -euo pipefail
python3 - <<'PY'
import json
import os
from pathlib import Path
release_info = Path("out/packages/release-info.json")
if not release_info.exists():
raise SystemExit("release-info.json was not generated")
data = json.loads(release_info.read_text())
assets = [asset["name"] for asset in data.get("assets", [])]
components = ("deb11", "deb12", "deb13", "ubuntu22", "ubuntu24")
for component in components:
has_assets = any(
name.endswith(".deb")
and (
name.startswith(f"{component}-")
or name.startswith(f"{component}.04-")
)
for name in assets
)
if not has_assets:
continue
for arch in ("amd64", "arm64"):
packages = Path(f"out/deb/dists/stable/{component}/binary-{arch}/Packages")
packages_gz = Path(f"out/deb/dists/stable/{component}/binary-{arch}/Packages.gz")
if not packages.exists() or not packages_gz.exists():
raise SystemExit(
f"Missing APT metadata for {component} {arch}: "
f"{packages} / {packages_gz}"
)
release_file = Path("out/deb/dists/stable/Release")
if release_file.exists() and any(name.startswith("deb13-") and name.endswith(".deb") for name in assets):
release_text = release_file.read_text()
if "deb13" not in release_text:
raise SystemExit("deb13 assets exist but deb13 is missing from the APT Release file")
# A run that imported a signing key must not publish an unsigned
# repository: apt rejects a suite whose InRelease/Release.gpg vanished,
# so a silently skipped signature is a client-visible outage rather
# than a cosmetic regression.
if os.environ.get("SIGN") == "true":
if any(name.endswith(".deb") for name in assets):
for artifact in (
Path("out/deb/dists/stable/Release.gpg"),
Path("out/deb/dists/stable/InRelease"),
Path("out/documentdb-archive-keyring.gpg"),
):
if not artifact.exists():
raise SystemExit(
f"Signing was enabled but {artifact} was not produced"
)
# RPM metadata signing is best-effort inside the download script,
# so surface it as a warning instead of failing the deployment.
for repomd in sorted(Path("out/rpm").glob("*/repodata/repomd.xml")):
if not Path(f"{repomd}.asc").exists():
print(f"::warning::{repomd} was not signed")
PY
- name: Upload artifact
uses: actions/upload-pages-artifact@v5
with:
path: ./out
# Deployment job
deploy:
name: Publish site to GitHub Pages
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
timeout-minutes: 10
needs:
- build
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
pages: write
id-token: write
steps:
- name: Setup Pages
uses: actions/configure-pages@v6
with:
# Automatically inject basePath in your Next.js configuration file and disable
# server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized).
#
# You may remove this line if you want to manage the configuration yourself.
static_site_generator: next
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5