Skip to content

Commit 8b6169b

Browse files
committed
Add config for preview website
1 parent 1e490ab commit 8b6169b

7 files changed

Lines changed: 495 additions & 6 deletions
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
name: Preview website down (k3s)
2+
# Remove a website preview: helm uninstall + delete its namespace.
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
ref:
7+
description: web-* branch whose preview to remove
8+
required: true
9+
delete:
10+
pull_request:
11+
types: [closed]
12+
13+
jobs:
14+
teardown:
15+
runs-on: ubuntu-22.04
16+
timeout-minutes: 20
17+
steps:
18+
- name: Resolve branch
19+
id: r
20+
run: |
21+
# branch name from whichever event fired
22+
case "${{ github.event_name }}" in
23+
workflow_dispatch) BRANCH="${{ github.event.inputs.ref }}" ;;
24+
delete) BRANCH="${{ github.event.ref }}" ;;
25+
pull_request) BRANCH="${{ github.event.pull_request.head.ref }}" ;;
26+
esac
27+
case "$BRANCH" in
28+
web-*) ;;
29+
*) echo "branch '$BRANCH' is not a web-* preview; nothing to do"; echo "skip=true" >> $GITHUB_OUTPUT; exit 0 ;;
30+
esac
31+
SLUG="$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]' \
32+
| sed 's/[^a-z0-9]/-/g; s/-\+/-/g; s/^-//; s/-$//' | cut -c1-40 | sed 's/-$//')"
33+
echo "skip=false" >> $GITHUB_OUTPUT
34+
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
35+
echo "release=$SLUG" >> $GITHUB_OUTPUT
36+
echo "namespace=preview-$SLUG" >> $GITHUB_OUTPUT
37+
echo "host=$SLUG.ohmstaging.org" >> $GITHUB_OUTPUT
38+
39+
- uses: actions/checkout@v4
40+
if: steps.r.outputs.skip != 'true'
41+
42+
- name: Install cloudflared
43+
if: steps.r.outputs.skip != 'true'
44+
run: |
45+
sudo curl -fsSL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 \
46+
-o /usr/local/bin/cloudflared
47+
sudo chmod +x /usr/local/bin/cloudflared
48+
49+
- name: Install helm
50+
if: steps.r.outputs.skip != 'true'
51+
uses: azure/setup-helm@v4
52+
with:
53+
version: v3.15.1
54+
55+
- name: Setup kubeconfig
56+
if: steps.r.outputs.skip != 'true'
57+
run: |
58+
mkdir -p $HOME/.kube
59+
echo "${{ secrets.STAGING_K3S_KUBECONFIG }}" | base64 -d > $HOME/.kube/config
60+
chmod 600 $HOME/.kube/config
61+
62+
- name: Open Cloudflare Access tunnel to k3s API
63+
if: steps.r.outputs.skip != 'true'
64+
env:
65+
TUNNEL_SERVICE_TOKEN_ID: ${{ secrets.STAGING_CF_ACCESS_CLIENT_ID }}
66+
TUNNEL_SERVICE_TOKEN_SECRET: ${{ secrets.STAGING_CF_ACCESS_CLIENT_SECRET }}
67+
run: |
68+
cloudflared access tcp --hostname k3s.ohmstaging.org --url 127.0.0.1:16443 &
69+
for i in {1..30}; do
70+
curl -sk -o /dev/null --max-time 5 https://127.0.0.1:16443/livez && exit 0
71+
sleep 2
72+
done
73+
echo "Tunnel failed to reach k3s" >&2; exit 1
74+
75+
- name: Helm uninstall + delete namespace
76+
if: steps.r.outputs.skip != 'true'
77+
run: |
78+
helm -n ${{ steps.r.outputs.namespace }} uninstall ${{ steps.r.outputs.release }} || echo "release already gone"
79+
kubectl delete namespace ${{ steps.r.outputs.namespace }} --ignore-not-found
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
name: Preview website (k3s)
2+
3+
# Throwaway website preview for a web-* branch on k3s staging, reachable at
4+
# web-<branch>.ohmstaging.org.
5+
on:
6+
workflow_dispatch:
7+
inputs:
8+
ref:
9+
description: web-* branch to preview
10+
required: true
11+
backup_url:
12+
description: apidb backup .sql.gz URL to load
13+
required: true
14+
push:
15+
branches:
16+
- 'web-*'
17+
18+
concurrency:
19+
# one run per branch; a new push cancels the in-flight preview build
20+
group: preview-web-${{ github.event.inputs.ref || github.ref_name }}
21+
cancel-in-progress: true
22+
23+
jobs:
24+
preview:
25+
runs-on: ubuntu-22.04
26+
timeout-minutes: 60
27+
steps:
28+
- name: Resolve names
29+
id: n
30+
run: |
31+
BRANCH="${{ github.event.inputs.ref || github.ref_name }}"
32+
case "$BRANCH" in
33+
web-*) ;;
34+
*) echo "::error::branch '$BRANCH' must start with web-"; exit 1 ;;
35+
esac
36+
# DNS-safe slug: lowercase, non-alnum -> '-', trim, max 40 chars.
37+
SLUG="$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]' \
38+
| sed 's/[^a-z0-9]/-/g; s/-\+/-/g; s/^-//; s/-$//' | cut -c1-40 | sed 's/-$//')"
39+
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
40+
echo "slug=$SLUG" >> $GITHUB_OUTPUT
41+
echo "release=$SLUG" >> $GITHUB_OUTPUT
42+
echo "namespace=preview-$SLUG" >> $GITHUB_OUTPUT
43+
echo "host=$SLUG.ohmstaging.org" >> $GITHUB_OUTPUT
44+
45+
- uses: actions/checkout@v4
46+
with:
47+
ref: ${{ steps.n.outputs.branch }}
48+
fetch-depth: 0
49+
50+
- name: Login to GitHub Container Registry
51+
uses: docker/login-action@v3
52+
with:
53+
registry: ghcr.io
54+
username: ${{ github.repository_owner }}
55+
password: ${{ secrets.GHCR_GITHUB_TOKEN }}
56+
57+
- name: Setup Python
58+
uses: actions/setup-python@v5
59+
with:
60+
python-version: '3.11'
61+
62+
- name: Setup git
63+
run: |
64+
git config --global user.email "noreply@developmentseed.org"
65+
git config --global user.name "Github Action"
66+
67+
- name: Install chartpress
68+
run: pip install chartpress==2.3.0 ruamel.yaml
69+
70+
- name: Run chartpress (build + push)
71+
env:
72+
GITHUB_TOKEN: ${{ secrets.GHCR_GITHUB_TOKEN }}
73+
run: chartpress --push
74+
75+
- name: Install cloudflared
76+
run: |
77+
sudo curl -fsSL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 \
78+
-o /usr/local/bin/cloudflared
79+
sudo chmod +x /usr/local/bin/cloudflared
80+
81+
- name: Install helm
82+
uses: azure/setup-helm@v4
83+
with:
84+
version: v3.15.1
85+
86+
- name: Setup kubeconfig
87+
run: |
88+
mkdir -p $HOME/.kube
89+
KCFG="${{ secrets.STAGING_K3S_KUBECONFIG }}"
90+
if [ -z "$KCFG" ]; then
91+
echo "ERROR: STAGING_K3S_KUBECONFIG is empty" >&2; exit 1
92+
fi
93+
echo "$KCFG" | base64 -d > $HOME/.kube/config
94+
chmod 600 $HOME/.kube/config
95+
96+
- name: Open Cloudflare Access tunnel to k3s API
97+
env:
98+
TUNNEL_SERVICE_TOKEN_ID: ${{ secrets.STAGING_CF_ACCESS_CLIENT_ID }}
99+
TUNNEL_SERVICE_TOKEN_SECRET: ${{ secrets.STAGING_CF_ACCESS_CLIENT_SECRET }}
100+
run: |
101+
cloudflared access tcp --hostname k3s.ohmstaging.org --url 127.0.0.1:16443 &
102+
CF_PID=$!
103+
for i in {1..30}; do
104+
if curl -sk -o /dev/null --max-time 5 https://127.0.0.1:16443/livez; then
105+
echo "tunnel up (k3s reachable)"; exit 0
106+
fi
107+
sleep 2
108+
done
109+
echo "Tunnel failed to reach k3s" >&2; kill $CF_PID 2>/dev/null || true; exit 1
110+
111+
- name: Verify access
112+
run: kubectl get nodes
113+
114+
- name: Substitute secrets into preview values
115+
uses: bluwy/substitute-string-action@v3
116+
with:
117+
_input-file: 'values.k3s.preview.template.yaml'
118+
_format-key: '{{key}}'
119+
_output-file: 'values.k3s.preview.yaml'
120+
PREVIEW_HOST: ${{ steps.n.outputs.host }}
121+
PREVIEW_NS: ${{ steps.n.outputs.namespace }}
122+
PREVIEW_DB_PASSWORD: ${{ secrets.PREVIEW_DB_PASSWORD }}
123+
MAILER_ADDRESS: ${{ secrets.MAILER_ADDRESS }}
124+
MAILER_USERNAME: ${{ secrets.MAILER_USERNAME }}
125+
MAILER_PASSWORD: ${{ secrets.MAILER_PASSWORD }}
126+
STAGING_OPENSTREETMAP_AUTH_ID: ${{ secrets.STAGING_OPENSTREETMAP_AUTH_ID }}
127+
STAGING_OPENSTREETMAP_AUTH_SECRET: ${{ secrets.STAGING_OPENSTREETMAP_AUTH_SECRET }}
128+
STAGING_WIKIPEDIA_AUTH_ID: ${{ secrets.STAGING_WIKIPEDIA_AUTH_ID }}
129+
STAGING_WIKIPEDIA_AUTH_SECRET: ${{ secrets.STAGING_WIKIPEDIA_AUTH_SECRET }}
130+
STAGING_RAILS_CREDENTIALS_YML_ENC: ${{ secrets.STAGING_RAILS_CREDENTIALS_YML_ENC }}
131+
STAGING_RAILS_MASTER_KEY: ${{ secrets.STAGING_RAILS_MASTER_KEY }}
132+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
133+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
134+
135+
- name: Helm dep up
136+
run: cd ohm && helm dep up
137+
138+
# Step 1: bring up db + memcached + cgimap, web OFF, so the restore can
139+
# load the apidb before web boots and runs migrations.
140+
- name: Deploy data tier (web off)
141+
run: |
142+
helm upgrade --install ${{ steps.n.outputs.release }} ./ohm \
143+
-n ${{ steps.n.outputs.namespace }} --create-namespace \
144+
-f ./ohm/values.yaml \
145+
-f ./values.k3s.preview.yaml \
146+
--set osm-seed.web.enabled=false \
147+
--wait --timeout=10m
148+
149+
- name: Restore apidb from backup
150+
env:
151+
NS: ${{ steps.n.outputs.namespace }}
152+
RELEASE: ${{ steps.n.outputs.release }}
153+
BACKUP_URL: ${{ github.event.inputs.backup_url || secrets.PREVIEW_BACKUP_URL }}
154+
run: |
155+
if [ -z "$BACKUP_URL" ]; then
156+
echo "::error::no backup_url input and PREVIEW_BACKUP_URL secret is empty"; exit 1
157+
fi
158+
WEB_IMAGE="$(helm -n "$NS" get values "$RELEASE" -a -o json \
159+
| python3 -c 'import sys,json;w=json.load(sys.stdin)["osm-seed"]["web"]["image"];print(f"{w[\"name\"]}:{w[\"tag\"]}")')"
160+
echo "restore image: $WEB_IMAGE"
161+
kubectl -n "$NS" delete job "$RELEASE-restore" --ignore-not-found
162+
cat <<EOF | kubectl -n "$NS" apply -f -
163+
apiVersion: batch/v1
164+
kind: Job
165+
metadata:
166+
name: $RELEASE-restore
167+
spec:
168+
backoffLimit: 1
169+
ttlSecondsAfterFinished: 600
170+
template:
171+
spec:
172+
restartPolicy: Never
173+
containers:
174+
- name: restore
175+
image: $WEB_IMAGE
176+
env:
177+
- name: PGPASSWORD
178+
value: "${{ secrets.PREVIEW_DB_PASSWORD }}"
179+
- name: BACKUP_URL
180+
value: "$BACKUP_URL"
181+
command: ["bash","-c"]
182+
args:
183+
- |
184+
set -euo pipefail
185+
echo "waiting for db..."
186+
until pg_isready -h $RELEASE-db -p 5432; do sleep 2; done
187+
echo "downloading + restoring backup..."
188+
curl -fsSL "\$BACKUP_URL" | gunzip -c \
189+
| psql -h $RELEASE-db -U postgres -d openhistoricalmap
190+
echo "restore done"
191+
EOF
192+
kubectl -n "$NS" wait --for=condition=complete --timeout=30m job/$RELEASE-restore \
193+
|| { kubectl -n "$NS" logs job/$RELEASE-restore --tail=80; exit 1; }
194+
195+
# The chart renders the web Ingress (osm-seed.web.ingress.enabled) with a
196+
# router.middlewares annotation pointing at this Middleware, so create it
197+
# first. It forces X-Forwarded-Proto=https: Cloudflare already terminated
198+
# TLS, but Traefik would otherwise send http and the app's force_ssl would
199+
# redirect to https forever.
200+
- name: Create Traefik middleware (force https proto)
201+
run: |
202+
cat <<EOF | kubectl -n ${{ steps.n.outputs.namespace }} apply -f -
203+
apiVersion: traefik.io/v1alpha1
204+
kind: Middleware
205+
metadata:
206+
name: https-proto
207+
spec:
208+
headers:
209+
customRequestHeaders:
210+
X-Forwarded-Proto: https
211+
EOF
212+
213+
# Step 2: enable web. It waits for db, runs rails db:migrate on the restored
214+
# data, then serves. The chart renders the Traefik Ingress for the host.
215+
- name: Deploy web
216+
run: |
217+
helm upgrade --install ${{ steps.n.outputs.release }} ./ohm \
218+
-n ${{ steps.n.outputs.namespace }} \
219+
-f ./ohm/values.yaml \
220+
-f ./values.k3s.preview.yaml \
221+
--wait --timeout=20m
222+
223+
- name: Summary
224+
if: always()
225+
run: |
226+
{
227+
echo "### Preview"
228+
echo ""
229+
echo "- branch: \`${{ steps.n.outputs.branch }}\`"
230+
echo "- url: https://${{ steps.n.outputs.host }}"
231+
echo "- release: \`${{ steps.n.outputs.release }}\` (ns \`${{ steps.n.outputs.namespace }}\`)"
232+
echo ""
233+
echo "Tear it down with the **Preview website teardown** workflow."
234+
} >> $GITHUB_STEP_SUMMARY

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,6 @@ images/tiler-server-martin/config/nginx.conf
4545
values.k3s.staging.direct.yaml
4646
ohm/charts/
4747
k3s.sh
48-
*.zip
48+
*.zip
49+
test-preview.sh
50+
values.k3s.preview.yaml

ohm/requirements.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
dependencies:
22
- name: osm-seed
3-
version: '0.1.0-0.dev.git.997.h7a921d5'
3+
version: '0.1.0-0.dev.git.1001.h1cd9673'
44
repository: https://osm-seed.github.io/osm-seed-chart/

0 commit comments

Comments
 (0)