494 lines
14 KiB
Nix
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;
|
|
};
|
|
}
|