|
| 1 | +# Azure DevOps Deployment Guide |
| 2 | + |
| 3 | +This repo already contains the pieces needed for Azure DevOps CI, self-test, image build, and demo deployment. This guide explains how the parts fit together and how to run them without reading the pipeline YAML first. |
| 4 | + |
| 5 | +## What This Covers |
| 6 | + |
| 7 | +- CI on Azure Pipelines with `uv`, `just`, `ruff`, `mypy`, and `pytest` |
| 8 | +- stable pipeline artifacts under `artifacts/` |
| 9 | +- self-test runs that emit derived ReqIF, SARIF, summary JSON, and evidence |
| 10 | +- container image build and push to Azure Container Registry |
| 11 | +- demo deployment to Azure Container Apps |
| 12 | +- smoke testing through the live `/health` endpoint |
| 13 | + |
| 14 | +```mermaid |
| 15 | +flowchart LR |
| 16 | + CODE[Repo] --> CI[CI stage] |
| 17 | + CI --> SELFTEST[Self-test stage] |
| 18 | + SELFTEST --> IMAGE[Image stage] |
| 19 | + IMAGE --> DEPLOY[Deploy demo stage] |
| 20 | + DEPLOY --> SMOKE[Smoke demo stage] |
| 21 | + SELFTEST --> ARTIFACTS[Published artifacts] |
| 22 | + SMOKE --> ARTIFACTS |
| 23 | +``` |
| 24 | + |
| 25 | +## Deployment Model |
| 26 | + |
| 27 | +The Azure path is intentionally simple: |
| 28 | + |
| 29 | +- build one container image |
| 30 | +- publish one stable artifact tree |
| 31 | +- deploy one demo app |
| 32 | +- smoke test the running endpoint |
| 33 | + |
| 34 | +```mermaid |
| 35 | +flowchart TD |
| 36 | + DEV[Developer or PR] --> PIPE[azure-pipelines.yml] |
| 37 | + PIPE --> TESTS[artifacts/tests] |
| 38 | + PIPE --> DEMO[artifacts/demo] |
| 39 | + PIPE --> SELF[artifacts/selftest] |
| 40 | + PIPE --> ACR[Azure Container Registry] |
| 41 | + ACR --> ACA[Azure Container Apps] |
| 42 | + ACA --> HEALTH[GET /health] |
| 43 | +``` |
| 44 | + |
| 45 | +## Prerequisites |
| 46 | + |
| 47 | +You need these before enabling deploy stages: |
| 48 | + |
| 49 | +- an Azure DevOps project and pipeline connected to this repo |
| 50 | +- an Azure service connection with permission to deploy to the target subscription |
| 51 | +- an existing Azure resource group, or permission to create one |
| 52 | +- permission to create: |
| 53 | + - Azure Container Registry |
| 54 | + - Azure Container Apps environment and app |
| 55 | + - Log Analytics workspace |
| 56 | + - Key Vault |
| 57 | + |
| 58 | +Repo-side prerequisites: |
| 59 | + |
| 60 | +- `azure-pipelines.yml` |
| 61 | +- `ado/templates/` |
| 62 | +- `infra/main.bicep` |
| 63 | +- `.env.demo.example` |
| 64 | +- `Dockerfile` |
| 65 | + |
| 66 | +## Quick Start |
| 67 | + |
| 68 | +### 1. Run the local preflight |
| 69 | + |
| 70 | +This verifies the same command surface the pipeline uses. |
| 71 | + |
| 72 | +```bash |
| 73 | +just ci-check |
| 74 | +just selftest-suite |
| 75 | +just demo-artifacts |
| 76 | +``` |
| 77 | + |
| 78 | +### 2. Configure Azure DevOps variables |
| 79 | + |
| 80 | +Set these pipeline variables: |
| 81 | + |
| 82 | +| Variable | Purpose | Example | |
| 83 | +| --- | --- | --- | |
| 84 | +| `azureServiceConnection` | Azure DevOps service connection name | `sc-reqif-opa-demo` | |
| 85 | +| `azureResourceGroup` | target resource group | `rg-reqif-opa-demo` | |
| 86 | +| `azureLocation` | Azure region | `australiaeast` | |
| 87 | +| `acrName` | Azure Container Registry name | `reqifopademoacr` | |
| 88 | +| `imageRepository` | repository name inside ACR | `reqif-opa-mcp` | |
| 89 | +| `containerAppEnvironmentName` | Container Apps environment | `reqif-opa-demo-env` | |
| 90 | +| `containerAppName` | deployed app name | `reqif-opa-mcp-demo` | |
| 91 | +| `containerPort` | exposed app port | `8000` | |
| 92 | + |
| 93 | +If `azureServiceConnection` is empty, the deploy stages are skipped and the pipeline behaves as CI plus self-test only. The image/deploy/smoke stages also require a non-empty `acrName` and only run on non–Pull Request builds (i.e., not when `Build.Reason == PullRequest`). |
| 94 | + |
| 95 | +### 3. Run the pipeline in CI and self-test mode |
| 96 | + |
| 97 | +Leave `azureServiceConnection` empty for the first run. This validates: |
| 98 | + |
| 99 | +- dependency restore via `uv` |
| 100 | +- CI checks |
| 101 | +- self-test outputs |
| 102 | +- artifact publishing |
| 103 | + |
| 104 | +### 4. Enable image and deploy stages |
| 105 | + |
| 106 | +Once CI and self-test pass, set `azureServiceConnection` and `acrName` and rerun a non‑PR build (for example, on `main`). In that case, the pipeline will: |
| 107 | + |
| 108 | +- build and push the image to ACR |
| 109 | +- deploy or update the Azure Container App |
| 110 | +- call `/health` |
| 111 | +- publish live smoke artifacts |
| 112 | + |
| 113 | +Pull Request (`Build.Reason == PullRequest`) validation runs will still execute CI and self-test stages only; image, deploy, and smoke stages are skipped for PR builds. |
| 114 | + |
| 115 | +## Pipeline Stages |
| 116 | + |
| 117 | +```mermaid |
| 118 | +flowchart LR |
| 119 | + CI[ci] --> ST[selftest] |
| 120 | + ST --> IMG[image] |
| 121 | + IMG --> DEP[deploy_demo] |
| 122 | + DEP --> SMK[smoke_demo] |
| 123 | +``` |
| 124 | + |
| 125 | +### `ci` |
| 126 | + |
| 127 | +Defined by `ado/templates/ci.yml`. |
| 128 | + |
| 129 | +Runs: |
| 130 | + |
| 131 | +- `uv sync` |
| 132 | +- `just ci-check` |
| 133 | +- `uv run ruff check .` |
| 134 | +- `uv run mypy reqif_mcp reqif_ingest_cli` |
| 135 | + |
| 136 | +Publishes: |
| 137 | + |
| 138 | +- `artifacts/tests/junit.xml` |
| 139 | +- `artifacts/tests/ruff.txt` |
| 140 | +- `artifacts/tests/mypy.txt` |
| 141 | + |
| 142 | +### `selftest` |
| 143 | + |
| 144 | +Defined by `ado/templates/selftest.yml`. |
| 145 | + |
| 146 | +Runs: |
| 147 | + |
| 148 | +- `just demo-artifacts "artifacts/demo" "artifacts/selftest" "<enforceGateFailures>"` |
| 149 | + |
| 150 | +This produces: |
| 151 | + |
| 152 | +- derived ReqIF samples |
| 153 | +- compliance summaries |
| 154 | +- merged SARIF |
| 155 | +- evidence outputs |
| 156 | +- demo summary files |
| 157 | + |
| 158 | +### `image` |
| 159 | + |
| 160 | +Defined in `azure-pipelines.yml`. |
| 161 | + |
| 162 | +Runs: |
| 163 | + |
| 164 | +- `docker build` |
| 165 | +- `docker push` |
| 166 | + |
| 167 | +Tags: |
| 168 | + |
| 169 | +- commit SHA |
| 170 | +- `latest` |
| 171 | + |
| 172 | +### `deploy_demo` |
| 173 | + |
| 174 | +Defined by `ado/templates/deploy-container-app.yml`. |
| 175 | + |
| 176 | +Runs: |
| 177 | + |
| 178 | +- `az deployment group create --template-file infra/main.bicep` |
| 179 | +- deploys the current image into Azure Container Apps |
| 180 | + |
| 181 | +Publishes: |
| 182 | + |
| 183 | +- `artifacts/deploy/container_app_url.txt` |
| 184 | + |
| 185 | +### `smoke_demo` |
| 186 | + |
| 187 | +Defined in `azure-pipelines.yml`. |
| 188 | + |
| 189 | +Runs: |
| 190 | + |
| 191 | +- resolves the deployed Container App FQDN |
| 192 | +- `GET /health` |
| 193 | +- stores the live response in `artifacts/demo/live/health.json` |
| 194 | + |
| 195 | +## Artifact Contract |
| 196 | + |
| 197 | +The Azure path depends on stable artifact locations. |
| 198 | + |
| 199 | +```mermaid |
| 200 | +flowchart TD |
| 201 | + ROOT[artifacts] --> TESTS[tests] |
| 202 | + ROOT --> SELF[selftest] |
| 203 | + ROOT --> DEMO[demo] |
| 204 | + ROOT --> DEPLOY[deploy] |
| 205 | +
|
| 206 | + TESTS --> JUNIT[junit.xml] |
| 207 | + TESTS --> LINT[ruff.txt] |
| 208 | + TESTS --> TYPE[mypy.txt] |
| 209 | + SELF --> SELF_SUM[selftest_summary.md and json] |
| 210 | + DEMO --> DEMO_SUM[demo_summary.md and json] |
| 211 | + DEMO --> REQIF[reqif outputs] |
| 212 | + DEPLOY --> URL[container_app_url.txt] |
| 213 | +``` |
| 214 | + |
| 215 | +Primary locations: |
| 216 | + |
| 217 | +| Path | Contents | |
| 218 | +| --- | --- | |
| 219 | +| `artifacts/tests/` | JUnit, lint, and typecheck outputs | |
| 220 | +| `artifacts/selftest/` | gate outputs, SARIF, evidence, self-test summary | |
| 221 | +| `artifacts/demo/` | demo summary plus copied self-test and derived ReqIF outputs | |
| 222 | +| `artifacts/deploy/` | deployment metadata such as the live app URL | |
| 223 | + |
| 224 | +See `artifacts/README.md` for the file-level contract. |
| 225 | + |
| 226 | +## Container Runtime |
| 227 | + |
| 228 | +The demo image is now meant to be useful by itself. It packages: |
| 229 | + |
| 230 | +- `reqif_mcp/` |
| 231 | +- `reqif_ingest_cli/` |
| 232 | +- `agents/` |
| 233 | +- `schemas/` |
| 234 | +- `samples/` |
| 235 | +- `opa-bundles/` |
| 236 | + |
| 237 | +```mermaid |
| 238 | +flowchart LR |
| 239 | + IMG[Docker image] --> SERVER[reqif_mcp HTTP server] |
| 240 | + IMG --> INGEST[reqif_ingest_cli] |
| 241 | + IMG --> BUNDLES[OPA bundles] |
| 242 | + IMG --> SAMPLES[tracked samples] |
| 243 | + SERVER --> HEALTH[/health] |
| 244 | +``` |
| 245 | + |
| 246 | +Container health is based on a real HTTP probe: |
| 247 | + |
| 248 | +- `GET http://127.0.0.1:8000/health` |
| 249 | + |
| 250 | +This is the probe Azure Container Apps and local Docker smoke tests rely on. |
| 251 | + |
| 252 | +## Infrastructure |
| 253 | + |
| 254 | +Infrastructure is defined in `infra/main.bicep`. |
| 255 | + |
| 256 | +Provisioned resources: |
| 257 | + |
| 258 | +- Azure Container Registry |
| 259 | +- Key Vault |
| 260 | +- Log Analytics workspace |
| 261 | +- Container Apps managed environment |
| 262 | +- Azure Container App |
| 263 | + |
| 264 | +```mermaid |
| 265 | +flowchart TD |
| 266 | + RG[Resource group] --> ACR[Container Registry] |
| 267 | + RG --> KV[Key Vault] |
| 268 | + RG --> LAW[Log Analytics] |
| 269 | + RG --> ENV[Container Apps environment] |
| 270 | + ENV --> APP[Container App] |
| 271 | + ACR --> APP |
| 272 | + LAW --> ENV |
| 273 | +``` |
| 274 | + |
| 275 | +Current defaults are pragmatic for demos, not hardened production settings: |
| 276 | + |
| 277 | +- ACR uses admin credentials |
| 278 | +- Container App scales from zero to one replica |
| 279 | +- public ingress is enabled |
| 280 | +- deploy stages are conditional on pipeline variables |
| 281 | + |
| 282 | +## Files To Know |
| 283 | + |
| 284 | +| File | Purpose | |
| 285 | +| --- | --- | |
| 286 | +| `README-azure.md` | top-level Azure DevOps deployment guide | |
| 287 | +| `ado/README.md` | concise Azure folder map | |
| 288 | +| `azure-pipelines.yml` | stage orchestration | |
| 289 | +| `ado/templates/ci.yml` | CI job template | |
| 290 | +| `ado/templates/selftest.yml` | self-test and demo artifact template | |
| 291 | +| `ado/templates/deploy-container-app.yml` | deployment template | |
| 292 | +| `infra/main.bicep` | Azure infrastructure | |
| 293 | +| `.env.demo.example` | local and Azure demo settings | |
| 294 | +| `artifacts/README.md` | artifact contract | |
| 295 | + |
| 296 | +## Demo Flow |
| 297 | + |
| 298 | +For a live demo, the shortest reliable path is: |
| 299 | + |
| 300 | +1. run `just ci-check` |
| 301 | +2. run `just demo-artifacts` |
| 302 | +3. run the Azure pipeline with deploy disabled |
| 303 | +4. inspect published artifacts |
| 304 | +5. enable deploy variables |
| 305 | +6. rerun and open the Container App URL from `artifacts/deploy/container_app_url.txt` |
| 306 | +7. verify the live health artifact in `artifacts/demo/live/health.json` |
| 307 | + |
| 308 | +## Operational Notes |
| 309 | + |
| 310 | +- self-test enforcement is parameterized; demos can publish artifacts even when the gate surfaces issues |
| 311 | +- the self-test stage is useful even without Azure deployment |
| 312 | +- the deploy path is intentionally separate from GitHub Actions so Azure DevOps can be evaluated on its own terms |
| 313 | +- the current design is container-first and artifact-first; that is what makes demo and review flows predictable |
| 314 | + |
| 315 | +## Recommended Next Hardening Steps |
| 316 | + |
| 317 | +For production use, the next changes should be: |
| 318 | + |
| 319 | +- replace ACR admin credentials with managed identity |
| 320 | +- add authenticated ingress or network restriction in front of the app |
| 321 | +- publish SARIF into Azure DevOps security tooling if that becomes part of the review workflow |
| 322 | +- split demo and production parameter files |
| 323 | +- add environment-specific approvals in Azure DevOps Environments |
| 324 | + |
0 commit comments