Refactor terranix code to be more reusable and maintainable

Hopefully
This commit is contained in:
Kaare Hoff Skovgaard 2025-07-06 10:33:40 +02:00
parent 2f725ca3ea
commit 624508dd14
Signed by: khs
GPG key ID: C7D890804F01E9F0
6 changed files with 337 additions and 115 deletions

View file

@ -66,5 +66,9 @@
inherit inputs; inherit inputs;
khscodesLib = inputs.self.lib; khscodesLib = inputs.self.lib;
}; };
terranixModules.hcloud = import ./nix/modules/terranix/hcloud {
inherit inputs;
khscodesLib = inputs.self.lib;
};
}; };
} }

View file

@ -0,0 +1,4 @@
{ ... }:
{
sanitize-terraform-name = name: builtins.replaceStrings [ "." ] [ "_" ] name;
}

View file

@ -149,14 +149,14 @@ in
config = inputs.terranix.lib.terranixConfiguration { config = inputs.terranix.lib.terranixConfiguration {
system = pkgs.hostPlatform.system; system = pkgs.hostPlatform.system;
modules = [ modules = [
(
{ config, ... }:
{ {
imports = [ imports = [
inputs.self.terranixModules.cloudflare inputs.self.terranixModules.cloudflare
inputs.terranix-hcloud.terranixModules.hcloud inputs.self.terranixModules.hcloud
]; ];
config = {
hcloud.enable = true;
terraform.required_providers.hcloud.version = "~> 1.45.0";
terraform.backend.s3 = { terraform.backend.s3 = {
bucket = "bw-terraform"; bucket = "bw-terraform";
key = cfg.bucket.key; key = cfg.bucket.key;
@ -172,25 +172,17 @@ in
skip_s3_checksum = true; skip_s3_checksum = true;
}; };
data.hcloud_ssh_key.khs = { khscodes.hcloud.data.ssh_key.khs = {
name = "ca.kaareskovgaard.net"; name = "ca.kaareskovgaard.net";
}; };
khscodes.hcloud.enable = true;
resource.hcloud_primary_ip.ipv4 = { khscodes.hcloud.server.compute = {
inherit (cfg) server_type datacenter;
inherit labels; inherit labels;
name = "${fqdn} ipv4"; name = fqdn;
datacenter = cfg.datacenter; initial_image = "debian-12";
type = "ipv4"; rdns = fqdn;
assignee_type = "server"; ssh_keys = [ config.khscodes.hcloud.output.data.ssh_key.khs.id ];
auto_delete = false;
};
resource.hcloud_primary_ip.ipv6 = {
inherit labels;
name = "${fqdn} ipv6";
datacenter = cfg.datacenter;
type = "ipv6";
assignee_type = "server";
auto_delete = false;
}; };
khscodes.cloudflare = { khscodes.cloudflare = {
enable = true; enable = true;
@ -200,13 +192,13 @@ in
aRecords = [ aRecords = [
{ {
inherit fqdn; inherit fqdn;
content = "\${ hcloud_server.compute.ipv4_address }"; content = config.khscodes.hcloud.output.server.compute.ipv4_address;
} }
]; ];
aaaaRecords = [ aaaaRecords = [
{ {
inherit fqdn; inherit fqdn;
content = "\${ hcloud_server.compute.ipv6_address }"; content = config.khscodes.hcloud.output.server.compute.ipv6_address;
} }
]; ];
}; };
@ -215,54 +207,19 @@ in
inherit labels; inherit labels;
name = fqdn; name = fqdn;
apply_to = { apply_to = {
server = "\${ hcloud_server.compute.id }"; server = config.khscodes.hcloud.output.server.compute.id;
}; };
rule = firewallRules; rule = firewallRules;
}; };
resource.hcloud_server.compute = {
inherit (cfg) server_type datacenter;
inherit labels;
name = fqdn;
image = "debian-12";
public_net = {
ipv4_enabled = true;
ipv4 = "\${ hcloud_primary_ip.ipv4.id }";
ipv6_enabled = true;
ipv6 = "\${ hcloud_primary_ip.ipv6.id }";
};
ssh_keys = [ "\${ data.hcloud_ssh_key.khs.id }" ];
lifecycle = {
ignore_changes = [
"ssh_keys"
"public_net"
"image"
];
};
};
output.ipv4_address = { output.ipv4_address = {
value = "\${ hcloud_server.compute.ipv4_address }"; value = config.khscodes.hcloud.output.server.compute.ipv4_address;
sensitive = false; sensitive = false;
}; };
output.ipv6_address = { output.ipv6_address = {
value = "\${ hcloud_server.compute.ipv6_address }"; value = config.khscodes.hcloud.output.server.compute.ipv6_address;
sensitive = false; sensitive = false;
}; };
}
(
{ lib, ... }:
{
config = lib.mkIf mapRdns {
resource.hcloud_rdns.ipv4 = {
primary_ip_id = "\${ hcloud_primary_ip.ipv4.id }";
ip_address = "\${ hcloud_server.compute.ipv4_address }";
dns_ptr = fqdn;
};
resource.hcloud_rdns.ipv6 = {
primary_ip_id = "\${ hcloud_primary_ip.ipv6.id }";
ip_address = "\${ hcloud_server.compute.ipv6_address }";
dns_ptr = fqdn;
};
}; };
} }
) )

View file

@ -13,7 +13,6 @@ let
"@" "@"
else else
fqdn; fqdn;
fqdnToTFname = fqdn: builtins.replaceStrings [ "." ] [ "_" ] fqdn;
dnsARecordModule = khscodesLib.mkSubmodule { dnsARecordModule = khscodesLib.mkSubmodule {
description = "Module for defining dns A/AAAA record"; description = "Module for defining dns A/AAAA record";
options = { options = {
@ -127,7 +126,7 @@ in
resource.cloudflare_record = lib.attrsets.optionalAttrs cfg.dns.enable ( resource.cloudflare_record = lib.attrsets.optionalAttrs cfg.dns.enable (
lib.listToAttrs ( lib.listToAttrs (
(lib.lists.map (record: { (lib.lists.map (record: {
name = "${fqdnToTFname record.fqdn}_a"; name = "${khscodesLib.sanitize-terraform-name record.fqdn}_a";
value = { value = {
inherit (record) content ttl proxied; inherit (record) content ttl proxied;
name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name; name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name;
@ -137,7 +136,7 @@ in
}; };
}) cfg.dns.aRecords) }) cfg.dns.aRecords)
++ (lib.lists.map (record: { ++ (lib.lists.map (record: {
name = "${fqdnToTFname record.fqdn}_aaaa"; name = "${khscodesLib.sanitize-terraform-name record.fqdn}_aaaa";
value = { value = {
inherit (record) content ttl proxied; inherit (record) content ttl proxied;
name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name; name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name;
@ -147,7 +146,7 @@ in
}; };
}) cfg.dns.aaaaRecords) }) cfg.dns.aaaaRecords)
++ (lib.lists.map (record: { ++ (lib.lists.map (record: {
name = "${fqdnToTFname record.fqdn}_txt"; name = "${khscodesLib.sanitize-terraform-name record.fqdn}_txt";
value = { value = {
inherit (record) content ttl; inherit (record) content ttl;
name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name; name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name;
@ -157,7 +156,7 @@ in
}; };
}) cfg.dns.txtRecords) }) cfg.dns.txtRecords)
++ (lib.lists.map (record: { ++ (lib.lists.map (record: {
name = "${fqdnToTFname record.fqdn}_mx"; name = "${khscodesLib.sanitize-terraform-name record.fqdn}_mx";
value = { value = {
inherit (record) content priority; inherit (record) content priority;
name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name; name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name;

View file

@ -0,0 +1,189 @@
{ inputs, khscodesLib }:
{ config, lib, ... }:
let
cfg = config.khscodes.hcloud;
serversWithRdns = lib.filterAttrs (_: value: value.rdns != null) cfg.server;
mapSanitizedAttrs =
f: list:
lib.listToAttrs (
lib.map (
{ name, value }:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = f {
inherit value;
name = sanitizedName;
};
}
) (lib.attrsToList list)
);
hcloudServerModule = khscodesLib.mkSubmodule {
description = "Module for defining hcloud server";
options = {
name = lib.mkOption {
type = lib.types.str;
description = "Name of the server";
};
server_type = lib.mkOption {
type = lib.types.str;
description = "Server type for the instance";
};
datacenter = lib.mkOption {
type = lib.types.str;
description = "Data center for the instance";
};
initial_image = lib.mkOption {
type = lib.types.str;
description = "Initial image of the server";
};
ssh_keys = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "The ssh keys added to the server";
};
labels = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
};
rdns = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "FQDN to map rDNS to";
};
};
};
hcloudDataSshKeys = khscodesLib.mkSubmodule {
description = "SSH Keys";
options = {
name = lib.mkOption {
type = lib.types.str;
description = "The name of the ssh keys";
};
id = lib.mkOption {
type = lib.types.str;
description = "A terraform expression for the ssh keys";
};
};
};
in
{
options.khscodes.hcloud = {
enable = lib.mkEnableOption "Enables khscodes hcloud terranix integration";
server = lib.mkOption {
type = lib.types.attrsOf hcloudServerModule;
description = "Defines an hcloud server";
default = { };
};
data.ssh_key = lib.mkOption {
type = lib.types.attrsOf hcloudDataSshKeys;
description = "Reads ssh keys as data source from hcloud";
default = { };
};
};
imports = [
inputs.terranix-hcloud.terranixModules.hcloud
(import ./output.nix { inherit inputs khscodesLib; })
];
config = lib.mkIf cfg.enable {
hcloud.enable = true;
terraform.required_providers.hcloud.version = "~> 1.45.0";
resource.hcloud_server = mapSanitizedAttrs (
{ name, value }:
{
inherit (value)
server_type
datacenter
labels
ssh_keys
name
;
image = value.initial_image;
public_net = {
ipv4_enabled = true;
ipv4 = "\${ hcloud_primary_ip.${name}_ipv4.id }";
ipv6_enabled = true;
ipv6 = "\${ hcloud_primary_ip.${name}_ipv6.id }";
};
lifecycle = {
ignore_changes = [
"ssh_keys"
"public_net"
"image"
];
};
}
) cfg.server;
resource.hcloud_primary_ip =
(lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = "${sanitizedName}_ipv4";
value = {
inherit (value) labels;
name = "${value.name} ipv4";
datacenter = value.datacenter;
type = "ipv4";
assignee_type = "server";
auto_delete = false;
};
}
) cfg.server)
// (lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = "${sanitizedName}_ipv6";
value = {
inherit (value) labels;
name = "${value.name} ipv6";
datacenter = value.datacenter;
type = "ipv6";
assignee_type = "server";
auto_delete = false;
};
}
) cfg.server);
resource.hcloud_rdns =
(lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = "${sanitizedName}_ipv4";
value = {
primary_ip_id = "\${ hcloud_primary_ip.${sanitizedName}_ipv4.id }";
ip_address = "\${ hcloud_server.${sanitizedName}.ipv4_address }";
dns_ptr = value.rdns;
};
}
) serversWithRdns)
// (lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = "${sanitizedName}_ipv6";
value = {
primary_ip_id = "\${ hcloud_primary_ip.${sanitizedName}_ipv6.id }";
ip_address = "\${ hcloud_server.${sanitizedName}.ipv6_address }";
dns_ptr = value.rdns;
};
}
) serversWithRdns);
data.hcloud_ssh_key = mapSanitizedAttrs (
{ value, ... }:
{
name = value.name;
}
) cfg.data.ssh_key;
};
}

View file

@ -0,0 +1,69 @@
{ inputs, khscodesLib }:
{ config, lib, ... }:
let
cfg = config.khscodes.hcloud;
hcloudOutputServerModule = khscodesLib.mkSubmodule {
description = "Module defined when a corresponding server has been defined";
options = {
id = lib.mkOption {
type = lib.types.str;
description = "ID of the instance, as a terraform string expression";
};
ipv4_address = lib.mkOption {
type = lib.types.str;
description = "IPv4 address of the instance, as a terraform string expression";
};
ipv6_address = lib.mkOption {
type = lib.types.str;
description = "IPv6 address of the instance, as a terraform string expression";
};
};
};
hcloudDataOutputSshKeyModule = khscodesLib.mkSubmodule {
description = "Module defined when a corresponding ssh key has ben retrieved";
options = {
id = lib.mkOption {
type = lib.types.str;
description = "ID of the ssh key, as a terraform string expression";
};
};
};
in
{
options.khscodes.hcloud = {
output.server = lib.mkOption {
type = lib.types.attrsOf hcloudOutputServerModule;
description = "Set by this module to be read by other modules when needing results of defining a server";
default = { };
};
output.data.ssh_key = lib.mkOption {
type = lib.types.attrsOf hcloudDataOutputSshKeyModule;
description = "Set by this module to be read by other modules when needing the result of the ssh key";
default = { };
};
};
config = {
khscodes.hcloud.output.server = lib.attrsets.mapAttrs (
name: value:
(
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
id = "\${ hcloud_server.${sanitizedName}.id }";
ipv4_address = "\${ hcloud_server.${sanitizedName}.ipv4_address }";
ipv6_address = "\${ hcloud_server.${sanitizedName}.ipv4_address }";
}
)
) cfg.server;
khscodes.hcloud.output.data.ssh_key = lib.attrsets.mapAttrs (
name: _:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
id = "\${ data.hcloud_ssh_key.${sanitizedName}.id }";
}
) cfg.data.ssh_key;
};
}