Skip to content

Commit 72c64df

Browse files
authored
Merge pull request #37 from VectorInstitute/automate_onboarding_dashboard_deployment
Automate deployment of onboarding dashboard
2 parents 5726691 + 6598478 commit 72c64df

File tree

4 files changed

+372
-8
lines changed

4 files changed

+372
-8
lines changed
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
name: Deploy Onboarding Status Web
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'services/onboarding-status-web/**'
8+
- '.github/workflows/deploy-onboarding-status-web.yml'
9+
pull_request:
10+
branches: [main]
11+
paths:
12+
- 'services/onboarding-status-web/**'
13+
- '.github/workflows/deploy-onboarding-status-web.yml'
14+
workflow_dispatch: # Allow manual triggers
15+
16+
# Prevent concurrent deployments
17+
concurrency:
18+
group: ${{ github.workflow }}-${{ github.ref }}
19+
cancel-in-progress: false # Don't cancel in-progress deployments
20+
21+
env:
22+
PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
23+
GAR_LOCATION: us-central1
24+
REPOSITORY: onboarding-status
25+
SERVICE_NAME: onboarding-status-web
26+
REGION: us-central1
27+
FIRESTORE_DATABASE_ID: onboarding
28+
29+
jobs:
30+
# Build and test the Next.js application
31+
build:
32+
name: Build and Test
33+
runs-on: ubuntu-latest
34+
permissions:
35+
contents: read
36+
id-token: write # Required for GCP authentication
37+
38+
steps:
39+
- name: Checkout code
40+
uses: actions/checkout@v4
41+
with:
42+
fetch-depth: 1 # Only fetch latest commit for faster checkouts
43+
44+
- name: Free up disk space
45+
run: |
46+
echo "Disk space before cleanup:"
47+
df -h
48+
49+
# Remove unnecessary tools and files
50+
sudo rm -rf /usr/share/dotnet
51+
sudo rm -rf /opt/ghc
52+
sudo rm -rf /usr/local/share/boost
53+
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
54+
55+
# Clean up Docker
56+
docker system prune -af --volumes
57+
58+
echo "Disk space after cleanup:"
59+
df -h
60+
61+
- name: Set up Docker Buildx
62+
uses: docker/setup-buildx-action@v3
63+
with:
64+
driver-opts: network=host
65+
66+
- name: Build Docker image
67+
uses: docker/build-push-action@v6
68+
with:
69+
context: ./services/onboarding-status-web
70+
file: ./services/onboarding-status-web/Dockerfile
71+
push: false
72+
load: true
73+
tags: onboarding-status-web:${{ github.sha }}
74+
cache-from: type=gha,scope=onboarding-status-web
75+
cache-to: type=gha,mode=max,scope=onboarding-status-web
76+
77+
# Test container (skip on main push since tests already ran in PR)
78+
- name: Test container
79+
if: github.actor != 'dependabot[bot]' && !(github.ref == 'refs/heads/main' && github.event_name == 'push')
80+
run: |
81+
set -e
82+
83+
echo "Starting Next.js container..."
84+
docker run -d \
85+
--name onboarding-status-test \
86+
-p 8080:8080 \
87+
-e GCP_PROJECT_ID=test \
88+
-e FIRESTORE_DATABASE_ID=test \
89+
-e GITHUB_TOKEN=test \
90+
onboarding-status-web:${{ github.sha }}
91+
92+
# Wait for container to start and verify it stays running
93+
echo "Waiting for container to start..."
94+
sleep 15
95+
96+
# Check if container is still running
97+
if [ "$(docker inspect --format='{{.State.Status}}' onboarding-status-test)" != "running" ]; then
98+
echo "✗ Container stopped unexpectedly"
99+
docker logs onboarding-status-test
100+
exit 1
101+
fi
102+
103+
echo "✓ Container started successfully"
104+
105+
# Check logs for successful Next.js startup
106+
echo "Checking container logs..."
107+
docker logs onboarding-status-test 2>&1 | grep -q "Ready in" && echo "✓ Next.js server is ready" || echo "⚠ Could not confirm Next.js ready state"
108+
109+
# Note: We don't test API endpoints here because they require valid GCP credentials
110+
# Full health checks are performed during deployment with real credentials
111+
112+
echo "✓ Container tests passed"
113+
114+
- name: Cleanup test container
115+
if: always()
116+
run: |
117+
docker stop onboarding-status-test || true
118+
docker rm onboarding-status-test || true
119+
# Keep image if pushing to main, otherwise remove it
120+
if [ "${{ github.ref }}" != "refs/heads/main" ] || [ "${{ github.event_name }}" != "push" ]; then
121+
docker rmi onboarding-status-web:${{ github.sha }} || true
122+
fi
123+
124+
# Push image to GAR (only on main push)
125+
- name: Authenticate to Google Cloud
126+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
127+
id: auth
128+
uses: google-github-actions/auth@v2
129+
with:
130+
workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
131+
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
132+
token_format: access_token
133+
134+
- name: Set up Cloud SDK
135+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
136+
uses: google-github-actions/setup-gcloud@v2
137+
138+
- name: Configure Docker for Artifact Registry
139+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
140+
run: |
141+
gcloud auth configure-docker ${{ env.GAR_LOCATION }}-docker.pkg.dev --quiet
142+
143+
- name: Create Artifact Registry repository
144+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
145+
run: |
146+
# Check if repository exists
147+
if ! gcloud artifacts repositories describe ${{ env.REPOSITORY }} \
148+
--location=${{ env.GAR_LOCATION }} \
149+
--format="get(name)" 2>/dev/null; then
150+
echo "Creating Artifact Registry repository: ${{ env.REPOSITORY }}"
151+
gcloud artifacts repositories create ${{ env.REPOSITORY }} \
152+
--repository-format=docker \
153+
--location=${{ env.GAR_LOCATION }} \
154+
--description="Docker repository for Onboarding Status Dashboard" \
155+
--quiet
156+
echo "✓ Repository created"
157+
else
158+
echo "✓ Repository already exists"
159+
fi
160+
161+
- name: Tag and push image to GAR
162+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
163+
run: |
164+
set -e
165+
166+
IMAGE_URL="${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE_NAME }}:${{ github.sha }}"
167+
LATEST_URL="${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE_NAME }}:latest"
168+
169+
echo "Tagging image..."
170+
docker tag onboarding-status-web:${{ github.sha }} "$IMAGE_URL"
171+
docker tag onboarding-status-web:${{ github.sha }} "$LATEST_URL"
172+
173+
echo "Pushing image to GAR..."
174+
docker push "$IMAGE_URL"
175+
docker push "$LATEST_URL"
176+
177+
echo "✓ Image pushed: $IMAGE_URL"
178+
179+
# Deploy to Cloud Run
180+
deploy:
181+
name: Deploy to Cloud Run
182+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
183+
needs: [build]
184+
runs-on: ubuntu-latest
185+
permissions:
186+
contents: read
187+
id-token: write
188+
189+
outputs:
190+
service-url: ${{ steps.deploy.outputs.url }}
191+
service-revision: ${{ steps.deploy.outputs.revision }}
192+
193+
steps:
194+
- name: Checkout code
195+
uses: actions/checkout@v4
196+
with:
197+
fetch-depth: 1
198+
199+
- name: Authenticate to Google Cloud
200+
id: auth
201+
uses: google-github-actions/auth@v2
202+
with:
203+
workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
204+
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
205+
token_format: access_token
206+
207+
- name: Set up Cloud SDK
208+
uses: google-github-actions/setup-gcloud@v2
209+
210+
- name: Set image URL
211+
id: set-image
212+
run: |
213+
IMAGE_URL="${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE_NAME }}:${{ github.sha }}"
214+
echo "image=$IMAGE_URL" >> $GITHUB_OUTPUT
215+
echo "Using image: $IMAGE_URL"
216+
217+
- name: Deploy to Cloud Run
218+
id: deploy
219+
run: |
220+
set -e
221+
222+
echo "Deploying to Cloud Run..."
223+
gcloud run deploy ${{ env.SERVICE_NAME }} \
224+
--image ${{ steps.set-image.outputs.image }} \
225+
--region ${{ env.REGION }} \
226+
--platform managed \
227+
--allow-unauthenticated \
228+
--memory=1Gi \
229+
--cpu=1 \
230+
--timeout=300s \
231+
--max-instances=10 \
232+
--min-instances=0 \
233+
--concurrency=80 \
234+
--port=8080 \
235+
--set-env-vars="GCP_PROJECT_ID=${{ env.PROJECT_ID }},FIRESTORE_DATABASE_ID=${{ env.FIRESTORE_DATABASE_ID }},GITHUB_TOKEN=${{ secrets.GH_ORG_TOKEN }},DEPLOYMENT_SHA=${{ github.sha }}" \
236+
--update-labels="deployed-by=github-actions,commit=${{ github.sha }}" \
237+
--update-annotations="git-commit=${{ github.sha }},git-ref=${{ github.ref }},deployed-at=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
238+
--quiet
239+
240+
# Route traffic to latest revision
241+
echo "Routing traffic to latest revision..."
242+
gcloud run services update-traffic ${{ env.SERVICE_NAME }} \
243+
--to-latest \
244+
--region ${{ env.REGION }} \
245+
--quiet
246+
247+
# Get deployment info
248+
SERVICE_URL=$(gcloud run services describe ${{ env.SERVICE_NAME }} \
249+
--region ${{ env.REGION }} \
250+
--format 'value(status.url)')
251+
252+
REVISION_NAME=$(gcloud run services describe ${{ env.SERVICE_NAME }} \
253+
--region ${{ env.REGION }} \
254+
--format 'value(status.latestReadyRevisionName)')
255+
256+
echo "url=$SERVICE_URL" >> $GITHUB_OUTPUT
257+
echo "revision=$REVISION_NAME" >> $GITHUB_OUTPUT
258+
259+
echo "✓ Service deployed"
260+
echo " URL: $SERVICE_URL"
261+
echo " Revision: $REVISION_NAME"
262+
263+
- name: Verify deployment health
264+
run: |
265+
set -e
266+
267+
SERVICE_URL="${{ steps.deploy.outputs.url }}"
268+
echo "Verifying deployment health at $SERVICE_URL..."
269+
270+
MAX_RETRIES=30
271+
RETRY_COUNT=0
272+
BACKOFF=5
273+
274+
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
275+
# Test the participants API endpoint
276+
if curl -sf --max-time 10 "${SERVICE_URL}/onboarding/api/participants" > /dev/null 2>&1; then
277+
echo "✓ Health check passed - API is responding"
278+
279+
# Test GitHub status endpoint
280+
STATUS_RESPONSE=$(curl -sf --max-time 10 -X POST \
281+
-H "Content-Type: application/json" \
282+
-d '{"github_handles":["test"]}' \
283+
"${SERVICE_URL}/onboarding/api/github-status" || echo "")
284+
285+
if [ -n "$STATUS_RESPONSE" ]; then
286+
echo "✓ GitHub status endpoint is responding"
287+
else
288+
echo "⚠ Warning: GitHub status endpoint did not respond"
289+
fi
290+
291+
exit 0
292+
fi
293+
294+
RETRY_COUNT=$((RETRY_COUNT + 1))
295+
echo "Attempt $RETRY_COUNT/$MAX_RETRIES failed, waiting ${BACKOFF}s..."
296+
sleep $BACKOFF
297+
298+
# Exponential backoff (max 30s)
299+
BACKOFF=$((BACKOFF < 30 ? BACKOFF + 5 : 30))
300+
done
301+
302+
echo "✗ Health check failed after $MAX_RETRIES attempts"
303+
echo "Fetching recent logs..."
304+
gcloud run services logs read ${{ env.SERVICE_NAME }} \
305+
--region ${{ env.REGION }} \
306+
--limit 50 || true
307+
exit 1
308+
309+
- name: Generate deployment summary
310+
if: always()
311+
run: |
312+
cat >> $GITHUB_STEP_SUMMARY << EOF
313+
## 🚀 Deployment Summary
314+
315+
**Commit:** \`${{ github.sha }}\`
316+
**Branch:** \`${{ github.ref_name }}\`
317+
**Triggered by:** @${{ github.actor }}
318+
319+
### Onboarding Status Dashboard
320+
- **URL:** ${{ steps.deploy.outputs.url }}/onboarding
321+
- **Revision:** \`${{ steps.deploy.outputs.revision }}\`
322+
- **Status:** ✅ Deployed
323+
324+
**API Endpoints:**
325+
- Participants: ${{ steps.deploy.outputs.url }}/onboarding/api/participants
326+
- GitHub Status: ${{ steps.deploy.outputs.url }}/onboarding/api/github-status
327+
328+
---
329+
*Deployment completed at $(date -u +"%Y-%m-%d %H:%M:%S UTC")*
330+
EOF

docs/developer_guide.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,15 @@ This guide covers:
5858
- Deployment and verification procedures
5959
- Troubleshooting common issues
6060

61-
**Deployment Command:**
61+
**Automated Deployment (Recommended):**
62+
63+
The service is automatically deployed via GitHub Actions when changes are pushed to the `main` branch. The workflow:
64+
- Builds and tests the Docker container
65+
- Pushes to Google Artifact Registry
66+
- Deploys to Cloud Run
67+
- Verifies health checks
68+
69+
**Manual Deployment:**
6270
```bash
6371
./scripts/admin/deploy_onboarding_status_web.sh
6472
```
@@ -68,6 +76,18 @@ This guide covers:
6876
https://platform.vectorinstitute.ai/onboarding
6977
```
7078

