machines/nix/modules/terranix/openstack/default.nix
Kaare Hoff Skovgaard d842025c81
Some checks failed
/ rust-packages (push) Successful in 4m2s
/ dev-shell (push) Successful in 1m3s
/ terraform-providers (push) Successful in 8m7s
/ check (push) Failing after 7m43s
/ systems (push) Successful in 30m24s
Support multiple dns zones per host
2025-07-23 23:28:15 +02:00

494 lines
14 KiB
Nix

{
lib,
config,
...
}:
let
cfg = config.khscodes.openstack;
firewallRuleModule = lib.khscodes.mkSubmodule {
description = "Firewall rule";
options = {
direction = lib.mkOption {
type = lib.types.enum [
"ingress"
"egress"
];
description = "The direction of the firewall rule";
};
ethertype = lib.mkOption {
type = lib.types.enum [
"IPv4"
"IPv6"
];
description = "The IP version";
};
protocol = lib.mkOption {
type = lib.types.enum [
"tcp"
"udp"
"icmp"
];
description = "The protocol";
};
port = lib.mkOption {
type = lib.types.int;
description = "The port (for udp and tcp rules) to apply the rule to";
};
remote_subnet = lib.mkOption {
type = lib.types.str;
description = "The remote subnet to apply the rule to";
};
};
};
mapFirewallRule =
security_group_id: rule:
{
inherit (rule) direction ethertype;
inherit security_group_id;
protocol =
if rule.ethertype == "IPv6" && rule.protocol == "icmp" then "ipv6-icmp" else rule.protocol;
remote_ip_prefix = rule.remote_subnet;
}
// (lib.attrsets.optionalAttrs (rule.protocol != "icmp") {
port_range_min = rule.port;
port_range_max = rule.port;
});
openstackComputeInstance = lib.khscodes.mkSubmodule {
description = "Openstack compute instance";
options = {
name = lib.mkOption {
type = lib.types.str;
description = "Name of the instance";
};
flavor = lib.mkOption {
type = lib.types.str;
description = "Flavor of the instance";
};
initial_image = lib.mkOption {
type = lib.types.str;
description = "Initial image of the server";
};
ssh_public_key = lib.mkOption {
type = lib.types.str;
description = "The ssh key added to the server";
};
ip4_cidr = lib.mkOption {
type = lib.types.str;
description = "IPv4 cidr of the private virtual network. Must override when using a named router";
default = "172.24.0.0/24";
};
ip4_dns_nameservers = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "DNS (IPv4) nameservers to set (in DHCP) for the ipv4 subnet";
default = [
"1.1.1.1"
"1.0.0.1"
];
};
user_data = lib.mkOption {
type = lib.types.str;
default = "";
description = "User data for the instance";
};
volume_size = lib.mkOption {
type = lib.types.int;
description = "Size of the root volume, in gigabytes";
default = 30;
};
volume_type = lib.mkOption {
type = lib.types.enum [
"Encrypted"
"__DEFAULT__"
];
description = "The type of volume to create";
default = "Encrypted";
};
ip6_dns_nameservers = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "DNS (IPv6) nameservers to set (in DHCP) for the ipv4 subnet";
default = [
"2606:4700:4700::1111"
"2606:4700:4700::1001"
];
};
router = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "monitoring.kaareskovgaard.net";
description = "Name of the router to attach to. If null will create a new router";
};
tags = lib.mkOption {
type = lib.types.listOf lib.types.str;
};
firewall_rules = lib.mkOption {
type = lib.types.listOf firewallRuleModule;
description = "List of firewall rules to apply to the server";
default = [ ];
};
};
};
in
{
options.khscodes.openstack = {
enable = lib.mkEnableOption "Enables the openstack provider";
compute_instance = lib.mkOption {
type = lib.types.attrsOf openstackComputeInstance;
description = "Defines an openstack compute instance";
default = { };
};
};
imports = [ ./output.nix ];
config = lib.mkIf cfg.enable {
terraform.required_providers.openstack = {
source = "terraform-provider-openstack/openstack";
version = "~> 3.2.0";
};
provider.openstack = {
user_name = "\${ var.openstack_username }";
tenant_name = "\${ var.openstack_tenant_name }";
password = "\${ var.openstack_password }";
auth_url = "\${ var.openstack_auth_url }";
region = "\${ var.openstack_region }";
endpoint_type = "\${ var.openstack_endpoint_type }";
};
variable = {
openstack_username = {
type = "string";
};
openstack_password = {
type = "string";
sensitive = true;
};
openstack_tenant_name = {
type = "string";
};
openstack_auth_url = {
type = "string";
};
openstack_endpoint_type = {
type = "string";
};
openstack_region = {
type = "string";
};
};
# DATA
# flavor
data.openstack_compute_flavor_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = value.flavor;
};
}
) cfg.compute_instance;
# image
data.openstack_images_image_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = value.initial_image;
};
}
) cfg.compute_instance;
# provider network
data.openstack_networking_network_v2.provider = {
name = "public";
};
# provider subnet pool (for ipv6)
data.openstack_networking_subnetpool_v2.provider = {
name = "provider-subnet-pool";
};
# RESOURCE
# keypair
resource.openstack_compute_keypair_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = lib.khscodes.sanitize-terraform-name value.name;
public_key = value.ssh_public_key;
};
}
) cfg.compute_instance;
# router
resource.openstack_networking_router_v2 = lib.filterAttrs (name: value: value != null) (
lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = sanitizedName;
value =
if value.router == null then
{
name = value.name;
external_network_id = "\${ data.openstack_networking_network_v2.provider.id }";
tags = value.tags;
}
else
null;
}
) cfg.compute_instance
);
data.openstack_networking_router_v2 = lib.filterAttrs (name: value: value != null) (
lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = sanitizedName;
value =
if value.router != null then
{
name = value.router;
}
else
null;
}
) cfg.compute_instance
);
# network
resource.openstack_networking_network_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = value.name;
tags = value.tags;
};
}
) cfg.compute_instance;
# subnet
resource.openstack_networking_subnet_v2 =
(lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = "${sanitizedName}_ip4";
value = {
name = "ip4: ${value.name}";
cidr = value.ip4_cidr;
ip_version = 4;
network_id = "\${ openstack_networking_network_v2.${sanitizedName}.id }";
dns_nameservers = value.ip4_dns_nameservers;
tags = value.tags;
};
}
) cfg.compute_instance)
// (lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = "${sanitizedName}_ip6";
value = {
name = "ip6: ${value.name}";
ip_version = 6;
ipv6_address_mode = "dhcpv6-stateless";
ipv6_ra_mode = "dhcpv6-stateless";
subnetpool_id = "\${ data.openstack_networking_subnetpool_v2.provider.id }";
dns_nameservers = value.ip6_dns_nameservers;
network_id = "\${ openstack_networking_network_v2.${sanitizedName}.id }";
tags = value.tags;
};
}
) cfg.compute_instance);
# router interface
resource.openstack_networking_router_interface_v2 =
(lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = "${sanitizedName}_ip4";
value = {
router_id = "\${ ${
lib.strings.optionalString (value.router != null) "data."
} openstack_networking_router_v2.${sanitizedName}.id }";
subnet_id = "\${ openstack_networking_subnet_v2.${sanitizedName}_ip4.id }";
};
}
) cfg.compute_instance)
// (lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = "${sanitizedName}_ip6";
value = {
router_id = "\${ ${
lib.strings.optionalString (value.router != null) "data."
}openstack_networking_router_v2.${sanitizedName}.id }";
subnet_id = "\${ openstack_networking_subnet_v2.${sanitizedName}_ip6.id }";
};
}
) cfg.compute_instance);
# floating ip
resource.openstack_networking_floatingip_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
pool = "public";
tags = value.tags;
};
}
) cfg.compute_instance;
# volume
resource.openstack_blockstorage_volume_v3 = lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = value.name;
size = value.volume_size;
image_id = "\${ data.openstack_images_image_v2.${sanitizedName}.id }";
volume_type = value.volume_type;
};
}
) cfg.compute_instance;
# security group
resource.openstack_networking_secgroup_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = value.name;
tags = value.tags;
};
}
) cfg.compute_instance;
# security group rules (firewall rules)
resource.openstack_networking_secgroup_rule_v2 = lib.attrsets.mergeAttrsList (
lib.lists.flatten (
lib.lists.map (
{ name, value }:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
lib.listToAttrs (
lib.lists.map (
rule:
let
protocol =
if rule.protocol == "icmp" then "icmp" else "${rule.protocol}_${builtins.toString rule.port}";
in
{
name = "${sanitizedName}_${rule.direction}_${rule.ethertype}_${protocol}_${lib.khscodes.sanitize-terraform-name rule.remote_subnet}";
value = mapFirewallRule "\${ resource.openstack_networking_secgroup_v2.${sanitizedName}.id }" rule;
}
) value.firewall_rules
)
) (lib.attrsToList cfg.compute_instance)
)
);
# instance
data.openstack_networking_port_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
device_id = "\${ openstack_compute_instance_v2.${sanitizedName}.id }";
network_id = "\${ openstack_networking_network_v2.${sanitizedName}.id }";
};
}
) cfg.compute_instance;
resource.openstack_compute_instance_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = value.name;
tags = value.tags;
flavor_id = "\${ data.openstack_compute_flavor_v2.${sanitizedName}.id }";
key_pair = "\${ openstack_compute_keypair_v2.${sanitizedName}.name }";
block_device = [
{
uuid = "\${ openstack_blockstorage_volume_v3.${sanitizedName}.id }";
source_type = "volume";
boot_index = 0;
destination_type = "volume";
delete_on_termination = false;
}
];
security_groups = [ "\${ openstack_networking_secgroup_v2.${sanitizedName}.name }" ];
network = [
{
uuid = "\${ openstack_networking_network_v2.${sanitizedName}.id }";
}
];
user_data = value.user_data;
};
}
) cfg.compute_instance;
# floating ip associate
resource.openstack_networking_floatingip_associate_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = lib.khscodes.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
floating_ip = "\${ openstack_networking_floatingip_v2.${sanitizedName}.address }";
port_id = "\${ data.openstack_networking_port_v2.${sanitizedName}.id }";
};
}
) cfg.compute_instance;
};
}