Skip to content

Commit 0bdde55

Browse files
Nicolas Brieusselclaude
authored andcommitted
refactor(ci): replace ArgoCD e2e with direct helm smoke-test script
The ArgoCD app-of-apps chain (bootstrap → clusters → platform → openbao) takes 10-15 min and is hard to debug in CI. Replace with a standalone scripts/smoke-test.sh that installs OpenBao + ESO + openbao-init directly via helm, then runs the full destroy/recreate canary assertion (SC-001). The script is also runnable locally: SCW_ACCESS_KEY=... bash scripts/smoke-test.sh Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KoJ6zVHxYtUKtSNXinmct7
1 parent 476fb0d commit 0bdde55

2 files changed

Lines changed: 158 additions & 199 deletions

File tree

.github/workflows/ci.yml

Lines changed: 14 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,7 @@ jobs:
5555
runs-on: ubuntu-latest
5656
environment: dev
5757
env:
58-
ARGOCD_VERSION: "v2.13.0"
5958
BAO_VERSION: "2.5.5"
60-
REPO_URL: https://github.com/${{ github.repository }}
61-
BRANCH: ${{ github.head_ref || github.ref_name }}
6259

6360
steps:
6461
- uses: actions/checkout@v4
@@ -68,6 +65,13 @@ jobs:
6865
with:
6966
version: "latest"
7067

68+
- name: Install yq
69+
run: |
70+
sudo wget -qO /usr/local/bin/yq \
71+
https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
72+
sudo chmod +x /usr/local/bin/yq
73+
yq --version
74+
7175
- name: Install bao CLI
7276
run: |
7377
curl -sSfL \
@@ -76,205 +80,16 @@ jobs:
7680
sudo dpkg -i openbao.deb
7781
bao version
7882
79-
- name: Install ArgoCD CLI
80-
run: |
81-
curl -sSfL \
82-
"https://github.com/argoproj/argo-cd/releases/download/${ARGOCD_VERSION}/argocd-linux-amd64" \
83-
-o argocd
84-
sudo install -m 755 argocd /usr/local/bin/argocd
85-
argocd version --client
86-
8783
- name: Start minikube
8884
uses: medyagh/setup-minikube@latest
8985

