-
Notifications
You must be signed in to change notification settings - Fork 0
160 lines (152 loc) · 6.92 KB
/
Copy pathdeploy.yml
File metadata and controls
160 lines (152 loc) · 6.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
name: CI/CD — Build, Test & Deploy
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
inputs:
deploy_infra:
description: 'Apply infra/main.bicep (az deployment sub create) after build'
type: boolean
default: false
# Block any other workflow or process from acquiring this lock while a deploy is
# in flight — App Service restarts are exclusive, so a parallel run can leave the
# slot in a half-upgraded state.
concurrency:
group: deploy-${{ github.repository }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
# OIDC federated credential — the workflow exchanges the GitHub OIDC token
# for an Azure access token via azure/login@v2; no client secret is stored.
id-token: write
env:
DOTNET_VERSION: '10.0.x'
AZURE_WEBAPP_NAME: app-porepolinetracker
# Casing matters: the runner is Linux, and the project is .API (not .Api).
API_PROJECT_PATH: src/PoRepoLineTracker.API/PoRepoLineTracker.API.csproj
jobs:
# Compile every Bicep file to ARM on every run — fast, no Azure login, and it
# catches template/type errors (and the kind of drift that broke prod) before
# any merge. Runs in parallel with the build so it does not add wall-clock time.
lint-infra:
name: Lint Infra (Bicep)
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
- name: Install Bicep CLI
run: az bicep install
- name: Compile Bicep → ARM (catches template errors)
run: |
az bicep build --file infra/main.bicep --stdout > /dev/null
az bicep build --file infra/resources.bicep --stdout > /dev/null
build:
name: Build
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
# NuGet cache — keyed on csproj + Directory.Packages.props, so a code-only
# change still reuses the cache and skips the network restore.
- name: Cache NuGet packages
uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', 'Directory.Packages.props') }}
restore-keys: nuget-${{ runner.os }}-
- name: Restore
run: dotnet restore
- name: Build (Release, no-restore, treat-warnings-as-errors)
run: dotnet build --no-restore -c Release
# Unit tier gates the deploy: no I/O, no Docker, ~1s, so there is no reason to ship
# without it. The Integration/E2E tiers still run locally and in the Test environment —
# they need Azurite via Testcontainers and a live host.
- name: Test (Unit)
run: dotnet test tests/PoRepoLineTracker.Unit --no-build -c Release --verbosity minimal
# Publish fresh every run. (Publish-output caching was removed: its key did not
# hash *.cs, so pure C# changes hit the cache and shipped stale binaries.)
- name: Publish API
run: dotnet publish ${{ env.API_PROJECT_PATH }} -c Release --no-restore -o publish
- name: Upload webapp artifact
uses: actions/upload-artifact@v7
with:
name: webapp
path: publish
retention-days: 1
deploy:
name: Deploy to Azure
runs-on: ubuntu-latest
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
timeout-minutes: 15
steps:
# Needed for infra/ — this job used to consume only the build artifact, so the Bicep
# the provision step applies would not otherwise be on disk.
- uses: actions/checkout@v6
- uses: actions/download-artifact@v8
with:
name: webapp
path: publish
# OIDC federated identity — no client secrets stored in GitHub.
# Federated credential subject: repo:punkouter26/PoRepoLineTracker:ref:refs/heads/master
- uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
# Apply the Bicep, do not merely compile it. lint-infra proves the templates are VALID;
# nothing here ever proved they were APPLIED. The App Service went missing and every run
# kept publishing green while the deploy step failed with "Resource app-porepolinetracker
# of type Microsoft.Web/Sites doesn't exist" — the pipeline had no way to create it.
# Idempotent: a no-op when the infra already matches. The apply-infra job below stays as
# the manual path, for previewing a change with what-if before pushing it.
- name: Provision infra (Bicep)
run: |
az deployment sub create \
--location eastus2 \
--template-file infra/main.bicep \
--name porepolinetracker-${{ github.run_id }} \
-o none
- name: Package and zip deploy
run: |
cd publish
zip -r ../webapp.zip . -q
cd ..
- name: Deploy to App Service
uses: azure/webapps-deploy@v3
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }}
package: webapp.zip
# Async deploy returns once the deployment is accepted, which removes the
# synchronous Kudu 504 risk. Nothing in this workflow verifies liveness
# afterwards — the post-deploy smoke test that used to do so was removed.
# The startup command (/home/site/wwwroot/startup.sh, which installs git before exec-ing
# the app) is owned by infra/resources.bicep — a step here used to re-set it after every
# provision because the bicep said `dotnet …dll`; two owners flip-flopped the value.
# Apply infrastructure (resource group, app settings, role assignments) from Bicep.
# Manual only: run the workflow via "Run workflow" with deploy_infra=true. This keeps
# surprise infra changes out of routine code deploys while making provisioning repeatable.
apply-infra:
name: Apply Infra (manual)
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' && inputs.deploy_infra
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
# Unique, per-run deployment name. A subscription-scoped deployment object is
# pinned to the location it was first created in, so a fixed name (e.g. "main")
# cannot move regions — use a fresh name each run to avoid InvalidDeploymentLocation.
- name: What-if (preview changes)
run: az deployment sub what-if --name "infra-${{ github.run_id }}" --location eastus2 --template-file infra/main.bicep || true
- name: Deploy
run: az deployment sub create --name "infra-${{ github.run_id }}" --location eastus2 --template-file infra/main.bicep