1+ name : Deploy INFINI Cloud Stack
2+
3+ on :
4+ push :
5+ branches : [main]
6+ paths :
7+ - ' apps/**'
8+ - ' .github/workflows/deploy-cloud.yml'
9+ workflow_dispatch :
10+ inputs :
11+ namespace :
12+ description : ' Target Kubernetes namespace'
13+ required : false
14+ default : ' dev'
15+ skip_smoke_test :
16+ description : ' Skip smoke tests (true/false)'
17+ required : false
18+ default : ' false'
19+ easysearch_version :
20+ description : ' Easysearch image version'
21+ required : false
22+ default : ' 2.1.2'
23+ cert_manager_version :
24+ description : ' cert-manager helm chart version'
25+ required : false
26+ default : ' v1.20.1'
27+ cloud_image_version :
28+ description : ' Cloud app image version'
29+ required : false
30+ default : ' 1.9.1'
31+ minio_image_tag :
32+ description : ' MinIO image tag'
33+ required : false
34+ default : ' RELEASE.2025-04-22T22-12-26Z'
35+
36+ env :
37+ NS : ${{ github.event.inputs.namespace || 'dev' }}
38+ EASYSEARCH_VERSION : ${{ github.event.inputs.easysearch_version || '2.1.2' }}
39+ CERT_MANAGER_VERSION : ${{ github.event.inputs.cert_manager_version || 'v1.20.1' }}
40+ CLOUD_IMAGE_VERSION : ${{ github.event.inputs.cloud_image_version || '1.9.1' }}
41+ MINIO_IMAGE : " ${{secrets.DOCKER_REGISTRY}}/minio/minio"
42+
43+ jobs :
44+ # ──────────────────────────────────────────────
45+ # Job 1: Namespace + Mailpit (no dependencies)
46+ # ──────────────────────────────────────────────
47+ deploy-mailpit :
48+ name : " 1 · Namespace & Mailpit"
49+ runs-on : ubuntu-latest
50+ steps :
51+ - name : Checkout
52+ uses : actions/checkout@v4
53+
54+ - name : Configure kubectl
55+ uses : azure/k8s-set-context@v4
56+ with :
57+ method : kubeconfig
58+ kubeconfig : ${{ secrets.KUBECONFIG }}
59+
60+ - name : Create namespace
61+ run : |
62+ kubectl create namespace $NS --dry-run=client -o yaml | kubectl apply -f -
63+ echo "✅ Namespace '$NS' ready"
64+
65+ - name : Deploy Mailpit
66+ run : |
67+ kubectl -n $NS apply -f apps/mailpit.yaml
68+ kubectl -n $NS rollout status deployment/mailpit --timeout=120s
69+ echo "✅ Mailpit deployed"
70+ echo "🌐 UI: http://mailpit.$NS.svc.infini.cloud:8025/"
71+ echo "📨 SMTP: mailpit.$NS.svc.infini.cloud:1025"
72+
73+ # ──────────────────────────────────────────────
74+ # Job 2: MinIO S3 (no dependencies)
75+ # ──────────────────────────────────────────────
76+ deploy-minio :
77+ name : " 2 · MinIO S3"
78+ runs-on : ubuntu-latest
79+ steps :
80+ - name : Checkout
81+ uses : actions/checkout@v4
82+
83+ - name : Configure kubectl
84+ uses : azure/k8s-set-context@v4
85+ with :
86+ method : kubeconfig
87+ kubeconfig : ${{ secrets.KUBECONFIG }}
88+
89+ - name : Create namespace
90+ run : kubectl create namespace $NS --dry-run=client -o yaml | kubectl apply -f -
91+
92+ - name : Create MinIO secret
93+ run : |
94+ kubectl create secret generic minio-secret \
95+ -n $NS \
96+ --from-literal=MINIO_ROOT_USER=${{ secrets.MINIO_ROOT_USER }} \
97+ --from-literal=MINIO_ROOT_PASSWORD=${{ secrets.MINIO_ROOT_PASSWORD }} \
98+ --dry-run=client -o yaml | kubectl apply -f -
99+ echo "✅ MinIO secret ready"
100+
101+ - name : Deploy MinIO
102+ run : |
103+ kubectl -n $NS apply -f apps/minio.yaml
104+ kubectl -n $NS rollout status deployment/minio --timeout=180s
105+ echo "✅ MinIO deployed"
106+ echo "🌐 Console: http://minio.$NS.svc.infini.cloud:9091"
107+
108+ - name : Verify MinIO health
109+ run : |
110+ # Wait for the MinIO API endpoint to respond
111+ for i in $(seq 1 12); do
112+ STATUS=$(kubectl -n $NS exec deploy/minio -- curl -s -o /dev/null -w "%{http_code}" http://localhost:9000/minio/health/live 2>/dev/null || echo "000")
113+ if [ "$STATUS" = "200" ]; then
114+ echo "✅ MinIO health check passed"
115+ exit 0
116+ fi
117+ echo " Waiting... attempt $i/12 (status=$STATUS)"
118+ sleep 10
119+ done
120+ echo "❌ MinIO health check failed after 2 minutes"
121+ exit 1
122+
123+ # ──────────────────────────────────────────────
124+ # Job 3: cert-manager (no dependencies)
125+ # ──────────────────────────────────────────────
126+ deploy-cert-manager :
127+ name : " 3 · cert-manager"
128+ runs-on : ubuntu-latest
129+ steps :
130+ - name : Checkout
131+ uses : actions/checkout@v4
132+
133+ - name : Configure kubectl
134+ uses : azure/k8s-set-context@v4
135+ with :
136+ method : kubeconfig
137+ kubeconfig : ${{ secrets.KUBECONFIG }}
138+
139+ - name : Add Helm repos
140+ run : |
141+ helm repo add jetstack https://charts.jetstack.io --force-update
142+ helm repo update
143+ echo "✅ Helm repos updated"
144+
145+ - name : Install / upgrade cert-manager
146+ run : |
147+ helm upgrade --install cert-manager jetstack/cert-manager \
148+ --namespace cert-manager \
149+ --create-namespace \
150+ --version $CERT_MANAGER_VERSION \
151+ --set crds.enabled=true \
152+ --set prometheus.enabled=false \
153+ --set webhook.timeoutSeconds=10 \
154+ --wait \
155+ --timeout 5m
156+ echo "✅ cert-manager $CERT_MANAGER_VERSION ready"
157+
158+ - name : Verify cert-manager pods
159+ run : |
160+ kubectl -n cert-manager wait --for=condition=Ready pods \
161+ -l app.kubernetes.io/instance=cert-manager \
162+ --timeout=120s
163+ echo "✅ cert-manager pods healthy"
164+
165+ # ──────────────────────────────────────────────
166+ # Job 4: Easysearch — depends on cert-manager
167+ # ──────────────────────────────────────────────
168+ deploy-easysearch :
169+ name : " 4 · Easysearch"
170+ runs-on : ubuntu-latest
171+ needs : [deploy-cert-manager]
172+ steps :
173+ - name : Checkout
174+ uses : actions/checkout@v4
175+
176+ - name : Configure kubectl
177+ uses : azure/k8s-set-context@v4
178+ with :
179+ method : kubeconfig
180+ kubeconfig : ${{ secrets.KUBECONFIG }}
181+
182+ - name : Add Helm repos
183+ run : |
184+ helm repo add jetstack https://charts.jetstack.io --force-update
185+ helm repo add infinilabs https://helm.infinilabs.com --force-update
186+ helm repo update
187+ helm search repo infinilabs/easysearch --versions | head -5
188+
189+ - name : Create namespace
190+ run : kubectl create namespace $NS --dry-run=client -o yaml | kubectl apply -f -
191+
192+ - name : Apply CA secret
193+ run : kubectl -n $NS apply -f apps/ca-secret.yaml
194+
195+ - name : Create Easysearch secrets
196+ run : |
197+ kubectl create secret generic easysearch-access-secret \
198+ --namespace $NS \
199+ --from-literal=easysearch-pasword='${{ secrets.EASYSEARCH_ACCESS_PASSWORD }}' \
200+ --dry-run=client -o yaml | kubectl apply -f -
201+ echo "✅ Easysearch secrets ready"
202+
203+ - name : Install / upgrade Easysearch
204+ run : |
205+ helm upgrade --install easysearch infinilabs/easysearch \
206+ --namespace $NS \
207+ --create-namespace \
208+ -f apps/easysearch.yaml \
209+ --wait \
210+ --timeout 10m
211+ echo "✅ Easysearch deployed"
212+ echo "🌐 API: http://easysearch.$NS.svc.infini.cloud:9200/"
213+
214+ - name : Verify Easysearch
215+ run : |
216+ kubectl -n $NS rollout status statefulset/easysearch --timeout=300s
217+ echo "✅ Easysearch StatefulSet healthy"
218+
219+ # ──────────────────────────────────────────────
220+ # Job 5: Cloud App — depends on mailpit + minio + easysearch
221+ # ──────────────────────────────────────────────
222+ deploy-cloud :
223+ name : " 5 · Cloud App"
224+ runs-on : ubuntu-latest
225+ needs : [deploy-mailpit, deploy-minio, deploy-easysearch]
226+ outputs :
227+ cloud_ip : ${{ steps.get-ip.outputs.ip }}
228+ steps :
229+ - name : Checkout
230+ uses : actions/checkout@v4
231+
232+ - name : Configure kubectl
233+ uses : azure/k8s-set-context@v4
234+ with :
235+ method : kubeconfig
236+ kubeconfig : ${{ secrets.KUBECONFIG }}
237+
238+ - name : Create namespace
239+ run : kubectl create namespace $NS --dry-run=client -o yaml | kubectl apply -f -
240+
241+ - name : Deploy Cloud App
242+ run : |
243+ kubectl -n $NS apply -f apps/cloud.yaml
244+ kubectl -n $NS rollout status deployment/cloud --timeout=180s
245+ echo "✅ Cloud app deployed"
246+
247+ - name : Configure LoadBalancer (remove NodePort conflicts)
248+ run : |
249+ kubectl -n $NS patch svc cloud -p '{"spec":{"type":"LoadBalancer"}}'
250+
251+ # Remove nodePort to avoid conflicts, disable auto-allocation
252+ kubectl -n $NS patch svc cloud --type='json' -p='[
253+ {"op":"add","path":"/spec/type","value":"LoadBalancer"},
254+ {"op":"add","path":"/spec/allocateLoadBalancerNodePorts","value":false},
255+ {"op":"remove","path":"/spec/ports/0/nodePort"}
256+ ]' 2>/dev/null || true
257+
258+ echo "✅ Service patched to LoadBalancer (no NodePort)"
259+
260+ - name : Wait for external IP
261+ id : get-ip
262+ run : |
263+ echo "⏳ Waiting for LoadBalancer IP (up to 5 min)..."
264+ for i in $(seq 1 30); do
265+ IP=$(kubectl -n $NS get svc cloud \
266+ -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "")
267+ if [ -n "$IP" ]; then
268+ echo "✅ External IP: $IP"
269+ echo "ip=$IP" >> $GITHUB_OUTPUT
270+ exit 0
271+ fi
272+ echo " Attempt $i/30 — no IP yet, retrying in 10s..."
273+ sleep 10
274+ done
275+ echo "⚠️ LoadBalancer IP not assigned — check cloud provider quota"
276+ echo "ip=pending" >> $GITHUB_OUTPUT
277+
278+ - name : Print access info
279+ run : |
280+ IP="${{ steps.get-ip.outputs.ip }}"
281+ echo ""
282+ echo "╔═══════════════════════════════════════════════════════════════════╗"
283+ echo "║ Deployment Summary ║"
284+ echo "╠═══════════════════════════════════════════════════════════════════╣"
285+ echo "║ Cloud IP: $IP" ╣
286+ echo "║ Mailpit UI: http://mailpit.$NS.svc.infini.cloud:8025/" ╣
287+ echo "║ MinIO: http://minio.$NS.svc.infini.cloud:9091" ╣
288+ echo "║ Easysearch: http://easysearch.$NS.svc.infini.cloud:9200/" ╣
289+ echo "╚═══════════════════════════════════════════════════════════════════╝"
290+
291+ # ──────────────────────────────────────────────
292+ # Job 6: Smoke Tests — depends on cloud deploy
293+ # ──────────────────────────────────────────────
294+ smoke-tests :
295+ name : " 6 · Smoke Tests"
296+ runs-on : ubuntu-latest
297+ needs : [deploy-cloud]
298+ if : github.event.inputs.skip_smoke_test != 'true'
299+ steps :
300+ - name : Configure kubectl
301+ uses : azure/k8s-set-context@v4
302+ with :
303+ method : kubeconfig
304+ kubeconfig : ${{ secrets.KUBECONFIG }}
305+
306+ - name : Install swaks
307+ run : sudo apt-get install -y swaks
308+
309+ - name : Test SMTP (Mailpit)
310+ run : |
311+ swaks --to cloud@infinilabs.com \
312+ --from no-reply@infinilabs.com \
313+ --server mailpit.$NS.svc.infini.cloud:1025 \
314+ --protocol SMTP \
315+ --data "Subject: [CI] Smoke test from GitHub Actions\n\nDeployed at $(date -u)"
316+ echo "✅ SMTP smoke test passed"
317+
318+ - name : Test Easysearch API
319+ run : |
320+ STATUS=$(kubectl -n $NS exec deploy/easysearch -- \
321+ curl -sk -o /dev/null -w "%{http_code}" \
322+ -u "admin:${{ secrets.EASYSEARCH_ACCESS_PASSWORD }}" \
323+ http://localhost:9200/_cluster/health 2>/dev/null || echo "000")
324+ if [ "$STATUS" = "200" ]; then
325+ echo "✅ Easysearch health endpoint OK"
326+ else
327+ echo "❌ Easysearch health check returned HTTP $STATUS"
328+ exit 1
329+ fi
330+
331+ - name : Test MinIO health
332+ run : |
333+ STATUS=$(kubectl -n $NS exec deploy/minio -- \
334+ curl -s -o /dev/null -w "%{http_code}" \
335+ http://localhost:9000/minio/health/live 2>/dev/null || echo "000")
336+ if [ "$STATUS" = "200" ]; then
337+ echo "✅ MinIO health check OK"
338+ else
339+ echo "❌ MinIO health check returned HTTP $STATUS"
340+ exit 1
341+ fi
342+
343+ - name : Smoke test summary
344+ run : |
345+ echo "✅ All smoke tests passed — stack is healthy"
0 commit comments