90-
- name: Install ArgoCD
91-
run: |
92-
kubectl create namespace argocd
93-
kubectl apply --server-side -n argocd \
94-
-f "https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml"
95-
kubectl rollout status deployment/argocd-server -n argocd --timeout=180s
96-
97-
- name: Expose ArgoCD + login
98-
id: argocd-login
99-
run: |
100-
kubectl port-forward svc/argocd-server -n argocd 8080:443 &
101-
sleep 8
102-
PASS=$(kubectl -n argocd get secret argocd-initial-admin-secret \
103-
-o jsonpath="{.data.password}" | base64 -d)
104-
argocd login localhost:8080 --username admin --password "${PASS}" --insecure
105-
# Allow ArgoCD to pull from this (possibly private) repo via GITHUB_TOKEN
106-
argocd repo add "${REPO_URL}" \
107-
--username x-access-token \
108-
--password "${{ secrets.GITHUB_TOKEN }}" \
109-
--insecure-skip-server-verification || true
110-
111-
- name: Inject S3 credentials Secret (pre-sync)
112-
run: |
113-
kubectl create namespace openbao --dry-run=client -o yaml | kubectl apply -f -
114-
kubectl create secret generic scaleway-s3-credentials \
115-
--namespace openbao \
116-
--from-literal=access_key="${{ secrets.SCW_ACCESS_KEY }}" \
117-
--from-literal=secret_key="${{ secrets.SCW_SECRET_KEY }}" \
118-
--from-literal=bucket="${{ vars.SCW_S3_BUCKET }}"
119-
120-
- name: Apply bootstrap Application
121-
run: |
122-
argocd app create bootstrap \
123-
--repo "${REPO_URL}" \
124-
--path bootstrap \
125-
--dest-server https://kubernetes.default.svc \
126-
--dest-namespace argocd \
127-
--helm-set env=local \
128-
--helm-set "revision=${BRANCH}" \
129-
--helm-set "repoURL=${REPO_URL}" \
130-
--sync-policy automated \
131-
--upsert
132-
133-
- name: Wait for OpenBao to unseal
134-
id: wait-unseal
135-
run: |
136-
# Poll health endpoint until initialized + unsealed (HTTP 200)
137-
BAO_ADDR="http://$(kubectl get svc openbao -n openbao \
138-
-o jsonpath='{.spec.clusterIP}' 2>/dev/null || echo 'pending'):8200"
139-
echo "BAO_ADDR=${BAO_ADDR}" >> "${GITHUB_ENV}"
140-
141-
for i in $(seq 1 72); do
142-
# Refresh ClusterIP in case the svc wasn't ready yet
143-
CIP=$(kubectl get svc openbao -n openbao -o jsonpath='{.spec.clusterIP}' 2>/dev/null || true)
144-
if [ -n "${CIP}" ]; then
145-
BAO_ADDR="http://${CIP}:8200"
146-
echo "BAO_ADDR=${BAO_ADDR}" >> "${GITHUB_ENV}"
147-
fi
148-
149-
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" \
150-
"${BAO_ADDR}/v1/sys/health" 2>/dev/null || echo "000")
151-
echo "Attempt ${i}/72 — OpenBao status: ${STATUS}"
152-
153-
if [ "${STATUS}" = "200" ] || [ "${STATUS}" = "429" ]; then
154-
echo "OpenBao is unsealed."
155-
break
156-
fi
157-
sleep 5
158-
done
159-
160-
if [ "${STATUS}" != "200" ] && [ "${STATUS}" != "429" ]; then
161-
echo "ERROR: OpenBao did not unseal within 6 minutes"
162-
kubectl get events -n openbao --sort-by='.lastTimestamp' | tail -20 || true
163-
kubectl logs -l 'app.kubernetes.io/name=openbao' -n openbao --tail=50 || true
164-
exit 1
165-
fi
166-
167-
- name: Write canary secret
168-
run: |
169-
CANARY_VALUE="survive-${{ github.run_id }}"
170-
echo "CANARY_VALUE=${CANARY_VALUE}" >> "${GITHUB_ENV}"
171-
172-
# Authenticate via Kubernetes auth using the external-secrets SA token
173-
SA_JWT=$(kubectl create token external-secrets -n external-secrets --duration=5m)
174-
BAO_TOKEN=$(curl -sf "${BAO_ADDR}/v1/auth/kubernetes/login" \
175-
-d "{\"role\":\"external-secrets\",\"jwt\":\"${SA_JWT}\"}" \
176-
| jq -r '.auth.client_token')
177-
178-
curl -sf "${BAO_ADDR}/v1/kv/data/apps/test/canary" \
179-
-H "X-Vault-Token: ${BAO_TOKEN}" \
180-
-d "{\"data\":{\"value\":\"${CANARY_VALUE}\"}}"
181-
182-
echo "Canary written: ${CANARY_VALUE}"
183-
184-
# ── Cluster destroy & recreate ─────────────────────────────────────────
185-
186-
- name: Destroy minikube
187-
run: minikube delete
188-
189-
- name: Recreate minikube
190-
uses: medyagh/setup-minikube@latest
191-
192-
- name: Re-install ArgoCD
193-
run: |
194-
kubectl create namespace argocd
195-
kubectl apply --server-side -n argocd \
196-
-f "https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml"
197-
kubectl rollout status deployment/argocd-server -n argocd --timeout=180s
198-
199-
- name: Re-expose ArgoCD + login
200-
run: |
201-
kubectl port-forward svc/argocd-server -n argocd 8080:443 &
202-
sleep 8
203-
PASS=$(kubectl -n argocd get secret argocd-initial-admin-secret \
204-
-o jsonpath="{.data.password}" | base64 -d)
205-
argocd login localhost:8080 --username admin --password "${PASS}" --insecure
206-
argocd repo add "${REPO_URL}" \
207-
--username x-access-token \
208-
--password "${{ secrets.GITHUB_TOKEN }}" \
209-
--insecure-skip-server-verification || true
210-
211-
- name: Re-inject S3 credentials Secret
212-
run: |
213-
kubectl create namespace openbao --dry-run=client -o yaml | kubectl apply -f -
214-
kubectl create secret generic scaleway-s3-credentials \
215-
--namespace openbao \
216-
--from-literal=access_key="${{ secrets.SCW_ACCESS_KEY }}" \
217-
--from-literal=secret_key="${{ secrets.SCW_SECRET_KEY }}" \
218-
--from-literal=bucket="${{ vars.SCW_S3_BUCKET }}"
219-
220-
- name: Re-apply bootstrap Application
221-
run: |
222-
argocd app create bootstrap \
223-
--repo "${REPO_URL}" \
224-
--path bootstrap \
225-
--dest-server https://kubernetes.default.svc \
226-
--dest-namespace argocd \
227-
--helm-set env=local \
228-
--helm-set "revision=${BRANCH}" \
229-
--helm-set "repoURL=${REPO_URL}" \
230-
--sync-policy automated \
231-
--upsert
232-
233-
- name: Wait for OpenBao to unseal after recreate
234-
id: wait-unseal-2
235-
run: |
236-
for i in $(seq 1 72); do
237-
CIP=$(kubectl get svc openbao -n openbao -o jsonpath='{.spec.clusterIP}' 2>/dev/null || true)
238-
if [ -n "${CIP}" ]; then BAO_ADDR="http://${CIP}:8200"; fi
239-
240-
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" \
241-
"${BAO_ADDR}/v1/sys/health" 2>/dev/null || echo "000")
242-
echo "Attempt ${i}/72 — OpenBao status: ${STATUS}"
243-
244-
if [ "${STATUS}" = "200" ] || [ "${STATUS}" = "429" ]; then
245-
echo "OpenBao is unsealed after recreate."
246-
break
247-
fi
248-
sleep 5
249-
done
250-
251-
if [ "${STATUS}" != "200" ] && [ "${STATUS}" != "429" ]; then
252-
echo "ERROR: OpenBao did not unseal after cluster recreate"
253-
kubectl get events -n openbao --sort-by='.lastTimestamp' | tail -20 || true
254-
exit 1
255-
fi
256-
257-
echo "BAO_ADDR=${BAO_ADDR}" >> "${GITHUB_ENV}"
258-
259-
- name: Assert canary secret restored (SC-001)
260-
run: |
261-
SA_JWT=$(kubectl create token external-secrets -n external-secrets --duration=5m)
262-
BAO_TOKEN=$(curl -sf "${BAO_ADDR}/v1/auth/kubernetes/login" \
263-
-d "{\"role\":\"external-secrets\",\"jwt\":\"${SA_JWT}\"}" \
264-
| jq -r '.auth.client_token')
265-
266-
RESTORED=$(curl -sf "${BAO_ADDR}/v1/kv/data/apps/test/canary" \
267-
-H "X-Vault-Token: ${BAO_TOKEN}" \
268-
| jq -r '.data.data.value')
269-
270-
echo "Expected : ${CANARY_VALUE}"
271-
echo "Restored : ${RESTORED}"
272-
273-
if [ "${RESTORED}" != "${CANARY_VALUE}" ]; then
274-
echo "ERROR: Canary mismatch — secret not restored after destroy/recreate"
275-
exit 1
276-
fi
277-
echo "OK: SC-001 verified — secrets survive cluster destroy/recreate"
86+
- name: Run smoke test (SC-001)
87+
env:
88+
SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY }}
89+
SCW_SECRET_KEY: ${{ secrets.SCW_SECRET_KEY }}
90+
SCW_S3_BUCKET: ${{ vars.SCW_S3_BUCKET }}
91+
CANARY_VALUE: survive-${{ github.run_id }}
92+
run: bash scripts/smoke-test.sh
27893

