diff --git a/environments/skeleton/{{cookiecutter.environment}}/tofu/baremetal-node-list.py b/environments/skeleton/{{cookiecutter.environment}}/tofu/baremetal-node-list.py new file mode 100755 index 000000000..14bc3ce4c --- /dev/null +++ b/environments/skeleton/{{cookiecutter.environment}}/tofu/baremetal-node-list.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +""" opentofu external data program to list baremetal nodes + + Example usage: + + data "external" "example" { + program = [this_file] + } + + The external data resource's result attribute then contains a mapping of + Ironic node names to their UUIDs. + + An empty list is returned if: + - There are no baremetal nodes + - The listing fails for any reason, e.g. + - there is no baremetal service + - admin credentials are required and are not provided +""" + +import openstack +import json + +nodes = [] +proxy = None +output = {} +conn = openstack.connection.from_config() +try: + proxy = getattr(conn, 'baremetal', None) +except Exception: + pass +if proxy is not None: + nodes = proxy.nodes() +for node in nodes: + output[node.name] = node.id +print(json.dumps(output)) diff --git a/environments/skeleton/{{cookiecutter.environment}}/tofu/compute.tf b/environments/skeleton/{{cookiecutter.environment}}/tofu/compute.tf index 9adc8ff18..fe614a101 100644 --- a/environments/skeleton/{{cookiecutter.environment}}/tofu/compute.tf +++ b/environments/skeleton/{{cookiecutter.environment}}/tofu/compute.tf @@ -20,11 +20,13 @@ module "compute" { volume_backed_instances = lookup(each.value, "volume_backed_instances", var.volume_backed_instances) root_volume_size = lookup(each.value, "root_volume_size", var.root_volume_size) - # optionally set for group + # optionally set for group: networks = concat(var.cluster_networks, lookup(each.value, "extra_networks", [])) extra_volumes = lookup(each.value, "extra_volumes", {}) compute_init_enable = lookup(each.value, "compute_init_enable", []) ignore_image_changes = lookup(each.value, "ignore_image_changes", false) + match_ironic_node = lookup(each.value, "match_ironic_node", false) + availability_zone = lookup(each.value, "availability_zone", "nova") # computed k3s_token = local.k3s_token @@ -32,4 +34,5 @@ module "compute" { # updates to node metadata on deletion/recreation of the control node: control_address = openstack_networking_port_v2.control[var.cluster_networks[0].network].all_fixed_ips[0] security_group_ids = [for o in data.openstack_networking_secgroup_v2.nonlogin: o.id] + baremetal_nodes = data.external.baremetal_nodes.result } diff --git a/environments/skeleton/{{cookiecutter.environment}}/tofu/data.tf b/environments/skeleton/{{cookiecutter.environment}}/tofu/data.tf new file mode 100644 index 000000000..9c6ba76c3 --- /dev/null +++ b/environments/skeleton/{{cookiecutter.environment}}/tofu/data.tf @@ -0,0 +1,13 @@ +data "external" "inventory_secrets" { + program = ["${path.module}/read-inventory-secrets.py"] + + query = { + path = var.inventory_secrets_path == "" ? "${path.module}/../inventory/group_vars/all/secrets.yml" : var.inventory_secrets_path + } +} + +data "external" "baremetal_nodes" { + # returns an empty map if cannot list baremetal nodes + program = ["${path.module}/baremetal-node-list.py"] + query = {} +} diff --git a/environments/skeleton/{{cookiecutter.environment}}/tofu/login.tf b/environments/skeleton/{{cookiecutter.environment}}/tofu/login.tf index f5115f394..d36c099b2 100644 --- a/environments/skeleton/{{cookiecutter.environment}}/tofu/login.tf +++ b/environments/skeleton/{{cookiecutter.environment}}/tofu/login.tf @@ -25,6 +25,8 @@ module "login" { extra_volumes = lookup(each.value, "extra_volumes", {}) fip_addresses = lookup(each.value, "fip_addresses", []) fip_network = lookup(each.value, "fip_network", "") + match_ironic_node = lookup(each.value, "match_ironic_node", false) + availability_zone = lookup(each.value, "availability_zone", "nova") # can't be set for login compute_init_enable = [] @@ -36,4 +38,5 @@ module "login" { # updates to node metadata on deletion/recreation of the control node: control_address = openstack_networking_port_v2.control[var.cluster_networks[0].network].all_fixed_ips[0] security_group_ids = [for o in data.openstack_networking_secgroup_v2.login: o.id] + baremetal_nodes = data.external.baremetal_nodes.result } diff --git a/environments/skeleton/{{cookiecutter.environment}}/tofu/node_group/nodes.tf b/environments/skeleton/{{cookiecutter.environment}}/tofu/node_group/nodes.tf index cf7e2f9b9..305b89e62 100644 --- a/environments/skeleton/{{cookiecutter.environment}}/tofu/node_group/nodes.tf +++ b/environments/skeleton/{{cookiecutter.environment}}/tofu/node_group/nodes.tf @@ -97,6 +97,8 @@ resource "openstack_compute_instance_v2" "compute_fixed_image" { fqdn: ${var.cluster_name}-${each.key}.${var.cluster_name}.${var.cluster_domain_suffix} EOF + availability_zone = var.match_ironic_node ? "${var.availability_zone}::${var.baremetal_nodes[each.key]}" : null + lifecycle { ignore_changes = [ image_id, @@ -140,7 +142,7 @@ resource "openstack_compute_instance_v2" "compute" { k3s_token = var.k3s_token control_address = var.control_address access_ip = openstack_networking_port_v2.compute["${each.key}-${var.networks[0].network}"].all_fixed_ips[0] - }, + }, {for e in var.compute_init_enable: e => true} ) @@ -149,6 +151,8 @@ resource "openstack_compute_instance_v2" "compute" { fqdn: ${var.cluster_name}-${each.key}.${var.cluster_name}.${var.cluster_domain_suffix} EOF + availability_zone = var.match_ironic_node ? "${var.availability_zone}::${var.baremetal_nodes[each.key]}" : null + } resource "openstack_networking_floatingip_associate_v2" "fip" { diff --git a/environments/skeleton/{{cookiecutter.environment}}/tofu/node_group/variables.tf b/environments/skeleton/{{cookiecutter.environment}}/tofu/node_group/variables.tf index 0e6360294..8a3f03876 100644 --- a/environments/skeleton/{{cookiecutter.environment}}/tofu/node_group/variables.tf +++ b/environments/skeleton/{{cookiecutter.environment}}/tofu/node_group/variables.tf @@ -114,3 +114,20 @@ variable "fip_network" { EOT default = "" } + +variable "match_ironic_node" { + type = bool + description = "Whether to launch instances on the Ironic node of the same name as each cluster node" + default = false +} + +variable "availability_zone" { + type = string + description = "Name of availability zone - ignored unless match_ironic_node is true" + default = "nova" +} + +variable "baremetal_nodes" { + type = map(string) + default = {} +} diff --git a/environments/skeleton/{{cookiecutter.environment}}/tofu/variables.tf b/environments/skeleton/{{cookiecutter.environment}}/tofu/variables.tf index 551da948f..a6f398d09 100644 --- a/environments/skeleton/{{cookiecutter.environment}}/tofu/variables.tf +++ b/environments/skeleton/{{cookiecutter.environment}}/tofu/variables.tf @@ -59,6 +59,8 @@ variable "login" { fip_network: Name of network containing ports to attach FIPs to. Only required if multiple networks are defined. + match_ironic_node: Set true to launch instances on the Ironic node of the same name as each cluster node + availability_zone: Name of availability zone - ignored unless match_ironic_node is true (default: "nova") EOF } @@ -93,6 +95,8 @@ variable "compute" { Values are a mapping with: size: Size of volume in GB **NB**: The order in /dev is not guaranteed to match the mapping + match_ironic_node: Set true to launch instances on the Ironic node of the same name as each cluster node + availability_zone: Name of availability zone - ignored unless match_ironic_node is true (default: "nova") EOF } @@ -186,14 +190,6 @@ variable "inventory_secrets_path" { default = "" } -data "external" "inventory_secrets" { - program = ["${path.module}/read-inventory-secrets.py"] - - query = { - path = var.inventory_secrets_path == "" ? "${path.module}/../inventory/group_vars/all/secrets.yml" : var.inventory_secrets_path - } -} - locals { k3s_token = data.external.inventory_secrets.result["vault_k3s_token"] }