package config import ( "encoding/json" "os" "path/filepath" "testing" "banger/internal/paths" "banger/internal/runtimebundle" ) func TestLoadDerivesArtifactPathsFromRuntimeDir(t *testing.T) { runtimeDir := t.TempDir() meta := runtimebundle.BundleMetadata{ FirecrackerBin: "bin/firecracker", SSHKeyPath: "keys/id_ed25519", NamegenPath: "bin/namegen", CustomizeScript: "scripts/customize.sh", VSockAgentPath: "bin/banger-vsock-agent", DefaultPackages: "config/packages.apt", DefaultRootfs: "images/rootfs-docker.ext4", DefaultWorkSeed: "images/rootfs-docker.work-seed.ext4", DefaultKernel: "kernels/vmlinux", DefaultInitrd: "kernels/initrd.img", DefaultModulesDir: "modules/current", } for _, rel := range []string{ meta.FirecrackerBin, meta.SSHKeyPath, meta.NamegenPath, meta.CustomizeScript, meta.VSockAgentPath, meta.DefaultPackages, meta.DefaultRootfs, meta.DefaultWorkSeed, meta.DefaultKernel, meta.DefaultInitrd, filepath.Join(meta.DefaultModulesDir, "modules.dep"), } { path := filepath.Join(runtimeDir, rel) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatalf("mkdir %s: %v", filepath.Dir(path), err) } if err := os.WriteFile(path, []byte("test"), 0o644); err != nil { t.Fatalf("write %s: %v", path, err) } } data, err := json.Marshal(meta) if err != nil { t.Fatalf("Marshal: %v", err) } if err := os.WriteFile(filepath.Join(runtimeDir, runtimebundle.BundleMetadataFile), data, 0o644); err != nil { t.Fatalf("write bundle metadata: %v", err) } t.Setenv("BANGER_RUNTIME_DIR", runtimeDir) cfg, err := Load(paths.Layout{ConfigDir: t.TempDir()}) if err != nil { t.Fatalf("Load: %v", err) } if cfg.RuntimeDir != runtimeDir { t.Fatalf("RuntimeDir = %q, want %q", cfg.RuntimeDir, runtimeDir) } if cfg.FirecrackerBin != filepath.Join(runtimeDir, meta.FirecrackerBin) { t.Fatalf("FirecrackerBin = %q", cfg.FirecrackerBin) } if cfg.SSHKeyPath != filepath.Join(runtimeDir, meta.SSHKeyPath) { t.Fatalf("SSHKeyPath = %q", cfg.SSHKeyPath) } if cfg.NamegenPath != filepath.Join(runtimeDir, meta.NamegenPath) { t.Fatalf("NamegenPath = %q", cfg.NamegenPath) } if cfg.CustomizeScript != filepath.Join(runtimeDir, meta.CustomizeScript) { t.Fatalf("CustomizeScript = %q", cfg.CustomizeScript) } if cfg.VSockAgentPath != filepath.Join(runtimeDir, meta.VSockAgentPath) { t.Fatalf("VSockAgentPath = %q", cfg.VSockAgentPath) } if cfg.DefaultRootfs != filepath.Join(runtimeDir, meta.DefaultRootfs) { t.Fatalf("DefaultRootfs = %q", cfg.DefaultRootfs) } if cfg.DefaultWorkSeed != filepath.Join(runtimeDir, meta.DefaultWorkSeed) { t.Fatalf("DefaultWorkSeed = %q", cfg.DefaultWorkSeed) } if cfg.DefaultBaseRootfs != filepath.Join(runtimeDir, meta.DefaultRootfs) { t.Fatalf("DefaultBaseRootfs = %q", cfg.DefaultBaseRootfs) } if cfg.DefaultKernel != filepath.Join(runtimeDir, meta.DefaultKernel) { t.Fatalf("DefaultKernel = %q", cfg.DefaultKernel) } if cfg.DefaultInitrd != filepath.Join(runtimeDir, meta.DefaultInitrd) { t.Fatalf("DefaultInitrd = %q", cfg.DefaultInitrd) } if cfg.DefaultModulesDir != filepath.Join(runtimeDir, meta.DefaultModulesDir) { t.Fatalf("DefaultModulesDir = %q", cfg.DefaultModulesDir) } if cfg.DefaultPackagesFile != filepath.Join(runtimeDir, meta.DefaultPackages) { t.Fatalf("DefaultPackagesFile = %q", cfg.DefaultPackagesFile) } } func TestLoadFallsBackToLegacyRuntimeLayoutWithoutBundleMetadata(t *testing.T) { runtimeDir := t.TempDir() for _, rel := range []string{ "firecracker", "id_ed25519", "namegen", "customize.sh", "banger-vsock-agent", "packages.apt", "rootfs-docker.ext4", "rootfs-docker.work-seed.ext4", "wtf/root/boot/vmlinux-6.8.0-94-generic", "wtf/root/boot/initrd.img-6.8.0-94-generic", "wtf/root/lib/modules/6.8.0-94-generic/modules.dep", } { path := filepath.Join(runtimeDir, rel) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatalf("mkdir %s: %v", filepath.Dir(path), err) } if err := os.WriteFile(path, []byte("test"), 0o644); err != nil { t.Fatalf("write %s: %v", path, err) } } t.Setenv("BANGER_RUNTIME_DIR", runtimeDir) cfg, err := Load(paths.Layout{ConfigDir: t.TempDir()}) if err != nil { t.Fatalf("Load: %v", err) } if cfg.FirecrackerBin != filepath.Join(runtimeDir, "firecracker") { t.Fatalf("FirecrackerBin = %q", cfg.FirecrackerBin) } if cfg.VSockAgentPath != filepath.Join(runtimeDir, "banger-vsock-agent") { t.Fatalf("VSockAgentPath = %q", cfg.VSockAgentPath) } if cfg.DefaultWorkSeed != filepath.Join(runtimeDir, "rootfs-docker.work-seed.ext4") { t.Fatalf("DefaultWorkSeed = %q", cfg.DefaultWorkSeed) } if cfg.DefaultKernel != filepath.Join(runtimeDir, "wtf/root/boot/vmlinux-6.8.0-94-generic") { t.Fatalf("DefaultKernel = %q", cfg.DefaultKernel) } } func TestLoadAppliesLogLevelEnvOverride(t *testing.T) { t.Setenv("BANGER_LOG_LEVEL", "debug") cfg, err := Load(paths.Layout{ConfigDir: t.TempDir()}) if err != nil { t.Fatalf("Load: %v", err) } if cfg.LogLevel != "debug" { t.Fatalf("LogLevel = %q", cfg.LogLevel) } } func TestLoadDefaultsLogLevelToInfo(t *testing.T) { cfg, err := Load(paths.Layout{ConfigDir: t.TempDir()}) if err != nil { t.Fatalf("Load: %v", err) } if cfg.LogLevel != "info" { t.Fatalf("LogLevel = %q, want info", cfg.LogLevel) } } func TestLoadIgnoresConfigSSHKeyOverrideForGuestAccess(t *testing.T) { runtimeDir := t.TempDir() meta := runtimebundle.BundleMetadata{ FirecrackerBin: "bin/firecracker", SSHKeyPath: "keys/id_ed25519", NamegenPath: "bin/namegen", CustomizeScript: "scripts/customize.sh", VSockAgentPath: "bin/banger-vsock-agent", DefaultPackages: "config/packages.apt", DefaultRootfs: "images/rootfs.ext4", DefaultWorkSeed: "images/rootfs.work-seed.ext4", DefaultKernel: "kernels/vmlinux", DefaultModulesDir: "modules/current", } for _, rel := range []string{ meta.FirecrackerBin, meta.SSHKeyPath, meta.NamegenPath, meta.CustomizeScript, meta.VSockAgentPath, meta.DefaultPackages, meta.DefaultRootfs, meta.DefaultWorkSeed, meta.DefaultKernel, filepath.Join(meta.DefaultModulesDir, "modules.dep"), } { path := filepath.Join(runtimeDir, rel) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatalf("mkdir %s: %v", filepath.Dir(path), err) } if err := os.WriteFile(path, []byte("test"), 0o644); err != nil { t.Fatalf("write %s: %v", path, err) } } data, err := json.Marshal(meta) if err != nil { t.Fatalf("Marshal: %v", err) } if err := os.WriteFile(filepath.Join(runtimeDir, runtimebundle.BundleMetadataFile), data, 0o644); err != nil { t.Fatalf("write bundle metadata: %v", err) } configDir := t.TempDir() if err := os.WriteFile(filepath.Join(configDir, "config.toml"), []byte("ssh_key_path = \"/tmp/override-key\"\n"), 0o644); err != nil { t.Fatalf("write config.toml: %v", err) } t.Setenv("BANGER_RUNTIME_DIR", runtimeDir) cfg, err := Load(paths.Layout{ConfigDir: configDir}) if err != nil { t.Fatalf("Load: %v", err) } want := filepath.Join(runtimeDir, meta.SSHKeyPath) if cfg.SSHKeyPath != want { t.Fatalf("SSHKeyPath = %q, want runtime key %q", cfg.SSHKeyPath, want) } } func TestLoadAcceptsLegacyBundleVsockPingHelperPath(t *testing.T) { runtimeDir := t.TempDir() meta := runtimebundle.BundleMetadata{ FirecrackerBin: "bin/firecracker", SSHKeyPath: "keys/id_ed25519", NamegenPath: "bin/namegen", CustomizeScript: "scripts/customize.sh", VSockPingHelperPath: "bin/banger-vsock-pingd", DefaultPackages: "config/packages.apt", DefaultRootfs: "images/rootfs.ext4", DefaultKernel: "kernels/vmlinux", } for _, rel := range []string{ meta.FirecrackerBin, meta.SSHKeyPath, meta.NamegenPath, meta.CustomizeScript, meta.VSockPingHelperPath, meta.DefaultPackages, meta.DefaultRootfs, meta.DefaultKernel, } { path := filepath.Join(runtimeDir, rel) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatalf("mkdir %s: %v", filepath.Dir(path), err) } if err := os.WriteFile(path, []byte("test"), 0o644); err != nil { t.Fatalf("write %s: %v", path, err) } } data, err := json.Marshal(meta) if err != nil { t.Fatalf("Marshal: %v", err) } if err := os.WriteFile(filepath.Join(runtimeDir, runtimebundle.BundleMetadataFile), data, 0o644); err != nil { t.Fatalf("write bundle metadata: %v", err) } t.Setenv("BANGER_RUNTIME_DIR", runtimeDir) cfg, err := Load(paths.Layout{ConfigDir: t.TempDir()}) if err != nil { t.Fatalf("Load: %v", err) } if cfg.VSockAgentPath != filepath.Join(runtimeDir, meta.VSockPingHelperPath) { t.Fatalf("VSockAgentPath = %q", cfg.VSockAgentPath) } } func TestLoadAcceptsLegacyConfigVsockPingHelperPath(t *testing.T) { configDir := t.TempDir() if err := os.WriteFile(filepath.Join(configDir, "config.toml"), []byte("vsock_ping_helper_path = \"/tmp/legacy-agent\"\n"), 0o644); err != nil { t.Fatalf("write config.toml: %v", err) } cfg, err := Load(paths.Layout{ConfigDir: configDir}) if err != nil { t.Fatalf("Load: %v", err) } if cfg.VSockAgentPath != "/tmp/legacy-agent" { t.Fatalf("VSockAgentPath = %q", cfg.VSockAgentPath) } }