skip to Main Content

I’m currently trying to create several instances under Openstack with Terraform. Afterwards I want to attach more of them. But I’m not getting anywhere with the last point.
Does anyone have an idea how I can solve it?

variable "instances" {
  description = "The Instances to be deployed"
  type = map(object({
    name             = string
    image            = string
    flavor           = string
    volume_size      = number
    security_groups  = list(string)
    network          = string
    keypair_name     = string
    floating_ip_pool = string
    tags             = list(string)
    additional_disks = list(object({
      disk        = string
      volume_size = number
    }))
  }))
}

instances = {
  "Websrv01" = {
    name             = "websrv01"
    image            = "Ubuntu 22.04"
    flavor           = "SCS-2V-4-20s"
    volume_size      = 20
    security_groups  = ["default"]
    network          = "test-intern"
    keypair_name     = "ssh-pub"
    floating_ip_pool = "public"
    tags             = ["general", "webserver"]
    additional_disks = []
  },
  "dbsrv01" = {
    name             = "dbsrv01"
    image            = "Ubuntu 22.04"
    flavor           = "SCS-2V-4-20s"
    volume_size      = 20
    security_groups  = ["default"]
    network          = "test-intern"
    keypair_name     = "ssh-pub"
    floating_ip_pool = "public"
    tags             = ["general", "dbserver"]
    additional_disks = [
      { disk = "db_data", volume_size = 30 },
      { disk = "db_log", volume_size = 10 }
    ]
  }
}

