66 - master
77
88jobs :
9- # ──────────────────────────────────────────────────────────────────────────
10- # Job 1: Build and deploy the existing Python / Flask weather app.
11- # Uses secrets: AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUB_ID,
12- # GHCR_USERNAME, GHCR_PAT, CONTAINER_APP_NAME, RESOURCE_GROUP,
13- # AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT
14- # ──────────────────────────────────────────────────────────────────────────
15- build-and-deploy :
16- name : Build and Deploy to Azure Container Apps
9+ deploy-container-apps :
10+ name : Build and Deploy Python and Blazor Apps
1711 runs-on : ubuntu-latest
1812 permissions :
19- id-token : write # Require write permission to Fetch an OIDC token.
13+ id-token : write
2014
21- steps :
22- # Checkout the repository
23- - name : Checkout code
24- uses : actions/checkout@v3
25-
26- # Log in to Azure
27- - name : Log in to Azure
28- uses : azure/login@v1
29- with :
30- client-id : ${{ secrets.AZURE_CLIENT_ID }}
31- tenant-id : ${{ secrets.AZURE_TENANT_ID }}
32- subscription-id : ${{ secrets.AZURE_SUB_ID }}
33- enable-AzPSSession : true
34-
35- # Build the Docker image
36- - name : Build Docker image
37- run : |
38- docker build -t ghcr.io/${{ secrets.GHCR_USERNAME }}/weather-py:latest .
39-
40- # Push the Docker image to GitHub Container Registry
41- - name : Push Docker image to ACR
42- run : |
43- echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u ${{ secrets.GHCR_USERNAME }} --password-stdin
44- docker push ghcr.io/${{ secrets.GHCR_USERNAME }}/weather-py:latest
45-
46- # Deploy the container to Azure Container Apps
47- - name : Deploy to Azure Container Apps
48- run : |
49- az containerapp update \
50- --name ${{ secrets.CONTAINER_APP_NAME }} \
51- --resource-group ${{ secrets.RESOURCE_GROUP }} \
52- --image ghcr.io/${{ secrets.GHCR_USERNAME }}/weather-py:latest \
53- --set-env-vars \
54- "AZURE_OPENAI_API_KEY=${{secrets.AZURE_OPENAI_API_KEY}}" \
55- "AZURE_OPENAI_ENDPOINT=${{ secrets.AZURE_OPENAI_ENDPOINT }}" \
56- "AZURE_OPENAI_DEPLOYMENT=${{ secrets.AZURE_OPENAI_DEPLOYMENT }}" \
57- "AZURE_OPENAI_API_VERSION=2024-10-21"
58-
59- # ──────────────────────────────────────────────────────────────────────────
60- # Job 2: Build and deploy the Blazor Server weather app.
61- #
62- # This job builds the WeatherBlazor project (located in WeatherBlazor/) using
63- # Dockerfile.blazor and deploys it as a *separate* Azure Container App so that
64- # Azure assigns it its own unique FQDN/URL.
65- #
66- # Required repository secrets (in addition to those shared with Job 1):
67- # BLAZOR_CONTAINER_APP_NAME – Unique name for the Blazor Container App
68- # (e.g. "weather-blazor"). Must differ from
69- # CONTAINER_APP_NAME to receive a distinct URL.
70- # CONTAINER_APP_ENVIRONMENT – Name of the existing Azure Container Apps
71- # managed environment (shared with the Python app).
72- # WEATHER_API_KEY – API key for weatherapi.com used by the Blazor app.
73- #
74- # Example: once deployed, the Blazor app will be reachable at a URL such as
75- # https://weather-blazor.<unique-suffix>.<region>.azurecontainerapps.io
76- # ──────────────────────────────────────────────────────────────────────────
77- build-and-deploy-blazor :
78- name : Build and Deploy Blazor App to Azure Container Apps
79- runs-on : ubuntu-latest
80- permissions :
81- id-token : write # Require write permission to fetch an OIDC token.
15+ env :
16+ RESOURCE_GROUP : ${{ secrets.RESOURCE_GROUP }}
17+ PYTHON_APP_NAME : ${{ secrets.CONTAINER_APP_NAME }}
18+ BLAZOR_APP_NAME : ${{ secrets.BLAZOR_CONTAINER_APP_NAME }}
19+ GHCR_USERNAME : ${{ secrets.GHCR_USERNAME }}
20+ GHCR_PAT : ${{ secrets.GHCR_PAT }}
21+ PYTHON_IMAGE : ghcr.io/${{ secrets.GHCR_USERNAME }}/weather-py:latest
22+ BLAZOR_IMAGE : ghcr.io/${{ secrets.GHCR_USERNAME }}/weather-blazor:latest
8223
8324 steps :
84- # Checkout the repository so both Dockerfile.blazor and WeatherBlazor/ are available.
85- - name : Checkout code
86- uses : actions/checkout@v3
87-
88- # Log in to Azure using the same OIDC-based federated credentials as the Python job.
89- - name : Log in to Azure
90- uses : azure/login@v1
91- with :
92- client-id : ${{ secrets.AZURE_CLIENT_ID }}
93- tenant-id : ${{ secrets.AZURE_TENANT_ID }}
94- subscription-id : ${{ secrets.AZURE_SUB_ID }}
95- enable-AzPSSession : true
96-
97- # Build the Blazor Docker image using the dedicated multi-stage Dockerfile.
98- # The build context is the repository root so that WeatherBlazor/ is accessible.
99- - name : Build Blazor Docker image
100- run : |
101- docker build \
102- -f Dockerfile.blazor \
103- -t ghcr.io/${{ secrets.GHCR_USERNAME }}/weather-blazor:latest \
104- .
105-
106- # Authenticate to GHCR and push the Blazor image under a distinct tag
107- # (weather-blazor) so it does not overwrite the Python app image (weather-py).
108- - name : Push Blazor Docker image to GHCR
109- run : |
110- echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u ${{ secrets.GHCR_USERNAME }} --password-stdin
111- docker push ghcr.io/${{ secrets.GHCR_USERNAME }}/weather-blazor:latest
112-
113- # Deploy the Blazor container to a new (or existing) Azure Container App.
114- #
115- # Strategy:
116- # 1. Check whether the Blazor Container App already exists.
117- # 2. If it does NOT exist, create it inside the same Container Apps environment
118- # and resource group as the Python app, with external ingress on port 80.
119- # Azure will assign a unique public URL automatically.
120- # 3. If it already exists, update the image and environment variables.
121- #
122- # ASP.NET Core reads nested config via double-underscore environment variables
123- # (e.g. AzureOpenAI__ApiKey maps to AzureOpenAI:ApiKey in appsettings.json).
124- - name : Deploy Blazor app to Azure Container Apps
125- run : |
126- BLAZOR_APP="${{ secrets.BLAZOR_CONTAINER_APP_NAME }}"
127- RESOURCE_GROUP="${{ secrets.RESOURCE_GROUP }}"
128- IMAGE="ghcr.io/${{ secrets.GHCR_USERNAME }}/weather-blazor:latest"
129-
130- # Validate that required secrets are present before attempting deployment.
131- if [ -z "$BLAZOR_APP" ] || [ -z "$RESOURCE_GROUP" ]; then
132- echo "ERROR: BLAZOR_CONTAINER_APP_NAME and RESOURCE_GROUP secrets must be set."
133- exit 1
134- fi
135-
136- # Check whether the Blazor Container App already exists in the resource group.
137- APP_EXISTS=$(az containerapp show \
138- --name "$BLAZOR_APP" \
139- --resource-group "$RESOURCE_GROUP" \
140- --query "name" \
141- --output tsv 2>/dev/null || echo "")
142-
143- if [ -n "$APP_EXISTS" ]; then
144- # ── Update path: app exists, refresh image and environment variables. ──
145- echo "Updating existing Blazor Container App: $BLAZOR_APP"
25+ - name : Checkout code
26+ uses : actions/checkout@v4
27+
28+ - name : Validate deployment configuration
29+ run : |
30+ set -euo pipefail
31+
32+ required_vars=(
33+ RESOURCE_GROUP
34+ PYTHON_APP_NAME
35+ BLAZOR_APP_NAME
36+ GHCR_USERNAME
37+ GHCR_PAT
38+ )
39+
40+ for var_name in "${required_vars[@]}"; do
41+ if [ -z "${!var_name}" ]; then
42+ echo "ERROR: Required workflow value '$var_name' is not configured."
43+ exit 1
44+ fi
45+ done
46+
47+ - name : Log in to Azure
48+ uses : azure/login@v2
49+ with :
50+ client-id : ${{ secrets.AZURE_CLIENT_ID }}
51+ tenant-id : ${{ secrets.AZURE_TENANT_ID }}
52+ subscription-id : ${{ secrets.AZURE_SUB_ID }}
53+
54+ - name : Log in to GitHub Container Registry
55+ run : echo "$GHCR_PAT" | docker login ghcr.io -u "$GHCR_USERNAME" --password-stdin
56+
57+ - name : Build Python Docker image
58+ run : docker build -t "$PYTHON_IMAGE" .
59+
60+ - name : Push Python Docker image
61+ run : docker push "$PYTHON_IMAGE"
62+
63+ - name : Deploy Python app to Azure Container Apps
64+ run : |
65+ set -euo pipefail
66+
14667 az containerapp update \
147- --name "$BLAZOR_APP " \
68+ --name "$PYTHON_APP_NAME " \
14869 --resource-group "$RESOURCE_GROUP" \
149- --image "$IMAGE " \
70+ --image "$PYTHON_IMAGE " \
15071 --set-env-vars \
151- "WeatherApiKey=${{ secrets.WEATHER_API_KEY }}" \
152- "AzureOpenAI__ApiKey=${{ secrets.AZURE_OPENAI_API_KEY }}" \
153- "AzureOpenAI__Endpoint=${{ secrets.AZURE_OPENAI_ENDPOINT }}" \
154- "AzureOpenAI__Deployment=${{ secrets.AZURE_OPENAI_DEPLOYMENT }}" \
155- "AzureOpenAI__ApiVersion=2024-10-21"
156- else
157- # ── Create path: first-time deployment; provision a new Container App. ──
158- echo "Creating new Blazor Container App: $BLAZOR_APP"
159- az containerapp create \
160- --name "$BLAZOR_APP" \
72+ "AZURE_OPENAI_API_KEY=${{ secrets.AZURE_OPENAI_API_KEY }}" \
73+ "AZURE_OPENAI_ENDPOINT=${{ secrets.AZURE_OPENAI_ENDPOINT }}" \
74+ "AZURE_OPENAI_DEPLOYMENT=${{ secrets.AZURE_OPENAI_DEPLOYMENT }}" \
75+ "AZURE_OPENAI_API_VERSION=2024-10-21"
76+
77+ - name : Build Blazor Docker image
78+ run : docker build -f Dockerfile.blazor -t "$BLAZOR_IMAGE" .
79+
80+ - name : Push Blazor Docker image
81+ run : docker push "$BLAZOR_IMAGE"
82+
83+ - name : Resolve existing Container Apps environment
84+ id : container-env
85+ run : |
86+ set -euo pipefail
87+
88+ MANAGED_ENVIRONMENT_ID=$(az containerapp show \
89+ --name "$PYTHON_APP_NAME" \
90+ --resource-group "$RESOURCE_GROUP" \
91+ --query "properties.managedEnvironmentId" \
92+ --output tsv)
93+
94+ if [ -z "$MANAGED_ENVIRONMENT_ID" ]; then
95+ echo "ERROR: Unable to resolve the managed environment from $PYTHON_APP_NAME."
96+ exit 1
97+ fi
98+
99+ echo "managed_environment_id=$MANAGED_ENVIRONMENT_ID" >> "$GITHUB_OUTPUT"
100+
101+ - name : Deploy Blazor app to Azure Container Apps
102+ env :
103+ MANAGED_ENVIRONMENT_ID : ${{ steps.container-env.outputs.managed_environment_id }}
104+ run : |
105+ set -euo pipefail
106+
107+ APP_EXISTS=$(az containerapp show \
108+ --name "$BLAZOR_APP_NAME" \
161109 --resource-group "$RESOURCE_GROUP" \
162- --environment "${{ secrets.CONTAINER_APP_ENVIRONMENT }}" \
163- --image "$IMAGE" \
164- --target-port 80 \
165- --ingress external \
166- --registry-server ghcr.io \
167- --registry-username "${{ secrets.GHCR_USERNAME }}" \
168- --registry-password "${{ secrets.GHCR_PAT }}" \
169- --env-vars \
170- "WeatherApiKey=${{ secrets.WEATHER_API_KEY }}" \
171- "AzureOpenAI__ApiKey=${{ secrets.AZURE_OPENAI_API_KEY }}" \
172- "AzureOpenAI__Endpoint=${{ secrets.AZURE_OPENAI_ENDPOINT }}" \
173- "AzureOpenAI__Deployment=${{ secrets.AZURE_OPENAI_DEPLOYMENT }}" \
174- "AzureOpenAI__ApiVersion=2024-10-21"
175- fi
110+ --query "name" \
111+ --output tsv 2>/dev/null || true)
112+
113+ if [ -n "$APP_EXISTS" ]; then
114+ echo "Updating existing Blazor Container App: $BLAZOR_APP_NAME"
115+ az containerapp update \
116+ --name "$BLAZOR_APP_NAME" \
117+ --resource-group "$RESOURCE_GROUP" \
118+ --image "$BLAZOR_IMAGE" \
119+ --set-env-vars \
120+ "WeatherApiKey=${{ secrets.WEATHER_API_KEY }}" \
121+ "AzureOpenAI__ApiKey=${{ secrets.AZURE_OPENAI_API_KEY }}" \
122+ "AzureOpenAI__Endpoint=${{ secrets.AZURE_OPENAI_ENDPOINT }}" \
123+ "AzureOpenAI__Deployment=${{ secrets.AZURE_OPENAI_DEPLOYMENT }}" \
124+ "AzureOpenAI__ApiVersion=2024-10-21"
125+ else
126+ echo "Creating new Blazor Container App: $BLAZOR_APP_NAME"
127+ az containerapp create \
128+ --name "$BLAZOR_APP_NAME" \
129+ --resource-group "$RESOURCE_GROUP" \
130+ --environment "$MANAGED_ENVIRONMENT_ID" \
131+ --image "$BLAZOR_IMAGE" \
132+ --target-port 80 \
133+ --ingress external \
134+ --registry-server ghcr.io \
135+ --registry-username "$GHCR_USERNAME" \
136+ --registry-password "$GHCR_PAT" \
137+ --env-vars \
138+ "WeatherApiKey=${{ secrets.WEATHER_API_KEY }}" \
139+ "AzureOpenAI__ApiKey=${{ secrets.AZURE_OPENAI_API_KEY }}" \
140+ "AzureOpenAI__Endpoint=${{ secrets.AZURE_OPENAI_ENDPOINT }}" \
141+ "AzureOpenAI__Deployment=${{ secrets.AZURE_OPENAI_DEPLOYMENT }}" \
142+ "AzureOpenAI__ApiVersion=2024-10-21"
143+ fi
0 commit comments