-
Notifications
You must be signed in to change notification settings - Fork 0
154 lines (143 loc) · 6.06 KB
/
Copy pathapply.yml
File metadata and controls
154 lines (143 loc) · 6.06 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
---
# infra — gated apply of k8s manifests to the prod cluster.
#
# APPROVAL MODEL: this workflow is `workflow_dispatch` ONLY — it never runs
# on push. Applying to prod requires a human to deliberately open the
# Actions tab, pick this workflow, and click "Run workflow", then type the
# confirm phrase. That deliberate manual trigger IS the approval gate.
# (GitHub's native Environment "required reviewers" gate needs a paid plan
# the org is not on — `workflow_dispatch` is the free-plan equivalent and
# matches the existing pin-prod-images.yml pattern.)
#
# SAFETY SCOPING — the apply DELIBERATELY excludes:
# * secret manifests (secrets.yaml, infra-secrets.yaml, data/*secret*,
# backups/instant-data-spaces-creds.yaml) — they carry CHANGE_ME
# placeholders; applying them would clobber the live prod secrets.
# * the 3 service Deployments (app.yaml, worker/deployment.yaml,
# provisioner/deployment.yaml) — they hardcode `:local` image tags and
# are owned by the api/worker/provisioner auto-deploy pipelines via
# `kubectl set image`. Applying them would revert prod to a dead image.
# * *-ha/values.yaml — Helm chart inputs, not k8s objects.
# It runs `kubectl diff` first and prints it so the operator sees exactly
# what will change before the apply step runs.
#
# Required repo secret: KUBECONFIG_B64 — base64 of a kubeconfig with apply
# rights on the prod cluster (the same one the api repo uses; grab a fresh
# static-token kubeconfig from the DigitalOcean control panel — the local
# doctl-exec kubeconfig cannot be embedded).
name: apply
on:
workflow_dispatch:
inputs:
confirm:
description: 'Type APPLY to confirm applying infra/k8s to PROD.'
required: true
type: string
env:
K8S_DIR: k8s
jobs:
validate:
name: Lint + schema-check before apply
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: yamllint
run: |
python3 -m pip install --quiet yamllint
cat > /tmp/yamllint.yml <<'YML'
extends: relaxed
rules:
line-length: disable
comments: disable
comments-indentation: disable
document-start: disable
truthy: disable
empty-lines: disable
trailing-spaces: disable
indentation: disable
YML
yamllint -c /tmp/yamllint.yml "${K8S_DIR}/"
- name: kubeconform
run: |
VER=v0.6.7
curl -sSL "https://github.com/yannh/kubeconform/releases/download/${VER}/kubeconform-linux-amd64.tar.gz" \
| tar -xz -C /usr/local/bin kubeconform
mapfile -t FILES < <(find "${K8S_DIR}" -type f \( -name '*.yaml' -o -name '*.yml' \) \
! -name 'values.yaml' ! -name 'helm-values.yaml' | sort)
kubeconform -strict -ignore-missing-schemas -kubernetes-version 1.31.0 -summary "${FILES[@]}"
apply:
name: Diff + apply to prod (manual gate)
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Confirm the APPLY phrase
run: |
if [ "${{ inputs.confirm }}" != "APPLY" ]; then
echo "::error::confirm input was '${{ inputs.confirm }}', expected 'APPLY'. Aborting."
exit 1
fi
echo "confirm phrase OK — proceeding."
- name: Set up kubectl
uses: azure/setup-kubectl@v5
with:
version: 'latest'
- name: Configure kubeconfig from KUBECONFIG_B64
env:
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }}
run: |
if [ -z "${KUBECONFIG_B64}" ]; then
echo "::error::KUBECONFIG_B64 repo secret is not set on the infra repo."
echo "Add it: a base64-encoded static-token kubeconfig for the prod cluster"
echo "(the same value the api repo uses). Settings → Secrets → Actions."
exit 1
fi
mkdir -p "$HOME/.kube"
echo "$KUBECONFIG_B64" | base64 -d > "$HOME/.kube/config"
chmod 600 "$HOME/.kube/config"
kubectl config current-context
- name: Build the apply file list (scoped — excludes secrets + service Deployments)
id: files
run: |
# Exclusions — see the header comment for the rationale.
EXCLUDE=(
"${K8S_DIR}/secrets.yaml"
"${K8S_DIR}/infra-secrets.yaml"
"${K8S_DIR}/data/minio-secret.yaml"
"${K8S_DIR}/data/redis-provision-ha/secret.yaml"
"${K8S_DIR}/backups/instant-data-spaces-creds.yaml"
"${K8S_DIR}/app.yaml"
"${K8S_DIR}/worker/deployment.yaml"
"${K8S_DIR}/provisioner/deployment.yaml"
"${K8S_DIR}/preview/10-quota-template.yaml"
)
KEEP=()
while IFS= read -r f; do
skip=false
for e in "${EXCLUDE[@]}"; do [ "$f" = "$e" ] && skip=true && break; done
$skip || KEEP+=("$f")
done < <(find "${K8S_DIR}" -type f \( -name '*.yaml' -o -name '*.yml' \) \
! -name 'values.yaml' ! -name 'helm-values.yaml' | sort)
echo "Will apply ${#KEEP[@]} manifest(s); excluded ${#EXCLUDE[@]} (secrets + service Deployments)."
printf '%s\n' "${KEEP[@]}" > /tmp/apply-files.txt
printf ' apply: %s\n' "${KEEP[@]}"
printf ' EXCLUDED: %s\n' "${EXCLUDE[@]}"
- name: kubectl diff (preview — what will change)
run: |
# diff exit code 1 just means "differences exist" — not an error.
while IFS= read -r f; do
echo "──── diff: $f ────"
kubectl diff -f "$f" || true
done < /tmp/apply-files.txt
- name: kubectl apply (scoped)
run: |
rc=0
while IFS= read -r f; do
echo "──── apply: $f ────"
kubectl apply -f "$f" || rc=$?
done < /tmp/apply-files.txt
if [ "$rc" -ne 0 ]; then
echo "::error::one or more manifests failed to apply (exit $rc)"
exit "$rc"
fi
echo "all scoped manifests applied."