diff --git a/docs/content/specs/terraform/_index.md b/docs/content/specs/terraform/_index.md index e01da2b27..fc63156e0 100644 --- a/docs/content/specs/terraform/_index.md +++ b/docs/content/specs/terraform/_index.md @@ -345,13 +345,13 @@ Meta-arguments, arguments and nested blocked are separated by blank lines. ```terraform dynamic "linux_profile" { - for_each = var.admin_username == null ? [] : ["linux_profile"] + for_each = var.linux_profile != null ? { this = var.linux_profile } : {} content { - admin_username = var.admin_username + admin_username = linux_profile.value.admin_username ssh_key { - key_data = replace(coalesce(var.public_ssh_key, tls_private_key.ssh[0].public_key_openssh), "\n", "") + key_data = linux_profile.value.ssh_key } } } @@ -500,28 +500,47 @@ Please use this technique under this use case only. #### ID: TFNFR12 - Category: Code Style - Optional nested object argument should use `dynamic` -An example from the community: +An example from the interfaces for managed_identity: ```terraform resource "azurerm_kubernetes_cluster" "main" { ... dynamic "identity" { - for_each = var.client_id == "" || var.client_secret == "" ? [1] : [] + for_each = local.managed_identities.user_assigned content { - type = var.identity_type - user_assigned_identity_id = var.user_assigned_identity_id + type = identity.value.type + identity_ids = identity.value.user_assigned_resource_ids } } ... } ``` -Please refer to the coding style in the example. If you just want to declare some nested block under conditions, please use: +Resources nested blocks are implemented differently depending on if they are optional or required and support one or more blocks. These **SHOULD** be implemented in the following ways: + +{{< /hint >}} + +{{< tabs "nested blocks" >}} + {{< tab "Required, one" >}} + {{< include file="/static/includes/spec/int.spec.nest.req.one.tf" language="terraform" options="linenos=false" >}} + {{< /tab >}} + {{< tab "Required, one or more" >}} + {{< include file="/static/includes/spec/int.spec.nest.req.more.tf" language="terraform" options="linenos=false" >}} + {{< /tab >}} + {{< tab "Optional, one" >}} + {{< include file="/static/includes/spec/int.spec.nest.opt.one.tf" language="terraform" options="linenos=false" >}} + {{< /tab >}} + {{< tab "Optional, one or more" >}} + {{< include file="/static/includes/spec/int.spec.nest.opt.more.tf" language="terraform" options="linenos=false" >}} + {{< /tab >}} + {{< tab "Nested dynamic blocks" >}} + {{< include file="/static/includes/spec/int.spec.nest.nested.tf" language="terraform" options="linenos=false" >}} + {{< /tab >}} +{{< /tabs >}} + +{{< hint type=note >}} -```terraform -for_each = ? [] : [] -```
diff --git a/docs/static/includes/specification/int.spec.nest.nested.tf b/docs/static/includes/specification/int.spec.nest.nested.tf new file mode 100644 index 000000000..161bca38f --- /dev/null +++ b/docs/static/includes/specification/int.spec.nest.nested.tf @@ -0,0 +1,46 @@ +# The resource has nested dynamic blocks + +# Variable declaration +variable "optional_multi_block" { + type = map(object({ + name = string + length = optional(number) + nested_block = optional(object(any)) + nested_multi_blocks = optional(map(object({ + name = string + length = optional(number) + }))) + })) + default = null +} + +# Resource declaration +resource "my_resource" "this" { + + dynamic "optional_multi_block" { + for_each = var.optional_multi_block != null ? var.optional_multi_block : {} + + content { + name = var.optional_multi_block.name + length = var.optional_multi_block.length + + dynamic "nested_block" { + for_each = try(optional_multi_block.value.nested_block, null) != null ? { this = var.optional_multi_block.value.nested_block } : {} + + content { + name = var.optional_multi_block.value.nested_block.name + length = var.optional_multi_block.value.nested_block.length + } + } + + dynamic "nested_multi_blocks" { + for_each = try(optional_multi_block.value.nested_multi_blocks, null) != null ? var.optional_multi_block.value.nested_multi_blocks : {} + + content { + name = nested_multi_blocks.value.name + length = nested_multi_blocks.value.length + } + } + } + } +} diff --git a/docs/static/includes/specification/int.spec.nest.opt.more.tf b/docs/static/includes/specification/int.spec.nest.opt.more.tf new file mode 100644 index 000000000..6ee0b7544 --- /dev/null +++ b/docs/static/includes/specification/int.spec.nest.opt.more.tf @@ -0,0 +1,25 @@ +# The resource supports one or more optional nested blocks + +# Variable declaration +# Default value is null, as it is optional and should not configure anything by default. +# Uses a map type to support multiple instances. +variable "optional_multi_block" { + type = map(object({ + name = string + length = optional(number) + })) + default = null +} + +# Resource declaration +# The dynamic block is used to support the optionality and looping. +resource "my_resource" "this" { + dynamic "optional_multi_block" { + for_each = var.optional_multi_block != null ? var.optional_multi_block : {} + + content { + name = optional_multi_block.value.name + length = optional_multi_block.value.length + } + } +} diff --git a/docs/static/includes/specification/int.spec.nest.opt.one.tf b/docs/static/includes/specification/int.spec.nest.opt.one.tf new file mode 100644 index 000000000..0ff32dfbc --- /dev/null +++ b/docs/static/includes/specification/int.spec.nest.opt.one.tf @@ -0,0 +1,26 @@ +# The resource supports a single optional nested block + +# Variable declaration +# Default value is null, as it is optional and should not configure anything by default. +# It uses an object type to support a single instance. +variable "optional_single_block" { + type = object({ + name = string + length = optional(number) + }) + default = null +} + +# Resource declaration +# The dynamic block provides the optionality. +# The `this` key is used to access the current instance and maps the content to the name of the block. +resource "my_resource" "this" { + dynamic "optional_single_block" { + for_each = var.optional_single_block != null ? { this = var.optional_single_block } : {} + + content { + name = optional_single_block.value.name + length = optional_single_block.value.length + } + } +} diff --git a/docs/static/includes/specification/int.spec.nest.req.more.tf b/docs/static/includes/specification/int.spec.nest.req.more.tf new file mode 100644 index 000000000..259e5b9c8 --- /dev/null +++ b/docs/static/includes/specification/int.spec.nest.req.more.tf @@ -0,0 +1,24 @@ +# The resource requires one or more nested blocks + +# Variable declaration +# No default value, as it is required in the module. +# Uses a map type to support multiple instances. +variable "required_multi_blocks" { + type = map(object({ + name = string + length = optional(number) + })) +} + +# Resource declaration +# The dynamic block is used, as there are multiple instances. +resource "my_resource" "this" { + dynamic "required_multi_blocks" { + for_each = var.required_multi_blocks + + content { + name = required_multi_blocks.value.name + length = required_multi_blocks.value.length + } + } +} diff --git a/docs/static/includes/specification/int.spec.nest.req.one.tf b/docs/static/includes/specification/int.spec.nest.req.one.tf new file mode 100644 index 000000000..df2307010 --- /dev/null +++ b/docs/static/includes/specification/int.spec.nest.req.one.tf @@ -0,0 +1,21 @@ +# The resource requires a single nested block + +# Variable declaration +# No default value, as it is required in the module. +# Uses an object type to support a single instance. +variable "required_single_block" { + type = object({ + name = string + length = optional(number) + }) +} + +# Resource declaration +# The block is used directly, as only one instance is supported and required. +resource "my_resource" "this" { + + required_single_block { + name = var.required_single_block.name + length = var.required_single_block.length + } +}