New to Terraform, struggling with literal string from var instead of resource id (Azurerm)

Hello. I have two resource for a shared image:

resource "azurerm_shared_image" "windows1"

and

resource "azurerm_shared_image" "windows2"

I also have an Azurerm Resource Group Template Deployment
resource "azurerm_resource_group_template_deployment" "example"

Within here, inside the parameters_content if want to call the resource “azurerm_shared_image” “windows1”. When I do this, the expected resource id is returned.

like
"galleryImageId": {"value":"${azurerm_shared_image.windows1.id}"}

However, let’s say I want to make the shared image a variable, so that the user can type “windows1” or “windows2” (actually, it would really be:)

azurerm_shared_image.windows1

If I create:
variable "image" { type = string }

when I call var.image in the template, perhaps like this:
"galleryImageId": {"value":"${var.image}.id"}

the terraform output errors because the literal string of the variable azurerm_shared_image.windows1.id is returned

Field 'galleryImageId' has a bad value: 'azurerm_shared_image.windows1.id'

How to solve?

Terraform does not have any way to do this - that is, make a dynamic reference to a resource block like this.

For this to be possible, it would be necessary to refactor your azurerm_shared_images to be defined as a single resource block using a for_each - in this way, you could reference azurerm_shared_image.some_name[var.image], moving the dynamic part into the resource index (between the square brackets), which Terraform does allow.

Thank you, but I am not understand. How would the template content then be called dynamically? It is the template resource that needs to be dynamic based on the entry from the user.

So if user a wants “windows1” it calls the resource:
azurerm_shared_image.windows1

if the user wants “windows2” it calls the resource:
azurerm_shared_image.windows2

and because the variable string value is either azurerm_shared_image.windows1 or azurerm_shared_image.windows2 the template using var.image is agnostic

EDIT sorry, reading again I think understand. the user could simply provide 0 or 1 (for an image) and that is the var.image content when asked. How to for each? is there example

I have now this:

variable "os_image" {
  description = "Shared Image Version"
  type        = string
  default     = "win10"
}

resource "azurerm_shared_image" "windows" {
  name                   = var.os_image
  gallery_name           = azurerm_shared_image_gallery.example.name
  resource_group_name    = azurerm_resource_group.example.name
  location               = azurerm_resource_group.example.location
  os_type                = "Windows"

  identifier {
    publisher = "microsoftwindowsdesktop"
    offer     = "office-365"
    sku       = var.os_image == "win10" ? "win10-22h2-avd-m365-g2" : "win11-22h2-avd-m365"
  }
}

then in template, simply

"galleryImageId": {"value":"${azurerm_shared_image.windows.id}"}

This works, except if the user then runs again with “win11” as the variable, the Win10 shared image is destroyed.

How to solve

Ok to fix wider:

Using Terraform, I need to create an azurerm_shared_image resource. It should be a single resource that can deploy two shared_images so I am assuming a count function of some sort is required. The name of the shared_image should be passed in through variable.

So, the count is 2.
If the variable value is “A” the shared_image_resource name should be “A”. And if it is “A” then the SKU value (which can come from the same variable) should also be created. However, if the variable value is “B”, then the shared_image_resource name should be “B” and the SKU value “B” should be deployed.

I kind of have this working. However, when deploying, terraform wants either “A” or “B”. I want both “A” and “B”

resource "azurerm_shared_image" "windows_image" {
  count                  = length(var.os_image)
  name                   = var.os_image[count.index]
  gallery_name           = azurerm_shared_image_gallery.example.name
  resource_group_name    = azurerm_resource_group.example.name
  location               = azurerm_resource_group.example.location
  os_type                = "Windows"

  identifier {
    publisher = "microsoftwindowsdesktop"
    offer     = "office-365"
    sku       = var.os_image[count.index]
  }
}

If this is used, and I pass in variable A, it creates fine. If I then use variable B terraform wants to update (which forces replacement) to deploy B. A is deleted form Azure.

I see this working as such:

Variable entered is Windows 10
A Shared Image Resource of Windows 10 is created (with corresponding image definition)
A Shared Image Resource of Windows 11 is created (with no corresponding image definition)
Run again, this time with the Variable of Windows 11, all that is created is an image definition for the Windows 11 Shared Image Resource.

Perhaps then, the names of the Shared Image Resource are static in the variable, relational to the “count”, so the variable could be

variable "shared_image_name" {
  type    = list(string)
  default = ["win10", "win11"]
}

And only the SKU is the variable called by the user during deployment? My head hurts now.

If you want to choose between two objects based on a string then the appropriate data structure for that is a map.

For example:

locals {
  image_ids = tomap({
    "windows1" = azurerm_shared_image.windows1.id
    "windows2" = azurerm_shared_image.windows2.id
  })
}

You can then combine that with an input variable that specifies a key from that map to use. For example:

variable "shared_image_name" {
  type = string

  validation {
    condition     = contains(["windows1", "windows2"], var.shared_image_name)
    error_message = "Unrecognized image name."
  }
}

When you want to refer to the id of the image that the module caller requested, you can use this expression:

local.image_ids[var.shared_image_name]

Alternatively, if the configurations of these various azurerm_shared_image resources are similar enough that you can describe the differences systematically then you could use a single resource "azurerm_shared_image" block with for_each to declare multiple images, and then Terraform will effectively construct a map of objects for you automatically because any resource with for_each is represented as a map.

You didn’t share the contents of your azurerm_shared_image blocks and I’m not familiar with that resource type so I’m just guessing a bit here about how it might work:

locals {
  shared_images = tomap({
    "windows1" = {
      name         = "something-windows1"
      gallery_name = "something-something"
    }
    "windows2" = {
      name         = "something-windows1"
      gallery_name = "something-something"
    }
  })
}

resource "azurerm_shared_image" "all" {
  for_each = local.shared_images

  name         = each.value.name
  gallery_name = each.value.name
  # (and whatever other arguments you need)
}

With this structure azurerm_shared_image.all will be a map with the same keys as local.shared_images, and so you could use azurerm_shared_image.all[var.shared_image_name] to refer to the one image the caller selected.

This does mean that all of the images will always be declared even though one of them will ever be used. In an earlier comment you mentioned that deleting an unselected image was not desirable so I assumed that was a requirement.

Yes correct, both shared images must always remain, but only one should have an image definition built if the users passes in the variable.

I posted my azurerm_shared_image resource above in my edit above, it’s the identifier block where the publisher, offer and sku are set. Publisher and offer remain constant, but the sku is what should be set by the variable too. This could be a different variable to the shared image name, but I’m not sure I see a need for that. At least with for each, the count is removed as I’m not sure that’s the best approach.

I also need to reference the publisher, offer and sku in another resource. (Template parameters in an azurerm_resource_group_deployment_template. I’m not sure how to do that with a for each statement.

I think I understand further. I have tried to implement your first approach, but I suspect this will destroy whatever shared image is not used. Also, I receive error

│     │ local.image_ids is map of string with 2 elements
│     │ var.shared_image_name is "windows1"
│ 
│ Can't access attributes on a primitive-typed value (string).

:confused: econd method fails too with

missing resource instance key on template.tf line 15, in resource "azurerm_resource_group_template_deployment" "example":
 "sku":  {"value":"${azurerm_shared_image.example.identifier[0].sku}"},

Because azurerm_shared_image.example has "for_each" set, its
attributes must be accessed on specific instances.

For example, to correlate with indices of a referring resource, use:
azurerm_shared_image.windows_image[each.key]

Problem is, I can’t use each.key here, as it’s in a different resource.

:confused:

Please share the entire error message Terraform returned, rather than just a subset of the lines.

My var file look this:

variable "sku" {
  type = string

  validation {
    condition     = contains(["win10-22h2-avd-m365-g2", "win11-22h2-avd-m365"], var.sku)
    error_message = "SKU type must be one of 'win10-22h2-avd-m365-g2' or 'win11-22h2-avd-m365'"
  }
}

My resource this:

locals {
  skus = tomap({
    "Windows10" = {
      name = "win10-22h2"
      sku  = var.sku
    }
    "Windows11" = {
      name = "win11-22h2"
      sku  = var.sku
    }
  })
}

# An image definition for Windows 10 22H2 (generation 2) which will be used for AVD deployments.
resource "azurerm_shared_image" "windows_image" {
  for_each               = local.skus
  name                   = each.value.name
  gallery_name           = azurerm_shared_image_gallery.example.name
  resource_group_name    = azurerm_resource_group.example.name
  location               = azurerm_resource_group.aib.location
  os_type                = "Windows"


  identifier {
    publisher = "microsoftwindowsdesktop"
    offer     = "office-365"
    sku       = each.value.sku
  }
}

And the template is built, and this is where the issue lies I think:

resource "azurerm_resource_group_template_deployment" "example" {
  name                = "terraform_deploy"
  resource_group_name = azurerm_resource_group.example.name
  deployment_mode     = "Incremental"
  template_content    = file("${path.module}/Template.json")

  parameters_content = <<PARAM
  {
    "publisher":                    {"value":"${azurerm_shared_image.windows_image[var.sku].identifier[0].publisher}"},
    "offer":                        {"value":"${azurerm_shared_image.windows_image[var.sku].identifier[0].offer}"},
    "sku":                          {"value":"${azurerm_shared_image.windows_image[var.sku].identifier[0].sku}"},
    "galleryImageId":               {"value":"${azurerm_shared_image.windows_image[var.sku].id}"}
  }

PARAM

}

Error comes

╷
│ Error: Invalid index
│ 
│   on aib_template.tf line 14, in resource "azurerm_resource_group_template_deployment" "aib":
│   14:     "publisher":                    {"value":"${azurerm_shared_image.windows_image[var.sku].identifier[0].publisher}"},
│     ├────────────────
│     │ azurerm_shared_image.windows_image is object with 2 attributes
│     │ var.sku is "win10-22h2-avd-m365-g2"
│ 
│ The given key does not identify an element in this collection value.
╵
╷
│ Error: Invalid index
│ 
│   on aib_template.tf line 15, in resource "azurerm_resource_group_template_deployment" "example":
│   15:     "offer":                        {"value":"${azurerm_shared_image.windows_image[var.sku].identifier[0].offer}"},
│     ├────────────────
│     │ azurerm_shared_image.windows_image is object with 2 attributes
│     │ var.sku is "win10-22h2-avd-m365-g2"
│ 
│ The given key does not identify an element in this collection value.
╵
╷
│ Error: Invalid index
│ 
│   on aib_template.tf line 16, in resource "azurerm_resource_group_template_deployment" "example":
│   16:     "sku":                          {"value":"${azurerm_shared_image.windows_image[var.sku].identifier[0].sku}"},
│     ├────────────────
│     │ azurerm_shared_image.windows_image is object with 2 attributes
│     │ var.sku is "win10-22h2-avd-m365-g2"
│ 
│ The given key does not identify an element in this collection value.
╵
╷
│ Error: Invalid index
│ 
│   on aib_template.tf line 32, in resource "azurerm_resource_group_template_deployment" "example":
│   32:     "galleryImageId":               {"value":"${azurerm_shared_image.windows_image[var.sku].id}"}
│     ├────────────────
│     │ azurerm_shared_image.windows_image is object with 2 attributes
│     │ var.sku is "win10-22h2-avd-m365-g2"
│ 
│ The given key does not identify an element in this collection value.