package plan import ( "os" "path/filepath" "strings" "testing" "metis/pkg/config" "metis/pkg/inventory" "metis/pkg/secrets" ) func TestBuildFilesProducesK3sConfig(t *testing.T) { cfg := &config.NodeConfig{ Hostname: "n1", IP: "10.0.0.10", SSHUser: "pi", SSHKeys: []string{"ssh-rsa AAA"}, K3s: config.K3sConfig{ Role: "agent", URL: "https://server:6443", Token: "secret", Version: "v1.31.5+k3s1", }, Fstab: []config.FstabEntry{ { UUID: "disk-uuid", Mountpoint: "/mnt/astreae", FS: "ext4", Options: "defaults,nofail", }, { UUID: "usb-uuid", Mountpoint: "/mnt/scratch", FS: "ext4", Options: "defaults,nofail", }, { Source: "/mnt/scratch", Mountpoint: "/var/lib/rancher", FS: "none", Options: "bind,nofail", }, }, Labels: map[string]string{"role": "worker", "zone": "a", "node-role.kubernetes.io/worker": "true"}, Taints: []string{"gpu=true:NoSchedule"}, } files, err := buildFiles(cfg, nil) if err != nil { t.Fatalf("buildFiles: %v", err) } pathMap := map[string]string{} for _, f := range files { pathMap[f.Path] = string(f.Content) } k3s, ok := pathMap["etc/rancher/k3s/config.yaml"] if !ok { t.Fatalf("missing k3s config") } if !strings.Contains(k3s, "server: https://server:6443") || !strings.Contains(k3s, "node-name: n1") { t.Fatalf("unexpected k3s config: %s", k3s) } if strings.Contains(k3s, "write-kubeconfig-mode") { t.Fatalf("agent config should not include write-kubeconfig-mode: %s", k3s) } if strings.Contains(k3s, "node-role.kubernetes.io/worker") { t.Fatalf("agent config should skip reserved node-role label: %s", k3s) } hostFile, ok := pathMap["etc/hostname"] if !ok || strings.TrimSpace(hostFile) != "n1" { t.Fatalf("hostname file missing/incorrect: %q", hostFile) } auth, ok := pathMap["home/pi/.ssh/authorized_keys"] if !ok || !strings.Contains(auth, "ssh-rsa AAA") { t.Fatalf("authorized_keys missing/incorrect: %s", auth) } firstboot, ok := pathMap["etc/metis/firstboot.env"] if !ok || !strings.Contains(firstboot, "METIS_K3S_VERSION='v1.31.5+k3s1'") { t.Fatalf("firstboot env missing/incorrect: %s", firstboot) } network, ok := pathMap["etc/NetworkManager/system-connections/end0-static.nmconnection"] if !ok || !strings.Contains(network, "address1=10.0.0.10/24,10.0.0.1") { t.Fatalf("networkmanager config missing/incorrect: %s", network) } networkEth0, ok := pathMap["etc/NetworkManager/system-connections/eth0-static.nmconnection"] if !ok || !strings.Contains(networkEth0, "interface-name=eth0") { t.Fatalf("eth0 networkmanager config missing/incorrect: %s", networkEth0) } networkd, ok := pathMap["etc/systemd/network/10-end0-static.network"] if !ok || !strings.Contains(networkd, "Name=end0 eth0") || !strings.Contains(networkd, "Address=10.0.0.10/24") || !strings.Contains(networkd, "Gateway=10.0.0.1") { t.Fatalf("systemd-networkd config missing/incorrect: %s", networkd) } fstab, ok := pathMap["etc/metis/fstab.append"] if !ok || !strings.Contains(fstab, "UUID=disk-uuid /mnt/astreae ext4 defaults,nofail 0 0") || !strings.Contains(fstab, "UUID=usb-uuid /mnt/scratch ext4 defaults,nofail 0 0") || !strings.Contains(fstab, "/mnt/scratch /var/lib/rancher none bind,nofail 0 0") { t.Fatalf("fstab append missing/incorrect: %s", fstab) } } func TestOverlayFiles(t *testing.T) { dir := t.TempDir() bootDir := filepath.Join(dir, "boot") rootDir := filepath.Join(dir, "root") if err := os.MkdirAll(filepath.Join(bootDir, "over"), 0o755); err != nil { t.Fatal(err) } if err := os.MkdirAll(filepath.Join(rootDir, "etc"), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(bootDir, "over", "cmdline.txt"), []byte("console=tty1"), 0o644); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(rootDir, "etc", "issue"), []byte("hello"), 0o644); err != nil { t.Fatal(err) } class := &inventory.NodeClass{ BootOverlay: bootDir, RootOverlay: rootDir, } files, err := collectOverlays(class) if err != nil { t.Fatalf("collectOverlays: %v", err) } if len(files) != 2 { t.Fatalf("expected 2 files, got %d", len(files)) } } func TestSecretsWrite(t *testing.T) { cfg := &config.NodeConfig{ Hostname: "n1", IP: "10.0.0.1", } sec := &secrets.NodeSecrets{K3sToken: "tok", SSHPassword: "pw", Extra: map[string]string{"foo": "bar"}} files, err := buildFiles(cfg, sec) if err != nil { t.Fatalf("buildFiles: %v", err) } found := false for _, f := range files { if f.Path == "etc/metis/secrets.json" && f.RootFS { found = true } } if !found { t.Fatalf("secrets file not written") } } func TestBuildFilesAddsHecateSudoersForAtlas(t *testing.T) { cfg := &config.NodeConfig{ Hostname: "n1", IP: "10.0.0.10", SSHUser: "atlas", SSHKeys: []string{"ssh-ed25519 AAA test"}, K3s: config.K3sConfig{ Role: "agent", }, } files, err := buildFiles(cfg, nil) if err != nil { t.Fatalf("buildFiles: %v", err) } pathMap := map[string]string{} for _, f := range files { pathMap[f.Path] = string(f.Content) } sudoers, ok := pathMap["etc/sudoers.d/90-hecate-atlas"] if !ok || !strings.Contains(sudoers, "atlas ALL=(ALL) NOPASSWD: /usr/bin/systemctl") { t.Fatalf("sudoers file missing/incorrect: %s", sudoers) } backup, ok := pathMap["etc/metis/sudoers-hecate"] if !ok || backup != sudoers { t.Fatalf("metis sudoers backup missing/incorrect: %s", backup) } }