Terraform: OpenStack

OpenStack Providery
https://registry.terraform.io/providers/terraform-provider-openstack/openstack/latest/docs

# Configure the OpenStack Provider
provider "openstack" {
  user_name   = "admin"
  tenant_name = "admin"
  password    = "pwd"
  auth_url    = "http://myauthurl:5000/v2.0"
  region      = "RegionOne"
}
 
# cloud.yaml
provider "openstack" {
  cloud      = "dev-foo"
}

Router
https://registry.terraform.io/providers/terraform-provider-openstack/openstack/latest/docs/resources/networking_router_v2

resource "openstack_networking_router_v2" "router_1" {
  name       = "foo-router"
  external_network_id = "88934cac-8d55-40d5-8ff9-bde65011741d"
}
 
resource "openstack_networking_router_interface_v2" "terraform" {
  router_id = openstack_networking_router_v2.router_1.id
  subnet_id = openstack_networking_subnet_v2.subnet_1.id
}

Compute
https://registry.terraform.io/providers/terraform-provider-openstack/openstack/latest/docs/resources/compute_instance_v2

resource "openstack_compute_instance_v2" "basic" {
  name            = "basic"
  image_id        = "ad091b52-742f-469e-8f3c-fd81cadf0743"
  flavor_id       = "3"
  key_pair        = "my_key_pair_name"
  security_groups = ["default"]
 
  metadata = {
    this = "that"
  }
 
  network {
    name = "my_network"
  }
}

Image

data "openstack_images_image_v2" "image_ubuntu_2204" {
  name = "Ubuntu 22.04"
  most_recent = true
}

Floating IP
# network
https://registry.terraform.io/providers/terraform-provider-openstack/openstack/latest/docs/resources/networking_floatingip_v2
# compute
https://registry.terraform.io/providers/terraform-provider-openstack/openstack/latest/docs/resources/compute_floatingip_v2

resource "openstack_networking_floatingip_v2" "fip_1" {
  pool = "public"
}
 
resource "openstack_compute_floatingip_associate_v2" "fip_1" {
  floating_ip = openstack_networking_floatingip_v2.fip_1.address
  instance_id = openstack_compute_instance_v2.bastion.id
}

openstack-check.tf

# Init provider
terraform {
  required_providers {
    openstack = {
      source = "terraform-provider-openstack/openstack"
    }
  }
}
 
 
# Define variables (can be overwritten in terraform.tfvars)
variable "test_cloud" {
  type    = string
  default = "test-cloud"
}
 
variable "az" {
  type    = set(string)
  default = ["az1", "az2", "az3"]
}
 
variable "test_keypair" {
  type    = string
  default = "test-keypair"
}
 
variable "test_keypair_public_key" {
  type    = string
  default = "~/openstack-checks-conf/id_rsa.pub"
}
 
variable "test_secgroup" {
  type    = string
  default = "test-secgroup"
}
 
variable "test_network" {
  type    = string
  default = "test-network"
}
 
variable "test_subnet" {
  type    = string
  default = "test-subnet"
}
 
variable "test_subnet_cidr" {
  type    = string
  default = "10.0.10.0/24"
}
 
variable "test_router" {
  type    = string
  default = "test-router"
}
 
variable "rule_cidr_list" {
  default = [
    "10.0.10.0/24"
  ]
}
 
variable "lb_floating_ip" {
  type    = string
  default = ""
}
 
 
# Init OpenStack
provider "openstack" {
  cloud = var.test_cloud
}
 
 
# Get OpenStack data
resource "openstack_compute_keypair_v2" "keypair_1" {
  name       = var.test_keypair
  public_key = file("${var.test_keypair_public_key}")
}
 
resource "openstack_networking_secgroup_v2" "secgroup_1" {
  name        = var.test_secgroup
  description = var.test_secgroup
}
 
resource "openstack_networking_secgroup_rule_v2" "secgroup_rule_icmp" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "icmp"
  security_group_id = openstack_networking_secgroup_v2.secgroup_1.id
}
 
