Issues with dynamic block in a nested block

I am looking to use the google_os_config_guest_policies resource and am having issues with being able to pass in values to the nested block of code for package_repositories.

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/os_config_guest_policies

Child module main.tf

package_repositories {
      dynamic "apt" {
        for_each = var.apt
        content {
          archive_type = lookup(apt.value, "archive_type", "abc")
          uri          = lookup(apt.value, "uri", "abc")
          distribution = lookup(apt.value, "distribution", null)
          components   = lookup(apt.value, "components", null)
          gpg_key      = lookup(apt.value, "gpg_key", null)
        }
      }
  }

Variables.tf

variable "apt" {
  description = "Variable used for the APT block supported in the package_repositories variable. Pass in variables for apt_archive_type, apt_components, apt_distribution, apt_uri, apt_gpg_key."
  type        = any
  default     = []
}

terraform.tfvars

apt = [
  {
    archive_type = "DEB"
    uri          = "https://packages.cloud.google.com/apt"
    distribution = "cloud-sdk-stretch"
    components       = ["main"]
  }
]

Whenever I try to pass in values from my tfvars I only get a blank package_repositories map passed in during my terraform plan step.

+ package_repositories { }

I have tried to remove the dynamic block and statically define the values forpackage_repositories which worked without any issues.

package_repositories {
    apt {
      archive_type = "DEB"
      uri          = "https://packages.cloud.google.com/apt"
      distribution = "cloud-sdk-stretch"
      components       = ["main"]
  }
}

If I try to rework my code to use the dynamic apt block it would try to pass in null values. Is there something wrong with syntax or is this due to being a beta resource where the dynamic block isn’t working?

Hi @rk92,

This should work as expected with current versions of terraform and the provider. What versions are you using here?

1 Like

@jbardin Sorry, forgot to update that I resolved this. In my parent module’s main.tf I did not include an input field for apt = var.apt so the tfvar value was not being used which is why I had an empty list as an output during my plan step.

I am now trying this out where package_repositories uses a dynamic block along with apt but can’t seem to get the syntax correct to reference the proper attributes. The resource example looks like each package_repositories needs its own set block.

package_repositories {
    apt {
      uri          = "https://packages.cloud.google.com/apt"
      archive_type = "DEB"
      distribution = "cloud-sdk-stretch"
      components   = ["main"]
    }
  }

  package_repositories {
    yum {
      id           = "google-cloud-sdk"
      display_name = "Google Cloud SDK"
      base_url     = "https://packages.cloud.google.com/yum/repos/cloud-sdk-el7-x86_64"
      gpg_keys     = ["https://packages.cloud.google.com/yum/doc/yum-key.gpg", "https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg"]
    }
  }

Child module main.tf

  dynamic "package_repositories" {
    for_each = var.package_repositories
    content {
      apt {
          archive_type = package_repositories.value["archive_type"]
          uri          = package_repositories.value["uri"]
          distribution = package_repositories.value["distribution"]
          components   = package_repositories.value["components"]
          gpg_key      = package_repositories.value["gpg_key"]
          }
        }
      }

.tfvars

package_repositories = [{
  apt = [
  {
    archive_type = "DEB"
    uri          = "https://packages.cloud.google.com/apt"
    distribution = "cloud-sdk-stretch"
    components   = ["main"]
    gpg_key      = "https://packages.cloud.google.com/apt/doc/apt-key.gpg.asc"
      }
    ]
  }
]

The variable for package_repositories is setup as a type of any for now. Is there a cleaner way to approach this? Especially with the amount of lists and maps to put into the tfvars file? Does apt need it’s own dynamic block?

You can hard-code the nested blocks if they do not change, but if you want to be able to conditionally add them based on the given parameters, you will need to use nested dynamic blocks here.

  dynamic "package_repositories" {
    for_each = var.package_repositories
    iterator = repo
    content {
      dynamic "apt" {
        for_each = lookup(repo.value, "apt", [])
        content {
          archive_type = apt.value["archive_type"]
          uri          = apt.value["uri"]
          distribution = apt.value["distribution"]
          components   = apt.value["components"]
          gpg_key      = apt.value["gpg_key"]
        }
      }
    }
  }
