Skip to content

Add the Web App with App Configuration and Key Vault sample (Python and .NET) - #121

Merged
paolosalvatori merged 7 commits into
mainfrom
feature/web-app-app-configuration
Sep 11, 2026
Merged

Add the Web App with App Configuration and Key Vault sample (Python and .NET)#121
paolosalvatori merged 7 commits into
mainfrom
feature/web-app-app-configuration

Conversation

@paolosalvatori

@paolosalvatori paolosalvatori commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Motivation

The repository had no sample showing Azure App Configuration, its Key Vault references, and the LocalStack for Azure emulator's implementation of them. This PR adds samples/web-app-app-configuration (Python and .NET): the Vacation Planner web app of the PostgreSQL flexible server sample, with its five PostgreSQL connection settings moved out of the Web App's app settings. PG_HOST, PG_PORT and PG_DATABASE are key-values of an Azure App Configuration store; PG_USER and PG_PASSWORD are Key Vault secrets (pg-user, pg-password) exposed through App Configuration Key Vault references. The app loads them at startup with the App Configuration provider (Python: azure-appconfiguration-provider, .NET: Microsoft.Azure.AppConfiguration.AspNetCore), authenticating to the store and to Key Vault with one DefaultAzureCredential that resolves to a user-assigned managed identity (AZURE_CLIENT_ID), and the provider resolves the Key Vault references itself. Both stores are reached through Private Endpoints with their Private DNS Zones linked to the sample's VNet, and the identity is authorized with App Configuration Data Reader on the store and Key Vault Secrets User on the vault. No connection string, access key or database credential appears in the app settings.

Fixes SMF-886