27994
- name: Assert no Infisical pods (SC-003)
28095
run: |

scripts/smoke-test.sh

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
#!/usr/bin/env bash
2+
# Smoke test: OpenBao S3-backed init + destroy/recreate canary assertion (SC-001).
3+
#
4+
# Requires: kubectl, helm, bao, yq, minikube already running
5+
# Env vars: SCW_ACCESS_KEY, SCW_SECRET_KEY, SCW_S3_BUCKET
6+
set -euo pipefail
7+
8+
CANARY_VALUE="${CANARY_VALUE:-smoke-test-$(date +%s)}"
9+
OPENBAO_VERSION="0.28.4"
10+
ESO_VERSION="2.6.0"
11+
BAO_ADDR="http://127.0.0.1:8200"
12+
13+
log() { echo "[$(date +%H:%M:%S)] $*"; }
14+
15+
# ── Helpers ────────────────────────────────────────────────────────────────────
16+
17+
add_repos() {
18+
helm repo add openbao https://openbao.github.io/openbao-helm --force-update
19+
helm repo add external-secrets https://charts.external-secrets.io --force-update
20+
helm repo update
21+
}
22+
23+
inject_secret() {
24+
kubectl create namespace openbao --dry-run=client -o yaml | kubectl apply -f -
25+
kubectl create namespace external-secrets --dry-run=client -o yaml | kubectl apply -f -
26+
kubectl create secret generic scaleway-s3-credentials \
27+
--namespace openbao \
28+
--from-literal=access_key="${SCW_ACCESS_KEY}" \
29+
--from-literal=secret_key="${SCW_SECRET_KEY}" \
30+
--from-literal=bucket="${SCW_S3_BUCKET}" \
31+
--dry-run=client -o yaml | kubectl apply -f -
32+
}
33+
34+
install_openbao() {
35+
log "Installing OpenBao ${OPENBAO_VERSION}..."
36+
# Extract inline Helm values from the ArgoCD Application manifest
37+
yq '.spec.source.helm.values' platform/local/openbao.yml > /tmp/openbao-values.yaml
38+
helm upgrade --install openbao openbao/openbao \
39+
--version "${OPENBAO_VERSION}" \
40+
--namespace openbao \
41+
--values /tmp/openbao-values.yaml \
42+
--wait --timeout 5m
43+
}
44+
45+
install_eso() {
46+
log "Installing External Secrets Operator ${ESO_VERSION}..."
47+
helm upgrade --install external-secrets external-secrets/external-secrets \
48+
--version "${ESO_VERSION}" \
49+
--namespace external-secrets \
50+
--wait --timeout 5m
51+
}
52+
53+
install_openbao_init() {
54+
log "Installing openbao-init chart..."
55+
helm upgrade --install openbao-init apps/openbao-init/ \
56+
--namespace openbao \
57+
--values apps/openbao-init/values-local.yaml \
58+
--wait --timeout 5m
59+
}
60+
61+
wait_unsealed() {
62+
log "Waiting for OpenBao to be initialized and unsealed..."
63+
kubectl port-forward svc/openbao -n openbao 8200:8200 &
64+
PF_PID=$!
65+
# shellcheck disable=SC2064
66+
trap "kill ${PF_PID} 2>/dev/null || true" EXIT
67+
68+
for i in $(seq 1 120); do
69+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${BAO_ADDR}/v1/sys/health" 2>/dev/null || echo "000")
70+
log "Attempt ${i}/120 — health status: ${STATUS}"
71+
if [ "${STATUS}" = "200" ] || [ "${STATUS}" = "429" ]; then
72+
log "OpenBao is unsealed."
73+
return 0
74+
fi
75+
sleep 5
76+
done
77+
78+
log "ERROR: OpenBao did not unseal within 10 minutes"
79+
kubectl logs -l 'app.kubernetes.io/name=openbao' -n openbao --tail=30 || true
80+
kubectl get jobs -n openbao || true
81+
kill "${PF_PID}" 2>/dev/null || true
82+
exit 1
83+
}
84+
85+
bao_token() {
86+
SA_JWT=$(kubectl create token external-secrets -n external-secrets --duration=5m)
87+
curl -sf "${BAO_ADDR}/v1/auth/kubernetes/login" \
88+
-d "{\"role\":\"external-secrets\",\"jwt\":\"${SA_JWT}\"}" \
89+
| jq -r '.auth.client_token'
90+
}
91+
92+
# ── Phase 1: first boot ────────────────────────────────────────────────────────
93+
94+
log "=== Phase 1: first boot ==="
95+
add_repos
96+
inject_secret
97+
install_openbao
98+
install_eso
99+
install_openbao_init
100+
wait_unsealed
101+
102+
log "Writing canary secret (value: ${CANARY_VALUE})..."
103+
TOKEN=$(bao_token)
104+
curl -sf "${BAO_ADDR}/v1/kv/data/apps/test/canary" \
105+
-H "X-Vault-Token: ${TOKEN}" \
106+
-d "{\"data\":{\"value\":\"${CANARY_VALUE}\"}}"
107+
log "Canary written."
108+
109+
# Kill port-forward from wait_unsealed
110+
kill "${PF_PID}" 2>/dev/null || true
111+
trap - EXIT
112+
113+
# ── Phase 2: destroy + recreate ───────────────────────────────────────────────
114+
115+
log "=== Phase 2: destroy cluster ==="
116+
minikube delete
117+
118+
log "=== Phase 2: recreate cluster ==="
119+
minikube start
120+
121+
add_repos
122+
inject_secret
123+
install_openbao
124+
install_eso
125+
install_openbao_init
126+
wait_unsealed
127+
128+
# ── Phase 3: assert canary restored ───────────────────────────────────────────
129+
130+
log "=== Phase 3: assert SC-001 ==="
131+
TOKEN=$(bao_token)
132+
RESTORED=$(curl -sf "${BAO_ADDR}/v1/kv/data/apps/test/canary" \
133+
-H "X-Vault-Token: ${TOKEN}" \
134+
| jq -r '.data.data.value')
135+
136+
log "Expected : ${CANARY_VALUE}"
137+
log "Restored : ${RESTORED}"
138+
139+
if [ "${RESTORED}" != "${CANARY_VALUE}" ]; then
140+
log "FAIL: secret not restored after destroy/recreate (SC-001)"
141+
exit 1
142+
fi
143+
144+
log "OK: SC-001 verified — secrets survive cluster destroy/recreate"

0 commit comments

Comments
 (0)