1 Like

I keep getting an error about terraform expecting a comma. I didn’t explicitly use an iterator like you did.

  on terraform.tfvars line 152:
 151:   apt = [
 152:     archive_type = "DEB"

Expected a comma to mark the beginning of the next item.

main.tf

  dynamic "package_repositories" {
    for_each = var.package_repositories
    content {
      dynamic "apt" {
        for_each = lookup(package_repositories.value, "apt", [])
        content {
          archive_type = apt.value["archive_type"]
          uri          = apt.value["uri"]
          distribution = apt.value["distribution"]
          components   = apt.value["components"]
          gpg_key      = apt.value["gpg_key"]
        }
      }
    }
  }

.tfvars

package_repositories = [{
  apt = [
    archive_type = "DEB"
    uri          = "https://packages.cloud.google.com/apt"
    distribution = "cloud-sdk-stretch"
    components   = ["main"]
    gpg_key      = "https://packages.cloud.google.com/apt/doc/apt-key.gpg.asc"
    ]
  }
]

My understanding of how this is setup is that the first for_each iterates over the first element out of the tfvar so in this case something like { apt = [] } then the second for_each is used to iterate over the lookup output from package_repositories.value map and the map it’s using should be { apt = [] } correct? Key in this case is apt so it should pull values from apt = []. Then set values in content based on the temporary iterator variable apt.value[<INPUT_FIELD>]. Just trying to make sense of this because dynamic blocks have confused me for a while.

The error is pointing to a syntax error in the tfvars file, not the dynamic block. You are missing the {} characters around the map in the apt list.

1 Like

Thanks, that worked.

Currently I have my setup as

  dynamic "package_repositories" {
    for_each = var.package_repositories
    content {
      dynamic "apt" {
        for_each = lookup(package_repositories.value, "apt", [])
        content {
          archive_type = apt.value["archive_type"]
          uri          = apt.value["uri"]
          distribution = apt.value["distribution"]
          components   = apt.value["components"]
          gpg_key      = apt.value["gpg_key"]
        }
      }
      dynamic "yum" {
        for_each = lookup(package_repositories.value, "yum", null)
        content {
          id           = yum.value["id"]
          display_name = yum.value["display_name"]
          base_url     = yum.value["base_url"]
          gpg_keys     = yum.value["gpg_keys"]
        }
      }
    }
  }

variables.tf

  type = list(object({
    apt = list(object({
      archive_type = string
      uri          = string
      distribution = string
      components   = list(string)
      gpg_key      = string
    }))
    yum = list(object({
      id           = string
      display_name = string
      base_url     = string
      gpg_keys     = list(string)
    }))
  }))
  default = []
}

I receive an error about The given value is not valid for variable "package_repositories": element 0: attribute "yum" is required.

What I’d like to have is my variable for package_repositories be descriptive enough to explain what is expected when others use it but ideally you would only need to use apt or yum and not both.

The lookup statement to pass in either [] or null makes sense but it seems like the variable requires yum if it is not declared in my tfvars.

.tfvars

package_repositories = [
  {
    apt = [{
      archive_type = "DEB"
      uri          = "https://packages.cloud.google.com/apt"
      distribution = "cloud-sdk-stretch"
      components   = ["main"]
      gpg_key      = "https://packages.cloud.google.com/apt/doc/apt-key.gpg.asc"
    }]
  },
  {
    apt = [{
      archive_type = "DEB_SRC"
      uri          = "https://packages.cloud.google.com/apt"
      distribution = "cloud-sdk-stretch"
      components   = ["main"]
      gpg_key      = "https://packages.cloud.google.com/apt/doc/apt-key.gpg.asc"
    }]
  }
]

This is what I’m looking for, but it seems like it wouldn’t work with 0.13.5.

As a workaround what is a good way to setup a variable such as above so that people could know how to use it?

Even with current version, optional attributes are still experimental, so I would not recommend using them in production. Besides leaving the value types to be inferred, and documenting how they should be used, you could try creating separate input variables for each possible repository type, which can default to null.

Makes sense with using documentation + placeholder variables. Thanks for all the help.