diff --git a/README.md b/README.md
index a057730..c0f8949 100644
--- a/README.md
+++ b/README.md
@@ -28,16 +28,29 @@
We use the AzAPI provider to interact with the Azure APIs.
The new features allow us to be more efficient and reliable, with orders of magnitude speed improvements and retry logic for transient errors.
-## Unknown Values
+## Unknown Values & Depends On
This module uses the ALZ Terraform provider. This uses a data source which **must** be read prior to creating the plan.
-If you pass an unknown (known after apply) value into the module, it will not be able to read the data source until the plan is being applied.
+
+The `depends_on` feature is therefore not supported in the ALZ provider.
+Please do not add a `depends_on` attribute to the module declaration.
+
+Similarly, if you pass an unknown (known after apply) value into the module, it will not be able to read the data source until the plan is being applied.
This may cause resources to be unnecessarily recreated.
-Such unknown values include resource ids. For example, if you are creating a resource and passing the id of the resource group to the module, this will cause the issue.
-Instead, use string interpolation or provider functions to pass the values. For example:
+To work around this, we have two features.
+Firstly we have a `dependencies` variable.
+This variable is used to ensure that policies and policy role assignments do not get created until dependent resources are available.
+
+Secondly, for values that are passed into the module, use string interpolation or provider functions to create the required. For example:
+
+### Using `var.dependencies`
-### Recommended
+This variable is used as a workaround for the lack of support for `depends_on` in the ALZ provider.
+Place values into this variable to ensure that policies and policy role assignments do not get created until dependent resources are available.
+See the variable documentation and the examples (private DNS and management) for more information.
+
+### Using Provider Functions
Either: Use known values as inputs, or use Terraform Stacks.
@@ -75,9 +88,16 @@ module "example" {
}
```
+### `var.dependencies`
+
+This variable is used as a workaround for the lack of support for `depends_on` in the ALZ provider.
+Place values into this variable to ensure that policies and policy role assignments do not get created until dependent resources are available.
+See the variable documentation and the examples (private DNS and management) for more information.
+
### Deferred Actions
-We are awaiting the results of the upstream Terraform language experiment *deferred actions*. This will provide a solution to this issue.
+We are awaiting the results of the upstream Terraform language experiment *deferred actions*.
+This will provide a solution to this issue.
See the release notes [here](https://github.com/hashicorp/terraform/releases/tag/v1.10.0-alpha20241023) for more information.
@@ -87,7 +107,7 @@ The following requirements are needed by this module:
- [terraform](#requirement\_terraform) (~> 1.8)
-- [alz](#requirement\_alz) (~> 0.16)
+- [alz](#requirement\_alz) (~> 0.16, >= 0.16.2)
- [azapi](#requirement\_azapi) (~> 2.0, >= 2.0.1)
@@ -118,6 +138,8 @@ The following resources are used by this module:
- [azapi_update_resource.hierarchy_settings](https://registry.terraform.io/providers/azure/azapi/latest/docs/resources/update_resource) (resource)
- [modtm_telemetry.telemetry](https://registry.terraform.io/providers/azure/modtm/latest/docs/resources/telemetry) (resource)
- [random_uuid.telemetry](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/uuid) (resource)
+- [terraform_data.policy_assignments_dependencies](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) (resource)
+- [terraform_data.policy_role_assignments_dependencies](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) (resource)
- [time_sleep.after_management_groups](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) (resource)
- [time_sleep.after_policy_definitions](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) (resource)
- [time_sleep.after_policy_set_definitions](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) (resource)
@@ -184,6 +206,36 @@ object({
Default: `{}`
+### [dependencies](#input\_dependencies)
+
+Description: Place dependent values into this variable to ensure that resources are created in the correct order.
+Ensure that the values placed here are computed/known after apply, e.g. the resource ids.
+
+This is necessary as the unknown values and `depends_on` are not supported by this module as we use the alz provider.
+See the "Unknown Values & Depends On" section above for more information.
+
+e.g.
+
+```hcl
+dependencies = {
+ policy_role_assignments = [
+ module.dependency_example1.output,
+ module.dependency_example2.output,
+ ]
+}
+```
+
+Type:
+
+```hcl
+object({
+ policy_role_assignments = optional(any, null)
+ policy_assignments = optional(any, null)
+ })
+```
+
+Default: `{}`
+
### [enable\_telemetry](#input\_enable\_telemetry)
Description: This variable controls whether or not telemetry is enabled for the module.
@@ -359,7 +411,7 @@ object({
}), {})
policy_role_assignments = optional(object({
error_message_regex = optional(list(string), [
- "RoleAssignmentNotFound" # Added to fix an eventual consistency error with a GET following soon after a PUT
+ "ResourceNotFound", # If the resource has just been created, retry until it is available.
])
interval_seconds = optional(number, null)
max_interval_seconds = optional(number, null)
diff --git a/_header.md b/_header.md
index cb8d3eb..7b08a1b 100644
--- a/_header.md
+++ b/_header.md
@@ -27,16 +27,29 @@
We use the AzAPI provider to interact with the Azure APIs.
The new features allow us to be more efficient and reliable, with orders of magnitude speed improvements and retry logic for transient errors.
-## Unknown Values
+## Unknown Values & Depends On
This module uses the ALZ Terraform provider. This uses a data source which **must** be read prior to creating the plan.
-If you pass an unknown (known after apply) value into the module, it will not be able to read the data source until the plan is being applied.
+
+The `depends_on` feature is therefore not supported in the ALZ provider.
+Please do not add a `depends_on` attribute to the module declaration.
+
+Similarly, if you pass an unknown (known after apply) value into the module, it will not be able to read the data source until the plan is being applied.
This may cause resources to be unnecessarily recreated.
-Such unknown values include resource ids. For example, if you are creating a resource and passing the id of the resource group to the module, this will cause the issue.
-Instead, use string interpolation or provider functions to pass the values. For example:
+To work around this, we have two features.
+Firstly we have a `dependencies` variable.
+This variable is used to ensure that policies and policy role assignments do not get created until dependent resources are available.
+
+Secondly, for values that are passed into the module, use string interpolation or provider functions to create the required. For example:
+
+### Using `var.dependencies`
-### Recommended
+This variable is used as a workaround for the lack of support for `depends_on` in the ALZ provider.
+Place values into this variable to ensure that policies and policy role assignments do not get created until dependent resources are available.
+See the variable documentation and the examples (private DNS and management) for more information.
+
+### Using Provider Functions
Either: Use known values as inputs, or use Terraform Stacks.
@@ -74,7 +87,14 @@ module "example" {
}
```
+### `var.dependencies`
+
+This variable is used as a workaround for the lack of support for `depends_on` in the ALZ provider.
+Place values into this variable to ensure that policies and policy role assignments do not get created until dependent resources are available.
+See the variable documentation and the examples (private DNS and management) for more information.
+
### Deferred Actions
-We are awaiting the results of the upstream Terraform language experiment *deferred actions*. This will provide a solution to this issue.
+We are awaiting the results of the upstream Terraform language experiment *deferred actions*.
+This will provide a solution to this issue.
See the release notes [here](https://github.com/hashicorp/terraform/releases/tag/v1.10.0-alpha20241023) for more information.
diff --git a/examples/management/README.md b/examples/management/README.md
index 0af99c6..908f624 100644
--- a/examples/management/README.md
+++ b/examples/management/README.md
@@ -54,6 +54,13 @@ module "alz" {
ama_user_assigned_managed_identity_name = jsonencode({ value = local.uami_name })
log_analytics_workspace_id = jsonencode({ value = provider::azapi::resource_group_resource_id(data.azapi_client_config.current.subscription_id, local.resource_group_name, "Microsoft.OperationalInsights/workspaces", [local.log_analytics_workspace_name]) })
}
+ dependencies = {
+ policy_assignments = [
+ module.management.data_collection_rule_ids,
+ module.management.resource_id,
+ module.management.user_assigned_identity_ids,
+ ]
+ }
policy_assignments_to_modify = {
connectivity = {
policy_assignments = {
diff --git a/examples/management/main.tf b/examples/management/main.tf
index 64c227f..5e1905c 100644
--- a/examples/management/main.tf
+++ b/examples/management/main.tf
@@ -48,6 +48,13 @@ module "alz" {
ama_user_assigned_managed_identity_name = jsonencode({ value = local.uami_name })
log_analytics_workspace_id = jsonencode({ value = provider::azapi::resource_group_resource_id(data.azapi_client_config.current.subscription_id, local.resource_group_name, "Microsoft.OperationalInsights/workspaces", [local.log_analytics_workspace_name]) })
}
+ dependencies = {
+ policy_assignments = [
+ module.management.data_collection_rule_ids,
+ module.management.resource_id,
+ module.management.user_assigned_identity_ids,
+ ]
+ }
policy_assignments_to_modify = {
connectivity = {
policy_assignments = {
diff --git a/examples/privatednszones/README.md b/examples/privatednszones/README.md
new file mode 100644
index 0000000..eb87e70
--- /dev/null
+++ b/examples/privatednszones/README.md
@@ -0,0 +1,123 @@
+
+# ALZ + Management
+
+This example shows how to deploy the ALZ reference architecture combines with the ALZ management module.
+
+```hcl
+provider "alz" {
+ library_references = [{
+ path = "platform/alz"
+ ref = "2024.11.0"
+ }]
+}
+
+provider "azurerm" {
+ features {}
+}
+
+variable "random_suffix" {
+ type = string
+ default = "fgcsnm"
+ description = "Change me to something unique"
+}
+
+data "azapi_client_config" "current" {}
+
+locals {
+ location = "swedencentral"
+ resource_group_name = "rg-private-dns-${var.random_suffix}"
+}
+
+module "private_dns_zones" {
+ source = "Azure/avm-ptn-network-private-link-private-dns-zones/azurerm"
+ version = "0.6.0"
+ location = local.location
+ resource_group_name = local.resource_group_name
+}
+
+module "alz" {
+ source = "../../"
+ architecture_name = "alz"
+ parent_resource_id = data.azapi_client_config.current.tenant_id
+ location = local.location
+ policy_default_values = {
+ private_dns_zone_subscription_id = jsonencode({ value = data.azapi_client_config.current.subscription_id })
+ private_dns_zone_region = jsonencode({ value = local.location })
+ private_dns_zone_resource_group_name = jsonencode({ value = local.resource_group_name })
+ }
+ dependencies = {
+ policy_assignments = [
+ module.private_dns_zones.private_dns_zone_resource_ids,
+ ]
+ }
+ policy_assignments_to_modify = {
+ connectivity = {
+ policy_assignments = {
+ # As we don't have a DDOS protection plan, we need to disable this policy
+ # to prevent a modify action from failing.
+ Enable-DDoS-VNET = {
+ enforcement_mode = "DoNotEnforce"
+ }
+ }
+ }
+ }
+}
+```
+
+
+## Requirements
+
+The following requirements are needed by this module:
+
+- [terraform](#requirement\_terraform) (~> 1.8)
+
+- [alz](#requirement\_alz) (~> 0.16)
+
+- [azapi](#requirement\_azapi) (~> 2.0, >= 2.0.1)
+
+- [azurerm](#requirement\_azurerm) (~> 3.100)
+
+## Resources
+
+The following resources are used by this module:
+
+- [azapi_client_config.current](https://registry.terraform.io/providers/Azure/azapi/latest/docs/data-sources/client_config) (data source)
+
+
+## Required Inputs
+
+No required inputs.
+
+## Optional Inputs
+
+The following input variables are optional (have default values):
+
+### [random\_suffix](#input\_random\_suffix)
+
+Description: Change me to something unique
+
+Type: `string`
+
+Default: `"fgcsnm"`
+
+## Outputs
+
+No outputs.
+
+## Modules
+
+The following Modules are called:
+
+### [alz](#module\_alz)
+
+Source: ../../
+
+Version:
+
+### [private\_dns\_zones](#module\_private\_dns\_zones)
+
+Source: Azure/avm-ptn-network-private-link-private-dns-zones/azurerm
+
+Version: 0.6.0
+
+
\ No newline at end of file
diff --git a/examples/privatednszones/_footer.md b/examples/privatednszones/_footer.md
new file mode 100644
index 0000000..e69de29
diff --git a/examples/privatednszones/_header.md b/examples/privatednszones/_header.md
new file mode 100644
index 0000000..dd4e722
--- /dev/null
+++ b/examples/privatednszones/_header.md
@@ -0,0 +1,3 @@
+# ALZ + Management
+
+This example shows how to deploy the ALZ reference architecture combines with the ALZ management module.
diff --git a/examples/privatednszones/main.tf b/examples/privatednszones/main.tf
new file mode 100644
index 0000000..190f547
--- /dev/null
+++ b/examples/privatednszones/main.tf
@@ -0,0 +1,58 @@
+provider "alz" {
+ library_references = [{
+ path = "platform/alz"
+ ref = "2024.11.0"
+ }]
+}
+
+provider "azurerm" {
+ features {}
+}
+
+variable "random_suffix" {
+ type = string
+ default = "fgcsnm"
+ description = "Change me to something unique"
+}
+
+data "azapi_client_config" "current" {}
+
+locals {
+ location = "swedencentral"
+ resource_group_name = "rg-private-dns-${var.random_suffix}"
+}
+
+module "private_dns_zones" {
+ source = "Azure/avm-ptn-network-private-link-private-dns-zones/azurerm"
+ version = "0.6.0"
+ location = local.location
+ resource_group_name = local.resource_group_name
+}
+
+module "alz" {
+ source = "../../"
+ architecture_name = "alz"
+ parent_resource_id = data.azapi_client_config.current.tenant_id
+ location = local.location
+ policy_default_values = {
+ private_dns_zone_subscription_id = jsonencode({ value = data.azapi_client_config.current.subscription_id })
+ private_dns_zone_region = jsonencode({ value = local.location })
+ private_dns_zone_resource_group_name = jsonencode({ value = local.resource_group_name })
+ }
+ dependencies = {
+ policy_assignments = [
+ module.private_dns_zones.private_dns_zone_resource_ids,
+ ]
+ }
+ policy_assignments_to_modify = {
+ connectivity = {
+ policy_assignments = {
+ # As we don't have a DDOS protection plan, we need to disable this policy
+ # to prevent a modify action from failing.
+ Enable-DDoS-VNET = {
+ enforcement_mode = "DoNotEnforce"
+ }
+ }
+ }
+ }
+}
diff --git a/examples/privatednszones/terraform.tf b/examples/privatednszones/terraform.tf
new file mode 100644
index 0000000..83c1dc2
--- /dev/null
+++ b/examples/privatednszones/terraform.tf
@@ -0,0 +1,17 @@
+terraform {
+ required_version = "~> 1.8"
+ required_providers {
+ alz = {
+ source = "Azure/alz"
+ version = "~> 0.16"
+ }
+ azapi = {
+ source = "Azure/azapi"
+ version = "~> 2.0, >= 2.0.1"
+ }
+ azurerm = {
+ source = "hashicorp/azurerm"
+ version = "~> 3.100"
+ }
+ }
+}
diff --git a/main.policy_assignments.tf b/main.policy_assignments.tf
index 9b506ed..1d158b4 100644
--- a/main.policy_assignments.tf
+++ b/main.policy_assignments.tf
@@ -1,3 +1,7 @@
+resource "terraform_data" "policy_assignments_dependencies" {
+ input = sha256(jsonencode(var.dependencies.policy_assignments))
+}
+
resource "azapi_resource" "policy_assignments" {
for_each = local.policy_assignments
@@ -56,7 +60,8 @@ resource "azapi_resource" "policy_assignments" {
}
depends_on = [
- time_sleep.after_policy_set_definitions
+ time_sleep.after_policy_set_definitions,
+ terraform_data.policy_assignments_dependencies,
]
lifecycle {
diff --git a/main.policy_role_assignments.tf b/main.policy_role_assignments.tf
index 0dcb4a9..3fa08c2 100644
--- a/main.policy_role_assignments.tf
+++ b/main.policy_role_assignments.tf
@@ -1,3 +1,7 @@
+resource "terraform_data" "policy_role_assignments_dependencies" {
+ input = sha256(jsonencode(var.dependencies.policy_role_assignments))
+}
+
resource "azapi_resource" "policy_role_assignments" {
for_each = local.policy_role_assignments
@@ -30,4 +34,11 @@ resource "azapi_resource" "policy_role_assignments" {
read = var.timeouts.policy_role_assignment.read
update = var.timeouts.policy_role_assignment.update
}
+
+ depends_on = [terraform_data.policy_role_assignments_dependencies]
+
+ lifecycle {
+ # https://github.com/Azure/terraform-provider-azapi/issues/671
+ ignore_changes = [output.properties.updatedOn]
+ }
}
diff --git a/terraform.tf b/terraform.tf
index 8ddca1d..ce8c4c9 100644
--- a/terraform.tf
+++ b/terraform.tf
@@ -3,7 +3,7 @@ terraform {
required_providers {
alz = {
source = "azure/alz"
- version = "~> 0.16"
+ version = "~> 0.16, >= 0.16.2"
}
azapi = {
source = "azure/azapi"
diff --git a/variables.tf b/variables.tf
index edec7ee..187c870 100644
--- a/variables.tf
+++ b/variables.tf
@@ -33,6 +33,33 @@ DESCRIPTION
}
}
+variable "dependencies" {
+ type = object({
+ policy_role_assignments = optional(any, null)
+ policy_assignments = optional(any, null)
+ })
+ default = {}
+ description = <