Changes

  • New sample folder samples/web-app-app-configuration/{python,dotnet}, copied from web-app-postgresql-flexible-server and adapted; the original sample is untouched.
  • README.md, scripts/README.md, bicep/README.md, terraform/README.md per language, rewritten for the new topology: configuration design, Azure-side prerequisites (roles of the deploying principal, globally unique names and the SUFFIX override, propagation, soft delete, costs), troubleshooting, and the App Service @Microsoft.AppConfiguration(...) references documented as the Azure-only alternative (the emulator does not resolve them yet).
  • scripts/deploy.sh: user-assigned identity, App Configuration store (public network access enabled explicitly), RBAC Key Vault with soft-delete recovery, Key Vault Secrets Officer for the deploying principal, the two secrets, the five key-values (az appconfig kv set / set-keyvault), the identity's two role assignments, three Private DNS zones/links/endpoints/zone groups, the usual PostgreSQL bootstrap, the web app created with --assign-identity, and diagnostics for the store and the vault. validate.sh lists all of it and fails if a PG_* app setting exists or the endpoint and client id settings are missing.
  • bicep/: new modules managed-identity, key-vault (secrets from @secure() parameters, Key Vault Secrets User, optional deployer Key Vault Secrets Officer) and app-configuration (keyValues children including the two Key Vault references, App Configuration Data Reader); web-app takes the identity and the store endpoint; zone groups are named default in every mode; deploy.sh recovers soft-deleted stores and vaults, asserts the five key-values and waits for the database before the psql bootstrap. Every value a module resource uses is a parameter with a default (SKU families, network ACL actions, principal types, role definition ids, DNS zone link names, delegation names, NSG rules, diagnostic categories and retention, WEBSITES_PORT, Oryx build flags, secret and key names, the Key Vault reference content type); main.bicep exposes the sample-level ones (pgUserSecretName, pg*KeyName, keyVaultReferenceContentType, websitesPort, the three Private DNS zone names and Private Endpoint group ids) and az bicep build reports no warning.
  • terraform/: azurerm 5.1.0 for everything, Azure/azapi 2.12.0 only for the five Microsoft.AppConfiguration/configurationStores/keyValues@2024-06-01 key-values (azurerm's azurerm_app_configuration_key cannot read back against the emulator), and a dual-target provider configuration driven by ARM_* variables that deploy.sh exports only for the emulator. One module per resource type, following the layout of the other Terraform samples: managed_identity, key_vault (vault, one azurerm_key_vault_secret per entry of a sensitive secrets map, the optional deployer Key Vault Secrets Officer assignment with its time_sleep, diagnostics), app_configuration (store, one azapi_resource per entry of a key_values map, diagnostics) and role_assignment (one instance per grant), next to the inherited virtual_network, nat_gateway, network_security_group, private_dns_zone, private_endpoint, postgres_flexible_server, app_service_plan, web_app and log_analytics modules. main.tf only composes modules; no resource block carries a literal: every value is a variable with a default in the module's variables.tf or in the root variables.tf (link names, delegation, network policies, allocation methods, principal types, role names, secret and key names, content type, Oryx flags, diagnostic categories).
  • src/: Python settings.py and .NET Services/AppConfigurationSettings.cs load PG_* with the provider (bounded retries, one log line with the keys loaded and the number of Key Vault references resolved, never the values); database.py / PostgresOptions read the loaded configuration instead of the environment.
  • run-samples.sh: six new entries (scripts, Terraform and Bicep for both languages), amd64 only like every other web-app-* sample. Root README.md: one Outline row.
  • Fixes to inherited code found while running on Azure: the PostgreSQL firewall rule and private endpoint now wait for the database (Azure runs one operation at a time on a flexible server and answered ServerIsBusy), the Terraform server ignores the Azure-assigned availability zone on later plans, and the CLI firewall-rule create uses the current --server-name/--name arguments (the inherited form failed silently on every run).
  • images/ and visio/ hold the copied originals as placeholders. The new diagram should show: the resource group with the VNet (app-subnet with the NAT gateway and its public IP prefix, pe-subnet with the three Private Endpoints), the two NSGs and the Log Analytics workspace; the PostgreSQL flexible server <prefix>-pgflex-<suffix> behind <prefix>-postgres-pe-<suffix> and privatelink.postgres.database.azure.com; the App Configuration store <prefix>-appconfig-<suffix> behind <prefix>-appconfig-pe-<suffix> and privatelink.azconfig.io; the Key Vault <prefix>-keyvault-<suffix> behind <prefix>-keyvault-pe-<suffix> and privatelink.vaultcore.azure.net; each zone linked to the VNet as link-to-vnet and each endpoint with a default zone group; the user-assigned identity <prefix>-identity-<suffix> attached to the web app with the two role arrows (App Configuration Data Reader to the store, Key Vault Secrets User to the vault); inside the store the key-values PG_HOST, PG_PORT, PG_DATABASE and the Key Vault references PG_USER and PG_PASSWORD pointing at the secrets pg-user and pg-password; the web app's read path (provider to the store through its private endpoint, then to the vault through its private endpoint) and its data path to PostgreSQL; the deployment-time seeding from the deploy machine (psql role and schema, az keyvault secret set, az appconfig kv set / set-keyvault, or the template equivalents).
  • Review follow-ups (commits 9b7e9db, 3441c0c): the eight GitHub Copilot findings are fixed and their threads resolved (Terraform log_analytics retention variable passed through, is_manual_connection typed as bool, the web_app principal_id output documented as the system-assigned id and null otherwise, the Python delete route and form keyed by the database id instead of the list index); the Terraform and main READMEs say to purge the soft-deleted vault and store before re-running the Terraform variant after a resource-group deletion, because the azurerm provider recovers the vault with its secrets and azurerm_key_vault_secret then refuses to adopt them (verified to be the provider's behaviour on the emulator built from main as on Azure).
  • Review follow-up (commit 0075d43, requested by @paolosalvatori): the Terraform variants no longer create the App Configuration store, the Key Vault, the secrets, the key-values, the identity and the role assignments in main.tf; they use the new managed_identity, key_vault, app_configuration and role_assignment modules, and no resource block in the Terraform or Bicep modules carries a literal any more (variables and parameters with defaults everywhere, see the two bullets above). Both variants were re-tested on the emulator and the Python variants on Azure (rows dated 2026-09-11 below).

Tests

Full matrix: 2 languages x 3 provisioning modes x 2 targets, each cell with validate.sh, the web app call paths, an add/edit/delete round trip, the app's configuration log line, a second deployment, and on Azure the private DNS resolution from inside the App Service container and the cleanup. All twelve cells pass; the six harness entries pass through make test SHARD=<n> SPLITS=65.

Emulator: localstack/localstack-azure:latest (image b3aa65e28c75, pulled 2026-09-10), LocalStack CLI 4.7.0, az 2.87.0, Terraform 1.13.5, azurerm 5.1.0, azapi 2.12.0, .NET SDK 10.0.103, Python 3.13.
Azure: subscription "Azure subscription 1" (8a733b0d-...), region westeurope, service principal login in an isolated AZURE_CONFIG_DIR, SUFFIX ps10, one PREFIX per cell.

Cell Target Command Date (UTC) Outcome
python / scripts emulator bash scripts/deploy.sh, validate.sh, call-web-app.sh, CRUD probe, re-run 2026-09-10 14:26-14:40 pass: validate exit 0, 4/4 call paths, add/edit/delete, log "Loaded 5 settings ... 2 Key Vault references resolved", re-run 63 already-exists probes
python / bicep emulator (fresh) bash deploy.sh, ../scripts/validate.sh, call-web-app.sh, CRUD, re-run 2026-09-10 17:36-17:45 pass: validate exit 0, 4/4, CRUD, re-run exit 0 with key-values unchanged (on the published image the second run reported the keyVault and appConfiguration module deployments Failed under a Succeeded parent, see the emulator fixes below)
python / terraform emulator (fresh) bash deploy.sh (48 resources), validate, call, CRUD, second plan, re-run, terraform destroy 2026-09-10 17:49-18:05 pass: AzAPI key-values created, second plan shows no diff on key-values/secrets/role assignments (inherited read-back drift only), destroy removed 48 resources incl. the 5 AzAPI key-values, no leftover store/vault
dotnet / scripts emulator (fresh) same as python 2026-09-10 18:00-18:10 pass: validate 0, 4/4, CRUD (antiforgery token), /health ok, log line, re-run 63 probes
dotnet / bicep emulator (fresh) same as python 2026-09-10 18:12-18:20 pass
dotnet / terraform emulator (fresh) same as python + destroy 2026-09-10 18:20-18:32 pass, second plan shows the same inherited drift only, destroy clean
harness emulator make test SHARD={19,20,42,43,61,62} SPLITS=65 2026-09-10 18:32-19:20 all six entries pass (each on a fresh emulator; back-to-back runs on one emulator fail on leaked role assignments/secrets, gaps 6 and 10)
python / scripts Azure (appcfg-rg) PREFIX=appcfg SUFFIX=ps10 bash scripts/deploy.sh, validate, curl, CRUD, Kudu getent, re-run, cleanup 2026-09-10 18:28-19:22 pass after adding --enable-public-network true (store lost public access when its private endpoint was created); validate 0 errors, HTTP 200 with 9 rows, add/edit/delete, app log "Loaded 5 settings from App Configuration https://appcfg-appconfig-ps10.azconfig.io ... 2 Key Vault references resolved", private DNS from the container: store 10.0.1.5, vault 10.0.1.6, PostgreSQL 10.0.1.4; re-run exit 0 with 63 probes and no duplicate role assignments; RG deleted, vault and store purged
python / bicep Azure (appcfgb-rg) PREFIX=appcfgb SUFFIX=ps10 bash deploy.sh (17m33s), ../scripts/validate.sh, curl, CRUD, Kudu getent, log download, re-run + what-if, cleanup 2026-09-10 19:10-19:45 pass: template deployed in one go (secrets written through ARM with Contributor, five key-values present, deployer Secrets Officer assignment created), validate 0 errors, HTTP 200 with 9 rows, add/edit/delete, private DNS from the container: store 10.0.1.6, vault 10.0.1.4, PostgreSQL 10.0.1.5, app log "Loaded 5 settings ... 2 Key Vault references resolved"; re-run exit 0 with the five key-values intact; az deployment group what-if reports no Create/Delete, 18 NoChange and 25 Modify entries that are what-if noise (unevaluated reference() expressions in the key-value values, server-populated defaults on diagnostic settings, NAT gateway SKU tier, private DNS link resolutionPolicy, private endpoint ipVersionType, site config); RG deleted, vault and store purged
python / terraform Azure (appcfgt-rg) PREFIX=appcfgt SUFFIX=ps10 bash deploy.sh (three runs: the first hit ServerIsBusy on the firewall rule created in parallel with the database, the second the provider's availability-zone check; both fixed in the module), validate, curl, CRUD, Kudu getent, log, terraform plan -detailed-exitcode, re-run, terraform destroy, cleanup 2026-09-10 19:14-19:55 pass: apply complete, five AzAPI key-values present on Azure, validate 0 errors, HTTP 200 with 9 rows, add/edit/delete, private DNS from the container: store 10.0.1.4, vault 10.0.1.5, PostgreSQL 10.0.1.6, app log "Loaded 5 settings ... 2 Key Vault references resolved", second plan "No changes. Your infrastructure matches the configuration." (exit 0); re-run of deploy.sh exit 0 (no changes applied, key-values present, zip redeployed); terraform destroy removed 49 resources including the five AzAPI key-values, no leftover resource group and no soft-deleted store or vault (azurerm purged them)
dotnet / scripts Azure (appcfgd-rg) PREFIX=appcfgd SUFFIX=ps10 bash scripts/deploy.sh (33m28s), validate, curl, CRUD (antiforgery), /health, Kudu getent, log, re-run, cleanup 2026-09-10 19:17-20:25 pass: the first az keyvault secret set was refused (Forbidden) while the deployer's Key Vault Secrets Officer assignment propagated and the retry succeeded 30 s later; store public network access Enabled (explicit flag); validate 0 errors, HTTP 200 with 9 rows, add/edit/delete, /health ok, private DNS from the container: store 10.0.1.5, vault 10.0.1.6, PostgreSQL 10.0.1.4, app log "Loaded 5 settings from App Configuration https://appcfgd-appconfig-ps10.azconfig.io ... 2 Key Vault references"; re-run exit 0 with 63 already-exists probes and 3 role assignments (no duplicates); RG deleted, vault and store purged
dotnet / bicep Azure (appcfgdb-rg) PREFIX=appcfgdb SUFFIX=ps10 bash deploy.sh, validate, curl, CRUD (antiforgery), /health, Kudu getent, log, re-run + what-if, cleanup 2026-09-10 19:24-20:15 pass: template deployed in one pass, validate 0 errors, HTTP 200 with 9 rows, add/edit/delete, /health ok, private DNS from the container: store 10.0.1.6, vault 10.0.1.4, PostgreSQL 10.0.1.5, app log "Loaded 5 settings from App Configuration https://appcfgdb-appconfig-ps10.azconfig.io ... 2 Key Vault references"; re-run exit 0 with the five key-values re-written with identical content (ARM PUT refreshes lastModified), what-if with the same profile as the Python Bicep cell (no Create/Delete, Modify entries from unevaluated expressions and server defaults); RG deleted, vault and store purged, no leftovers
dotnet / terraform Azure (appcfgdt-rg, SUFFIX=ps10 then a full redo with SUFFIX=ps11) PREFIX=appcfgdt SUFFIX=ps11 bash deploy.sh (18m44s, 49 resources), validate, curl, CRUD (antiforgery), Kudu getent, log, terraform plan -detailed-exitcode, terraform destroy, leftover check 2026-09-10 19:23-20:20 and 20:42-21:15 pass: five AzAPI key-values asserted, validate 0 errors, HTTP 200 with 9 rows, add/edit/delete, private DNS from the container: store 10.0.1.4, vault 10.0.1.5, PostgreSQL 10.0.1.6, app log "Loaded 5 settings ... 2 Key Vault references", second plan "No changes. Your infrastructure matches the configuration." (exit 0); terraform destroy removed 45 resources, stopped on a connection reset while deleting a diagnostic setting (HTTP response was nil; connection may have been reset) and the retried destroy removed the remaining 4 ("Destroy complete"); afterwards no resource group, no soft-deleted vault or store, 0 role assignments, 0 resources in the state
python / scripts (review fixes) emulator (fresh) bash scripts/deploy.sh, validate.sh, call-web-app.sh, add/edit/delete probe deleting by database id 2026-09-10 20:42-20:52 pass: deploy exit 0, validate 0 errors, 4/4 call paths, add/edit/delete (Activity deleted: <id> in the app log), "Loaded 5 settings ... 2 Key Vault references resolved"
python / bicep, twice with az group delete in between emulator built from localstack-pro main (a65564a652) plus the emulator fixes below bash deploy.sh, validate, call-web-app.sh, CRUD, az deployment group list, az group delete --name local-rg, then the same again 2026-09-10 21:22-21:31 pass: both runs exit 0 with all 15 deployments Succeeded, five key-values, validate 0 errors, 3 role assignments each time (no duplicates), 4/4 call paths, add/edit/delete; after the group deletion 0 role assignments were left in the subscription and the web app and PostgreSQL containers were gone
shellcheck 0.11.0 static shellcheck -S warning on the eight new scripts and on the four scripts of the original sample 2026-09-10 0 findings on every script
python / terraform (modules refactor) emulator (fresh, published image) bash deploy.sh (49 resources), ../scripts/validate.sh, call-web-app.sh, CRUD, second plan, terraform destroy 2026-09-11 09:24-09:35 pass: validate 0 errors, five key-values, three role assignments, 4/4 call paths, add/edit/delete, log line, second plan shows the inherited read-back drift only, destroy 49 with no leftover group, store, vault or role assignment
python / bicep (modules refactor) emulator (fresh, published image) bash deploy.sh, validate, call, CRUD 2026-09-11 09:35-09:39 pass: deploy exit 0, validate 0 errors, five key-values, three role assignments, 4/4, add/edit/delete, log line
dotnet / terraform (modules refactor) emulator (fresh, published image) same as python + destroy 2026-09-11 09:39-09:49 pass: 49 added, validate 0 errors, 4/4, CRUD, second plan inherited drift only, destroy 49 clean
dotnet / bicep (modules refactor) emulator (fresh, published image) same as python 2026-09-11 09:49-09:53 pass: deploy exit 0, validate 0 errors, five key-values, three role assignments, 4/4, CRUD, log line
python / bicep (modules refactor), twice with az group delete in between emulator built from localstack-pro PR #8822 (525ae8199a) bash deploy.sh, validate, call-web-app.sh, CRUD, az group delete --name local-rg, then the same again 2026-09-11 09:54-10:01 pass: both runs exit 0 with all 15 deployments Succeeded, five key-values, 3 role assignments each time, 4/4 call paths, add/edit/delete, 0 role assignments left after the group deletion
python / terraform (modules refactor) Azure (appcfgt-rg, SUFFIX=ps12) PREFIX=appcfgt SUFFIX=ps12 bash deploy.sh (18m12s, 49 resources), ../scripts/validate.sh, curl, CRUD, Kudu DNS, log, terraform plan -detailed-exitcode, terraform destroy 2026-09-11 09:36-10:02 pass: validate 0 errors, HTTP 200 with the nine seeded rows, add/edit/delete, private DNS 10.0.1.4/5/6 for the store, the vault and the server, "Loaded 5 settings" log line, second plan No changes, destroy 49, no group, soft-deleted store, vault or role assignment left
python / bicep (modules refactor) Azure (appcfgb-rg, SUFFIX=ps12) PREFIX=appcfgb SUFFIX=ps12 bash deploy.sh (18m34s), ../scripts/validate.sh, curl, CRUD, Kudu DNS, log, USE_WHAT_IF=1 bash deploy.sh, cleanup and purge 2026-09-11 09:40-10:15 pass: 15/15 deployments Succeeded, validate 0 errors, HTTP 200 with nine rows, add/edit/delete, private DNS 10.0.1.4/5/6, log line; re-run exit 0 (18 min incl. the zip deploy), what-if 0 create / 0 delete (24 modify, 19 no change: the same server-default noise as the ps10 run), five key-values held, group deleted, store and vault purged, 0 role assignments left

Notes:

  • az appconfig kv list --fields key content_type label crashes in the Azure CLI (2.87.0) when the store holds a Key Vault reference (--fields makes the CLI request only those fields and its SDK then parses a null value); the scripts and READMEs project with --query instead. This happens against Azure too and is worth an azure-cli issue.
  • az deployment group what-if on Azure reports no Create or Delete, 18 NoChange and 25 Modify entries per variant; the Modify entries are what-if noise (the five key-value values depend on reference() expressions what-if cannot evaluate, and the rest are server-populated defaults such as resolutionPolicy, sku.tier, ipVersionType, privateEndpointVNetPolicies, PostgreSQL authConfig/storage and web app siteConfig defaults, the same noise the original PostgreSQL sample shows); the second deployment changed nothing, which is the check that matters.
  • Re-running the Terraform variant after az group delete needs a purge of the soft-deleted vault and store first (README): the azurerm provider recovers the vault with its secrets and azurerm_key_vault_secret refuses to adopt them. This is the provider's behaviour on Azure and on the emulator alike; the CLI and Bicep variants recover and upsert. CI starts a fresh emulator per job, so the harness is unaffected.

Related

Emulator behaviours found on the way are written up with reproduction and fix outline in localstack-pro-azure/md/APP_CONFIG_APP_FIX_GAPS_PROMPT.md (localstack-pro repository). None of them is worked around silently in the sample. Status on 2026-09-10 evening:

  1. App Service does not resolve @Microsoft.AppConfiguration(...) / @Microsoft.KeyVault(...) app settings and always echoes keyVaultReferenceIdentity: SystemAssigned: the documented alternative path; the deployed sample does not depend on it. Open.
  2. Key Vault vaultUri has no trailing slash and the ARM secret resource returns malformed secretUri / secretUriWithVersion (null inside a deployment); the Bicep module assembles the identifiers from vaultUri + secrets/<name> like the CLI mode does. Open.
  3. The ARM string() template function renders an object as a Python dictionary; the templates use string interpolation for the reference JSON. Open.
  4. A failed nested deployment (Bicep module) did not fail the parent deployment. Fixed on the localstack-pro branch fix/azure-role-assignment-scope-cascade-and-nested-deployment-failure: the parent reports Failed with Azure's DeploymentFailed > ResourceDeploymentFailure > module DeploymentFailed > inner error chain, the module reads back as Failed, the parent's operation on it reports Failed/Conflict with the module's error, and a module that never finishes fails the parent after a bounded wait. Parity test test_arm_template_exceptions.py::test_failed_nested_deployment_fails_parent_deployment, recorded against Azure.
  5. Role assignments survived the deletion of their scope and a same-name re-creation answered 409 with a Terraform-worded message. Fixed on the same branch: a repeated identical PUT returns the existing assignment (201, as Azure), a same-name PUT with other properties answers 400 RoleAssignmentUpdateNotPermitted, a second name for the same grant answers 409 RoleAssignmentExists, and deleting a resource, a Key Vault (also soft delete), an App Configuration store or a resource group removes the assignments scoped at or below it. Parity tests test_role_assignments.py::test_create_assignment_same_name_and_grant and ::test_role_assignments_are_removed_with_their_scope, recorded against Azure (Azure removed the assignments within seconds of the deletions). Verified end to end: the Bicep variant deploys, survives az group delete and deploys again with every module Succeeded on the emulator built from main with the branch.
  6. Key Vault secrets reappearing in a re-created vault: not an emulator defect. On the emulator built from main the deleted vault is listed as soft-deleted, a same-name create answers ConflictError, and a vault re-created after a purge holds no secrets, all as on Azure; the harness failure was the azurerm provider recovering the soft-deleted vault with its secrets (see the Terraform README).
  7. The PostgreSQL flexible server database child can appear after the deployment reports success (the IaC scripts wait for it). Open. The container surviving a resource-group deletion is not reproducible on the emulator built from main.
  8. App Service containers leaking across resource-group deletion: not reproducible on the emulator built from main (the redeploy path was not re-tested). Open for that path.
  9. Microsoft Graph cannot resolve the az login --service-principal principal and /me answers for an app-only login; the scripts fall back to the oid claim of the CLI token. Open.
  10. Key Vault does not echo tags on read (Terraform drift), plus the pre-existing read-back drift of the inherited resources on the emulator. Open.

The emulator fixes are in localstack-pro PR #8822 (make lint clean, the affected parity and unit tests passing on the emulator, the three new parity tests recorded against Azure).

Plan and execution notes: localstack-pro-azure/md/APP_CONFIG_APP_SERVICE_SAMPLES_PLAN.md.

🤖 Generated with Claude Code

…nd .NET)

A permutation of the PostgreSQL flexible server sample in which the five PostgreSQL
connection settings leave the Web App's app settings: PG_HOST, PG_PORT and PG_DATABASE
become key-values of an Azure App Configuration store, PG_USER and PG_PASSWORD become
Key Vault secrets exposed through App Configuration Key Vault references, and the app
loads them at startup with the App Configuration provider, authenticating to the store
and to Key Vault with a user-assigned managed identity over Private Endpoints.

All three provisioning modes (Azure CLI scripts, Bicep, Terraform with AzAPI for the
key-values) create the same topology in both languages, are enrolled in run-samples.sh
and were run on the emulator and on Azure. The root README gets one Outline row.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@paolosalvatori
paolosalvatori requested a review from a team as a code owner September 10, 2026 20:13
Copilot AI lite review requested due to automatic review settings September 10, 2026 20:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Terraform has type-checking issues (bool/string mismatch and invalid numeric/string comparison) and the Python delete path can delete the wrong row because it uses a mutable list index instead of a stable id.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a new end-to-end “Vacation Planner” sample (samples/web-app-app-configuration) demonstrating Azure App Configuration + Key Vault references for PostgreSQL connection settings, with both Python and .NET implementations and three provisioning modes (Azure CLI scripts, Bicep, Terraform) targeting Azure and the LocalStack for Azure emulator.

Changes:

  • Introduces new Python and .NET sample apps that load PG_* settings from App Configuration, with PG_USER/PG_PASSWORD resolved via Key Vault references using DefaultAzureCredential.
  • Adds complete IaC + scripting for the new topology (managed identity, App Config, Key Vault, private endpoints/DNS zones, and PostgreSQL) across scripts/Bicep/Terraform.
  • Wires the sample into the repo harness (run-samples.sh) and documents it in the root README.md.
File summaries
File Description
samples/web-app-app-configuration/python/README.md Python sample documentation (topology + usage).
samples/web-app-app-configuration/python/bicep/README.md Python Bicep deployment documentation.
samples/web-app-app-configuration/python/bicep/deploy.sh Python Bicep deployment script.
samples/web-app-app-configuration/python/bicep/main.bicep Python Bicep main template.
samples/web-app-app-configuration/python/bicep/main.bicepparam Python Bicep parameters.
samples/web-app-app-configuration/python/bicep/modules/app-configuration.bicep Bicep module for App Configuration + seed key-values + RBAC.
samples/web-app-app-configuration/python/bicep/modules/app-service-plan.bicep Bicep module for App Service Plan + diagnostics.
samples/web-app-app-configuration/python/bicep/modules/key-vault.bicep Bicep module for Key Vault + secrets + RBAC + secret URIs.
samples/web-app-app-configuration/python/bicep/modules/log-analytics.bicep Bicep module for Log Analytics workspace.
samples/web-app-app-configuration/python/bicep/modules/managed-identity.bicep Bicep module for user-assigned managed identity.
samples/web-app-app-configuration/python/bicep/modules/postgresql-flexible-server.bicep Bicep module for PostgreSQL flexible server + DB + firewall + diagnostics.
samples/web-app-app-configuration/python/bicep/modules/private-dns-zone.bicep Bicep module for Private DNS zone + vnet link.
samples/web-app-app-configuration/python/bicep/modules/private-endpoint.bicep Bicep module for Private Endpoint + zone group.
samples/web-app-app-configuration/python/bicep/modules/virtual-network.bicep Bicep module for VNet/subnets/NSGs/NAT + diagnostics.
samples/web-app-app-configuration/python/bicep/modules/web-app.bicep Bicep module for Web App + app settings (endpoint/client id).
samples/web-app-app-configuration/python/images/architecture.png Sample diagram asset.
samples/web-app-app-configuration/python/images/vacation-planner.png Sample UI image asset.
samples/web-app-app-configuration/python/scripts/README.md Python CLI-script deployment documentation.
samples/web-app-app-configuration/python/scripts/call-web-app.sh Python sample call-path script.
samples/web-app-app-configuration/python/scripts/deploy.sh Python CLI-script deployment script.
samples/web-app-app-configuration/python/scripts/validate.sh Python CLI-script validation script.
samples/web-app-app-configuration/python/src/app.py Python Flask app wiring + CRUD routes.
samples/web-app-app-configuration/python/src/database.py Python PostgreSQL client using loaded configuration.
samples/web-app-app-configuration/python/src/gunicorn.conf.py Gunicorn worker signal handling config.
samples/web-app-app-configuration/python/src/requirements.txt Python dependencies (Flask + Azure App Config provider + identity).
samples/web-app-app-configuration/python/src/settings.py Python App Configuration + Key Vault reference loading logic.
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-grid.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-grid.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-grid.min.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-grid.min.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-grid.rtl.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-grid.rtl.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-grid.rtl.min.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-grid.rtl.min.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-reboot.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-reboot.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-reboot.min.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-reboot.min.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-reboot.rtl.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-reboot.rtl.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-reboot.rtl.min.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-reboot.rtl.min.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-utilities.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-utilities.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-utilities.min.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-utilities.min.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-utilities.rtl.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-utilities.rtl.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-utilities.rtl.min.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap-utilities.rtl.min.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap.min.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap.min.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap.rtl.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap.rtl.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap.rtl.min.css Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/css/bootstrap.rtl.min.css.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/js/bootstrap.bundle.js Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/js/bootstrap.bundle.js.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/js/bootstrap.bundle.min.js Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/js/bootstrap.bundle.min.js.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/js/bootstrap.esm.js Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/js/bootstrap.esm.js.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/js/bootstrap.esm.min.js Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/js/bootstrap.esm.min.js.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/js/bootstrap.js Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/js/bootstrap.js.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/bootstrap/js/bootstrap.min.js Static asset (Bootstrap).
samples/web-app-app-configuration/python/src/static/bootstrap/js/bootstrap.min.js.map Static asset (Bootstrap sourcemap).
samples/web-app-app-configuration/python/src/static/favicon.ico Static asset.
samples/web-app-app-configuration/python/src/static/style.css Sample styling.
samples/web-app-app-configuration/python/src/templates/index.html Python app UI template.
samples/web-app-app-configuration/python/terraform/README.md Python Terraform deployment documentation.
samples/web-app-app-configuration/python/terraform/deploy.sh Python Terraform deployment script.
samples/web-app-app-configuration/python/terraform/main.tf Python Terraform root module (resources + wiring).
samples/web-app-app-configuration/python/terraform/modules/app_service_plan/main.tf Terraform module: App Service Plan + diagnostics.
samples/web-app-app-configuration/python/terraform/modules/app_service_plan/outputs.tf Terraform module outputs.
samples/web-app-app-configuration/python/terraform/modules/app_service_plan/variables.tf Terraform module inputs.
samples/web-app-app-configuration/python/terraform/modules/log_analytics/main.tf Terraform module: Log Analytics workspace.
samples/web-app-app-configuration/python/terraform/modules/log_analytics/output.tf Terraform module outputs.
samples/web-app-app-configuration/python/terraform/modules/log_analytics/variables.tf Terraform module inputs.
samples/web-app-app-configuration/python/terraform/modules/nat_gateway/main.tf Terraform module: NAT gateway + public IP.
samples/web-app-app-configuration/python/terraform/modules/nat_gateway/output.tf Terraform module outputs.
samples/web-app-app-configuration/python/terraform/modules/nat_gateway/variables.tf Terraform module inputs.
samples/web-app-app-configuration/python/terraform/modules/network_security_group/main.tf Terraform module: NSG + diag settings + subnet association.
samples/web-app-app-configuration/python/terraform/modules/network_security_group/outputs.tf Terraform module outputs.
samples/web-app-app-configuration/python/terraform/modules/network_security_group/variables.tf Terraform module inputs.
samples/web-app-app-configuration/python/terraform/modules/postgres_flexible_server/main.tf Terraform module: PostgreSQL server + DB + firewall + diagnostics.
samples/web-app-app-configuration/python/terraform/modules/postgres_flexible_server/outputs.tf Terraform module outputs.
samples/web-app-app-configuration/python/terraform/modules/postgres_flexible_server/variables.tf Terraform module inputs.
samples/web-app-app-configuration/python/terraform/modules/private_dns_zone/main.tf Terraform module: Private DNS zone + vnet link.
samples/web-app-app-configuration/python/terraform/modules/private_dns_zone/outputs.tf Terraform module outputs.
samples/web-app-app-configuration/python/terraform/modules/private_dns_zone/variables.tf Terraform module inputs.
samples/web-app-app-configuration/python/terraform/modules/private_endpoint/main.tf Terraform module: Private Endpoint + DNS zone group.
samples/web-app-app-configuration/python/terraform/modules/private_endpoint/outputs.tf Terraform module outputs.
samples/web-app-app-configuration/python/terraform/modules/private_endpoint/variables.tf Terraform module inputs.
samples/web-app-app-configuration/python/terraform/modules/virtual_network/main.tf Terraform module: VNet + subnets + diagnostics.
samples/web-app-app-configuration/python/terraform/modules/virtual_network/outputs.tf Terraform module outputs.
samples/web-app-app-configuration/python/terraform/modules/virtual_network/variables.tf Terraform module inputs.
samples/web-app-app-configuration/python/terraform/modules/web_app/main.tf Terraform module: Linux web app + diagnostics.
samples/web-app-app-configuration/python/terraform/modules/web_app/outputs.tf Terraform module outputs.
samples/web-app-app-configuration/python/terraform/modules/web_app/variables.tf Terraform module inputs.
samples/web-app-app-configuration/python/terraform/outputs.tf Terraform root outputs.
samples/web-app-app-configuration/python/terraform/providers.tf Terraform providers + dual-target notes.
samples/web-app-app-configuration/python/terraform/terraform.tfvars Terraform tfvars defaults.
samples/web-app-app-configuration/python/terraform/variables.tf Terraform root variables.
samples/web-app-app-configuration/python/visio/architecture.vsdx Diagram source asset.
samples/web-app-app-configuration/dotnet/README.md .NET sample documentation (topology + usage).
samples/web-app-app-configuration/dotnet/bicep/README.md .NET Bicep deployment documentation.
samples/web-app-app-configuration/dotnet/bicep/deploy.sh .NET Bicep deployment script.
samples/web-app-app-configuration/dotnet/bicep/main.bicep .NET Bicep main template.
samples/web-app-app-configuration/dotnet/bicep/main.bicepparam .NET Bicep parameters.
samples/web-app-app-configuration/dotnet/bicep/modules/app-configuration.bicep Bicep module for App Configuration + seed key-values + RBAC.
samples/web-app-app-configuration/dotnet/bicep/modules/app-service-plan.bicep Bicep module for App Service Plan + diagnostics.
samples/web-app-app-configuration/dotnet/bicep/modules/key-vault.bicep Bicep module for Key Vault + secrets + RBAC + secret URIs.
samples/web-app-app-configuration/dotnet/bicep/modules/log-analytics.bicep Bicep module for Log Analytics workspace.
samples/web-app-app-configuration/dotnet/bicep/modules/managed-identity.bicep Bicep module for user-assigned managed identity.
samples/web-app-app-configuration/dotnet/bicep/modules/postgresql-flexible-server.bicep Bicep module for PostgreSQL flexible server + DB + firewall + diagnostics.
samples/web-app-app-configuration/dotnet/bicep/modules/private-dns-zone.bicep Bicep module for Private DNS zone + vnet link.
samples/web-app-app-configuration/dotnet/bicep/modules/private-endpoint.bicep Bicep module for Private Endpoint + zone group.
samples/web-app-app-configuration/dotnet/bicep/modules/virtual-network.bicep Bicep module for VNet/subnets/NSGs/NAT + diagnostics.
samples/web-app-app-configuration/dotnet/bicep/modules/web-app.bicep Bicep module for Web App + app settings (endpoint/client id).
samples/web-app-app-configuration/dotnet/images/architecture.png Sample diagram asset.
samples/web-app-app-configuration/dotnet/images/vacation-planner.png Sample UI image asset.
samples/web-app-app-configuration/dotnet/scripts/README.md .NET CLI-script deployment documentation.
samples/web-app-app-configuration/dotnet/scripts/call-web-app.sh .NET sample call-path script.
samples/web-app-app-configuration/dotnet/scripts/deploy.sh .NET CLI-script deployment script.
samples/web-app-app-configuration/dotnet/scripts/validate.sh .NET CLI-script validation script.
samples/web-app-app-configuration/dotnet/src/Models/Activity.cs .NET model types.
samples/web-app-app-configuration/dotnet/src/Pages/Delete.cshtml .NET Razor page route for delete.
samples/web-app-app-configuration/dotnet/src/Pages/Delete.cshtml.cs .NET delete handler (id-based delete).
samples/web-app-app-configuration/dotnet/src/Pages/Index.cshtml .NET Razor UI page.
samples/web-app-app-configuration/dotnet/src/Pages/Index.cshtml.cs .NET UI handlers for list/add/update.
samples/web-app-app-configuration/dotnet/src/Pages/_ViewImports.cshtml .NET Razor imports.
samples/web-app-app-configuration/dotnet/src/Program.cs .NET app startup + App Config load + health endpoint.
samples/web-app-app-configuration/dotnet/src/Services/ActivityId.cs .NET activity id generation.
samples/web-app-app-configuration/dotnet/src/Services/AppConfigurationSettings.cs .NET App Configuration provider bootstrap + retries + logging.
samples/web-app-app-configuration/dotnet/src/Services/IActivityStore.cs .NET store interface.
samples/web-app-app-configuration/dotnet/src/Services/PostgresActivityStore.cs .NET PostgreSQL implementation (Npgsql).
samples/web-app-app-configuration/dotnet/src/Services/PostgresOptions.cs .NET config binding/validation for PG_* settings.
samples/web-app-app-configuration/dotnet/src/Services/StoreInitializer.cs .NET hosted service for bounded initialization retries.
samples/web-app-app-configuration/dotnet/src/VacationPlanner.csproj .NET project file + package refs.
samples/web-app-app-configuration/dotnet/src/appsettings.json .NET base app configuration.
samples/web-app-app-configuration/dotnet/src/wwwroot/favicon.ico Static asset.
samples/web-app-app-configuration/dotnet/src/wwwroot/style.css Static styling asset.
samples/web-app-app-configuration/dotnet/terraform/README.md .NET Terraform deployment documentation.
samples/web-app-app-configuration/dotnet/terraform/deploy.sh .NET Terraform deployment script.
samples/web-app-app-configuration/dotnet/terraform/main.tf .NET Terraform root module (resources + wiring).
samples/web-app-app-configuration/dotnet/terraform/modules/app_service_plan/main.tf Terraform module: App Service Plan + diagnostics.
samples/web-app-app-configuration/dotnet/terraform/modules/app_service_plan/outputs.tf Terraform module outputs.
samples/web-app-app-configuration/dotnet/terraform/modules/app_service_plan/variables.tf Terraform module inputs.
samples/web-app-app-configuration/dotnet/terraform/modules/log_analytics/main.tf Terraform module: Log Analytics workspace.
samples/web-app-app-configuration/dotnet/terraform/modules/log_analytics/output.tf Terraform module outputs.
samples/web-app-app-configuration/dotnet/terraform/modules/log_analytics/variables.tf Terraform module inputs.
samples/web-app-app-configuration/dotnet/terraform/modules/nat_gateway/main.tf Terraform module: NAT gateway + public IP.
samples/web-app-app-configuration/dotnet/terraform/modules/nat_gateway/output.tf Terraform module outputs.
samples/web-app-app-configuration/dotnet/terraform/modules/nat_gateway/variables.tf Terraform module inputs.
samples/web-app-app-configuration/dotnet/terraform/modules/network_security_group/main.tf Terraform module: NSG + diag settings + subnet association.
samples/web-app-app-configuration/dotnet/terraform/modules/network_security_group/outputs.tf Terraform module outputs.
samples/web-app-app-configuration/dotnet/terraform/modules/network_security_group/variables.tf Terraform module inputs.
samples/web-app-app-configuration/dotnet/terraform/modules/postgres_flexible_server/main.tf Terraform module: PostgreSQL server + DB + firewall + diagnostics.
samples/web-app-app-configuration/dotnet/terraform/modules/postgres_flexible_server/outputs.tf Terraform module outputs.
samples/web-app-app-configuration/dotnet/terraform/modules/postgres_flexible_server/variables.tf Terraform module inputs.
samples/web-app-app-configuration/dotnet/terraform/modules/private_dns_zone/main.tf Terraform module: Private DNS zone + vnet link.
samples/web-app-app-configuration/dotnet/terraform/modules/private_dns_zone/outputs.tf Terraform module outputs.
samples/web-app-app-configuration/dotnet/terraform/modules/private_dns_zone/variables.tf Terraform module inputs.
samples/web-app-app-configuration/dotnet/terraform/modules/private_endpoint/main.tf Terraform module: Private Endpoint + DNS zone group.
samples/web-app-app-configuration/dotnet/terraform/modules/private_endpoint/outputs.tf Terraform module outputs.
samples/web-app-app-configuration/dotnet/terraform/modules/private_endpoint/variables.tf Terraform module inputs.
samples/web-app-app-configuration/dotnet/terraform/modules/virtual_network/main.tf Terraform module: VNet + subnets + diagnostics.
samples/web-app-app-configuration/dotnet/terraform/modules/virtual_network/outputs.tf Terraform module outputs.
samples/web-app-app-configuration/dotnet/terraform/modules/virtual_network/variables.tf Terraform module inputs.
samples/web-app-app-configuration/dotnet/terraform/modules/web_app/main.tf Terraform module: Linux web app + diagnostics.
samples/web-app-app-configuration/dotnet/terraform/modules/web_app/outputs.tf Terraform module outputs.
samples/web-app-app-configuration/dotnet/terraform/modules/web_app/variables.tf Terraform module inputs.
samples/web-app-app-configuration/dotnet/terraform/outputs.tf Terraform root outputs.
samples/web-app-app-configuration/dotnet/terraform/providers.tf Terraform providers + dual-target notes.
samples/web-app-app-configuration/dotnet/terraform/terraform.tfvars Terraform tfvars defaults.
samples/web-app-app-configuration/dotnet/terraform/variables.tf Terraform root variables.
samples/web-app-app-configuration/dotnet/visio/architecture.vsdx Diagram source asset.
run-samples.sh Adds new harness entries for the sample (scripts/Bicep/Terraform).
README.md Adds the sample to the repository outline table.
Review details
  • Files reviewed: 103/183 changed files
  • Comments generated: 8
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread samples/web-app-app-configuration/python/src/app.py Outdated
Comment thread samples/web-app-app-configuration/python/src/templates/index.html
paolosalvatori and others added 5 commits September 10, 2026 23:15
- Terraform log_analytics module: pass the numeric retention_in_days variable through instead of comparing it with an empty string (both variants).
- Terraform private_endpoint module: declare is_manual_connection as bool, the type the private service connection expects (both variants).
- Terraform web_app module: the principal_id output is the system-assigned principal id and is null for a user-assigned identity; say so and return null instead of failing (both variants).
- Python app: the delete form posts the database id of the activity and the route deletes by that id, so the delete no longer depends on the position of the row in the last rendered list (which differs between requests and gunicorn workers).

Retested on the emulator: python/scripts deploy, validate, call-web-app and the add/edit/delete probe pass with the delete-by-id change; terraform fmt and validate pass in both variants.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…er a resource-group deletion

After az group delete, the azurerm provider recovers the soft-deleted Key Vault together with its secrets and azurerm_key_vault_secret then refuses to adopt the existing pg-user and pg-password secrets. This is the provider's behaviour on Azure and on the emulator alike (verified against the emulator built from main on 2026-09-10: the vault is listed as soft-deleted, a same-name create answers ConflictError, and a vault re-created after a purge holds no secrets). The Terraform and main READMEs of both variants now say to purge the vault and the store before re-running the Terraform variant; the Azure CLI and Bicep variants recover and upsert, so they stay re-run safe.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ple READMEs

The section now explains how the five PostgreSQL settings are stored (plain key-values in App Configuration, credentials as Key Vault secrets exposed through Key Vault references with versionless identifiers) and how the application reads them at startup with the App Configuration provider and one DefaultAzureCredential, without referring to any other sample and without the App Service references alternative. The list of Azure-subscription considerations under Prerequisites is removed; its two operational facts (globally unique store and vault names, purge after cleanup) moved to one short note in the Deployment section, and the troubleshooting bullet on unresolved App Service references no longer points at the removed text.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…les workflow

The CI matrix is built dynamically from run-samples.sh --list, so the six web-app-app-configuration entries already run as their own jobs (shards 19, 20, 42, 43, 61 and 62 of 65, amd64 only like every other web-app-* sample); the workflow needs no per-sample entry. The comment on the postgresql-client dependency now names the second sample that relies on psql. Both Terraform variants initialize and validate with the Terraform 1.5.0 the workflow pins (azurerm 5.1.0, azapi 2.12.0, time 0.14.1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@DrisDary DrisDary left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great Job @paolosalvatori LGTM

…source and remove every literal from the IaC

The Terraform variants of the Python and .NET samples created the App Configuration store,
the Key Vault, the secrets, the AzAPI key-values, the managed identity and the role assignments
directly in main.tf. They now follow the layout of the other Terraform samples: one module per
resource type.

- modules/managed_identity: the user-assigned identity, outputs id, client_id, principal_id.
- modules/key_vault: the vault, one azurerm_key_vault_secret per entry of a sensitive secrets map,
  the optional Key Vault Secrets Officer assignment for the deploying principal with the
  time_sleep that waits for RBAC propagation, the diagnostic settings; outputs the versionless
  identifier of every secret.
- modules/app_configuration: the store, one azapi_resource of type
  Microsoft.AppConfiguration/configurationStores/keyValues per entry of a key_values map
  (value, optional content type and label), the diagnostic settings; declares Azure/azapi in
  its own versions.tf.
- modules/role_assignment: one grant (scope, role definition name, principal id and type).
- main.tf only composes modules; the two Key Vault references are built from the key_vault
  module outputs and passed to the app_configuration module as key-values.

No resource block in the Terraform modules or in the Bicep modules carries a literal any more.
Every value is a Terraform variable with a default in the module's variables.tf (or in the root
variables.tf for the sample-level names, key names, secret names, role names, content type,
DNS zone names, subresource names, network policies, principal types, Oryx flags and diagnostic
categories) or a Bicep parameter with a default (SKU families, network ACLs, principal types,
role definition ids, link and connection names, NSG rules, diagnostic categories and retention,
WEBSITES_PORT, build flags). The inherited modules (app_service_plan, nat_gateway,
network_security_group, private_dns_zone, private_endpoint, postgres_flexible_server,
virtual_network, web_app and their Bicep counterparts) were parameterized the same way; the
diagnostic settings use dynamic blocks driven by category variables. The Bicep linter's
secure-secrets-in-params false positives on the role definition id and key name parameters are
suppressed with a justification. The Terraform and Bicep READMEs of both variants describe the
modules and the rule.

Tests (2026-09-11): terraform fmt and validate on Terraform 1.13.5 and 1.5.0 (the CI version)
for both variants; az bicep build with no warning for both variants; the four IaC cells on a
fresh published-image emulator (python and dotnet, Terraform and Bicep: deploy, validate.sh
with 0 errors, five key-values, three role assignments, 4/4 call paths, add/edit/delete, the
"Loaded 5 settings" log line, second plan or re-run, terraform destroy with no leftovers); the
Python Bicep variant deployed twice around an az group delete on an emulator built from the
localstack-pro fix branch (all 15 deployments Succeeded both times, 0 role assignments left after
the deletion); the Python Terraform and Bicep variants on Azure (westeurope, prefixes appcfgt and
appcfgb, suffix ps12: deploy, validate.sh 0 errors, HTTP 200 with the nine seeded rows,
add/edit/delete, private DNS resolution to 10.0.1.x from the App Service container, the log line,
terraform plan with no changes and what-if with no create or delete, destroy and cleanup).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants