Create resources iterating through values of a map, while Overriding a single value in a map using tfvars file

I have used implemented same structure as this post:
Override a single value in a map.

I am able to create a number of resources based on each type. However I would also like the ability to override the “instance_count” values from either command line, but preferably in a .tfvars file without having to specify all the other attributes as well. Any help would be appreciated !!

#main.tf 
# A list of objects with one object per instance.
  instances = flatten([
    for image_key, image in var.images : [
      for index in range(image.instance_count) : {
        flavor            = image.flavor
        instance_index    = index
        image_key         = image_key
        instance_name     = image.instance_name
      }
    ]
  ])
  
}

resource "openstack_compute_instance_v2" "instance" {
  for_each = {
    # Generate a unique string identifier for each instance
    for inst in local.instances : format("%s-%02d", inst.image_key, inst.instance_index + 1) => inst
  }

  image_name        = each.value.image_name
  flavor_id         = each.value.flavor
  name              = each.key
  security_groups   = var.security_groups
  availability_zone = each.value.availability_zones
  key_pair          = "foptst"
  network {
    name = var.network_name
  }
}

#variables.tf
variable "images" {
    type = map(object({
        instance_name = string
        flavor = string
        instance_count = string 
        
    }))
    default = {
        "web" = {
            instance_name = "web"
            flavor = "1"
            "instance_count" = 4
        }
        "worker" = {
            instance_name = "wrk"
            flavor = "2"
            "instance_count" = 3
        } 
        "db" = {
            instance_name  = "sql"
            flavor = "3"
            "instance_count" = 2
        }
    }  
#terraform.tfvars
images =  {
            db = {
                instance_name = "sql"
                flavor = "1"
                "instance_count" = 0
            },
            "web" = {
                instance_name = "web"
                flavor = "2"
                "instance_count" = 0
            },
            "worker" = {
                instance_name = "wrk"
                flavor = "3"
                "instance_count" = 1
            }
            
}

Hi @dominic.viau,

From what you’ve described it seems like you’ve currently defined “number of instances” as a property of the image itself, which therefore means that it needs to be defined as part of the set of images.

When I think about the goal you described, I imagine “compute instance set” as something separate that has an image. So I might define that like this:

variable "instance_sets" {
  type = map(object({
    instance_count = number
    image_key      = string
    # (availability_zones?)
  }))
}

variable "images" {
  type = map(object({
    name      = string
    flavor_id = string
  }))
}

To actually declare the instances, we can combine those two data structures to produce a collection with one element per compute instance:

locals {
  instances = flatten([
    for instset_key, instset in var.instance_sets : [
      for index in range(instset.instance_count) : {
        instset_key    = instset_key
        instance_index = index
        image_key      = instset.image_key
        image          = var.images[instset.image_key]
      }
    ]
  ])
}

resource "openstack_compute_instance_v2" "instance" {
  for_each = {
    for inst in local.instances :
    format("%s-%02d", inst.instset_key, inst.instance_index + 1) => inst
  }

  image_name        = each.value.image.name
  flavor_id         = each.value.image.flavor_id
  name              = each.key
  security_groups   = var.security_groups
  availability_zone = each.value.availability_zones
  key_pair          = "foptst"
  network {
    name = var.network_name
  }
}

If you don’t anticipate ever needing to create more than one instance set for the same image then you could simplify this by removing image_key from the instance_sets type and using instset_key also as the image_key, as long as you make sure to use the same keys for the instance_sets map as for the images map.

I am not sure I completely understand the use of the “instset.image_key” as it does not seem to be used in resource creation. would it not be simple to simply seperate both maps and simply use instset_key and forget about instset.image_key.

Anyways I declare this in my variables.tf to structure on how would like to define my instances. Then I simply overwrite them using a .tfvars file with values I want.

So taking your example , here’s what I have

#######################
#variables.tf 
#######################

locals {

instances = flatten([

    for instset_key, instset in var.instance_sets : [
      for index in range(instset.instance_count) : {
        instset_key    = instset_key
        instance_index = index
        image_key      = instset.image_key
        image          = var.images[instset.image_key]
          #have not implemented multiple disks,
          # that would be the next step.
         #os_disk_count     = image.os_disk_count
      }
    ]
  ])

}

variable "instance_sets" {
    type = map(object({

    image_key      = string
    instance_count = number
     # Have not implemented multiple disks, set value for future use.   
    # It simply an empty value not being used
    os_disk_count = number   

  }))


   default = {

        "vm1"= {
             "image_key" = "db"
            "instance_count" = 0
             "os_disk_count" = 0
        }
        "vm2" = {
            "image_key" = "worker"
            "instance_count" = 0
            "os_disk_count" = 0
        } 
        "vm3" = {
            "image_key" = "web"
            "instance_count" = 1
            "os_disk_count" = 0
        }
    }  
}

variable "images" {

    type = map(object({
      instance_name  = string
      flavor_id = string
        
    }))
    default = {
        "db" = {
            image_name = "rhel8"
            instance_name = "vm1"
            flavor_id = "xlarge"
           
        }
        "worker" = {
             image_name = "ubuntu"
            instance_name = "vm2"
            flavor_id = "medium"
           
        } 
        "web" = {
              image_name = "win2022"
             instance_name  = "vm3"
            flavor_id = "large"
                    }
    }  
 
}

#######################
#end of variables.tf file
#######################

############################################
If I run the following below I get as instance name : vm3-01
############################################

#######################
***example 1 of resource.tf***
#######################


resource "openstack_compute_instance_v2" "instance" {

  for_each = {
    for inst in local.instances : 
    format("%s-%02d", inst.instset_key, inst.instance_index + 1) => inst
  }

  image_name        = each.value.image.instance_name
  flavor_id         = each.value.image.flavor_id
  name              = each.key
  security_groups   = var.security_groups
  availability_zone = each.value.availability_zones
  key_pair          = "foptst"
  network {
    name = var.network_name
  }
}
**outputs**
~ vm_name                 = {
      + vm3-01 = "vm3-01"
}

############################################
If I run the following below I get as instance name : web
############################################

#######################
***example 2 of resource.tf***
#######################



resource "openstack_compute_instance_v2" "instance" {

  for_each = {
    for inst in local.instances :
    format("%s-%02d", inst.instset_key, inst.instance_index + 1) => inst

  }

  image_name        = each.value.image.instance_name
  flavor_id         = each.value.image.flavor_id
  name              = each.value.image_key
  security_groups   = var.security_groups
  availability_zone = each.value.availability_zones
  key_pair          = "foptst"
  network {
    name = var.network_name
  }
}
**outputs**
~ vm_name                 = {
      + vm3-01 = "web"
}