Creates Azure App Configuration stores with their geo-replicas, configuration keys, and feature flags.
App Configuration stores keyed by name, covering the full azurerm_app_configuration surface (sku,
identity, customer-managed-key encryption, geo-replicas, soft delete and purge protection, ARM data
plane proxy) plus the data plane: configuration keys (azurerm_app_configuration_key) and
feature flags (azurerm_app_configuration_feature, with targeting, timewindow, custom, and
percentage filters). Secure by default: access keys disabled (local_auth_enabled = false, the
data plane is Entra ID only, which is also how this module writes keys and features). The store has
no IP firewall, so public_network_access defaults to Enabled; set it to Disabled and reach the
store through the private-endpoint module for network isolation. The resource group is passed by id.
Keys and features target a store created in the same call (by its map key) or an existing store
(by configuration_store_id). The split matters because writing the data plane needs the
App Configuration Data Owner role on the store, and a fresh grant is eventually consistent: a
consumer typically creates the store in one call, grants the role (role-assignment module plus a
time_sleep), then writes keys and features in a second, data-only call against the store id (which
needs no resource_group_id or location).
A
kvvalue lands in the store and in Terraform state in plain text (the provider has no write-only variant for App Configuration keys). Put secrets in Key Vault and reference them withtype = "vault"andvault_key_reference; a check block warns when a kv key name looks like a credential.
module "app_configuration" {
source = "libre-devops/app-configuration/azurerm"
version = "~> 4.0"
resource_group_id = module.rg.ids["rg-ldo-uks-prd-001"]
location = "uksouth"
tags = module.tags.tags
app_configurations = {
"appcs-ldo-uks-prd-001" = {
replicas = {
"ukwest" = { location = "ukwest" }
}
}
}
app_configuration_keys = {
"app/greeting" = {
app_configuration_key = "appcs-ldo-uks-prd-001"
value = "hello"
}
}
}The inline app_configuration_keys form above only works when the applier already holds a data-plane
role on new stores' scope (for example an inherited subscription-level grant); otherwise use the
two-call pattern shown in examples/complete.
examples/minimal- a single store with the secure defaults.examples/complete- a store with a geo-replica and a system-assigned identity, the App Configuration Data Owner grant with an RBAC settle, and a data-only second call writing keys (plain, JSON, locked, and optionally vault-referenced) and feature flags exercising every filter type.
Local work needs PowerShell 7+ and just, because the recipes
wrap the LibreDevOpsHelpers
PowerShell module (the same engine the libre-devops/terraform-azure action runs in CI). Install
just with brew install just, or uv tool add rust-just then uv run just <recipe>.
Run just to list recipes: just update-ldo-pwsh (install or force-update LibreDevOpsHelpers from
PSGallery), just validate, just scan (Trivy only), just pwsh-analyze (PSScriptAnalyzer only),
just plan, just apply, just destroy, just e2e, just test, and just docs (the
plan/apply/destroy recipes mirror the action, including the storage firewall dance; just e2e
applies an example then always destroys it, defaulting to minimal, so nothing is left running).
Releasing is also just:
just increment-release [patch|minor|major] bumps, tags, and publishes a GitHub release, and the
Terraform Registry picks up the tag.
This module is scanned with Trivy; HIGH and CRITICAL
findings fail the build. Any waiver is a deliberate, reviewed decision, never a way to quiet a
finding that should be fixed. Waivers live in .trivyignore.yaml (the
machine-applied source of truth, passed to Trivy with --ignorefile) and are mirrored in the table
below so the reason is auditable.
| Trivy ID | Resource | Finding | Justification |
|---|---|---|---|
| None |
To add an exception: add an entry to .trivyignore.yaml (id, optional paths to scope it, and a
statement recording why), then add a matching row here. Where the finding is out of this module's
scope, point the justification at the Libre DevOps module that does address it (for example the
private-endpoint module). Both the file and this table are reviewed in the pull request.
The Requirements, Providers, Inputs, Outputs, and Resources below are generated by terraform-docs.
| Name | Version |
|---|---|
| terraform | >= 1.9.0, < 2.0.0 |
| azurerm | >= 4.0.0, < 5.0.0 |
| Name | Version |
|---|---|
| azurerm | >= 4.0.0, < 5.0.0 |
No modules.
| Name | Type |
|---|---|
| azurerm_app_configuration.this | resource |
| azurerm_app_configuration_feature.this | resource |
| azurerm_app_configuration_key.this | resource |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| app_configuration_features | Feature flags to write, keyed by a friendly name (the flag name defaults to the map key). Each entry targets exactly one store: app_configuration_key references a store created in this call by its map key, configuration_store_id an existing store by id. Filters (targeting, timewindow, custom, percentage) gate when the flag evaluates true. Writing features needs the App Configuration Data Owner role on the store; a fresh grant is eventually consistent, so settle it (time_sleep) before the first write. |
map(object({ |
{} |
no |
| app_configuration_keys | Configuration keys to write, keyed by a friendly name (the configuration key defaults to the map key). Each entry targets exactly one store: app_configuration_key references a store created in this call by its map key, configuration_store_id an existing store by id. type kv stores value directly (it lands in the store AND in Terraform state, so put secrets in Key Vault and use type vault with vault_key_reference instead). Writing keys needs the App Configuration Data Owner role on the store; a fresh grant is eventually consistent, so settle it (time_sleep) before the first write. |
map(object({ |
{} |
no |
| app_configurations | App Configuration stores to create, keyed by store name. Full azurerm_app_configuration surface: sku (free, developer, standard, premium), identity, customer-managed-key encryption, replicas (geo-replication, keyed by replica name), soft delete and purge protection, and the ARM data plane proxy settings. Secure defaults: local_auth_enabled false (access keys disabled, data plane is Entra ID only, which is also how this module writes keys and features). public_network_access defaults to Enabled because the store has no IP firewall: to lock it down set it to Disabled and reach the store through a private endpoint (private-endpoint module). Purge protection follows the provider default (off); soft delete (7 days) still protects deleted stores, and disposable stores stay reusable by name. |
map(object({ |
{} |
no |
| location | Azure region for the stores. Only needed when app_configurations is non-empty; a data-only call (keys and features against existing stores) can omit it. | string |
null |
no |
| resource_group_id | Resource id of the resource group to create the stores in. The name and subscription are parsed from it (pass the rg module's ids output). Only needed when app_configurations is non-empty; a data-only call (keys and features against existing stores) can omit it. | string |
null |
no |
| tags | Tags applied to the stores. | map(string) |
{} |
no |
| Name | Description |
|---|---|
| app_configuration_ids_zipmap | Map of store name to an object of its name and resource id, for easy composition. |
| app_configurations | Map of store name to its useful (non-sensitive) attributes. |
| endpoints | Map of store name to its data-plane endpoint URL. |
| feature_ids | Map of feature entry key to its resource id. |
| identities | Map of store name to its managed identity (principal_id, tenant_id), null when the store has none. |
| ids | Map of store name to resource id. |
| key_ids | Map of key entry key to its resource id. |
| primary_read_keys | Map of store name to its primary read access key (id, secret, connection_string). Empty objects when local auth is disabled. |
| primary_write_keys | Map of store name to its primary write access key (id, secret, connection_string). Empty objects when local auth is disabled. |
| replica_endpoints | Map of store name to a map of replica name to replica endpoint URL. |
| resource_group_name | Resource group name parsed from resource_group_id, null on a data-only call. |
| secondary_read_keys | Map of store name to its secondary read access key (id, secret, connection_string). Empty objects when local auth is disabled. |
| secondary_write_keys | Map of store name to its secondary write access key (id, secret, connection_string). Empty objects when local auth is disabled. |
| subscription_id | Subscription id parsed from resource_group_id, null on a data-only call. |