terraformopenstack

Openstack: Terraform multiple Instances with additional Disks - for_each list(object)


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
}

Solution

  • 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 a flatten structure vom var.instances
    locals {
      additional_disks = flatten([
        for instance_key, instance in var.instances : [
          for disk_name, disk in instance.additional_disks : {
            instance_key = instance_key
            disk_name    = disk_name
            disk_values  = disk
          }
        ]
      ])
    }
    
    ## 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?