79+
**Required GitHub Secrets for Automated Deployment:**
80+
81+
Configure these secrets in GitHub repository settings (`Settings``Secrets and variables``Actions`):
82+
83+
1. **GCP_PROJECT_ID**: Your GCP project ID (e.g., `coderd`)
84+
2. **WIF_PROVIDER**: Workload Identity Federation provider
85+
- Format: `projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID`
86+
3. **GCP_SERVICE_ACCOUNT**: Service account email for deployment
87+
- Format: `SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com`
88+
- Required roles: `roles/run.admin`, `roles/iam.serviceAccountUser`, `roles/artifactregistry.admin`
89+
4. **GH_ORG_TOKEN**: Personal access token with `read:org` scope for checking GitHub organization membership
90+
7191
---
7292

7393
## Service Architecture

services/onboarding-status-web/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ WORKDIR /app
2929
ENV NODE_ENV=production
3030
ENV NEXT_TELEMETRY_DISABLED=1
3131

32-
# Install GitHub CLI
33-
RUN apk add --no-cache github-cli
32+
# Install GitHub CLI and curl for health checks
33+
RUN apk add --no-cache github-cli curl
3434

3535
# Create a non-root user for security
3636
RUN addgroup --system --gid 1001 nodejs

0 commit comments

Comments
 (0)