Skip to content

Commit a630b7a

Browse files
committed
ci: add GitHub Actions workflow to build and deploy SPA + API
Adds .github/workflows/deploy.yml that builds the Angular SPA and the Spring Boot API, then deploys both to the existing App Services in rg-evidence-workshop. Runs on push to main (path-filtered to sample-app/**) and on workflow_dispatch. Authentication uses Azure OIDC federated identity (no client secrets in the repo). The workflow assumes: * The Entra ID app registrations have already been provisioned by scripts/setup-entra-apps.ps1 (their IDs live in .entra-apps.json). * The Azure infrastructure has already been deployed by scripts/deploy.ps1 at least once. The CI does not run Bicep. * Sample evidence PDFs have been seeded once. The CI does not reseed. Required GitHub repository secrets: AZURE_CLIENT_ID SP with Federated Identity Credential bound to repo:devopsabcs-engineering/msal-java:ref:refs/heads/main and Website Contributor on rg-evidence-workshop. AZURE_TENANT_ID AZURE_SUBSCRIPTION_ID Smoke test asserts the SPA returns 200 and the API returns 401 on the unauthenticated /api/cases endpoint (proving JWT validation is on and the app actually started).
1 parent 3965331 commit a630b7a

1 file changed

Lines changed: 198 additions & 0 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# ---------------------------------------------------------------------------
2+
# Build + deploy the Evidence Portal SPA (Angular 19) and API (Spring Boot 3
3+
# on Java 17) to the existing Azure App Services in resource group
4+
# rg-evidence-workshop.
5+
#
6+
# This workflow does NOT:
7+
# * Bootstrap Entra ID app registrations (run scripts/setup-entra-apps.ps1
8+
# once locally; the resulting app reg IDs are committed in
9+
# .entra-apps.json).
10+
# * Provision or modify Azure infrastructure (run scripts/deploy.ps1
11+
# locally for the first deploy; this workflow only updates the running
12+
# code on the existing App Services).
13+
# * Reseed the sample evidence PDFs in storage (one-time, done by
14+
# scripts/deploy.ps1).
15+
#
16+
# Required GitHub repository secrets (configured under
17+
# Settings -> Secrets and variables -> Actions):
18+
#
19+
# AZURE_CLIENT_ID App registration with Federated Identity Credential
20+
# for this repository (subject:
21+
# repo:devopsabcs-engineering/msal-java:ref:refs/heads/main).
22+
# The SP must have Website Contributor on
23+
# rg-evidence-workshop.
24+
# AZURE_TENANT_ID Entra tenant id.
25+
# AZURE_SUBSCRIPTION_ID Subscription id hosting rg-evidence-workshop.
26+
# ---------------------------------------------------------------------------
27+
name: Deploy SPA and API
28+
29+
on:
30+
push:
31+
branches: [main]
32+
paths:
33+
- 'sample-app/**'
34+
- '.github/workflows/deploy.yml'
35+
workflow_dispatch:
36+
37+
# Required for OIDC federated login to Azure (no client secret needed).
38+
permissions:
39+
id-token: write
40+
contents: read
41+
42+
concurrency:
43+
group: deploy-workshop
44+
cancel-in-progress: false
45+
46+
env:
47+
RESOURCE_GROUP: rg-evidence-workshop
48+
API_APP_NAME: app-evidence-api-workshop
49+
SPA_APP_NAME: app-evidence-spa-workshop
50+
51+
jobs:
52+
# -------------------------------------------------------------------------
53+
# Build SPA + API in parallel-friendly steps and publish artefacts.
54+
# -------------------------------------------------------------------------
55+
build:
56+
name: Build artefacts
57+
runs-on: ubuntu-latest
58+
steps:
59+
- name: Checkout
60+
uses: actions/checkout@v4
61+
62+
- name: Set up Java 17 (Temurin)
63+
uses: actions/setup-java@v4
64+
with:
65+
distribution: temurin
66+
java-version: '17'
67+
cache: maven
68+
69+
- name: Set up Node 20
70+
uses: actions/setup-node@v4
71+
with:
72+
node-version: '20'
73+
cache: npm
74+
cache-dependency-path: sample-app/spa/package-lock.json
75+
76+
# ---- API ----
77+
- name: Build Spring Boot API
78+
working-directory: sample-app/api
79+
run: mvn -B -ntp clean package -DskipTests
80+
81+
- name: Capture API jar name
82+
id: api
83+
working-directory: sample-app/api/target
84+
run: |
85+
jar=$(ls evidence-api-*.jar | grep -v -E '\-(sources|javadoc)\.jar$' | head -1)
86+
echo "Found jar: $jar"
87+
echo "jar=$jar" >> "$GITHUB_OUTPUT"
88+
89+
- name: Upload API artefact
90+
uses: actions/upload-artifact@v4
91+
with:
92+
name: api-jar
93+
path: sample-app/api/target/${{ steps.api.outputs.jar }}
94+
if-no-files-found: error
95+
retention-days: 7
96+
97+
# ---- SPA ----
98+
- name: Fetch Ontario Design System assets
99+
shell: pwsh
100+
run: ./scripts/fetch-ontario-design-system.ps1
101+
102+
- name: Install SPA dependencies
103+
working-directory: sample-app/spa
104+
run: npm ci
105+
106+
- name: Build Angular SPA (production)
107+
working-directory: sample-app/spa
108+
run: npx ng build --configuration production
109+
110+
- name: Upload SPA artefact
111+
uses: actions/upload-artifact@v4
112+
with:
113+
name: spa-dist
114+
path: sample-app/spa/dist/evidence-portal/browser/
115+
if-no-files-found: error
116+
retention-days: 7
117+
118+
# -------------------------------------------------------------------------
119+
# Deploy to App Service via OIDC. Runs after build succeeds.
120+
# -------------------------------------------------------------------------
121+
deploy:
122+
name: Deploy to App Service
123+
needs: build
124+
runs-on: ubuntu-latest
125+
environment:
126+
name: workshop
127+
url: https://${{ env.SPA_APP_NAME }}.azurewebsites.net
128+
steps:
129+
- name: Download API artefact
130+
uses: actions/download-artifact@v4
131+
with:
132+
name: api-jar
133+
path: ./api
134+
135+
- name: Download SPA artefact
136+
uses: actions/download-artifact@v4
137+
with:
138+
name: spa-dist
139+
path: ./spa-dist
140+
141+
- name: Azure login (OIDC)
142+
uses: azure/login@v2
143+
with:
144+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
145+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
146+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
147+
148+
- name: Resolve API jar path
149+
id: jar
150+
run: |
151+
jar=$(ls ./api/evidence-api-*.jar | head -1)
152+
echo "Found: $jar"
153+
echo "path=$jar" >> "$GITHUB_OUTPUT"
154+
155+
- name: Deploy API jar to App Service
156+
uses: azure/webapps-deploy@v3
157+
with:
158+
app-name: ${{ env.API_APP_NAME }}
159+
package: ${{ steps.jar.outputs.path }}
160+
type: jar
161+
162+
- name: Package SPA into zip
163+
run: |
164+
cd ./spa-dist
165+
zip -qr ../spa.zip .
166+
ls -lh ../spa.zip
167+
168+
- name: Deploy SPA zip to App Service
169+
uses: azure/webapps-deploy@v3
170+
with:
171+
app-name: ${{ env.SPA_APP_NAME }}
172+
package: ./spa.zip
173+
type: zip
174+
175+
- name: Smoke test
176+
run: |
177+
set -euo pipefail
178+
echo "Waiting 90s for warmup..."
179+
sleep 90
180+
181+
spa_url="https://${SPA_APP_NAME}.azurewebsites.net"
182+
api_url="https://${API_APP_NAME}.azurewebsites.net/api/cases"
183+
184+
spa_status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 "$spa_url" || echo "000")
185+
echo "SPA $spa_url => $spa_status"
186+
if [ "$spa_status" != "200" ]; then
187+
echo "::warning::SPA did not return 200 (got $spa_status); may still be warming up."
188+
fi
189+
190+
api_status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 30 "$api_url" || echo "000")
191+
echo "API $api_url => $api_status"
192+
# Anonymous /api/cases must return 401 (JWT enforced). 200 means auth
193+
# is broken; 5xx means the app failed to start or the storage path
194+
# regression is back.
195+
if [ "$api_status" != "401" ]; then
196+
echo "::error::API returned $api_status; expected 401 (JWT validation enforced)."
197+
exit 1
198+
fi

0 commit comments

Comments
 (0)