package plan import ( "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" "metis/pkg/inventory" ) func TestFilesAndInjectWithSecretsAndOverlays(t *testing.T) { vault := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/v1/secret/data/nodes/titan-15" { http.NotFound(w, r) return } _ = json.NewEncoder(w).Encode(map[string]any{ "data": map[string]any{ "data": map[string]any{ "cloud_init": "#cloud-config\nmanage_etc_hosts: true\n", "k3s_token": "secret-token", "extra": map[string]string{"foo": "bar"}, }, }, }) })) defer vault.Close() t.Setenv("VAULT_ADDR", vault.URL) t.Setenv("VAULT_TOKEN", "tok") dir := t.TempDir() bootOverlay := filepath.Join(dir, "boot-overlay") rootOverlay := filepath.Join(dir, "root-overlay") if err := os.MkdirAll(filepath.Join(bootOverlay, "over"), 0o755); err != nil { t.Fatal(err) } if err := os.MkdirAll(filepath.Join(rootOverlay, "etc"), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(bootOverlay, "over", "cmdline.txt"), []byte("console=tty1"), 0o644); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(rootOverlay, "etc", "issue"), []byte("hello"), 0o644); err != nil { t.Fatal(err) } inv := &inventory.Inventory{ Classes: []inventory.NodeClass{{ Name: "c1", Arch: "arm64", OS: "linux", Image: "file:///tmp/base.img", BootOverlay: bootOverlay, RootOverlay: rootOverlay, }}, Nodes: []inventory.NodeSpec{{ Name: "titan-15", Class: "c1", Hostname: "titan-15", IP: "192.168.22.43", K3sRole: "agent", SSHUser: "atlas", SSHAuthorized: []string{"ssh-ed25519 AAA"}, }}, } files, err := Files(inv, "titan-15") if err != nil { t.Fatalf("Files: %v", err) } var sawSecret, sawBootOverlay, sawRootOverlay, sawCloudInit bool for _, f := range files { switch { case f.Path == "etc/metis/secrets.json": sawSecret = true case f.Path == "over/cmdline.txt": sawBootOverlay = true case f.Path == "etc/issue": sawRootOverlay = true case f.Path == "user-data": sawCloudInit = strings.Contains(string(f.Content), "manage_etc_hosts: true") } } if !sawSecret || !sawBootOverlay || !sawRootOverlay || !sawCloudInit { t.Fatalf("missing generated files: secret=%v boot=%v root=%v cloudinit=%v", sawSecret, sawBootOverlay, sawRootOverlay, sawCloudInit) } bootDir := filepath.Join(dir, "boot") rootDir := filepath.Join(dir, "root") if err := os.MkdirAll(bootDir, 0o755); err != nil { t.Fatal(err) } if err := os.MkdirAll(rootDir, 0o755); err != nil { t.Fatal(err) } if err := Inject(inv, "titan-15", bootDir, rootDir); err != nil { t.Fatalf("Inject: %v", err) } if _, err := os.Stat(filepath.Join(bootDir, "over", "cmdline.txt")); err != nil { t.Fatalf("expected boot overlay file: %v", err) } if _, err := os.Stat(filepath.Join(rootDir, "etc/metis/node.json")); err != nil { t.Fatalf("expected injected rootfs file: %v", err) } } func TestNextRunStale(t *testing.T) { if !NextRunStale(time.Now().Add(-2*time.Hour), time.Hour) { t.Fatal("expected stale run") } if NextRunStale(time.Now(), time.Hour) { t.Fatal("did not expect fresh run to be stale") } } func TestAllowK3sNodeLabelRules(t *testing.T) { if allowK3sNodeLabel("agent", "node-role.kubernetes.io/worker") { t.Fatal("agent should block node-role labels") } if !allowK3sNodeLabel("server", "node-role.kubernetes.io/worker") { t.Fatal("server should allow node-role labels") } }