From 62ec09cc02549b8282681c3ebc676d9baa0414f6 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Sun, 11 Jan 2026 09:55:57 -0300 Subject: [PATCH] feat: add config builder and CLI command --- cmd/metis/config_cmd.go | 28 +++++++++++++ cmd/metis/main.go | 2 + pkg/config/config.go | 89 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 cmd/metis/config_cmd.go create mode 100644 pkg/config/config.go diff --git a/cmd/metis/config_cmd.go b/cmd/metis/config_cmd.go new file mode 100644 index 0000000..f998bfc --- /dev/null +++ b/cmd/metis/config_cmd.go @@ -0,0 +1,28 @@ +package main + +import ( + "encoding/json" + "flag" + "log" + "os" + + "metis/pkg/config" +) + +func configCmd(args []string) { + fs := flag.NewFlagSet("config", flag.ExitOnError) + invPath := fs.String("inventory", "inventory.yaml", "inventory file") + node := fs.String("node", "", "target node") + fs.Parse(args) + if *node == "" { + log.Fatalf("--node is required") + } + inv := loadInventory(*invPath) + cfg, err := config.Build(inv, *node) + if err != nil { + log.Fatalf("config build: %v", err) + } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + _ = enc.Encode(cfg) +} diff --git a/cmd/metis/main.go b/cmd/metis/main.go index cd4b26b..a485faf 100644 --- a/cmd/metis/main.go +++ b/cmd/metis/main.go @@ -22,6 +22,8 @@ func main() { planCmd(os.Args[2:]) case "burn": burnCmd(os.Args[2:]) + case "config": + configCmd(os.Args[2:]) default: usage() os.Exit(1) diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..a8cbf99 --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,89 @@ +package config + +import ( + "fmt" + + "metis/pkg/inventory" +) + +// NodeConfig represents boot-time configuration to inject. +type NodeConfig struct { + Hostname string `json:"hostname"` + IP string `json:"ip"` + K3s K3sConfig `json:"k3s"` + SSHUser string `json:"ssh_user,omitempty"` + SSHKeys []string `json:"ssh_keys,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Taints []string `json:"taints,omitempty"` + Fstab []FstabEntry `json:"fstab,omitempty"` +} + +// K3sConfig includes role and token/url. +type K3sConfig struct { + Role string `json:"role"` + URL string `json:"url,omitempty"` + Token string `json:"token,omitempty"` + Args []string `json:"args,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Taints []string `json:"taints,omitempty"` +} + +// FstabEntry for Longhorn or other mounts. +type FstabEntry struct { + UUID string `json:"uuid"` + Mountpoint string `json:"mountpoint"` + FS string `json:"fs"` + Options string `json:"options"` +} + +// Build creates a NodeConfig from inventory. +func Build(inv *inventory.Inventory, nodeName string) (*NodeConfig, error) { + n, cls, err := inv.FindNode(nodeName) + if err != nil { + return nil, err + } + labels := map[string]string{} + for k, v := range cls.DefaultLabels { + labels[k] = v + } + for k, v := range n.Labels { + labels[k] = v + } + taints := append([]string{}, cls.DefaultTaints...) + taints = append(taints, n.Taints...) + + fstab := []FstabEntry{} + for _, d := range n.LonghornDisks { + fs := d.FS + if fs == "" { + fs = "ext4" + } + fstab = append(fstab, FstabEntry{ + UUID: d.UUID, + Mountpoint: d.Mountpoint, + FS: fs, + Options: "defaults,nofail", + }) + } + + cfg := &NodeConfig{ + Hostname: n.Hostname, + IP: n.IP, + SSHUser: n.SSHUser, + SSHKeys: n.SSHAuthorized, + Labels: labels, + Taints: taints, + Fstab: fstab, + K3s: K3sConfig{ + Role: n.K3sRole, + URL: n.K3sURL, + Token: n.K3sToken, + Labels: labels, + Taints: taints, + }, + } + if cfg.Hostname == "" || cfg.IP == "" { + return nil, fmt.Errorf("hostname/ip required for node %s", nodeName) + } + return cfg, nil +}