resource "openstack_compute_instance_v2" "instances" {
  for_each = var.instances

  name            = each.value.name
  flavor_id       = data.openstack_compute_flavor_v2.flavor[each.key].id
  key_pair        = each.value.keypair_name
  security_groups = each.value.security_groups

  ## Boot Disk
  block_device {
    uuid                  = data.openstack_images_image_v2.image[each.key].id
    source_type           = "image"
    volume_size           = each.value.volume_size
    boot_index            = 0
    destination_type      = "volume"
    delete_on_termination = true
  }
 
  lifecycle {
    prevent_destroy = true

  network {
    name = each.value.network
  }

  tags = each.value.tags
}

## Get floating IP if needed
resource "openstack_networking_floatingip_v2" "floating_ip" {
  for_each = { for k, v in var.instances : k => v if v.floating_ip_pool != "" }

  pool = each.value.floating_ip_pool
}

## Associate floating ip to Instances
resource "openstack_compute_floatingip_associate_v2" "associate" {
  for_each = openstack_networking_floatingip_v2.floating_ip

  instance_id = openstack_compute_instance_v2.instances[each.key].id
  floating_ip = each.value.address
}

## Additional Disks
resource "openstack_blockstorage_volume_v3" "additional_disks" {
  for_each = {
    for instance, disks in var.instances : instance => disks.additional_disks
    if length(disks.additional_disks) > 0
  }

  name                 = "${each.key}-${each.value[0].disk}"
  size                 = each.value[0].volume_size
  enable_online_resize = true
}

## Attach additional disks to instances
resource "openstack_compute_volume_attach_v2" "attach_additional_disks" {
  for_each = {
    for instance, disks in var.instances : instance => disks.additional_disks
    if length(disks.additional_disks) > 0
  }

  instance_id = openstack_compute_instance_v2.instances[each.key].id
  volume_id   = openstack_blockstorage_volume_v3.additional_disks[each.key].id
}

Only the first hard disk is taken into account here.
Ich i try to loop truth the list, with [*] it fails.

regards
Eddi

with single addition Disk it works:

resource "openstack_blockstorage_volume_v3" "additional_disks" {
  for_each = {
    for instance, disks in var.instances : instance => disks.additional_disks
    if length(disks.additional_disks) > 0
  }

  name                 = "${each.key}-${each.value[0].disk}"
  size                 = each.value[0].volume_size
  enable_online_resize = true
}

resource "openstack_compute_volume_attach_v2" "attach_additional_disks" {
  for_each = {
    for instance, disks in var.instances : instance => disks.additional_disks
    if length(disks.additional_disks) > 0
  }

  instance_id = openstack_compute_instance_v2.instances[each.key].id
  volume_id   = openstack_blockstorage_volume_v3.additional_disks[each.key].id
}

2

Answers


  1. Chosen as BEST ANSWER

    I had already tried to solve it with ChatGPT. And he was already on the right track. But had not suggested a solution with locals. Had tried it in the for_each loop using flatten. I am not yet familiar with flatten and locals. Now I have changed the solution a bit, in a simpler notation, after ChatGPT had suggested to convert to a map(object).

    This is what my solution looks like now:

    variable "instances" {
      description = "The Instances to be deployed"
      type = map(object({
        name             = string
        image            = string
        flavor           = string
        volume_size      = number
        security_groups  = list(string)
        network          = string
        keypair_name     = string
        floating_ip_pool = string
        tags             = list(string)
        additional_disks = map(object({
          volume_size = number
        }))
      }))
    }
    
    instances = {
      "Websrv01" = {
        name             = "websrv01"
        image            = "Ubuntu 22.04"
        flavor           = "SCS-2V-4-20s"
        volume_size      = 20
        security_groups  = ["default"]
        network          = "test-intern"
        keypair_name     = "ssh_general"
        floating_ip_pool = "public"
        tags             = ["general", "webserver"]
        additional_disks = {}
      },
      "Websrv02" = {
        name             = "websrv02"
        image            = "Ubuntu 22.04"
        flavor           = "SCS-2V-4-20s"
        volume_size      = 20
        security_groups  = ["default"]
        network          = "test-intern"
        keypair_name     = "ssh_general"
        floating_ip_pool = "public"
        tags             = ["general", "webserver"]
        additional_disks = {}
      },
      "dbsrv01" = {
        name             = "dbsrv01"
        image            = "Ubuntu 22.04"
        flavor           = "SCS-2V-4-20s"
        volume_size      = 20
        security_groups  = ["default"]
        network          = "test-intern"
        keypair_name     = "ssh_general"
        floating_ip_pool = "public"
        tags             = ["general", "dbserver"]
        additional_disks = {
          db_data = { volume_size = 30 },
          db_log  = { volume_size = 10 }
        }
      }
    }
    
    ## Create Additional Disks for Instances  
    resource "openstack_blockstorage_volume_v3" "additional_disks" {
      for_each = {
        for disk in local.additional_disks : "${disk.instance_key}-${disk.disk_name}" => disk
      }
    
      name                 = each.key
      size                 = each.value.disk_values.volume_size
      enable_online_resize = true
    }
    
    ## Attach additional disks to instances
    resource "openstack_compute_volume_attach_v2" "attach_additional_disks" {
      for_each = {
        for disk in local.additional_disks : "${disk.instance_key}-${disk.disk_name}" => disk
      }
    
      instance_id = openstack_compute_instance_v2.instances[each.value.instance_key].id
      volume_id   = openstack_blockstorage_volume_v3.additional_disks[each.key].id
    }
    

    I will also implement the tip with enable_online_resize.

    Another question. I would like to use the code in several environments, so it will certainly make sense to create a module from it? Then I'll give it a try now.

    And finally, I would like to generate a dynamic inventory in INI format for Ansible and group it with the tags. Are there any easy ways to do this?


  2. This question is more or less similar to the one asked & answered here. Nevertheless, I want to make it easy for you.

    If you want iterate over multiple objects in a json object & have appropriate correlation with parent objects, it is recommended to use flatten function & extract relevant fields of your interest for further iteration.

    Below locals var does exactly the same ::

    locals {
      additional_disks = flatten([
        for instance_key, instance in var.instances : [
          for index in range(length(instance.additional_disks)) : {
            local_disk_index = index % length(instance.additional_disks)
            instance_key     = instance_key
            disk_key         = format("%s-%s", instance_key, instance.additional_disks[index % length(instance.additional_disks)].disk)
            disk_values      = instance.additional_disks[index % length(instance.additional_disks)]
          }
        ]
      ])
    }
    

    We construct a list of objects with instance_key for lookup of instances & then associated disk keys & values to it.

    The above local.additional_disks could be used like below to provision the resources.

    ## Additional Disks    
    resource "openstack_blockstorage_volume_v3" "additional_disks" {
      for_each = {
        for disk in local.additional_disks: disk.disk_key => disk
      }     
          
      name                 = each.value.disk_values.disk
      size                 = each.value.disk_values.volume_size
      enable_online_resize = true
    }
    
    ## Attach additional disks to instances
    resource "openstack_compute_volume_attach_v2" "attach_additional_disks" {   
            
      for_each = {
        for disk in local.additional_disks: disk.disk_key => disk
      } 
      
      instance_id = openstack_compute_instance_v2.instances[each.value.instance_key].id
      volume_id   = openstack_blockstorage_volume_v3.additional_disks[each.key].id
    } 
    

    And the terraform plan gives below output ::

    Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
      + create
    
    Terraform will perform the following actions:
    
    ...blah-blah..
    # openstack_blockstorage_volume_v3.additional_disks["dbsrv01-db_data"] will be created
      + resource "openstack_blockstorage_volume_v3" "additional_disks" {
          + attachment           = (known after apply)
          + availability_zone    = (known after apply)
          + enable_online_resize = true
          + id                   = (known after apply)
          + metadata             = (known after apply)
          + name                 = "db_data"
          + region               = (known after apply)
          + size                 = 30
          + volume_type          = (known after apply)
        }
    
      # openstack_blockstorage_volume_v3.additional_disks["dbsrv01-db_log"] will be created
      + resource "openstack_blockstorage_volume_v3" "additional_disks" {
          + attachment           = (known after apply)
          + availability_zone    = (known after apply)
          + enable_online_resize = true
          + id                   = (known after apply)
          + metadata             = (known after apply)
          + name                 = "db_log"
          + region               = (known after apply)
          + size                 = 10
          + volume_type          = (known after apply)
        }
    ...blah-blah..
     # openstack_compute_volume_attach_v2.attach_additional_disks["dbsrv01-db_data"] will be created
      + resource "openstack_compute_volume_attach_v2" "attach_additional_disks" {
          + device      = (known after apply)
          + id          = (known after apply)
          + instance_id = (known after apply)
          + region      = (known after apply)
          + volume_id   = (known after apply)
        }
    
      # openstack_compute_volume_attach_v2.attach_additional_disks["dbsrv01-db_log"] will be created
      + resource "openstack_compute_volume_attach_v2" "attach_additional_disks" {
          + device      = (known after apply)
          + id          = (known after apply)
          + instance_id = (known after apply)
          + region      = (known after apply)
          + volume_id   = (known after apply)
        }
    

    Another unasked advice, I see you hardcode enable_online_resize directly on the resource. This isn’t a good idea, pull it from the additional_disks attribute by hardcoding it to true.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search