terraformterraform-provider-aws

Terraform dynamic block to be executed with default values


This is my existing terraform module code for AWS Batch resources:

resource "aws_batch_compute_environment" "compute_environment" {
  count = var.create_compute_environment ? 1 : 0
  ...
  ...
}

resource "aws_batch_job_queue" "job_queue" {
  count = var.create_batch_job_queue ? 1 : 0
  ...
  compute_environments = length(var.batch_job_queue_ce) == 0 ? [aws_batch_compute_environment.compute_environment[0].arn] : var.batch_job_queue_ce
}

This allows me to create an AWS Batch Compute environment and a Job queue. If my job queue has just 1 compute environment to be associated with, I don't have to define anything in the calling module and it associates with the corresponding compute environment created as part of the module. If there are more than 1, I pass a list of those in the variable batch_job_queue_ce.

Now, I'm trying to replace the compute_environments argument with the compute_environment_order as it is deprecated. Read here

I'm trying to add a dynamic block for compute_environment_order in my module:

resource "aws_batch_job_queue" "job_queue" {
  count = var.create_batch_job_queue ? 1 : 0
  ...
  dynamic "compute_environment_order" {
    for_each = var.compute_environment
    content {
      order = compute_environment_order.value.order == "" ? 0 : compute_environment_order.value.order
      compute_environment = compute_environment_order.value.compute_environment == "" ? aws_batch_compute_environment.analytics_platform_ce[0].arn : compute_environment_order.value.compute_environment
    }
  }
}

variable "compute_environment" {
description = "Compute environments to be associated with the job queue"
type = list(object({
  order  = number
  compute_environment = string
}))
default = []
}

What I now want is to not define anything related to this dynamic block in the calling module, like it was the case earlier, if there is just 1 compute environment to be associated with the job queue, again as was the condition earlier.

In my calling submodule, if I don't pass anything for compute_environment variable or if I just pass compute_environment = [], it fails with the error:

ā”‚ Exactly one of these attributes must be configured: ā”‚ [compute_environments,compute_environment_order]

If I define a value like this in my calling module:

compute_environment = [
  {
    order = 0
    compute_environment = module.dev_dsm_batch_sandbox.compute_environment_arn[0]
  }
]

it works fine, but I would like to avoid doing this, for the reasons explained above.

I think I'm missing a point here regarding the use of dynamic blocks for such a use case. Would appreciate pointing me in the right direction.


Solution

  • I don't have access to an AWS environment to reproduce your code, but the below demonstrates the concept of using a dynamic block and then having a default if the var is an empty list, if not then use the var.

    mod1/main.tf Here we can set the default value, then use the coalesclist function to pick the first non empty list. We start with the var then fall back to the default

    terraform {
      required_providers {
        docker = {
          source  = "kreuzwerker/docker"
          version = ">=3.0.0"
        }
      }
    }
    
    locals {
      default_volumes = [
        {
          name   = "myapp_config"
          path = "/etc/my/app/config"
        }
      ]
      container_volumes = coalescelist(var.volumes, local.default_volumes)
    }
    
    variable "volumes" {
      default = []
    }
    
    resource "docker_container" "myapp" {
      name  = "myapp"
      image = "myapphub/myapp"
      dynamic volumes {
        for_each = local.container_volumes
        content {
          volume_name    = volumes.value["name"]
          container_path = volumes.value["path"]
        }
      }
    }
    

    main.tf we call the module twice, once with an empty var and once with a list in the var

    module "test1" {
      source = "./mod1"
    }
    
    module "test2" {
      source = "./mod1"
      volumes = [
        {
          name = "myapp_logs"
          path = "/etc/my/app/log"
        },
        {
          name = "myapp_profile"
          path = "/etc/my/app/profile"
        }
      ]
    }
    

    And what we see in the output is for the first module call which didnt define a var, it uses the default in the module.

    
    Terraform will perform the following actions:
    
      # module.test1.docker_container.myapp will be created
      + resource "docker_container" "myapp" {
    ...
    ...
          + volumes {
              + container_path = "/etc/my/app/config"
              + volume_name    = "myapp_config"
                # (2 unchanged attributes hidden)
            }
    }
    

    In the second call where we have defined the var as a list of two items, it uses those two instead of the default.

      # module.test2.docker_container.myapp will be created
      + resource "docker_container" "myapp" {
    ...
    ...
          + volumes {
              + container_path = "/etc/my/app/log"
              + volume_name    = "myapp_logs"
                # (2 unchanged attributes hidden)
            }
          + volumes {
              + container_path = "/etc/my/app/profile"
              + volume_name    = "myapp_profile"
                # (2 unchanged attributes hidden)
            }
    }