diff --git a/.gitignore b/.gitignore index 8269fa6..e3faf45 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,6 @@ avm.tflint_example.merged.hcl .alzlib avm.tflint_module.hcl avm.tflint_module.merged.hcl + +*.tfrc + diff --git a/.tflint.override.hcl b/.tflint.override.hcl new file mode 100644 index 0000000..d950f7d --- /dev/null +++ b/.tflint.override.hcl @@ -0,0 +1,3 @@ +rule "required_output_rmfr7" { + enabled = false +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index c74bd81..cdcb783 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,7 @@ { "recommendations": [ "hashicorp.terraform", - "EditorConfig.EditorConfig" + "EditorConfig.EditorConfig", + "redhat.vscode-yaml" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 60bdb3b..952415f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "editor.bracketPairColorization.enabled": true + "editor.bracketPairColorization.enabled": true } diff --git a/README.md b/README.md index 40f163a..d6683e4 100644 --- a/README.md +++ b/README.md @@ -3,23 +3,74 @@ # ALZ Terraform Module -> ⚠️ ***Warning*** ⚠️ This module is still in development but is ready for initial testing and feedback via [GitHub Issues](https://github.com/Azure/terraform-azurerm-avm-ptn-alz/issues). +> [!WARNING] +> This module is still in development but is ready for initial testing and feedback via [GitHub Issues](https://github.com/Azure/terraform-azurerm-avm-ptn-alz/issues). - This repository contains a Terraform module for deploying Azure Landing Zones (ALZs). - Make sure to review the examples. +> [!IMPORTANT] +> Do not pass unknown (computed) values into the properties of the module. Instead use string interpolation and other means to pass the values. Seem below for details. + +## Unknown Values + +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/computed 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 to pass the values. For example: + +### Recommended + +This is the recommended way to use this module: + +> [!NOTE] +> We assume that all variable inputs are literals. + +```terraform + +locals { + foo_resource_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/resourceGroups/${var.resource_group_name}/providers/Microsoft.FooResourceProvider/${var.foo_resource_name}" +} + +module "example" { + source = "Azure/terraform-azurerm-avm-ptn-alz/azurerm" + + policy_assignments_to_modify = { + alzroot = { + policy_assignments = { + mypolicy = { + parameters = jsonencode({ + parameterName = local.foo_resource_id + }) + } + } + } + } +} +``` + +### Deferred Actions + +We are awaiting the results of the upstream Terraform language experiment *deferred actions*. This may provide a solution to this issue. +See the release notes [here](https://github.com/hashicorp/terraform/releases/tag/v1.10.0-alpha20240619) for more information. + ## Requirements The following requirements are needed by this module: -- [terraform](#requirement\_terraform) (~> 1.0) +- [terraform](#requirement\_terraform) (~> 1.6) -- [alz](#requirement\_alz) (~> 0.11) +- [alz](#requirement\_alz) (~> 0.12, >= 0.12.5) -- [azurerm](#requirement\_azurerm) (~> 3.74) +- [azapi](#requirement\_azapi) (~> 1.14) -- [random](#requirement\_random) (~> 3.5) +- [modtm](#requirement\_modtm) (~> 0.3) + +- [random](#requirement\_random) (~> 3.6) - [time](#requirement\_time) (~> 0.9) @@ -27,11 +78,13 @@ The following requirements are needed by this module: The following providers are used by this module: -- [alz](#provider\_alz) (~> 0.11) +- [alz](#provider\_alz) (~> 0.12, >= 0.12.5) + +- [azapi](#provider\_azapi) (~> 1.14) -- [azurerm](#provider\_azurerm) (~> 3.74) +- [modtm](#provider\_modtm) (~> 0.3) -- [random](#provider\_random) (~> 3.5) +- [random](#provider\_random) (~> 3.6) - [time](#provider\_time) (~> 0.9) @@ -39,57 +92,36 @@ The following providers are used by this module: The following resources are used by this module: -- [alz_policy_role_assignments.this](https://registry.terraform.io/providers/azure/alz/latest/docs/resources/policy_role_assignments) (resource) -- [azurerm_management_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/management_group) (resource) -- [azurerm_management_group_policy_assignment.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/management_group_policy_assignment) (resource) -- [azurerm_management_group_subscription_association.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/management_group_subscription_association) (resource) -- [azurerm_management_group_template_deployment.telemetry](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/management_group_template_deployment) (resource) -- [azurerm_policy_definition.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/policy_definition) (resource) -- [azurerm_policy_set_definition.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/policy_set_definition) (resource) -- [azurerm_role_assignment.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) (resource) -- [azurerm_role_definition.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_definition) (resource) -- [random_id.telem](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) (resource) -- [time_sleep.before_management_group_creation](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) (resource) -- [time_sleep.before_policy_assignments](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) (resource) -- [time_sleep.before_policy_role_assignments](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) (resource) -- [alz_archetype.this](https://registry.terraform.io/providers/azure/alz/latest/docs/data-sources/archetype) (data source) -- [alz_archetype_keys.this](https://registry.terraform.io/providers/azure/alz/latest/docs/data-sources/archetype_keys) (data source) -- [azurerm_subscription.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subscription) (data source) +- [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) +- [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) +- [alz_architecture.this](https://registry.terraform.io/providers/azure/alz/latest/docs/data-sources/architecture) (data source) +- [azapi_client_config.telemetry](https://registry.terraform.io/providers/azure/azapi/latest/docs/data-sources/client_config) (data source) +- [modtm_module_source.telemetry](https://registry.terraform.io/providers/azure/modtm/latest/docs/data-sources/module_source) (data source) ## Required Inputs The following input variables are required: -### [base\_archetype](#input\_base\_archetype) +### [architecture\_name](#input\_architecture\_name) -Description: The archetype of the management group. -This should be one of the built in archetypes, or a custom one defined in one of the `lib_dirs`. +Description: The name of the architecture to create. This needs to be of the `*.alz_architecture_definition.[json|yaml|yml]` files. Type: `string` -### [default\_location](#input\_default\_location) +### [location](#input\_location) Description: The default location for resources in this management group. Used for policy managed identities. Type: `string` -### [display\_name](#input\_display\_name) - -Description: The display name of the management group. - -Type: `string` - -### [id](#input\_id) - -Description: The id of the management group. This must be unique and cannot be changed after creation. - -Type: `string` - ### [parent\_resource\_id](#input\_parent\_resource\_id) Description: The resource id of the parent management group. Use the tenant id to create a child of the tenant root group. -The `azurerm_client_config` data source from the AzureRM provider is useful to get the tenant id. +The `azurerm_client_config`/`azapi_client_config` data sources are able to retrieve the tenant id. Type: `string` @@ -97,22 +129,6 @@ Type: `string` The following input variables are optional (have default values): -### [default\_log\_analytics\_workspace\_id](#input\_default\_log\_analytics\_workspace\_id) - -Description: The resource id of the default log analytics workspace to use for policy parameters. - -Type: `string` - -Default: `null` - -### [default\_private\_dns\_zone\_resource\_group\_id](#input\_default\_private\_dns\_zone\_resource\_group\_id) - -Description: Resource group id for the private dns zones to use in policy parameters. - -Type: `string` - -Default: `null` - ### [delays](#input\_delays) Description: A map of delays to apply to the creation and destruction of resources. @@ -122,16 +138,16 @@ Type: ```hcl object({ - before_management_group = optional(object({ + after_management_group = optional(object({ create = optional(string, "30s") destroy = optional(string, "0s") }), {}) - before_policy_assignments = optional(object({ + after_policy_definitions = optional(object({ create = optional(string, "30s") destroy = optional(string, "0s") }), {}) - before_policy_role_assignments = optional(object({ - create = optional(string, "60s") + after_policy_set_definitions = optional(object({ + create = optional(string, "30s") destroy = optional(string, "0s") }), {}) }) @@ -151,11 +167,12 @@ Default: `true` ### [policy\_assignments\_to\_modify](#input\_policy\_assignments\_to\_modify) -Description: A map of policy assignment objects to modify the ALZ archetype with. +Description: A map of policy assignment objects to modify the ALZ architecture with. You only need to specify the properties you want to change. -The key is the name of the policy assignment. -The value is a map of the properties of the policy assignment. +The key is the id of the management group. The value is an object with a single attribute, `policy_assignments`. +The `policy_assignments` value is a map of policy assignments to modify. +The key of this map is the assignment name, and the value is an object with optional attributes for modifying the policy assignments. - `enforcement_mode` - (Optional) The enforcement mode of the policy assignment. Possible values are `Default` and `DoNotEnforce`. - `identity` - (Optional) The identity of the policy assignment. Possible values are `SystemAssigned` and `UserAssigned`. @@ -182,82 +199,203 @@ Type: ```hcl map(object({ - enforcement_mode = optional(string, null) - identity = optional(string, null) - identity_ids = optional(list(string), null) - parameters = optional(string, null) - non_compliance_message = optional(set(object({ - message = string - policy_definition_reference_id = optional(string, null) - })), null) - resource_selectors = optional(list(object({ - name = string - selectors = optional(list(object({ - kind = string - in = optional(set(string), null) - not_in = optional(set(string), null) - })), []) - }))) - overrides = optional(list(object({ - kind = string - value = string - selectors = optional(list(object({ - kind = string - in = optional(set(string), null) - not_in = optional(set(string), null) - })), []) - }))) + policy_assignments = map(object({ + enforcement_mode = optional(string, null) + identity = optional(string, null) + identity_ids = optional(list(string), null) + parameters = optional(string, null) + non_compliance_message = optional(set(object({ + message = string + policy_definition_reference_id = optional(string, null) + })), null) + resource_selectors = optional(list(object({ + name = string + selectors = optional(list(object({ + kind = string + in = optional(set(string), null) + not_in = optional(set(string), null) + })), []) + }))) + overrides = optional(list(object({ + kind = string + value = string + selectors = optional(list(object({ + kind = string + in = optional(set(string), null) + not_in = optional(set(string), null) + })), []) + }))) + })) })) ``` Default: `{}` -### [role\_assignments](#input\_role\_assignments) - -Description: A map of role assignments to associated principals and role definitions to the management group. +### [timeouts](#input\_timeouts) -The key is the your reference for the role assignment. The value is a map of the properties of the role assignment. - -- `role_definition_id` - (Optional) The id of the role definition to assign to the principal. Conflicts with `role_definition_name`. `role_definition_id` and `role_definition_name` are mutually exclusive and one of them must be supplied. -- `role_definition_name` - (Optional) The name of the role definition to assign to the principal. Conflicts with `role_definition_id`. -- `principal_id` - (Required) The id of the principal to assign the role definition to. -- `description` - (Optional) The description of the role assignment. +Description: A map of timeouts to apply to the creation and destruction of resources. Type: ```hcl -map(object({ - role_definition_id = optional(string, "") - role_definition_name = optional(string, "") - principal_id = string - description = optional(string, null) - })) +object({ + management_group = optional(object({ + create = optional(string, "10m") + delete = optional(string, "10m") + update = optional(string, "10m") + read = optional(string, "10m") + }), {} + ) + role_definition = optional(object({ + create = optional(string, "10m") + delete = optional(string, "10m") + update = optional(string, "10m") + read = optional(string, "10m") + }), {} + ) + policy_definition = optional(object({ + create = optional(string, "10m") + delete = optional(string, "10m") + update = optional(string, "10m") + read = optional(string, "10m") + }), {} + ) + policy_set_definition = optional(object({ + create = optional(string, "10m") + delete = optional(string, "10m") + update = optional(string, "10m") + read = optional(string, "10m") + }), {} + ) + policy_assignment = optional(object({ + create = optional(string, "10m") + delete = optional(string, "10m") + update = optional(string, "10m") + read = optional(string, "10m") + }), {} + ) + policy_role_assignment = optional(object({ + create = optional(string, "10m") + delete = optional(string, "10m") + update = optional(string, "10m") + read = optional(string, "10m") + }), {} + ) + }) ``` Default: `{}` -### [subscription\_ids](#input\_subscription\_ids) +## Outputs -Description: A set of subscription ids to move under this management group. +The following outputs are exported: -Type: `set(string)` +### [management\_group\_resource\_ids](#output\_management\_group\_resource\_ids) -Default: `[]` +Description: n/a -## Outputs +### [policy\_assignment\_identity\_ids](#output\_policy\_assignment\_identity\_ids) -The following outputs are exported: +Description: A map of policy assignment names to their identity ids. + +### [policy\_assignment\_resource\_ids](#output\_policy\_assignment\_resource\_ids) + +Description: A map of policy assignment names to their resource ids. + +### [policy\_definition\_resource\_ids](#output\_policy\_definition\_resource\_ids) + +Description: A map of policy definition names to their resource ids. + +### [policy\_role\_assignment\_resource\_ids](#output\_policy\_role\_assignment\_resource\_ids) -### [management\_group\_resource\_id](#output\_management\_group\_resource\_id) +Description: A map of policy role assignments to their resource ids. -Description: The resource id of the created management group. +### [policy\_set\_definition\_resource\_ids](#output\_policy\_set\_definition\_resource\_ids) + +Description: A map of policy set definition names to their resource ids. + +### [role\_definition\_resource\_ids](#output\_role\_definition\_resource\_ids) + +Description: A map of role definition names to their resource ids. ## Modules -No modules. +The following Modules are called: + +### [management\_groups\_level\_0](#module\_management\_groups\_level\_0) + +Source: ./modules/azapi_helper + +Version: + +### [management\_groups\_level\_1](#module\_management\_groups\_level\_1) + +Source: ./modules/azapi_helper + +Version: + +### [management\_groups\_level\_2](#module\_management\_groups\_level\_2) + +Source: ./modules/azapi_helper + +Version: + +### [management\_groups\_level\_3](#module\_management\_groups\_level\_3) + +Source: ./modules/azapi_helper + +Version: + +### [management\_groups\_level\_4](#module\_management\_groups\_level\_4) + +Source: ./modules/azapi_helper + +Version: + +### [management\_groups\_level\_5](#module\_management\_groups\_level\_5) + +Source: ./modules/azapi_helper + +Version: + +### [management\_groups\_level\_6](#module\_management\_groups\_level\_6) + +Source: ./modules/azapi_helper + +Version: + +### [policy\_assignment](#module\_policy\_assignment) + +Source: ./modules/azapi_helper + +Version: + +### [policy\_definitions](#module\_policy\_definitions) + +Source: ./modules/azapi_helper + +Version: + +### [policy\_role\_assignments](#module\_policy\_role\_assignments) + +Source: ./modules/azapi_helper + +Version: + +### [policy\_set\_definitions](#module\_policy\_set\_definitions) + +Source: ./modules/azapi_helper + +Version: + +### [role\_definitions](#module\_role\_definitions) + +Source: ./modules/azapi_helper + +Version: ## Data Collection The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. - + \ No newline at end of file diff --git a/_header.md b/_header.md index 3fa68f9..590d52d 100644 --- a/_header.md +++ b/_header.md @@ -2,8 +2,57 @@ # ALZ Terraform Module -> ⚠️ ***Warning*** ⚠️ This module is still in development but is ready for initial testing and feedback via [GitHub Issues](https://github.com/Azure/terraform-azurerm-avm-ptn-alz/issues). +> [!WARNING] +> This module is still in development but is ready for initial testing and feedback via [GitHub Issues](https://github.com/Azure/terraform-azurerm-avm-ptn-alz/issues). - This repository contains a Terraform module for deploying Azure Landing Zones (ALZs). - Make sure to review the examples. +> [!IMPORTANT] +> Do not pass unknown (computed) values into the properties of the module. Instead use string interpolation and other means to pass the values. Seem below for details. + +## Unknown Values + +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/computed 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 to pass the values. For example: + +### Recommended + +This is the recommended way to use this module: + +> [!NOTE] +> We assume that all variable inputs are literals. + +```terraform + +locals { + foo_resource_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/resourceGroups/${var.resource_group_name}/providers/Microsoft.FooResourceProvider/${var.foo_resource_name}" +} + + +module "example" { + source = "Azure/terraform-azurerm-avm-ptn-alz/azurerm" + + policy_assignments_to_modify = { + alzroot = { + policy_assignments = { + mypolicy = { + parameters = jsonencode({ + parameterName = local.foo_resource_id + }) + } + } + } + } +} +``` + +### Deferred Actions + +We are awaiting the results of the upstream Terraform language experiment *deferred actions*. This may provide a solution to this issue. +See the release notes [here](https://github.com/hashicorp/terraform/releases/tag/v1.10.0-alpha20240619) for more information. diff --git a/examples/alzreference/README.md b/examples/alzreference/README.md deleted file mode 100644 index c209620..0000000 --- a/examples/alzreference/README.md +++ /dev/null @@ -1,249 +0,0 @@ - -# Deploying the ALZ Reference Architecture - -This example shows how to deploy the ALZ reference architecture. -It uses the ALZ management module to deploy the Log Analytics workspace and Automation Account. - -```hcl -# This helps keep naming unique -resource "random_pet" "this" { - length = 1 -} - -module "naming" { - source = "Azure/naming/azurerm" - version = "~> 0.3" - suffix = [random_pet.this.id] - prefix = ["test-avm-ptn-alz"] -} - -module "alz_management_resources" { - source = "Azure/alz-management/azurerm" - version = "~> 0.1" - - automation_account_name = module.naming.automation_account.name - location = local.default_location - log_analytics_workspace_name = module.naming.log_analytics_workspace.name - resource_group_name = module.naming.resource_group.name -} - -# This allows us to get the tenant id -data "azurerm_client_config" "current" {} - -module "alz_archetype_root" { - source = "../../" - id = "${random_pet.this.id}-alz-root" - display_name = "${random_pet.this.id}-alz-root" - parent_resource_id = "/providers/Microsoft.Management/managementGroups/${data.azurerm_client_config.current.tenant_id}" - base_archetype = "root" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = merge(local.default_delays, { - before_management_group_creation = { - create = "0s" - } - }) -} - -module "alz_archetype_landing_zones" { - source = "../../" - id = "${random_pet.this.id}-landing-zones" - display_name = "${random_pet.this.id}-landing-zones" - parent_resource_id = module.alz_archetype_root.management_group_resource_id - base_archetype = "landing_zones" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = local.default_delays -} - -module "alz_archetype_platform" { - source = "../../" - id = "${random_pet.this.id}-platform" - display_name = "${random_pet.this.id}-platform" - parent_resource_id = module.alz_archetype_root.management_group_resource_id - base_archetype = "platform" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = local.default_delays -} - -module "alz_archetype_identity" { - source = "../../" - id = "${random_pet.this.id}-identity" - display_name = "${random_pet.this.id}-identity" - parent_resource_id = module.alz_archetype_platform.management_group_resource_id - base_archetype = "identity" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = local.default_delays -} - -module "alz_archetype_connectivity" { - source = "../../" - id = "${random_pet.this.id}-connectivity" - display_name = "${random_pet.this.id}-connectivity" - parent_resource_id = module.alz_archetype_platform.management_group_resource_id - base_archetype = "connectivity" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = local.default_delays -} - -module "alz_archetype_management" { - source = "../../" - id = "${random_pet.this.id}-management" - display_name = "${random_pet.this.id}-management" - parent_resource_id = module.alz_archetype_platform.management_group_resource_id - base_archetype = "management" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - subscription_ids = [data.azurerm_client_config.current.subscription_id] - delays = local.default_delays -} - -module "alz_archetype_corp" { - source = "../../" - id = "${random_pet.this.id}-corp" - display_name = "${random_pet.this.id}-corp" - parent_resource_id = module.alz_archetype_landing_zones.management_group_resource_id - base_archetype = "corp" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = local.default_delays -} - -module "alz_archetype_online" { - source = "../../" - id = "${random_pet.this.id}-online" - display_name = "${random_pet.this.id}-online" - parent_resource_id = module.alz_archetype_landing_zones.management_group_resource_id - base_archetype = "online" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = local.default_delays -} - -module "alz_archetype_sandboxes" { - source = "../../" - id = "${random_pet.this.id}-sandboxes" - display_name = "${random_pet.this.id}-sandboxes" - parent_resource_id = module.alz_archetype_root.management_group_resource_id - base_archetype = "sandboxes" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = local.default_delays -} -``` - - -## Requirements - -The following requirements are needed by this module: - -- [terraform](#requirement\_terraform) (~> 1.0) - -- [alz](#requirement\_alz) (~> 0.10) - -- [azurerm](#requirement\_azurerm) (~> 3.74) - -- [random](#requirement\_random) (~> 3.5) - -## Providers - -The following providers are used by this module: - -- [azurerm](#provider\_azurerm) (~> 3.74) - -- [random](#provider\_random) (~> 3.5) - -## Resources - -The following resources are used by this module: - -- [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) (resource) -- [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) (data source) - - -## Required Inputs - -No required inputs. - -## Optional Inputs - -No optional inputs. - -## Outputs - -No outputs. - -## Modules - -The following Modules are called: - -### [alz\_archetype\_connectivity](#module\_alz\_archetype\_connectivity) - -Source: ../../ - -Version: - -### [alz\_archetype\_corp](#module\_alz\_archetype\_corp) - -Source: ../../ - -Version: - -### [alz\_archetype\_identity](#module\_alz\_archetype\_identity) - -Source: ../../ - -Version: - -### [alz\_archetype\_landing\_zones](#module\_alz\_archetype\_landing\_zones) - -Source: ../../ - -Version: - -### [alz\_archetype\_management](#module\_alz\_archetype\_management) - -Source: ../../ - -Version: - -### [alz\_archetype\_online](#module\_alz\_archetype\_online) - -Source: ../../ - -Version: - -### [alz\_archetype\_platform](#module\_alz\_archetype\_platform) - -Source: ../../ - -Version: - -### [alz\_archetype\_root](#module\_alz\_archetype\_root) - -Source: ../../ - -Version: - -### [alz\_archetype\_sandboxes](#module\_alz\_archetype\_sandboxes) - -Source: ../../ - -Version: - -### [alz\_management\_resources](#module\_alz\_management\_resources) - -Source: Azure/alz-management/azurerm - -Version: ~> 0.1 - -### [naming](#module\_naming) - -Source: Azure/naming/azurerm - -Version: ~> 0.3 - - \ No newline at end of file diff --git a/examples/alzreference/_footer.md b/examples/alzreference/_footer.md deleted file mode 100644 index e69de29..0000000 diff --git a/examples/alzreference/_header.md b/examples/alzreference/_header.md deleted file mode 100644 index bbb1577..0000000 --- a/examples/alzreference/_header.md +++ /dev/null @@ -1,4 +0,0 @@ -# Deploying the ALZ Reference Architecture - -This example shows how to deploy the ALZ reference architecture. -It uses the ALZ management module to deploy the Log Analytics workspace and Automation Account. diff --git a/examples/alzreference/locals.tf b/examples/alzreference/locals.tf deleted file mode 100644 index 97a82b7..0000000 --- a/examples/alzreference/locals.tf +++ /dev/null @@ -1,17 +0,0 @@ -# These locals help keep the code DRY -locals { - default_delays = { - before_management_group_creation = { - create = "30s" - } - before_policy_assignments = { - create = "300s" - destroy = "120s" - } - before_policy_role_assignments = { - create = "60s" - destroy = "60s" - } - } - default_location = "westus2" -} diff --git a/examples/alzreference/main.tf b/examples/alzreference/main.tf deleted file mode 100644 index 6b42d30..0000000 --- a/examples/alzreference/main.tf +++ /dev/null @@ -1,128 +0,0 @@ -# This helps keep naming unique -resource "random_pet" "this" { - length = 1 -} - -module "naming" { - source = "Azure/naming/azurerm" - version = "~> 0.3" - suffix = [random_pet.this.id] - prefix = ["test-avm-ptn-alz"] -} - -module "alz_management_resources" { - source = "Azure/alz-management/azurerm" - version = "~> 0.1" - - automation_account_name = module.naming.automation_account.name - location = local.default_location - log_analytics_workspace_name = module.naming.log_analytics_workspace.name - resource_group_name = module.naming.resource_group.name -} - -# This allows us to get the tenant id -data "azurerm_client_config" "current" {} - -module "alz_archetype_root" { - source = "../../" - id = "${random_pet.this.id}-alz-root" - display_name = "${random_pet.this.id}-alz-root" - parent_resource_id = "/providers/Microsoft.Management/managementGroups/${data.azurerm_client_config.current.tenant_id}" - base_archetype = "root" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = merge(local.default_delays, { - before_management_group_creation = { - create = "0s" - } - }) -} - -module "alz_archetype_landing_zones" { - source = "../../" - id = "${random_pet.this.id}-landing-zones" - display_name = "${random_pet.this.id}-landing-zones" - parent_resource_id = module.alz_archetype_root.management_group_resource_id - base_archetype = "landing_zones" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = local.default_delays -} - -module "alz_archetype_platform" { - source = "../../" - id = "${random_pet.this.id}-platform" - display_name = "${random_pet.this.id}-platform" - parent_resource_id = module.alz_archetype_root.management_group_resource_id - base_archetype = "platform" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = local.default_delays -} - -module "alz_archetype_identity" { - source = "../../" - id = "${random_pet.this.id}-identity" - display_name = "${random_pet.this.id}-identity" - parent_resource_id = module.alz_archetype_platform.management_group_resource_id - base_archetype = "identity" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = local.default_delays -} - -module "alz_archetype_connectivity" { - source = "../../" - id = "${random_pet.this.id}-connectivity" - display_name = "${random_pet.this.id}-connectivity" - parent_resource_id = module.alz_archetype_platform.management_group_resource_id - base_archetype = "connectivity" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = local.default_delays -} - -module "alz_archetype_management" { - source = "../../" - id = "${random_pet.this.id}-management" - display_name = "${random_pet.this.id}-management" - parent_resource_id = module.alz_archetype_platform.management_group_resource_id - base_archetype = "management" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - subscription_ids = [data.azurerm_client_config.current.subscription_id] - delays = local.default_delays -} - -module "alz_archetype_corp" { - source = "../../" - id = "${random_pet.this.id}-corp" - display_name = "${random_pet.this.id}-corp" - parent_resource_id = module.alz_archetype_landing_zones.management_group_resource_id - base_archetype = "corp" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = local.default_delays -} - -module "alz_archetype_online" { - source = "../../" - id = "${random_pet.this.id}-online" - display_name = "${random_pet.this.id}-online" - parent_resource_id = module.alz_archetype_landing_zones.management_group_resource_id - base_archetype = "online" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = local.default_delays -} - -module "alz_archetype_sandboxes" { - source = "../../" - id = "${random_pet.this.id}-sandboxes" - display_name = "${random_pet.this.id}-sandboxes" - parent_resource_id = module.alz_archetype_root.management_group_resource_id - base_archetype = "sandboxes" - default_location = local.default_location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - delays = local.default_delays -} diff --git a/examples/alzreference/terraform.tf b/examples/alzreference/terraform.tf deleted file mode 100644 index 0d7a912..0000000 --- a/examples/alzreference/terraform.tf +++ /dev/null @@ -1,24 +0,0 @@ -terraform { - required_version = "~> 1.0" - required_providers { - alz = { - source = "azure/alz" - version = "~> 0.10" - } - azurerm = { - source = "hashicorp/azurerm" - version = "~> 3.74" - } - random = { - source = "hashicorp/random" - version = "~> 3.5" - } - } -} - -provider "alz" { -} - -provider "azurerm" { - features {} -} diff --git a/examples/default/.gitkeep b/examples/default/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/examples/default/README.md b/examples/default/README.md index 3e7ddb6..3d3a7a0 100644 --- a/examples/default/README.md +++ b/examples/default/README.md @@ -1,12 +1,17 @@ -# Default +# Deploying the ALZ Reference Architecture -Left deliberately empty for now. +This example shows how to deploy the ALZ reference architecture. ```hcl -terraform { - required_version = ">= 1.0.0" - required_providers {} +# This allows us to get the tenant id +data "azapi_client_config" "current" {} + +module "alz_architecture" { + source = "../../" + architecture_name = "alz" + parent_resource_id = data.azapi_client_config.current.tenant_id + location = "northeurope" } ``` @@ -15,15 +20,21 @@ terraform { The following requirements are needed by this module: -- [terraform](#requirement\_terraform) (>= 1.0.0) +- [terraform](#requirement\_terraform) (~> 1.6) + +- [azapi](#requirement\_azapi) (~> 1.14) ## Providers -No providers. +The following providers are used by this module: + +- [azapi](#provider\_azapi) (~> 1.14) ## Resources -No 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 @@ -40,6 +51,12 @@ No outputs. ## Modules -No modules. +The following Modules are called: + +### [alz\_architecture](#module\_alz\_architecture) + +Source: ../../ + +Version: \ No newline at end of file diff --git a/examples/default/_header.md b/examples/default/_header.md index df844a5..b75592d 100644 --- a/examples/default/_header.md +++ b/examples/default/_header.md @@ -1,3 +1,3 @@ -# Default +# Deploying the ALZ Reference Architecture -Left deliberately empty for now. +This example shows how to deploy the ALZ reference architecture. diff --git a/examples/default/main.tf b/examples/default/main.tf index c257092..0da3820 100644 --- a/examples/default/main.tf +++ b/examples/default/main.tf @@ -1,4 +1,9 @@ -terraform { - required_version = ">= 1.0.0" - required_providers {} +# This allows us to get the tenant id +data "azapi_client_config" "current" {} + +module "alz_architecture" { + source = "../../" + architecture_name = "alz" + parent_resource_id = data.azapi_client_config.current.tenant_id + location = "northeurope" } diff --git a/examples/default/terraform.tf b/examples/default/terraform.tf new file mode 100644 index 0000000..39685a8 --- /dev/null +++ b/examples/default/terraform.tf @@ -0,0 +1,9 @@ +terraform { + required_version = "~> 1.6" + required_providers { + azapi = { + source = "azure/azapi" + version = "~> 1.14" + } + } +} diff --git a/examples/dynamic-input/README.md b/examples/dynamic-input/README.md deleted file mode 100644 index 14c654e..0000000 --- a/examples/dynamic-input/README.md +++ /dev/null @@ -1,213 +0,0 @@ - -# Deploying the ALZ Reference Architecture with a Dynamic Configuration File - -This example shows how to deploy the ALZ reference architecture from a dynamic YAML configuration file. -It uses the ALZ management module to deploy the Log Analytics workspace and Automation Account. -It then uses a YAML file to define the hierarchy. - -```hcl -# This helps keep naming unique -resource "random_pet" "this" { - length = 1 -} - -module "naming" { - source = "Azure/naming/azurerm" - version = "~> 0.3" - suffix = [random_pet.this.id] - prefix = ["test-avm-ptn-alz"] -} - -module "alz_management_resources" { - source = "Azure/alz-management/azurerm" - version = "~> 0.1" - - automation_account_name = module.naming.automation_account.name - location = local.location - log_analytics_workspace_name = module.naming.log_analytics_workspace.name - resource_group_name = module.naming.resource_group.name -} - -data "azurerm_client_config" "current" {} - -module "management_groups_layer_1" { - source = "../../" - for_each = local.management_groups_layer_1 - id = "${each.value.id}-${random_pet.this.id}" - display_name = try(each.value.display_name, each.value.id) - parent_resource_id = "/providers/Microsoft.Management/managementGroups/${data.azurerm_client_config.current.tenant_id}" - base_archetype = each.value.base_archetype - default_location = local.location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - subscription_ids = try(each.value.subscription_ids, []) - delays = merge(local.default_delays, { - before_management_group_creation = { - create = "0s" - } - }) -} - -module "management_groups_layer_2" { - source = "../../" - for_each = local.management_groups_layer_2 - id = "${each.value.id}-${random_pet.this.id}" - display_name = try(each.value.display_name, each.value.id) - parent_resource_id = module.management_groups_layer_1[each.value.parent].management_group_resource_id - base_archetype = each.value.base_archetype - default_location = local.location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - subscription_ids = try(each.value.subscription_ids, []) - delays = local.default_delays -} - -module "management_groups_layer_3" { - source = "../../" - for_each = local.management_groups_layer_3 - id = "${each.value.id}-${random_pet.this.id}" - display_name = try(each.value.display_name, each.value.id) - parent_resource_id = module.management_groups_layer_2[each.value.parent].management_group_resource_id - base_archetype = each.value.base_archetype - default_location = local.location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - subscription_ids = try(each.value.subscription_ids, []) - delays = local.default_delays -} - -module "management_groups_layer_4" { - source = "../../" - for_each = local.management_groups_layer_4 - id = "${each.value.id}-${random_pet.this.id}" - display_name = try(each.value.display_name, each.value.id) - parent_resource_id = module.management_groups_layer_3[each.value.parent].management_group_resource_id - base_archetype = each.value.base_archetype - default_location = local.location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - subscription_ids = try(each.value.subscription_ids, []) - delays = local.default_delays -} - -module "management_groups_layer_5" { - source = "../../" - for_each = local.management_groups_layer_5 - id = "${each.value.id}-${random_pet.this.id}" - display_name = try(each.value.display_name, each.value.id) - parent_resource_id = module.management_groups_layer_4[each.value.parent].management_group_resource_id - base_archetype = each.value.base_archetype - default_location = local.location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - subscription_ids = try(each.value.subscription_ids, []) - delays = local.default_delays -} - -module "management_groups_layer_6" { - source = "../../" - for_each = local.management_groups_layer_6 - id = "${each.value.id}-${random_pet.this.id}" - display_name = try(each.value.display_name, each.value.id) - parent_resource_id = module.management_groups_layer_5[each.value.parent].management_group_resource_id - base_archetype = each.value.base_archetype - default_location = local.location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - subscription_ids = try(each.value.subscription_ids, []) - delays = local.default_delays -} -``` - - -## Requirements - -The following requirements are needed by this module: - -- [terraform](#requirement\_terraform) (~> 1.0) - -- [alz](#requirement\_alz) (~> 0.11) - -- [azurerm](#requirement\_azurerm) (~> 3.74) - -- [random](#requirement\_random) (~> 3.5) - -## Providers - -The following providers are used by this module: - -- [azurerm](#provider\_azurerm) (~> 3.74) - -- [random](#provider\_random) (~> 3.5) - -## Resources - -The following resources are used by this module: - -- [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) (resource) -- [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) (data source) - - -## Required Inputs - -No required inputs. - -## Optional Inputs - -No optional inputs. - -## Outputs - -The following outputs are exported: - -### [test](#output\_test) - -Description: An object containing the management groups for each layer. - -## Modules - -The following Modules are called: - -### [alz\_management\_resources](#module\_alz\_management\_resources) - -Source: Azure/alz-management/azurerm - -Version: ~> 0.1 - -### [management\_groups\_layer\_1](#module\_management\_groups\_layer\_1) - -Source: ../../ - -Version: - -### [management\_groups\_layer\_2](#module\_management\_groups\_layer\_2) - -Source: ../../ - -Version: - -### [management\_groups\_layer\_3](#module\_management\_groups\_layer\_3) - -Source: ../../ - -Version: - -### [management\_groups\_layer\_4](#module\_management\_groups\_layer\_4) - -Source: ../../ - -Version: - -### [management\_groups\_layer\_5](#module\_management\_groups\_layer\_5) - -Source: ../../ - -Version: - -### [management\_groups\_layer\_6](#module\_management\_groups\_layer\_6) - -Source: ../../ - -Version: - -### [naming](#module\_naming) - -Source: Azure/naming/azurerm - -Version: ~> 0.3 - - \ No newline at end of file diff --git a/examples/dynamic-input/_footer.md b/examples/dynamic-input/_footer.md deleted file mode 100644 index e69de29..0000000 diff --git a/examples/dynamic-input/_header.md b/examples/dynamic-input/_header.md deleted file mode 100644 index 2d79012..0000000 --- a/examples/dynamic-input/_header.md +++ /dev/null @@ -1,5 +0,0 @@ -# Deploying the ALZ Reference Architecture with a Dynamic Configuration File - -This example shows how to deploy the ALZ reference architecture from a dynamic YAML configuration file. -It uses the ALZ management module to deploy the Log Analytics workspace and Automation Account. -It then uses a YAML file to define the hierarchy. diff --git a/examples/dynamic-input/locals.tf b/examples/dynamic-input/locals.tf deleted file mode 100644 index 183034b..0000000 --- a/examples/dynamic-input/locals.tf +++ /dev/null @@ -1,23 +0,0 @@ -locals { - default_delays = { - before_management_group_creation = { - create = "30s" - } - before_policy_assignments = { - create = "300s" - destroy = "120s" - } - before_policy_role_assignments = { - create = "60s" - destroy = "60s" - } - } - location = "uksouth" - management_group_config = yamldecode(file("${path.root}/managementgroups.yaml")) - management_groups_layer_1 = { for k, v in local.management_group_config : k => v if v.parent == "base" } - management_groups_layer_2 = { for k, v in local.management_group_config : k => v if contains(keys(local.management_groups_layer_1), v.parent) } - management_groups_layer_3 = { for k, v in local.management_group_config : k => v if contains(keys(local.management_groups_layer_2), v.parent) } - management_groups_layer_4 = { for k, v in local.management_group_config : k => v if contains(keys(local.management_groups_layer_3), v.parent) } - management_groups_layer_5 = { for k, v in local.management_group_config : k => v if contains(keys(local.management_groups_layer_4), v.parent) } - management_groups_layer_6 = { for k, v in local.management_group_config : k => v if contains(keys(local.management_groups_layer_5), v.parent) } -} diff --git a/examples/dynamic-input/main.tf b/examples/dynamic-input/main.tf deleted file mode 100644 index 2085d3f..0000000 --- a/examples/dynamic-input/main.tf +++ /dev/null @@ -1,105 +0,0 @@ -# This helps keep naming unique -resource "random_pet" "this" { - length = 1 -} - -module "naming" { - source = "Azure/naming/azurerm" - version = "~> 0.3" - suffix = [random_pet.this.id] - prefix = ["test-avm-ptn-alz"] -} - -module "alz_management_resources" { - source = "Azure/alz-management/azurerm" - version = "~> 0.1" - - automation_account_name = module.naming.automation_account.name - location = local.location - log_analytics_workspace_name = module.naming.log_analytics_workspace.name - resource_group_name = module.naming.resource_group.name -} - -data "azurerm_client_config" "current" {} - -module "management_groups_layer_1" { - source = "../../" - for_each = local.management_groups_layer_1 - id = "${each.value.id}-${random_pet.this.id}" - display_name = try(each.value.display_name, each.value.id) - parent_resource_id = "/providers/Microsoft.Management/managementGroups/${data.azurerm_client_config.current.tenant_id}" - base_archetype = each.value.base_archetype - default_location = local.location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - subscription_ids = try(each.value.subscription_ids, []) - delays = merge(local.default_delays, { - before_management_group_creation = { - create = "0s" - } - }) -} - -module "management_groups_layer_2" { - source = "../../" - for_each = local.management_groups_layer_2 - id = "${each.value.id}-${random_pet.this.id}" - display_name = try(each.value.display_name, each.value.id) - parent_resource_id = module.management_groups_layer_1[each.value.parent].management_group_resource_id - base_archetype = each.value.base_archetype - default_location = local.location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - subscription_ids = try(each.value.subscription_ids, []) - delays = local.default_delays -} - -module "management_groups_layer_3" { - source = "../../" - for_each = local.management_groups_layer_3 - id = "${each.value.id}-${random_pet.this.id}" - display_name = try(each.value.display_name, each.value.id) - parent_resource_id = module.management_groups_layer_2[each.value.parent].management_group_resource_id - base_archetype = each.value.base_archetype - default_location = local.location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - subscription_ids = try(each.value.subscription_ids, []) - delays = local.default_delays -} - -module "management_groups_layer_4" { - source = "../../" - for_each = local.management_groups_layer_4 - id = "${each.value.id}-${random_pet.this.id}" - display_name = try(each.value.display_name, each.value.id) - parent_resource_id = module.management_groups_layer_3[each.value.parent].management_group_resource_id - base_archetype = each.value.base_archetype - default_location = local.location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - subscription_ids = try(each.value.subscription_ids, []) - delays = local.default_delays -} - -module "management_groups_layer_5" { - source = "../../" - for_each = local.management_groups_layer_5 - id = "${each.value.id}-${random_pet.this.id}" - display_name = try(each.value.display_name, each.value.id) - parent_resource_id = module.management_groups_layer_4[each.value.parent].management_group_resource_id - base_archetype = each.value.base_archetype - default_location = local.location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - subscription_ids = try(each.value.subscription_ids, []) - delays = local.default_delays -} - -module "management_groups_layer_6" { - source = "../../" - for_each = local.management_groups_layer_6 - id = "${each.value.id}-${random_pet.this.id}" - display_name = try(each.value.display_name, each.value.id) - parent_resource_id = module.management_groups_layer_5[each.value.parent].management_group_resource_id - base_archetype = each.value.base_archetype - default_location = local.location - default_log_analytics_workspace_id = module.alz_management_resources.log_analytics_workspace.id - subscription_ids = try(each.value.subscription_ids, []) - delays = local.default_delays -} diff --git a/examples/dynamic-input/managementgroups.yaml b/examples/dynamic-input/managementgroups.yaml deleted file mode 100644 index f2ad5dc..0000000 --- a/examples/dynamic-input/managementgroups.yaml +++ /dev/null @@ -1,51 +0,0 @@ ---- -root: # `key`: the unique identifier for the management group within the Terraform Module this is used in the `parent` field to build the hierarchy - id: root # `id`: the id the management group will be created with in Azure - display_name: Intermediate Root # `display_name`: the name the management group will be created with in Azure - parent: base # `parent`: for the root management group this should set to `base` - base_archetype: root # `archetype`: the archetype to use for this management group -landing_zones: - id: landing-zones - display_name: Landing Zones - parent: root # Note that `parent` refers to the `key` of it's parent as opposed to the `id` which can be different - base_archetype: landing_zones -platform: - id: platform - display_name: Platform - parent: root - base_archetype: platform -identity: - id: identity - display_name: Identity - parent: platform - base_archetype: identity -connectivity: - id: connectivity - display_name: Connectivity - parent: platform - base_archetype: connectivity -management: - id: management - display_name: Management - parent: platform - base_archetype: management -corp: - id: corp - display_name: Corp - parent: landing_zones - base_archetype: corp -online: - id: online - display_name: Online - parent: landing_zones - base_archetype: online -sandboxes: - id: sandboxes - display_name: Sandboxes - parent: root - base_archetype: sandboxes -decommissioned: - id: decommissioned - display_name: Decommissioned - parent: root - base_archetype: decommissioned diff --git a/examples/dynamic-input/outputs.tf b/examples/dynamic-input/outputs.tf deleted file mode 100644 index 2291e89..0000000 --- a/examples/dynamic-input/outputs.tf +++ /dev/null @@ -1,11 +0,0 @@ -output "test" { - description = "An object containing the management groups for each layer." - value = { - management_groups_layer_1 = local.management_groups_layer_1 - management_groups_layer_2 = local.management_groups_layer_2 - management_groups_layer_3 = local.management_groups_layer_3 - management_groups_layer_4 = local.management_groups_layer_4 - management_groups_layer_5 = local.management_groups_layer_5 - management_groups_layer_6 = local.management_groups_layer_6 - } -} diff --git a/examples/dynamic-input/terraform.tf b/examples/dynamic-input/terraform.tf deleted file mode 100644 index 2205d0b..0000000 --- a/examples/dynamic-input/terraform.tf +++ /dev/null @@ -1,23 +0,0 @@ -terraform { - required_version = "~> 1.0" - required_providers { - alz = { - source = "azure/alz" - version = "~> 0.11" - } - azurerm = { - source = "hashicorp/azurerm" - version = "~> 3.74" - } - random = { - source = "hashicorp/random" - version = "~> 3.5" - } - } -} - -provider "alz" {} - -provider "azurerm" { - features {} -} diff --git a/examples/policy-assignment-modification-with-custom-lib/README.md b/examples/policy-assignment-modification-with-custom-lib/README.md index 61b74a1..ad57b2b 100644 --- a/examples/policy-assignment-modification-with-custom-lib/README.md +++ b/examples/policy-assignment-modification-with-custom-lib/README.md @@ -3,7 +3,7 @@ This example demonstrates some common patterns: -- Deploying a custom management group hierarchy +- Deploying a custom management group hierarchy defined by an architecture definition file in the local library - The use of a custom library, with an archetype override and additional policy assignment - Modification of a policy assignment to supply new parameters to an assigned policy @@ -12,22 +12,31 @@ Thanks to [@phx-tim-butters](https://github.com/phx-tim-butters) for this exampl ```hcl # Include the additional policies and override archetypes provider "alz" { - lib_urls = ["${path.root}/lib"] + library_references = [ + { + path = "platform/alz", + ref = "2024.07.02" + }, + { + custom_url = "${path.cwd}/lib" + } + ] } # This allows us to get the tenant id data "azurerm_client_config" "current" {} resource "azurerm_resource_group" "update_manager" { - location = "uksouth" - name = "rg_test" + location = "northeurope" + name = local.update_manager_rg_name } resource "azurerm_maintenance_configuration" "this" { - location = azurerm_resource_group.update_manager.location - name = "ring1" - resource_group_name = azurerm_resource_group.update_manager.name - scope = "InGuestPatch" + location = azurerm_resource_group.update_manager.location + name = local.maintenance_configuration_name + resource_group_name = azurerm_resource_group.update_manager.name + scope = "InGuestPatch" + in_guest_user_patch_mode = "User" install_patches { reboot = "IfRequired" @@ -44,39 +53,31 @@ resource "azurerm_maintenance_configuration" "this" { } } -module "alz_archetype_root" { +# The provider shouldn't have any unknown values passed in, or it will mark +# all resources as needing replacement. +locals { + maintenance_configuration_name = "ring1" + maintenance_configuration_resource_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/resourceGroups/${local.update_manager_rg_name}/providers/Microsoft.Maintenance/maintenanceConfigurations/${local.maintenance_configuration_name}" + update_manager_rg_name = "rg-update-manager" +} + +module "alz" { source = "../../" - id = "root" - display_name = "root" - parent_resource_id = "/providers/Microsoft.Management/managementGroups/${data.azurerm_client_config.current.tenant_id}" - base_archetype = "root_override" - default_location = "uksouth" + architecture_name = "custom" + parent_resource_id = data.azurerm_client_config.current.tenant_id + location = "northeurope" policy_assignments_to_modify = { - Update-Ring1 = { - parameters = jsonencode({ - maintenanceConfigurationResourceId = azurerm_maintenance_configuration.this.id - }) + myroot = { + policy_assignments = { + Update-Ring1 = { + parameters = jsonencode({ + maintenanceConfigurationResourceId = local.maintenance_configuration_resource_id + }) + } + } } } } - -module "alz_archetype_platform" { - source = "../../" - id = "plat" - display_name = "plat" - parent_resource_id = module.alz_archetype_root.management_group_resource_id - base_archetype = "platform" - default_location = "uksouth" -} - -module "alz_archetype_landing_zones" { - source = "../../" - id = "landing_zones" - display_name = "landing_zones" - parent_resource_id = module.alz_archetype_root.management_group_resource_id - base_archetype = "landing_zones" - default_location = "uksouth" -} ``` @@ -84,17 +85,17 @@ module "alz_archetype_landing_zones" { The following requirements are needed by this module: -- [terraform](#requirement\_terraform) (~> 1.0) +- [terraform](#requirement\_terraform) (~> 1.6) -- [alz](#requirement\_alz) (~> 0.11) +- [alz](#requirement\_alz) (~> 0.12) -- [azurerm](#requirement\_azurerm) (~> 3.74) +- [azurerm](#requirement\_azurerm) (~> 3.107) ## Providers The following providers are used by this module: -- [azurerm](#provider\_azurerm) (~> 3.74) +- [azurerm](#provider\_azurerm) (~> 3.107) ## Resources @@ -121,19 +122,7 @@ No outputs. The following Modules are called: -### [alz\_archetype\_landing\_zones](#module\_alz\_archetype\_landing\_zones) - -Source: ../../ - -Version: - -### [alz\_archetype\_platform](#module\_alz\_archetype\_platform) - -Source: ../../ - -Version: - -### [alz\_archetype\_root](#module\_alz\_archetype\_root) +### [alz](#module\_alz) Source: ../../ diff --git a/examples/policy-assignment-modification-with-custom-lib/_header.md b/examples/policy-assignment-modification-with-custom-lib/_header.md index 7b93cad..a377541 100644 --- a/examples/policy-assignment-modification-with-custom-lib/_header.md +++ b/examples/policy-assignment-modification-with-custom-lib/_header.md @@ -2,7 +2,7 @@ This example demonstrates some common patterns: -- Deploying a custom management group hierarchy +- Deploying a custom management group hierarchy defined by an architecture definition file in the local library - The use of a custom library, with an archetype override and additional policy assignment - Modification of a policy assignment to supply new parameters to an assigned policy diff --git a/examples/policy-assignment-modification-with-custom-lib/lib/alz_custom.alz_architecture_definition.yaml b/examples/policy-assignment-modification-with-custom-lib/lib/alz_custom.alz_architecture_definition.yaml new file mode 100644 index 0000000..8638f0d --- /dev/null +++ b/examples/policy-assignment-modification-with-custom-lib/lib/alz_custom.alz_architecture_definition.yaml @@ -0,0 +1,9 @@ +--- +name: custom +management_groups: + - archetypes: + - root-custom + display_name: ALZ root + exists: false + id: myroot + parent_id: null diff --git a/examples/policy-assignment-modification-with-custom-lib/lib/archetype_override_root.json b/examples/policy-assignment-modification-with-custom-lib/lib/archetype_override_root.json deleted file mode 100644 index 92fc19e..0000000 --- a/examples/policy-assignment-modification-with-custom-lib/lib/archetype_override_root.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "base_archetype": "root", - "name": "root_override", - "policy_assignments_to_add": [ - "Update-Ring1" - ] -} diff --git a/examples/policy-assignment-modification-with-custom-lib/lib/custom_root.alz_archetype_override.yaml b/examples/policy-assignment-modification-with-custom-lib/lib/custom_root.alz_archetype_override.yaml new file mode 100644 index 0000000..af337b9 --- /dev/null +++ b/examples/policy-assignment-modification-with-custom-lib/lib/custom_root.alz_archetype_override.yaml @@ -0,0 +1,5 @@ +--- +base_archetype: root +name: root-custom +policy_assignments_to_add: + - "Update-Ring1" diff --git a/examples/policy-assignment-modification-with-custom-lib/lib/policy_assignment_updatering1.json b/examples/policy-assignment-modification-with-custom-lib/lib/updatering1.alz_policy_assignment.json similarity index 97% rename from examples/policy-assignment-modification-with-custom-lib/lib/policy_assignment_updatering1.json rename to examples/policy-assignment-modification-with-custom-lib/lib/updatering1.alz_policy_assignment.json index 204e014..b619ae5 100644 --- a/examples/policy-assignment-modification-with-custom-lib/lib/policy_assignment_updatering1.json +++ b/examples/policy-assignment-modification-with-custom-lib/lib/updatering1.alz_policy_assignment.json @@ -8,7 +8,7 @@ "properties": { "description": "You can use Azure Update Manager in Azure to save recurring deployment schedules to install operating system updates for your Windows Server and Linux machines in Azure, in on-premises environments, and in other cloud environments connected using Azure Arc-enabled servers. This policy will also change the patch mode for the Azure Virtual Machine to 'AutomaticByPlatform'. See more: https://aka.ms/umc-scheduled-patching", "displayName": "Schedule recurring updates using Azure Update Manager - Ring 1 (Tuesday Midnight)", - "enforcementMode": null, + "enforcementMode": "Default", "nonComplianceMessages": [ { "message": "Azure Update Manager Update not applied" diff --git a/examples/policy-assignment-modification-with-custom-lib/main.tf b/examples/policy-assignment-modification-with-custom-lib/main.tf index 9f79a74..209d98f 100644 --- a/examples/policy-assignment-modification-with-custom-lib/main.tf +++ b/examples/policy-assignment-modification-with-custom-lib/main.tf @@ -1,21 +1,30 @@ # Include the additional policies and override archetypes provider "alz" { - lib_urls = ["${path.root}/lib"] + library_references = [ + { + path = "platform/alz", + ref = "2024.07.02" + }, + { + custom_url = "${path.cwd}/lib" + } + ] } # This allows us to get the tenant id data "azurerm_client_config" "current" {} resource "azurerm_resource_group" "update_manager" { - location = "uksouth" - name = "rg_test" + location = "northeurope" + name = local.update_manager_rg_name } resource "azurerm_maintenance_configuration" "this" { - location = azurerm_resource_group.update_manager.location - name = "ring1" - resource_group_name = azurerm_resource_group.update_manager.name - scope = "InGuestPatch" + location = azurerm_resource_group.update_manager.location + name = local.maintenance_configuration_name + resource_group_name = azurerm_resource_group.update_manager.name + scope = "InGuestPatch" + in_guest_user_patch_mode = "User" install_patches { reboot = "IfRequired" @@ -32,36 +41,28 @@ resource "azurerm_maintenance_configuration" "this" { } } -module "alz_archetype_root" { +# The provider shouldn't have any unknown values passed in, or it will mark +# all resources as needing replacement. +locals { + maintenance_configuration_name = "ring1" + maintenance_configuration_resource_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/resourceGroups/${local.update_manager_rg_name}/providers/Microsoft.Maintenance/maintenanceConfigurations/${local.maintenance_configuration_name}" + update_manager_rg_name = "rg-update-manager" +} + +module "alz" { source = "../../" - id = "root" - display_name = "root" - parent_resource_id = "/providers/Microsoft.Management/managementGroups/${data.azurerm_client_config.current.tenant_id}" - base_archetype = "root_override" - default_location = "uksouth" + architecture_name = "custom" + parent_resource_id = data.azurerm_client_config.current.tenant_id + location = "northeurope" policy_assignments_to_modify = { - Update-Ring1 = { - parameters = jsonencode({ - maintenanceConfigurationResourceId = azurerm_maintenance_configuration.this.id - }) + myroot = { + policy_assignments = { + Update-Ring1 = { + parameters = jsonencode({ + maintenanceConfigurationResourceId = local.maintenance_configuration_resource_id + }) + } + } } } } - -module "alz_archetype_platform" { - source = "../../" - id = "plat" - display_name = "plat" - parent_resource_id = module.alz_archetype_root.management_group_resource_id - base_archetype = "platform" - default_location = "uksouth" -} - -module "alz_archetype_landing_zones" { - source = "../../" - id = "landing_zones" - display_name = "landing_zones" - parent_resource_id = module.alz_archetype_root.management_group_resource_id - base_archetype = "landing_zones" - default_location = "uksouth" -} diff --git a/examples/policy-assignment-modification-with-custom-lib/terraform.tf b/examples/policy-assignment-modification-with-custom-lib/terraform.tf index dcab6dc..a3ab947 100644 --- a/examples/policy-assignment-modification-with-custom-lib/terraform.tf +++ b/examples/policy-assignment-modification-with-custom-lib/terraform.tf @@ -1,17 +1,22 @@ terraform { - required_version = "~> 1.0" + required_version = "~> 1.6" required_providers { alz = { source = "azure/alz" - version = "~> 0.11" + version = "~> 0.12" + } azurerm = { source = "hashicorp/azurerm" - version = "~> 3.74" + version = "~> 3.107" } } } provider "azurerm" { - features {} + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } } diff --git a/locals.telemetry.tf b/locals.telemetry.tf deleted file mode 100644 index 85a0a64..0000000 --- a/locals.telemetry.tf +++ /dev/null @@ -1,39 +0,0 @@ -locals { - # TODO: change this to the name of the module. See https://azure.github.io/Azure-Verified-Modules/specs/shared/#id-sfr3---category-telemetry---deploymentusage-telemetry - module_name = "alz" - # TODO: Change this. Should be either `res` or `ptn` - module_type = "ptn" - # This constructs the ARM deployment name that is used for the telemetry. - # We shouldn't ever hit the 64 character limit but use substr just in case. - telem_arm_deployment_name = substr( - format( - "%s.%s.%s.v%s.%s", - local.telem_puid, - local.module_type, - substr(local.module_name, 0, 30), - replace(local.module_version, ".", "-"), - local.telem_random_hex - ), - 0, - 64 - ) - # This is an empty ARM deployment template. - telem_arm_template_content = jsonencode({ - "$schema" = "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - contentVersion = "1.0.0.0" - parameters : {} - variables = {} - resources = [] - outputs = { - telemetry = { - type = "String", - value = "For more information, see https://aka.ms/avm/telemetry" - } - } - }) - # This is the unique id AVM Terraform modules that is supplied by the AVM team. - # See https://azure.github.io/Azure-Verified-Modules/specs/shared/#id-sfr3---category-telemetry---deploymentusage-telemetry - telem_puid = "46d3xgtf" - # This ensures we don't get errors if telemetry is disabled. - telem_random_hex = can(random_id.telem[0].hex) ? random_id.telem[0].hex : "" -} diff --git a/locals.tf b/locals.tf index 793458f..4f32069 100644 --- a/locals.tf +++ b/locals.tf @@ -1,24 +1,79 @@ -# Jsondecode the data source but use known (at plan time) map keys from `alz_archetype_keys` -# and combine with (potentially) known after apply data from the `alz_archetype` data source. locals { - alz_policy_assignments_decoded = { for k in data.alz_archetype_keys.this.alz_policy_assignment_keys : k => jsondecode(data.alz_archetype.this.alz_policy_assignments[k]) } - alz_policy_definitions_decoded = { for k in data.alz_archetype_keys.this.alz_policy_definition_keys : k => jsondecode(data.alz_archetype.this.alz_policy_definitions[k]) } - alz_policy_set_definitions_decoded = { for k in data.alz_archetype_keys.this.alz_policy_set_definition_keys : k => jsondecode(data.alz_archetype.this.alz_policy_set_definitions[k]) } - alz_role_definitions_decoded = { for k in data.alz_archetype_keys.this.alz_role_definition_keys : k => jsondecode(data.alz_archetype.this.alz_role_definitions[k]) } + management_groups = { for v in data.alz_architecture.this.management_groups : v.id => { + id = v.id + level = v.level + exists = v.exists + display_name = v.display_name + parent_id = v.parent_id + } } + management_groups_level_0 = { for k, v in local.management_groups : k => v if v.level == 0 && !v.exists } + management_groups_level_1 = { for k, v in local.management_groups : k => v if v.level == 1 && !v.exists } + management_groups_level_2 = { for k, v in local.management_groups : k => v if v.level == 2 && !v.exists } + management_groups_level_3 = { for k, v in local.management_groups : k => v if v.level == 3 && !v.exists } + management_groups_level_4 = { for k, v in local.management_groups : k => v if v.level == 4 && !v.exists } + management_groups_level_5 = { for k, v in local.management_groups : k => v if v.level == 5 && !v.exists } + management_groups_level_6 = { for k, v in local.management_groups : k => v if v.level == 6 && !v.exists } } -# Create a map of role assignment for the scope of the management group locals { - policy_role_assignments = data.alz_archetype.this.alz_policy_role_assignments != null ? { - for pra_key, pra_val in data.alz_archetype.this.alz_policy_role_assignments : pra_key => { - scope = pra_val.scope - role_definition_id = pra_val.role_definition_id - principal_id = one(azurerm_management_group_policy_assignment.this[pra_val.assignment_name].identity).principal_id - } + policy_definitions = { + for pdval in flatten([ + for mg in data.alz_architecture.this.management_groups : [ + for pdname, pd in mg.policy_definitions : { + key = pdname + definition = jsondecode(pd) + mg = mg.id + } + ] + ]) : "${pdval.mg}/${pdval.key}" => pdval } +} + +locals { + policy_set_definitions = { + for psdval in flatten([ + for mg in data.alz_architecture.this.management_groups : [ + for psdname, psd in mg.policy_set_definitions : { + key = psdname + set_definition = jsondecode(psd) + mg = mg.id + } + ] + ]) : "${psdval.mg}/${psdval.key}" => psdval } +} + +locals { + policy_assignments = { + for paval in flatten([ + for mg in data.alz_architecture.this.management_groups : [ + for paname, pa in mg.policy_assignments : { + key = paname + assignment = jsondecode(pa) + mg = mg.id + } + ] + ]) : "${paval.mg}/${paval.key}" => paval } +} + +locals { + policy_role_assignments = data.alz_architecture.this.policy_role_assignments != null ? { + for pra in data.alz_architecture.this.policy_role_assignments : uuidv5("url", "${pra.policy_assignment_name}${pra.scope}${pra.management_group_id}${pra.role_definition_id}") => { + principal_id = module.policy_assignment["${pra.management_group_id}/${pra.policy_assignment_name}"].identity.principal_id + role_definition_id = startswith(lower(pra.scope), "/subscriptions") ? "/subscriptions/${split("/", pra.scope)[2]}${pra.role_definition_id}" : pra.role_definition_id + scope = pra.scope + } if !strcontains(pra.scope, "00000000-0000-0000-0000-000000000000") } : {} } -# Get parent management group name from the parent_id locals { - parent_management_group_name = element(split("/", var.parent_resource_id), length(split("/", var.parent_resource_id)) - 1) + role_definitions = { + for rdval in flatten([ + for mg in data.alz_architecture.this.management_groups : [ + for rdname, rd in mg.role_definitions : { + key = rdname + role_definition = jsondecode(rd) + mg = mg.id + } + ] + ]) : "${rdval.mg}/${rdval.key}" => rdval + } } diff --git a/locals.version.tf.json b/locals.version.tf.json deleted file mode 100644 index 0182f23..0000000 --- a/locals.version.tf.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "locals": { - "module_version": "0.6.0" - } -} diff --git a/main.management_groups.tf b/main.management_groups.tf new file mode 100644 index 0000000..a99ded4 --- /dev/null +++ b/main.management_groups.tf @@ -0,0 +1,178 @@ +module "management_groups_level_0" { + source = "./modules/azapi_helper" + for_each = local.management_groups_level_0 + name = each.value.id + type = "Microsoft.Management/managementGroups@2023-04-01" + parent_id = "/" + body = { + properties = { + details = { + parent = { + id = "/providers/Microsoft.Management/managementGroups/${each.value.parent_id}" + } + } + displayName = each.value.display_name + } + } + + timeouts = var.timeouts.management_group + + replace_triggered_by = [ + each.value.parent_id, + ] +} +module "management_groups_level_1" { + source = "./modules/azapi_helper" + for_each = local.management_groups_level_1 + name = each.value.id + type = "Microsoft.Management/managementGroups@2023-04-01" + parent_id = "/" + body = { + properties = { + details = { + parent = { + id = "/providers/Microsoft.Management/managementGroups/${each.value.parent_id}" + } + } + displayName = each.value.display_name + } + } + + timeouts = var.timeouts.management_group + + replace_triggered_by = [ + each.value.parent_id, + ] + + depends_on = [module.management_groups_level_0] +} + +module "management_groups_level_2" { + source = "./modules/azapi_helper" + for_each = local.management_groups_level_2 + name = each.value.id + type = "Microsoft.Management/managementGroups@2023-04-01" + parent_id = "/" + body = { + properties = { + details = { + parent = { + id = "/providers/Microsoft.Management/managementGroups/${each.value.parent_id}" + } + } + displayName = each.value.display_name + } + } + + timeouts = var.timeouts.management_group + + replace_triggered_by = [ + each.value.parent_id, + ] + + depends_on = [module.management_groups_level_1] +} + +module "management_groups_level_3" { + source = "./modules/azapi_helper" + for_each = local.management_groups_level_3 + name = each.value.id + type = "Microsoft.Management/managementGroups@2023-04-01" + parent_id = "/" + body = { + properties = { + details = { + parent = { + id = "/providers/Microsoft.Management/managementGroups/${each.value.parent_id}" + } + } + displayName = each.value.display_name + } + } + + timeouts = var.timeouts.management_group + + replace_triggered_by = [ + each.value.parent_id, + ] + + depends_on = [module.management_groups_level_2] +} + +module "management_groups_level_4" { + source = "./modules/azapi_helper" + for_each = local.management_groups_level_4 + name = each.value.id + type = "Microsoft.Management/managementGroups@2023-04-01" + parent_id = "/" + body = { + properties = { + details = { + parent = { + id = "/providers/Microsoft.Management/managementGroups/${each.value.parent_id}" + } + } + displayName = each.value.display_name + } + } + + timeouts = var.timeouts.management_group + + replace_triggered_by = [ + each.value.parent_id, + ] + + depends_on = [module.management_groups_level_3] +} + +module "management_groups_level_5" { + source = "./modules/azapi_helper" + for_each = local.management_groups_level_5 + name = each.value.id + type = "Microsoft.Management/managementGroups@2023-04-01" + parent_id = "/" + body = { + properties = { + details = { + parent = { + id = "/providers/Microsoft.Management/managementGroups/${each.value.parent_id}" + } + } + displayName = each.value.display_name + } + } + + timeouts = var.timeouts.management_group + + replace_triggered_by = [ + each.value.parent_id, + ] + + depends_on = [module.management_groups_level_4] +} + +module "management_groups_level_6" { + source = "./modules/azapi_helper" + for_each = local.management_groups_level_6 + type = "Microsoft.Management/managementGroups@2023-04-01" + name = each.value.id + parent_id = "/" + body = { + properties = { + details = { + parent = { + id = "/providers/Microsoft.Management/managementGroups/${each.value.parent_id}" + } + } + displayName = each.value.display_name + } + } + + timeouts = var.timeouts.management_group + + replace_triggered_by = [ + each.value.parent_id, + ] + + depends_on = [module.management_groups_level_5] +} diff --git a/main.policy_assignments.tf b/main.policy_assignments.tf new file mode 100644 index 0000000..aeae42d --- /dev/null +++ b/main.policy_assignments.tf @@ -0,0 +1,45 @@ +module "policy_assignment" { + source = "./modules/azapi_helper" + for_each = local.policy_assignments + + name = each.value.assignment.name + type = "Microsoft.Authorization/policyAssignments@2024-04-01" + ignore_missing_property = true + identity = lookup(each.value.assignment, "identity", null) != null ? { + type = each.value.assignment.identity.type + identity_ids = lookup(each.value.assignment.identity, "identity_ids", null) + } : null + + body = { + properties = { + description = lookup(each.value.assignment.properties, "description", null) + displayName = lookup(each.value.assignment.properties, "displayName", null) + enforcementMode = lookup(each.value.assignment.properties, "enforcementMode", null) + metadata = lookup(each.value.assignment.properties, "metadata", { + createdBy = "" + createdOn = "" + updatedBy = "" + updatedOn = "" + }) + nonComplianceMessages = lookup(each.value.assignment.properties, "nonComplianceMessages", null) + notScopes = lookup(each.value.assignment.properties, "notScopes", null) + overrides = lookup(each.value.assignment.properties, "overrides", null) + parameters = lookup(each.value.assignment.properties, "parameters", null) + policyDefinitionId = lookup(each.value.assignment.properties, "policyDefinitionId", null) + resourceSelectors = lookup(each.value.assignment.properties, "resourceSelectors", null) + } + } + parent_id = "/providers/Microsoft.Management/managementGroups/${each.value.mg}" + location = var.location + + timeouts = var.timeouts.policy_assignment + + replace_triggered_by = [ + lookup(each.value.assignment.properties, "policyDefinitionId", null), + var.location, + ] + + depends_on = [ + time_sleep.after_policy_set_definitions + ] +} diff --git a/main.policy_definitions.tf b/main.policy_definitions.tf new file mode 100644 index 0000000..f98fa78 --- /dev/null +++ b/main.policy_definitions.tf @@ -0,0 +1,16 @@ +module "policy_definitions" { + source = "./modules/azapi_helper" + for_each = local.policy_definitions + type = "Microsoft.Authorization/policyDefinitions@2023-04-01" + parent_id = "/providers/Microsoft.Management/managementGroups/${each.value.mg}" + name = each.value.definition.name + body = { + properties = each.value.definition.properties + } + + timeouts = var.timeouts.policy_definition + + depends_on = [ + time_sleep.after_management_groups + ] +} diff --git a/main.policy_role_assignments.tf b/main.policy_role_assignments.tf new file mode 100644 index 0000000..459ed62 --- /dev/null +++ b/main.policy_role_assignments.tf @@ -0,0 +1,22 @@ +module "policy_role_assignments" { + source = "./modules/azapi_helper" + for_each = local.policy_role_assignments + type = "Microsoft.Authorization/roleAssignments@2022-04-01" + name = each.key + parent_id = each.value.scope + body = { + properties = { + principalId = each.value.principal_id + roleDefinitionId = each.value.role_definition_id + description = "Created by ALZ Terraform provider. Assignment required for Azure Policy." + principalType = "ServicePrincipal" + } + } + + timeouts = var.timeouts.policy_role_assignment + + replace_triggered_by = [ + each.value.principal_id, + each.value.role_definition_id, + ] +} diff --git a/main.policy_set_definitions.tf b/main.policy_set_definitions.tf new file mode 100644 index 0000000..40c7e49 --- /dev/null +++ b/main.policy_set_definitions.tf @@ -0,0 +1,19 @@ +module "policy_set_definitions" { + source = "./modules/azapi_helper" + for_each = local.policy_set_definitions + type = "Microsoft.Authorization/policySetDefinitions@2023-04-01" + parent_id = "/providers/Microsoft.Management/managementGroups/${each.value.mg}" + name = each.value.set_definition.name + body = { + properties = each.value.set_definition.properties + } + depends_on = [ + time_sleep.after_policy_definitions + ] + + timeouts = var.timeouts.policy_set_definition + + replace_triggered_by = [ + lookup(each.value.set_definition.properties, "policyType", null), + ] +} diff --git a/main.role_definitions.tf b/main.role_definitions.tf new file mode 100644 index 0000000..94f0c7a --- /dev/null +++ b/main.role_definitions.tf @@ -0,0 +1,22 @@ +module "role_definitions" { + source = "./modules/azapi_helper" + for_each = local.role_definitions + type = "Microsoft.Authorization/roleDefinitions@2022-04-01" + parent_id = "/providers/Microsoft.Management/managementGroups/${each.value.mg}" + name = each.value.role_definition.name + body = { + properties = { + assignableScopes = each.value.role_definition.properties.assignableScopes + description = each.value.role_definition.properties.description + permissions = each.value.role_definition.properties.permissions + roleName = "${each.value.role_definition.properties.roleName} (${each.value.mg})" + type = each.value.role_definition.properties.type + } + } + + timeouts = var.timeouts.role_definition + + depends_on = [ + time_sleep.after_management_groups + ] +} diff --git a/main.telemetry.tf b/main.telemetry.tf index 5ef2a39..ff2019a 100644 --- a/main.telemetry.tf +++ b/main.telemetry.tf @@ -1,17 +1,24 @@ -resource "random_id" "telem" { +data "azapi_client_config" "telemetry" { count = var.enable_telemetry ? 1 : 0 +} + +data "modtm_module_source" "telemetry" { + count = var.enable_telemetry ? 1 : 0 + module_path = path.module +} - byte_length = 4 +resource "random_uuid" "telemetry" { + count = var.enable_telemetry ? 1 : 0 } -# This is the module telemetry deployment that is only created if telemetry is enabled. -# It is deployed to the management group. -resource "azurerm_management_group_template_deployment" "telemetry" { +resource "modtm_telemetry" "telemetry" { count = var.enable_telemetry ? 1 : 0 - location = var.default_location - management_group_id = azurerm_management_group.this.id - name = local.telem_arm_deployment_name - tags = null - template_content = local.telem_arm_template_content + tags = { + subscription_id = one(data.azapi_client_config.telemetry).subscription_id + tenant_id = one(data.azapi_client_config.telemetry).tenant_id + module_source = one(data.modtm_module_source.telemetry).module_source + module_version = one(data.modtm_module_source.telemetry).module_version + random_id = one(random_uuid.telemetry).result + } } diff --git a/main.tf b/main.tf index 2d4f0ee..671247b 100644 --- a/main.tf +++ b/main.tf @@ -1,218 +1,6 @@ -data "alz_archetype_keys" "this" { - base_archetype = var.base_archetype -} - -data "alz_archetype" "this" { - id = var.id - defaults = { - location = var.default_location - log_analytics_workspace_id = var.default_log_analytics_workspace_id - private_dns_zone_resource_group_id = var.default_private_dns_zone_resource_group_id - } - display_name = var.display_name - base_archetype = var.base_archetype - parent_id = local.parent_management_group_name +data "alz_architecture" "this" { + name = var.architecture_name + root_management_group_id = var.parent_resource_id + location = var.location policy_assignments_to_modify = var.policy_assignments_to_modify } - -resource "azurerm_management_group" "this" { - display_name = data.alz_archetype.this.display_name - name = data.alz_archetype.this.id - parent_management_group_id = var.parent_resource_id - - depends_on = [time_sleep.before_management_group_creation] -} - -data "azurerm_subscription" "this" { - for_each = var.subscription_ids - - subscription_id = each.key -} - -resource "azurerm_management_group_subscription_association" "this" { - for_each = var.subscription_ids - - management_group_id = azurerm_management_group.this.id - subscription_id = data.azurerm_subscription.this[each.key].id -} - -resource "azurerm_policy_definition" "this" { - for_each = local.alz_policy_definitions_decoded - - display_name = try(each.value.properties.displayName, "") - mode = each.value.properties.mode - name = each.key - policy_type = try(each.value.properties.policyType, "Custom") - description = try(each.value.properties.description, "") - management_group_id = azurerm_management_group.this.id - metadata = jsonencode(try(each.value.properties.metadata, {})) - parameters = try(each.value.properties.parameters, null) != null && try(each.value.properties.parameters, {}) != {} ? jsonencode(each.value.properties.parameters) : null - policy_rule = jsonencode(try(each.value.properties.policyRule, {})) -} - -resource "azurerm_policy_set_definition" "this" { - for_each = local.alz_policy_set_definitions_decoded - - display_name = try(each.value.properties.displayName, "") - name = each.key - policy_type = try(each.value.properties.policyType, "Custom") - management_group_id = azurerm_management_group.this.id - metadata = jsonencode(try(each.value.properties.metadata, {})) - parameters = try(each.value.properties.parameters, null) != null && try(each.value.properties.parameters, {}) != {} ? jsonencode(each.value.properties.parameters) : null - - dynamic "policy_definition_reference" { - for_each = try(each.value.properties.policyDefinitions, []) - - content { - policy_definition_id = policy_definition_reference.value.policyDefinitionId - parameter_values = try(jsonencode(policy_definition_reference.value.parameters), jsonencode({})) - policy_group_names = try(policy_definition_reference.value.groupNames, []) - reference_id = try(policy_definition_reference.value.policyDefinitionReferenceId, "") - } - } - dynamic "policy_definition_group" { - for_each = try(each.value.properties.policyDefinitionGroups, []) - - content { - name = policy_definition_group.value.name - additional_metadata_resource_id = try(policy_definition_group.value.additionalMetadataId, "") - category = try(policy_definition_group.value.category, "") - description = try(policy_definition_group.value.description, "") - display_name = try(policy_definition_group.value.displayName, "") - } - } - - depends_on = [azurerm_policy_definition.this] -} - -resource "azurerm_management_group_policy_assignment" "this" { - for_each = local.alz_policy_assignments_decoded - - management_group_id = azurerm_management_group.this.id - name = each.key - policy_definition_id = each.value.properties.policyDefinitionId - description = try(each.value.properties.description, "") - display_name = try(each.value.properties.displayName, "") - enforce = try(each.value.properties.enforce, "Default") == "Default" ? true : false - location = try(each.value.location, null) - metadata = jsonencode(try(each.value.properties.metadata, {})) - not_scopes = try(each.value.properties.notScopes, []) - parameters = try(each.value.properties.parameters, null) != null && try(each.value.properties.parameters, {}) != {} ? jsonencode(each.value.properties.parameters) : null - - dynamic "identity" { - for_each = try(each.value.identity.type, "None") != "None" ? [each.value.identity] : [] - - content { - type = identity.value.type - identity_ids = identity.value.type == "SystemAssigned" ? [] : toset(keys(identity.value.userAssignedIdentities)) - } - } - dynamic "non_compliance_message" { - for_each = try(each.value.properties.nonComplianceMessages, []) - - content { - content = non_compliance_message.value.message - policy_definition_reference_id = try(non_compliance_message.value.policyDefinitionReferenceId, null) - } - } - dynamic "overrides" { - for_each = try(each.value.properties.overrides, []) - - content { - value = overrides.value.value - - dynamic "selectors" { - for_each = try(overrides.value.selectors, []) - - content { - in = try(selectors.value.in, null) - not_in = try(selectors.value.notIn, null) - } - } - } - } - dynamic "resource_selectors" { - for_each = try(each.value.properties.resourceSelectors, []) - - content { - name = resource_selectors.value.name - - dynamic "selectors" { - for_each = try(resource_selectors.value.selectors, []) - - content { - kind = selectors.value.kind - in = try(selectors.value.in, null) - not_in = try(selectors.value.notIn, null) - } - } - } - } - - depends_on = [time_sleep.before_policy_assignments] -} - -resource "azurerm_role_definition" "this" { - for_each = local.alz_role_definitions_decoded - - name = "${each.key}-${data.alz_archetype.this.id}" - scope = azurerm_management_group.this.id - assignable_scopes = try(each.value.properties.assignableScopes, []) - description = try(each.value.properties.description, null) - - permissions { - actions = try(one(each.value.properties.permissions).actions, []) - data_actions = try(one(each.value.properties.permissions).dataActions, []) - not_actions = try(one(each.value.properties.permissions).notActions, []) - not_data_actions = try(one(each.value.properties.permissions).notDataActions, []) - } -} - -resource "azurerm_role_assignment" "this" { - for_each = var.role_assignments - - principal_id = each.value.principal_id - scope = azurerm_management_group.this.id - description = each.value.description - role_definition_id = each.value.role_definition_id != "" ? each.value.role_definition_id : null - role_definition_name = each.value.role_definition_name != "" ? each.value.role_definition_name : null -} - -resource "alz_policy_role_assignments" "this" { - id = data.alz_archetype.this.id - assignments = local.policy_role_assignments - depends_on = [time_sleep.before_policy_role_assignments] -} - -resource "time_sleep" "before_management_group_creation" { - create_duration = var.delays.before_management_group.create - destroy_duration = var.delays.before_management_group.destroy -} - -resource "time_sleep" "before_policy_assignments" { - count = local.alz_policy_assignments_decoded != {} ? 1 : 0 - - create_duration = var.delays.before_policy_assignments.create - destroy_duration = var.delays.before_policy_assignments.destroy - triggers = { - policy_definitions = sha256(jsonencode(azurerm_policy_definition.this)) - policy_set_definitions = sha256(jsonencode(azurerm_policy_set_definition.this)) - } - - depends_on = [ - azurerm_policy_definition.this, - azurerm_policy_set_definition.this, - ] -} - -resource "time_sleep" "before_policy_role_assignments" { - count = local.alz_policy_assignments_decoded != {} ? 1 : 0 - - create_duration = var.delays.before_policy_role_assignments.create - destroy_duration = var.delays.before_policy_role_assignments.destroy - triggers = { - policy_assignments = sha256(jsonencode(azurerm_management_group_policy_assignment.this)) - } - - depends_on = [azurerm_management_group_policy_assignment.this] -} diff --git a/main.time_sleep.tf b/main.time_sleep.tf new file mode 100644 index 0000000..f506e58 --- /dev/null +++ b/main.time_sleep.tf @@ -0,0 +1,41 @@ +resource "time_sleep" "after_management_groups" { + create_duration = var.delays.after_management_group.create + destroy_duration = var.delays.after_management_group.destroy + triggers = { + management_groups = sha256(jsonencode(local.management_groups)) + } + + depends_on = [ + module.management_groups_level_0, + module.management_groups_level_1, + module.management_groups_level_2, + module.management_groups_level_3, + module.management_groups_level_4, + module.management_groups_level_5, + module.management_groups_level_6, + ] +} + +resource "time_sleep" "after_policy_definitions" { + create_duration = var.delays.after_policy_definitions.create + destroy_duration = var.delays.after_policy_definitions.destroy + triggers = { + policy_definitions = sha256(jsonencode(local.policy_definitions)) + } + + depends_on = [ + module.policy_definitions + ] +} + +resource "time_sleep" "after_policy_set_definitions" { + create_duration = var.delays.after_policy_set_definitions.create + destroy_duration = var.delays.after_policy_set_definitions.destroy + triggers = { + policy_definitions = sha256(jsonencode(local.policy_set_definitions)) + } + + depends_on = [ + module.policy_set_definitions + ] +} diff --git a/modules/.terraform-docs.yml b/modules/.terraform-docs.yml new file mode 100644 index 0000000..f97f3a3 --- /dev/null +++ b/modules/.terraform-docs.yml @@ -0,0 +1,70 @@ +### To generate the output file to partially incorporate in the README.md, +### Execute this command in the Terraform module's code folder: +# terraform-docs -c .terraform-docs.yml . + +formatter: "markdown document" # this is required + +version: "~> 0.17.0" + +header-from: "_header.md" +footer-from: "_footer.md" + +recursive: + enabled: false + path: modules + +sections: + hide: [] + show: [] + +content: |- + {{ .Header }} + + ```hcl + {{ include "main.tf" }} + ``` + + + {{ .Requirements }} + + {{ .Providers }} + + {{ .Resources }} + + + {{ .Inputs }} + + {{ .Outputs }} + + {{ .Modules }} + + {{ .Footer }} +output: + file: README.md + mode: replace + template: |- + + {{ .Content }} + +output-values: + enabled: false + from: "" + +sort: + enabled: true + by: required + +settings: + anchor: true + color: true + default: true + description: false + escape: true + hide-empty: false + html: true + indent: 2 + lockfile: false + read-comments: true + required: true + sensitive: true + type: true diff --git a/modules/azapi_helper/README.md b/modules/azapi_helper/README.md new file mode 100644 index 0000000..0d00274 --- /dev/null +++ b/modules/azapi_helper/README.md @@ -0,0 +1,219 @@ + +[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/Azure/terraform-azurerm-avm-ptn-alz/badge)](https://scorecard.dev/viewer/?uri=github.com/Azure/terraform-azurerm-avm-ptn-alz) + +# ALZ Terraform Module (azapi\_helper) + +> ⚠️ **\_Warning\_** ⚠️ This module is still in development but is ready for initial testing and feedback via [GitHub Issues](https://github.com/Azure/terraform-azurerm-avm-ptn-alz/issues). + +This module is a wrapper for `azapi_resource`, adding convenience features. +As we are using AzAPI, we do not have the full Terraform schema options available for detecting attribute changes that require recreating the resource. + +This submodule is therefore used to enable the `terraform_data` method for `replace_triggered_by`: . +This allows us to replace the resource when certain key attributes change. + +You can use the `var.replace_triggered_by` attribute to specify any data, that when changed, will trigger a replacement of the resource. + +```hcl +resource "azapi_resource" "this" { + type = var.type + body = var.body + ignore_missing_property = var.ignore_missing_property + location = var.location + name = var.name + parent_id = var.parent_id + response_export_values = var.response_export_values + + dynamic "identity" { + for_each = var.identity == null ? [] : var.identity.type != "None" ? [var.identity] : [] + content { + type = identity.value.type + identity_ids = identity.value.identity_ids + } + } + timeouts { + create = var.timeouts.create + delete = var.timeouts.delete + read = var.timeouts.read + update = var.timeouts.update + } + + lifecycle { + ignore_changes = [ + body.properties.metadata.createdBy, + body.properties.metadata.createdOn, + body.properties.metadata.updatedBy, + body.properties.metadata.updatedOn, + ] + replace_triggered_by = [ + terraform_data.replace_trigger + ] + } +} + +resource "terraform_data" "replace_trigger" { + input = var.replace_triggered_by +} +``` + + +## Requirements + +The following requirements are needed by this module: + +- [terraform](#requirement\_terraform) (~> 1.6) + +- [azapi](#requirement\_azapi) (~> 1.14) + +## Providers + +The following providers are used by this module: + +- [azapi](#provider\_azapi) (~> 1.14) + +- [terraform](#provider\_terraform) + +## Resources + +The following resources are used by this module: + +- [azapi_resource.this](https://registry.terraform.io/providers/azure/azapi/latest/docs/resources/resource) (resource) +- [terraform_data.replace_trigger](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) (resource) + + +## Required Inputs + +The following input variables are required: + +### [body](#input\_body) + +Description: The body object of the resource. + +Type: `any` + +### [name](#input\_name) + +Description: The name of resource. + +Type: `string` + +### [parent\_id](#input\_parent\_id) + +Description: The parent ID of the resource. + +Type: `string` + +### [type](#input\_type) + +Description: The type and API version of the resource. + +Type: `string` + +## Optional Inputs + +The following input variables are optional (have default values): + +### [identity](#input\_identity) + +Description: Controls the Managed Identity configuration on this resource. The following properties can be specified: + + - `type` - Either: `SystemAssigned`, `SystemAssigned, UserAssigned`, or `UserAssigned`. + - `user_assigned_resource_ids` - (Optional) Specifies a list of User Assigned Managed Identity resource IDs to be assigned to this resource. + +Type: + +```hcl +object({ + type = string + identity_ids = optional(set(string), []) + }) +``` + +Default: `null` + +### [ignore\_missing\_property](#input\_ignore\_missing\_property) + +Description: If set to true, the resource will not be replaced if a property is missing. + +Type: `bool` + +Default: `false` + +### [location](#input\_location) + +Description: Location of the resource. + +Type: `string` + +Default: `null` + +### [replace\_triggered\_by](#input\_replace\_triggered\_by) + +Description: Values that trigger a replacement. + +Type: `any` + +Default: `null` + +### [response\_export\_values](#input\_response\_export\_values) + +Description: List of values to export from the response, made available in the output. + +Type: `set(string)` + +Default: `null` + +### [timeouts](#input\_timeouts) + +Description: A map of timeouts to apply to the creation and destruction of the resource. + +Type: + +```hcl +object({ + create = string + delete = string + update = string + read = string + }) +``` + +Default: + +```json +{ + "create": "10m", + "delete": "10m", + "read": "10m", + "update": "10m" +} +``` + +## Outputs + +The following outputs are exported: + +### [identity](#output\_identity) + +Description: The identity configuration of the resource. + +### [name](#output\_name) + +Description: The name of the resource. + +### [output](#output\_output) + +Description: The output values of the resource as defined by `response_export_values`. + +### [resource\_id](#output\_resource\_id) + +Description: The Azure resource id of the resource. + +## Modules + +No modules. + + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. + \ No newline at end of file diff --git a/modules/azapi_helper/_footer.md b/modules/azapi_helper/_footer.md new file mode 100644 index 0000000..bc56bcb --- /dev/null +++ b/modules/azapi_helper/_footer.md @@ -0,0 +1,4 @@ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/modules/azapi_helper/_header.md b/modules/azapi_helper/_header.md new file mode 100644 index 0000000..5c03042 --- /dev/null +++ b/modules/azapi_helper/_header.md @@ -0,0 +1,13 @@ +[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/Azure/terraform-azurerm-avm-ptn-alz/badge)](https://scorecard.dev/viewer/?uri=github.com/Azure/terraform-azurerm-avm-ptn-alz) + +# ALZ Terraform Module (azapi_helper) + +> ⚠️ **_Warning_** ⚠️ This module is still in development but is ready for initial testing and feedback via [GitHub Issues](https://github.com/Azure/terraform-azurerm-avm-ptn-alz/issues). + +This module is a wrapper for `azapi_resource`, adding convenience features. +As we are using AzAPI, we do not have the full Terraform schema options available for detecting attribute changes that require recreating the resource. + +This submodule is therefore used to enable the `terraform_data` method for `replace_triggered_by`: . +This allows us to replace the resource when certain key attributes change. + +You can use the `var.replace_triggered_by` attribute to specify any data, that when changed, will trigger a replacement of the resource. diff --git a/modules/azapi_helper/main.tf b/modules/azapi_helper/main.tf new file mode 100644 index 0000000..fbe85f1 --- /dev/null +++ b/modules/azapi_helper/main.tf @@ -0,0 +1,39 @@ +resource "azapi_resource" "this" { + type = var.type + body = var.body + ignore_missing_property = var.ignore_missing_property + location = var.location + name = var.name + parent_id = var.parent_id + response_export_values = var.response_export_values + + dynamic "identity" { + for_each = var.identity == null ? [] : var.identity.type != "None" ? [var.identity] : [] + content { + type = identity.value.type + identity_ids = identity.value.identity_ids + } + } + timeouts { + create = var.timeouts.create + delete = var.timeouts.delete + read = var.timeouts.read + update = var.timeouts.update + } + + lifecycle { + ignore_changes = [ + body.properties.metadata.createdBy, + body.properties.metadata.createdOn, + body.properties.metadata.updatedBy, + body.properties.metadata.updatedOn, + ] + replace_triggered_by = [ + terraform_data.replace_trigger + ] + } +} + +resource "terraform_data" "replace_trigger" { + input = var.replace_triggered_by +} diff --git a/modules/azapi_helper/outputs.tf b/modules/azapi_helper/outputs.tf new file mode 100644 index 0000000..df1f150 --- /dev/null +++ b/modules/azapi_helper/outputs.tf @@ -0,0 +1,19 @@ +output "identity" { + description = "The identity configuration of the resource." + value = try(azapi_resource.this.identity[0], null) +} + +output "name" { + description = "The name of the resource." + value = azapi_resource.this.name +} + +output "output" { + description = "The output values of the resource as defined by `response_export_values`." + value = azapi_resource.this.output +} + +output "resource_id" { + description = "The Azure resource id of the resource." + value = azapi_resource.this.id +} diff --git a/modules/azapi_helper/terraform.tf b/modules/azapi_helper/terraform.tf new file mode 100644 index 0000000..39685a8 --- /dev/null +++ b/modules/azapi_helper/terraform.tf @@ -0,0 +1,9 @@ +terraform { + required_version = "~> 1.6" + required_providers { + azapi = { + source = "azure/azapi" + version = "~> 1.14" + } + } +} diff --git a/modules/azapi_helper/variables.tf b/modules/azapi_helper/variables.tf new file mode 100644 index 0000000..085f4e6 --- /dev/null +++ b/modules/azapi_helper/variables.tf @@ -0,0 +1,87 @@ +variable "body" { + type = any + description = "The body object of the resource." + nullable = false +} + +variable "name" { + type = string + description = "The name of resource." + nullable = false +} + +variable "parent_id" { + type = string + description = "The parent ID of the resource." + nullable = false +} + +variable "type" { + type = string + description = "The type and API version of the resource." +} + +variable "identity" { + type = object({ + type = string + identity_ids = optional(set(string), []) + }) + default = null + description = < v.id } +} + +output "policy_assignment_identity_ids" { + description = "A map of policy assignment names to their identity ids." + value = { for k, v in module.policy_assignment : k => v.identity.principal_id if v.identity != null } +} + +output "policy_assignment_resource_ids" { + description = "A map of policy assignment names to their resource ids." + value = { for k, v in module.policy_assignment : k => v.resource_id } +} + +output "policy_definition_resource_ids" { + description = "A map of policy definition names to their resource ids." + value = { for k, v in module.policy_definitions : k => v.resource_id } +} + +output "policy_role_assignment_resource_ids" { + description = "A map of policy role assignments to their resource ids." + value = { for k, v in module.policy_role_assignments : k => v.resource_id } +} + +output "policy_set_definition_resource_ids" { + description = "A map of policy set definition names to their resource ids." + value = { for k, v in module.policy_set_definitions : k => v.resource_id } +} + +output "role_definition_resource_ids" { + description = "A map of role definition names to their resource ids." + value = { for k, v in module.role_definitions : k => v.resource_id } } diff --git a/terraform.tf b/terraform.tf index f283524..b60a19a 100644 --- a/terraform.tf +++ b/terraform.tf @@ -1,17 +1,21 @@ terraform { - required_version = "~> 1.0" + required_version = "~> 1.6" required_providers { alz = { source = "azure/alz" - version = "~> 0.11" + version = "~> 0.12, >= 0.12.5" } - azurerm = { - source = "hashicorp/azurerm" - version = "~> 3.74" + azapi = { + source = "azure/azapi" + version = "~> 1.14" + } + modtm = { + source = "azure/modtm" + version = "~> 0.3" } random = { source = "hashicorp/random" - version = "~> 3.5" + version = "~> 3.6" } time = { source = "hashicorp/time" diff --git a/variables.tf b/variables.tf index 71e1421..b973aa7 100644 --- a/variables.tf +++ b/variables.tf @@ -1,73 +1,39 @@ -variable "base_archetype" { +variable "architecture_name" { type = string description = < 0 && length(v.role_definition_name) > 0), - !(length(v.role_definition_id) == 0 && length(v.role_definition_name) == 0) - ]) - ]) - error_message = "Specify one (and only one) of `role_definition_id` and `role_definition_name`." - } - validation { - condition = length(toset(values(var.role_assignments))) == length(var.role_assignments) - error_message = "Role assignment values must not be duplicates." - } -} - -variable "subscription_ids" { - type = set(string) - default = [] - description = <