resource "openstack_networking_secgroup_rule_v2" "secgroup_rule_tcp_22" {
  for_each          = toset(var.rule_cidr_list)
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 22
  port_range_max    = 22
  remote_ip_prefix  = each.value
  security_group_id = openstack_networking_secgroup_v2.secgroup_1.id
}
 
resource "openstack_networking_secgroup_rule_v2" "secgroup_rule_tcp_80" {
  for_each          = toset(var.rule_cidr_list)
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 80
  port_range_max    = 80
  remote_ip_prefix  = each.value
  security_group_id = openstack_networking_secgroup_v2.secgroup_1.id
}
 
resource "openstack_networking_network_v2" "network_1" {
  name = var.test_network
}
 
resource "openstack_networking_subnet_v2" "subnet_1" {
  network_id = openstack_networking_network_v2.network_1.id
  name       = var.test_subnet
  cidr       = var.test_subnet_cidr
}
 
resource "openstack_networking_router_v2" "router_1" {
  name                = var.test_router
  external_network_id = data.openstack_networking_network_v2.public_network.id
}
 
resource "openstack_networking_router_interface_v2" "router_interface_1" {
  router_id = openstack_networking_router_v2.router_1.id
  subnet_id = openstack_networking_subnet_v2.subnet_1.id
}

openstack-check-instance.tf

data "template_file" "user_data" {
  template = <<EOF
#cloud-config
package_update: true
packages:
 - nginx
runcmd:
 - hostname -f | sudo tee /var/www/html/index.nginx-debian.html
EOF
}
 
 
# Create instances in each AZ
resource "openstack_compute_instance_v2" "instance" {
  for_each          = var.az
  name              = "test-u2204-${each.key}"
  image_name        = "Ubuntu 22.04"
  flavor_name       = "m1.micro"
  key_pair          = openstack_compute_keypair_v2.keypair_1.name
  security_groups   = [openstack_networking_secgroup_v2.secgroup_1.id]
  availability_zone = "ch-zh1-${each.key}"
  user_data         = data.template_file.user_data.rendered
  force_delete      = true
 
  network {
    uuid = openstack_networking_network_v2.network_1.id
  }
 
  depends_on = [openstack_networking_subnet_v2.subnet_1]
}
 
 
# Create instance with additional volume and floating IP in each AZ
resource "openstack_compute_instance_v2" "instance_from_volume" {
  for_each          = var.az
  name              = "test-u2404-vol-${each.key}"
  flavor_name       = "m1.micro"
  key_pair          = openstack_compute_keypair_v2.keypair_1.name
  security_groups   = [openstack_networking_secgroup_v2.secgroup_1.id]
  availability_zone = "ch-zh1-${each.key}"
  user_data         = data.template_file.user_data.rendered
  force_delete      = true
 
  block_device {
    uuid                  = openstack_blockstorage_volume_v3.volume_boot[each.key].id
    source_type           = "volume"
    boot_index            = 0
    destination_type      = "volume"
    delete_on_termination = false
  }
 
  network {
    uuid = openstack_networking_network_v2.network_1.id
  }
 
  depends_on = [openstack_networking_subnet_v2.subnet_1, openstack_blockstorage_volume_v3.volume_boot]
}
 
resource "openstack_compute_volume_attach_v2" "volume_attach" {
  for_each    = var.az
  instance_id = openstack_compute_instance_v2.instance_from_volume[each.key].id
  volume_id   = openstack_blockstorage_volume_v3.volume[each.key].id
}
 
data "openstack_networking_network_v2" "public_network" {
  name = "public"
}
 
resource "openstack_networking_floatingip_v2" "floatingip" {
  pool = data.openstack_networking_network_v2.public_network.name
}
 
data "openstack_networking_port_v2" "port1" {
  device_id  = openstack_compute_instance_v2.instance_from_volume[tolist(var.az)[0]].id
  network_id = openstack_compute_instance_v2.instance_from_volume[tolist(var.az)[0]].network.0.uuid
}
 
resource "openstack_networking_floatingip_associate_v2" "instance_fip_association" {
  floating_ip = openstack_networking_floatingip_v2.floatingip.address
  port_id     = data.openstack_networking_port_v2.port1.id
}

openstack-check-volume.tf

