provisioning-code/templates/workspace/nickel/lib/helpers.ncl

119 lines
3.8 KiB
Text
Raw Normal View History

# Helper Functions for Workspace Configuration Composition
# Provides utilities for deep merging, validation, and composition
let std = import "std" in
{
# Deep merge records recursively
# Unlike & operator (shallow merge), this preserves nested structure
compose_config | doc "Recursively merge three records: base → mode_config → overrides"
= fun base => fun mode_config => fun user_overrides => (
let merge_records = fun left => fun right => (
# For each field in right, either:
# - If both are records: recursively merge
# - Otherwise: right overwrites left
std.record.to_array left
| std.array.fold_left
(fun acc => fun kv =>
let key = kv.key in
let left_val = kv.value in
let right_val = right | get key? in
if right_val != null && left_val != null then
if (std.typeof left_val) == "record" && (std.typeof right_val) == "record" then
acc & {[key] = merge_records left_val right_val}
else
acc & {[key] = right_val}
else if right_val != null then
acc & {[key] = right_val}
else
acc & {[key] = left_val}
)
right
) in
merge_records
(merge_records base mode_config)
user_overrides
),
# Validate required fields in config
validate_required | doc "Ensure all required fields exist in config"
= fun required_fields => fun config => (
let missing = (
required_fields
| std.array.filter (fun field =>
(config | get field? | std.value.is_null)
)
) in
if (missing | std.array.length) > 0 then
std.contract.blame $"Missing required fields: " ++ (std.array.join ", " missing)
else
config
),
# Validate no_use field and filter inactive servers
filter_active_servers | doc "Filter out servers with not_use = true"
= fun servers => (
servers
| std.array.filter (fun server =>
(server | get not_use? | default false) == false
)
),
# Extract provider type from config
get_provider | doc "Extract provider name from workspace config"
= fun config => (
config | get provider? | default "generic"
),
# Map server to NixOS flake path
to_flake_path | doc "Generate NixOS flake.nix directory path for server"
= fun server => fun workspace_name => (
let hostname = server.hostname in
let explicit_path = server | get flake_path? in
if explicit_path != null && explicit_path != "" then
explicit_path
else
$"workspaces/($workspace_name)/nixos/($hostname)"
),
# Collect all unique taskservs across servers
collect_taskservs | doc "Extract and deduplicate taskserv names from all servers"
= fun servers => (
servers
| std.array.fold_left
(fun acc => fun server =>
let ts = server | get taskservs? | default [] in
if (std.typeof ts) == "array" then
ts
| std.array.fold_left
(fun acc2 => fun taskserv =>
let name = taskserv | get name? | default "unknown" in
if (acc2 | std.array.contains name) then
acc2
else
acc2 | std.array.push name
)
acc
else
acc
)
[]
),
# Create minimal server stub for testing
minimal_server | doc "Generate minimal valid server record for testing"
= fun hostname => {
hostname = hostname,
title = $"Server ($hostname)",
plan = "1xCPU-1GB",
not_use = false,
extra_hostnames = [hostname],
labels = "",
taskservs = [],
},
}