Add the Web App with App Configuration and Key Vault sample (Python and .NET) - #121
Conversation
…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>
There was a problem hiding this comment.
🟡 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, withPG_USER/PG_PASSWORDresolved via Key Vault references usingDefaultAzureCredential. - 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 rootREADME.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.
- 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
left a comment
There was a problem hiding this comment.
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>
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_PORTandPG_DATABASEare key-values of an Azure App Configuration store;PG_USERandPG_PASSWORDare 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 oneDefaultAzureCredentialthat 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 withApp Configuration Data Readeron the store andKey Vault Secrets Useron the vault. No connection string, access key or database credential appears in the app settings.Fixes SMF-886
Changes
samples/web-app-app-configuration/{python,dotnet}, copied fromweb-app-postgresql-flexible-serverand adapted; the original sample is untouched.README.md,scripts/README.md,bicep/README.md,terraform/README.mdper language, rewritten for the new topology: configuration design, Azure-side prerequisites (roles of the deploying principal, globally unique names and theSUFFIXoverride, 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 Officerfor 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.shlists all of it and fails if aPG_*app setting exists or the endpoint and client id settings are missing.bicep/: new modulesmanaged-identity,key-vault(secrets from@secure()parameters,Key Vault Secrets User, optional deployerKey Vault Secrets Officer) andapp-configuration(keyValueschildren including the two Key Vault references,App Configuration Data Reader);web-apptakes the identity and the store endpoint; zone groups are nameddefaultin every mode;deploy.shrecovers soft-deleted stores and vaults, asserts the five key-values and waits for the database before thepsqlbootstrap. 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.bicepexposes the sample-level ones (pgUserSecretName,pg*KeyName,keyVaultReferenceContentType,websitesPort, the three Private DNS zone names and Private Endpoint group ids) andaz bicep buildreports no warning.terraform/: azurerm 5.1.0 for everything,Azure/azapi2.12.0 only for the fiveMicrosoft.AppConfiguration/configurationStores/keyValues@2024-06-01key-values (azurerm'sazurerm_app_configuration_keycannot read back against the emulator), and a dual-target provider configuration driven byARM_*variables thatdeploy.shexports only for the emulator. One module per resource type, following the layout of the other Terraform samples:managed_identity,key_vault(vault, oneazurerm_key_vault_secretper entry of a sensitivesecretsmap, the optional deployerKey Vault Secrets Officerassignment with itstime_sleep, diagnostics),app_configuration(store, oneazapi_resourceper entry of akey_valuesmap, diagnostics) androle_assignment(one instance per grant), next to the inheritedvirtual_network,nat_gateway,network_security_group,private_dns_zone,private_endpoint,postgres_flexible_server,app_service_plan,web_appandlog_analyticsmodules.main.tfonly composes modules; no resource block carries a literal: every value is a variable with a default in the module'svariables.tfor in the rootvariables.tf(link names, delegation, network policies, allocation methods, principal types, role names, secret and key names, content type, Oryx flags, diagnostic categories).src/: Pythonsettings.pyand .NETServices/AppConfigurationSettings.csloadPG_*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/PostgresOptionsread the loaded configuration instead of the environment.run-samples.sh: six new entries (scripts, Terraform and Bicep for both languages), amd64 only like every otherweb-app-*sample. RootREADME.md: oneOutlinerow.ServerIsBusy), the Terraform server ignores the Azure-assigned availability zone on later plans, and the CLIfirewall-rule createuses the current--server-name/--namearguments (the inherited form failed silently on every run).images/andvisio/hold the copied originals as placeholders. The new diagram should show: the resource group with the VNet (app-subnetwith the NAT gateway and its public IP prefix,pe-subnetwith the three Private Endpoints), the two NSGs and the Log Analytics workspace; the PostgreSQL flexible server<prefix>-pgflex-<suffix>behind<prefix>-postgres-pe-<suffix>andprivatelink.postgres.database.azure.com; the App Configuration store<prefix>-appconfig-<suffix>behind<prefix>-appconfig-pe-<suffix>andprivatelink.azconfig.io; the Key Vault<prefix>-keyvault-<suffix>behind<prefix>-keyvault-pe-<suffix>andprivatelink.vaultcore.azure.net; each zone linked to the VNet aslink-to-vnetand each endpoint with adefaultzone group; the user-assigned identity<prefix>-identity-<suffix>attached to the web app with the two role arrows (App Configuration Data Readerto the store,Key Vault Secrets Userto the vault); inside the store the key-valuesPG_HOST,PG_PORT,PG_DATABASEand the Key Vault referencesPG_USERandPG_PASSWORDpointing at the secretspg-userandpg-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 (psqlrole and schema,az keyvault secret set,az appconfig kv set/set-keyvault, or the template equivalents).9b7e9db,3441c0c): the eight GitHub Copilot findings are fixed and their threads resolved (Terraformlog_analyticsretention variable passed through,is_manual_connectiontyped asbool, theweb_appprincipal_idoutput documented as the system-assigned id andnullotherwise, 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 andazurerm_key_vault_secretthen refuses to adopt them (verified to be the provider's behaviour on the emulator built frommainas on Azure).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 inmain.tf; they use the newmanaged_identity,key_vault,app_configurationandrole_assignmentmodules, 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 throughmake 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.
bash scripts/deploy.sh,validate.sh,call-web-app.sh, CRUD probe, re-runbash deploy.sh,../scripts/validate.sh,call-web-app.sh, CRUD, re-runkeyVaultandappConfigurationmodule deploymentsFailedunder aSucceededparent, see the emulator fixes below)bash deploy.sh(48 resources), validate, call, CRUD, second plan, re-run,terraform destroymake test SHARD={19,20,42,43,61,62} SPLITS=65PREFIX=appcfg SUFFIX=ps10 bash scripts/deploy.sh, validate, curl, CRUD, Kudu getent, re-run, cleanup--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 purgedPREFIX=appcfgb SUFFIX=ps10 bash deploy.sh(17m33s),../scripts/validate.sh, curl, CRUD, Kudu getent, log download, re-run + what-if, cleanupaz deployment group what-ifreports no Create/Delete, 18 NoChange and 25 Modify entries that are what-if noise (unevaluatedreference()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 purgedPREFIX=appcfgt SUFFIX=ps10 bash deploy.sh(three runs: the first hitServerIsBusyon 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, cleanupdeploy.shexit 0 (no changes applied, key-values present, zip redeployed);terraform destroyremoved 49 resources including the five AzAPI key-values, no leftover resource group and no soft-deleted store or vault (azurerm purged them)PREFIX=appcfgd SUFFIX=ps10 bash scripts/deploy.sh(33m28s), validate, curl, CRUD (antiforgery), /health, Kudu getent, log, re-run, cleanupaz keyvault secret setwas 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 purgedPREFIX=appcfgdb SUFFIX=ps10 bash deploy.sh, validate, curl, CRUD (antiforgery), /health, Kudu getent, log, re-run + what-if, cleanupSUFFIX=ps10then a full redo withSUFFIX=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 checkterraform destroyremoved 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 statebash scripts/deploy.sh,validate.sh,call-web-app.sh, add/edit/delete probe deleting by database idActivity deleted: <id>in the app log), "Loaded 5 settings ... 2 Key Vault references resolved"az group deletein betweenmain(a65564a652) plus the emulator fixes belowbash deploy.sh, validate,call-web-app.sh, CRUD,az deployment group list,az group delete --name local-rg, then the same againSucceeded, 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 goneshellcheck -S warningon the eight new scripts and on the four scripts of the original samplebash deploy.sh(49 resources),../scripts/validate.sh,call-web-app.sh, CRUD, second plan,terraform destroybash deploy.sh, validate, call, CRUDaz group deletein between525ae8199a)bash deploy.sh, validate,call-web-app.sh, CRUD,az group delete --name local-rg, then the same againSucceeded, five key-values, 3 role assignments each time, 4/4 call paths, add/edit/delete, 0 role assignments left after the group deletionSUFFIX=ps12)PREFIX=appcfgt SUFFIX=ps12 bash deploy.sh(18m12s, 49 resources),../scripts/validate.sh, curl, CRUD, Kudu DNS, log,terraform plan -detailed-exitcode,terraform destroyNo changes, destroy 49, no group, soft-deleted store, vault or role assignment leftSUFFIX=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 purgeSucceeded, 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 leftNotes:
az appconfig kv list --fields key content_type labelcrashes in the Azure CLI (2.87.0) when the store holds a Key Vault reference (--fieldsmakes the CLI request only those fields and its SDK then parses anullvalue); the scripts and READMEs project with--queryinstead. This happens against Azure too and is worth an azure-cli issue.az deployment group what-ifon Azure reports noCreateorDelete, 18NoChangeand 25Modifyentries per variant; theModifyentries are what-if noise (the five key-value values depend onreference()expressions what-if cannot evaluate, and the rest are server-populated defaults such asresolutionPolicy,sku.tier,ipVersionType,privateEndpointVNetPolicies, PostgreSQLauthConfig/storageand web appsiteConfigdefaults, the same noise the original PostgreSQL sample shows); the second deployment changed nothing, which is the check that matters.az group deleteneeds a purge of the soft-deleted vault and store first (README): the azurerm provider recovers the vault with its secrets andazurerm_key_vault_secretrefuses 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:@Microsoft.AppConfiguration(...)/@Microsoft.KeyVault(...)app settings and always echoeskeyVaultReferenceIdentity: SystemAssigned: the documented alternative path; the deployed sample does not depend on it. Open.vaultUrihas no trailing slash and the ARM secret resource returns malformedsecretUri/secretUriWithVersion(nullinside a deployment); the Bicep module assembles the identifiers fromvaultUri+secrets/<name>like the CLI mode does. Open.string()template function renders an object as a Python dictionary; the templates use string interpolation for the reference JSON. Open.fix/azure-role-assignment-scope-cascade-and-nested-deployment-failure: the parent reportsFailedwith Azure'sDeploymentFailed>ResourceDeploymentFailure> moduleDeploymentFailed> inner error chain, the module reads back asFailed, the parent's operation on it reportsFailed/Conflictwith the module's error, and a module that never finishes fails the parent after a bounded wait. Parity testtest_arm_template_exceptions.py::test_failed_nested_deployment_fails_parent_deployment, recorded against Azure.RoleAssignmentUpdateNotPermitted, a second name for the same grant answers 409RoleAssignmentExists, 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 teststest_role_assignments.py::test_create_assignment_same_name_and_grantand::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, survivesaz group deleteand deploys again with every moduleSucceededon the emulator built frommainwith the branch.mainthe deleted vault is listed as soft-deleted, a same-name create answersConflictError, 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).main.main(the redeploy path was not re-tested). Open for that path.az login --service-principalprincipal and/meanswers for an app-only login; the scripts fall back to theoidclaim of the CLI token. Open.The emulator fixes are in localstack-pro PR #8822 (
make lintclean, 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