# Create boot volume
resource "openstack_blockstorage_volume_v3" "volume_boot" {
  for_each          = var.az
  name              = "test-vol1-boot-${each.key}"
  size              = 10
  availability_zone = "ch-zh1-${each.key}"
  image_id          = openstack_images_image_v2.image_ubuntu_2404_minimal.id
}
 
# Create additional volume
resource "openstack_blockstorage_volume_v3" "volume" {
  for_each          = var.az
  name              = "test-vol1-${each.key}"
  size              = 10
  availability_zone = "ch-zh1-${each.key}"
}

openstack-loadbalancer.tf

# Certificate
resource "openstack_keymanager_secret_v1" "server_cert1" {
  name                     = "cert1"
  algorithm                = "aes"
  bit_length               = 256
  mode                     = "cbc"
  payload                  = filebase64("cert1.p12")
  secret_type              = "opaque"
  payload_content_type     = "application/octet-stream"
  payload_content_encoding = "base64"
}
 
resource "openstack_keymanager_secret_v1" "server_cert2" {
  name                     = "cert2"
  algorithm                = "aes"
  bit_length               = 256
  mode                     = "cbc"
  payload                  = filebase64("cert2.p12")
  secret_type              = "opaque"
  payload_content_type     = "application/octet-stream"
  payload_content_encoding = "base64"
}
 
# Create loadbalancer
resource "openstack_lb_loadbalancer_v2" "lb1" {
  name          = "test-lb1"
  vip_subnet_id = openstack_networking_subnet_v2.subnet_1.id
}
 
# Create HTTP listener
resource "openstack_lb_listener_v2" "lb_listener_http" {
  name            = "test-lb1-http-listener"
  protocol        = "HTTP"
  protocol_port   = 80
  loadbalancer_id = openstack_lb_loadbalancer_v2.lb1.id
  default_pool_id = openstack_lb_pool_v2.lb_pool_http.id
}
 
# Create HTTPS listener
resource "openstack_lb_listener_v2" "lb_listener_https" {
  name                      = "test-lb1-https-listener"
  protocol                  = "TERMINATED_HTTPS"
  protocol_port             = 443
  loadbalancer_id           = openstack_lb_loadbalancer_v2.lb1.id
  default_pool_id           = openstack_lb_pool_v2.lb_pool_http.id
  default_tls_container_ref = openstack_keymanager_secret_v1.server_cert1.secret_ref
  sni_container_refs        = ["${openstack_keymanager_secret_v1.server_cert1.secret_ref}", "${openstack_keymanager_secret_v1.server_cert2.secret_ref}"]
}
 
# Create pool
resource "openstack_lb_pool_v2" "lb_pool_http" {
  name            = "test-lb1-http-pool1"
  protocol        = "HTTP"
  lb_method       = "ROUND_ROBIN"
  loadbalancer_id = openstack_lb_loadbalancer_v2.lb1.id
}
 
# Add member to pool
resource "openstack_lb_member_v2" "lb_member" {
  for_each      = var.az
  address       = openstack_compute_instance_v2.instance[each.key].access_ip_v4
  protocol_port = 80
  pool_id       = openstack_lb_pool_v2.lb_pool_http.id
  subnet_id     = openstack_networking_subnet_v2.subnet_1.id
}
 
# Add monitor for pool
resource "openstack_lb_monitor_v2" "monitor_1" {
  pool_id     = openstack_lb_pool_v2.lb_pool_http.id
  type        = "HTTP"
  delay       = 5
  timeout     = 10
  max_retries = 5
}
 
# Get floating IP
resource "openstack_networking_floatingip_v2" "floatingip_1" {
  pool = "public"
}
 
# Associate floating IP to LoadBalancer
resource "openstack_networking_floatingip_associate_v2" "floatip_1" {
  floating_ip = try(length(var.lb_floating_ip), 0) > 0 ? var.lb_floating_ip : openstack_networking_floatingip_v2.floatingip_1.address
  port_id     = openstack_lb_loadbalancer_v2.lb1.vip_port_id
}

Cloud-init
https://learn.hashicorp.com/tutorials/terraform/cloud-init