11name : Deploy to Staging
22
33on :
4- # Manual trigger with options
54 workflow_dispatch :
65 inputs :
76 image_tag :
3231 GCP_CLUSTER : staging-core-application
3332
3433jobs :
35- deploy-pathroute :
36- name : Deploy to staging-pathroute
37- if : ${{ inputs.environment == 'both' || inputs.environment == 'pathroute' }}
34+ deploy :
35+ name : Deploy to staging-${{ matrix.env.name }}
3836 runs-on : ubuntu-24.04
3937 permissions :
4038 contents : read
4139 id-token : write
42- env :
43- NAMESPACE : openhands-pathroute
44- HELM_RELEASE : openhands-pathroute
45- ENV_DIR : envs/staging-pathroute
40+ strategy :
41+ fail-fast : false
42+ matrix :
43+ env :
44+ - name : pathroute
45+ namespace : openhands-pathroute
46+ helm_release : openhands-pathroute
47+ env_dir : envs/staging-pathroute
48+ - name : subdomain
49+ namespace : openhands-subdomain
50+ helm_release : openhands-subdomain
51+ env_dir : envs/staging-subdomain
52+ # Only run for selected environment(s)
53+ if : ${{ inputs.environment == 'both' || inputs.environment == matrix.env.name }}
4654
4755 steps :
4856 - name : Checkout repository
@@ -69,149 +77,7 @@ jobs:
6977 uses : google-github-actions/setup-gcloud@v2
7078
7179 - name : Install gke-gcloud-auth-plugin
72- run : |
73- gcloud components install gke-gcloud-auth-plugin
74-
75- - name : Configure kubectl
76- run : |
77- gcloud container clusters get-credentials ${{ env.GCP_CLUSTER }} \
78- --zone ${{ env.GCP_ZONE }} \
79- --project ${{ env.GCP_PROJECT }}
80-
81- - name : Create namespace if not exists
82- if : ${{ !inputs.dry_run }}
83- run : |
84- kubectl create namespace ${{ env.NAMESPACE }} --dry-run=client -o yaml | kubectl apply -f -
85-
86- - name : Decrypt and apply secrets
87- if : ${{ !inputs.skip_secrets && !inputs.dry_run }}
88- run : |
89- SECRETS_DIR="${{ env.ENV_DIR }}/secrets"
90-
91- if [[ -d "$SECRETS_DIR" ]]; then
92- echo "Applying secrets from $SECRETS_DIR"
93- for file in "$SECRETS_DIR"/*.yaml; do
94- # Skip .gitkeep or non-existent files
95- [[ -e "$file" ]] || continue
96- [[ "$(basename "$file")" == ".gitkeep" ]] && continue
97-
98- echo "Decrypting and applying: $file"
99- sops --decrypt "$file" | kubectl apply -n ${{ env.NAMESPACE }} -f -
100- done
101- echo "All secrets applied successfully"
102- else
103- echo "No secrets directory found at $SECRETS_DIR"
104- fi
105-
106- - name : Update Helm dependencies
107- run : |
108- helm dependency update charts/openhands
109-
110- - name : Helm template (dry run)
111- if : ${{ inputs.dry_run }}
112- run : |
113- helm template ${{ env.HELM_RELEASE }} charts/openhands \
114- --namespace ${{ env.NAMESPACE }} \
115- --values ${{ env.ENV_DIR }}/values.yaml \
116- --set image.tag=${{ inputs.image_tag }} \
117- --debug
118-
119- - name : Deploy with Helm
120- if : ${{ !inputs.dry_run }}
121- run : |
122- # Check current release status
123- if helm status ${{ env.HELM_RELEASE }} -n ${{ env.NAMESPACE }} &>/dev/null; then
124- status=$(helm status ${{ env.HELM_RELEASE }} -n ${{ env.NAMESPACE }} -o json | jq -r '.info.status')
125- if [[ "$status" != "deployed" ]]; then
126- echo "Found release in non-deployed state ($status). Attempting rollback..."
127- helm rollback ${{ env.HELM_RELEASE }} -n ${{ env.NAMESPACE }} || true
128- fi
129- fi
130-
131- helm upgrade --install \
132- --wait \
133- --timeout 10m \
134- ${{ env.HELM_RELEASE }} \
135- charts/openhands \
136- --namespace ${{ env.NAMESPACE }} \
137- --values ${{ env.ENV_DIR }}/values.yaml \
138- --set image.tag=${{ inputs.image_tag }} \
139- --debug
140-
141- - name : Get deployment info
142- if : ${{ !inputs.dry_run }}
143- id : deployment_info
144- run : |
145- echo "## Deployment Summary (pathroute)" >> $GITHUB_STEP_SUMMARY
146- echo "" >> $GITHUB_STEP_SUMMARY
147- echo "- **Environment:** staging-pathroute" >> $GITHUB_STEP_SUMMARY
148- echo "- **Namespace:** ${{ env.NAMESPACE }}" >> $GITHUB_STEP_SUMMARY
149- echo "- **Image Tag:** ${{ inputs.image_tag }}" >> $GITHUB_STEP_SUMMARY
150- echo "" >> $GITHUB_STEP_SUMMARY
151-
152- # Get ingress hostname
153- hostname=$(kubectl get ing -n ${{ env.NAMESPACE }} -o jsonpath='{.items[0].spec.rules[0].host}' 2>/dev/null || echo "N/A")
154- echo "- **Hostname:** https://$hostname" >> $GITHUB_STEP_SUMMARY
155- echo "hostname=$hostname" >> $GITHUB_OUTPUT
156-
157- echo "" >> $GITHUB_STEP_SUMMARY
158- echo "### Pods Status" >> $GITHUB_STEP_SUMMARY
159- echo '```' >> $GITHUB_STEP_SUMMARY
160- kubectl get pods -n ${{ env.NAMESPACE }} -l app.kubernetes.io/instance=${{ env.HELM_RELEASE }} >> $GITHUB_STEP_SUMMARY
161- echo '```' >> $GITHUB_STEP_SUMMARY
162-
163- - name : Verify deployment health
164- if : ${{ !inputs.dry_run }}
165- run : |
166- echo "Waiting for deployment to stabilize..."
167- kubectl rollout status deployment -n ${{ env.NAMESPACE }} -l app.kubernetes.io/instance=${{ env.HELM_RELEASE }} --timeout=5m || true
168-
169- echo ""
170- echo "Current pod status:"
171- kubectl get pods -n ${{ env.NAMESPACE }} -l app.kubernetes.io/instance=${{ env.HELM_RELEASE }}
172-
173- outputs :
174- hostname : ${{ steps.deployment_info.outputs.hostname }}
175-
176- deploy-subdomain :
177- name : Deploy to staging-subdomain
178- if : ${{ inputs.environment == 'both' || inputs.environment == 'subdomain' }}
179- runs-on : ubuntu-24.04
180- permissions :
181- contents : read
182- id-token : write
183- env :
184- NAMESPACE : openhands-subdomain
185- HELM_RELEASE : openhands-subdomain
186- ENV_DIR : envs/staging-subdomain
187-
188- steps :
189- - name : Checkout repository
190- uses : actions/checkout@v4
191-
192- - name : Install SOPS
193- run : |
194- curl -L "https://github.com/mozilla/sops/releases/download/v3.9.1/sops-v3.9.1.linux.amd64" -o sops
195- chmod +x sops
196- sudo mv sops /usr/local/bin/sops
197- sops --version
198-
199- - name : Install Helm
200- uses : azure/setup-helm@v3
201- with :
202- version : ' latest'
203-
204- - name : Authenticate with Google Cloud
205- uses : google-github-actions/auth@v2
206- with :
207- credentials_json : ${{ secrets.GCP_SERVICE_KEY }}
208-
209- - name : Set up Google Cloud SDK
210- uses : google-github-actions/setup-gcloud@v2
211-
212- - name : Install gke-gcloud-auth-plugin
213- run : |
214- gcloud components install gke-gcloud-auth-plugin
80+ run : gcloud components install gke-gcloud-auth-plugin
21581
21682 - name : Configure kubectl
21783 run : |
@@ -222,94 +88,89 @@ jobs:
22288 - name : Create namespace if not exists
22389 if : ${{ !inputs.dry_run }}
22490 run : |
225- kubectl create namespace ${{ env.NAMESPACE }} --dry-run=client -o yaml | kubectl apply -f -
91+ kubectl create namespace ${{ matrix. env.namespace }} --dry-run=client -o yaml | kubectl apply -f -
22692
22793 - name : Decrypt and apply secrets
22894 if : ${{ !inputs.skip_secrets && !inputs.dry_run }}
22995 run : |
230- SECRETS_DIR="${{ env.ENV_DIR }}/secrets"
231-
96+ SECRETS_DIR="${{ matrix.env.env_dir }}/secrets"
23297 if [[ -d "$SECRETS_DIR" ]]; then
23398 echo "Applying secrets from $SECRETS_DIR"
23499 for file in "$SECRETS_DIR"/*.yaml; do
235- # Skip .gitkeep or non-existent files
236100 [[ -e "$file" ]] || continue
237101 [[ "$(basename "$file")" == ".gitkeep" ]] && continue
238-
239102 echo "Decrypting and applying: $file"
240- sops --decrypt "$file" | kubectl apply -n ${{ env.NAMESPACE }} -f -
103+ sops --decrypt "$file" | kubectl apply -n ${{ matrix. env.namespace }} -f -
241104 done
242- echo "All secrets applied successfully"
243105 else
244106 echo "No secrets directory found at $SECRETS_DIR"
245107 fi
246108
247109 - name : Update Helm dependencies
248- run : |
249- helm dependency update charts/openhands
110+ run : helm dependency update charts/openhands
250111
251112 - name : Helm template (dry run)
252113 if : ${{ inputs.dry_run }}
253114 run : |
254- helm template ${{ env.HELM_RELEASE }} charts/openhands \
255- --namespace ${{ env.NAMESPACE }} \
256- --values ${{ env.ENV_DIR }}/values.yaml \
115+ helm template ${{ matrix.env.helm_release }} charts/openhands \
116+ --namespace ${{ matrix.env.namespace }} \
117+ --values envs/common/values.yaml \
118+ --values ${{ matrix.env.env_dir }}/values.yaml \
257119 --set image.tag=${{ inputs.image_tag }} \
258120 --debug
259121
260122 - name : Deploy with Helm
261123 if : ${{ !inputs.dry_run }}
262124 run : |
263- # Check current release status
264- if helm status ${{ env.HELM_RELEASE }} -n ${{ env.NAMESPACE }} &>/dev/null; then
265- status=$(helm status ${{ env.HELM_RELEASE }} -n ${{ env.NAMESPACE }} -o json | jq -r '.info.status')
125+ # Check current release status and rollback if needed
126+ if helm status ${{ matrix. env.helm_release }} -n ${{ matrix. env.namespace }} &>/dev/null; then
127+ status=$(helm status ${{ matrix. env.helm_release }} -n ${{ matrix. env.namespace }} -o json | jq -r '.info.status')
266128 if [[ "$status" != "deployed" ]]; then
267129 echo "Found release in non-deployed state ($status). Attempting rollback..."
268- helm rollback ${{ env.HELM_RELEASE }} -n ${{ env.NAMESPACE }} || true
130+ helm rollback ${{ matrix. env.helm_release }} -n ${{ matrix. env.namespace }} || true
269131 fi
270132 fi
271133
272134 helm upgrade --install \
273135 --wait \
274136 --timeout 10m \
275- ${{ env.HELM_RELEASE }} \
137+ ${{ matrix. env.helm_release }} \
276138 charts/openhands \
277- --namespace ${{ env.NAMESPACE }} \
278- --values ${{ env.ENV_DIR }}/values.yaml \
139+ --namespace ${{ matrix.env.namespace }} \
140+ --values envs/common/values.yaml \
141+ --values ${{ matrix.env.env_dir }}/values.yaml \
279142 --set image.tag=${{ inputs.image_tag }} \
280143 --debug
281144
282145 - name : Get deployment info
283146 if : ${{ !inputs.dry_run }}
284147 id : deployment_info
285148 run : |
286- echo "## Deployment Summary (subdomain )" >> $GITHUB_STEP_SUMMARY
149+ echo "## Deployment Summary (${{ matrix.env.name }} )" >> $GITHUB_STEP_SUMMARY
287150 echo "" >> $GITHUB_STEP_SUMMARY
288- echo "- **Environment:** staging-subdomain " >> $GITHUB_STEP_SUMMARY
289- echo "- **Namespace:** ${{ env.NAMESPACE }}" >> $GITHUB_STEP_SUMMARY
151+ echo "- **Environment:** staging-${{ matrix.env.name }} " >> $GITHUB_STEP_SUMMARY
152+ echo "- **Namespace:** ${{ matrix. env.namespace }}" >> $GITHUB_STEP_SUMMARY
290153 echo "- **Image Tag:** ${{ inputs.image_tag }}" >> $GITHUB_STEP_SUMMARY
291154 echo "" >> $GITHUB_STEP_SUMMARY
292155
293- # Get ingress hostname
294- hostname=$(kubectl get ing -n ${{ env.NAMESPACE }} -o jsonpath='{.items[0].spec.rules[0].host}' 2>/dev/null || echo "N/A")
156+ hostname=$(kubectl get ing -n ${{ matrix.env.namespace }} -o jsonpath='{.items[0].spec.rules[0].host}' 2>/dev/null || echo "N/A")
295157 echo "- **Hostname:** https://$hostname" >> $GITHUB_STEP_SUMMARY
296158 echo "hostname=$hostname" >> $GITHUB_OUTPUT
297159
298160 echo "" >> $GITHUB_STEP_SUMMARY
299161 echo "### Pods Status" >> $GITHUB_STEP_SUMMARY
300162 echo '```' >> $GITHUB_STEP_SUMMARY
301- kubectl get pods -n ${{ env.NAMESPACE }} -l app.kubernetes.io/instance=${{ env.HELM_RELEASE }} >> $GITHUB_STEP_SUMMARY
163+ kubectl get pods -n ${{ matrix. env.namespace }} -l app.kubernetes.io/instance=${{ matrix. env.helm_release }} >> $GITHUB_STEP_SUMMARY
302164 echo '```' >> $GITHUB_STEP_SUMMARY
303165
304166 - name : Verify deployment health
305167 if : ${{ !inputs.dry_run }}
306168 run : |
307169 echo "Waiting for deployment to stabilize..."
308- kubectl rollout status deployment -n ${{ env.NAMESPACE }} -l app.kubernetes.io/instance=${{ env.HELM_RELEASE }} --timeout=5m || true
309-
170+ kubectl rollout status deployment -n ${{ matrix.env.namespace }} -l app.kubernetes.io/instance=${{ matrix.env.helm_release }} --timeout=5m || true
310171 echo ""
311172 echo "Current pod status:"
312- kubectl get pods -n ${{ env.NAMESPACE }} -l app.kubernetes.io/instance=${{ env.HELM_RELEASE }}
173+ kubectl get pods -n ${{ matrix. env.namespace }} -l app.kubernetes.io/instance=${{ matrix. env.helm_release }}
313174
314175 outputs :
315176 hostname : ${{ steps.deployment_info.outputs.hostname }}
0 commit comments