Remove runtime-bundle image dependencies
Hard-cut banger away from source-checkout runtime bundles as an implicit source of\nimage and host defaults. Managed images now own their full boot set,\nimage build starts from an existing registered image, and daemon startup\nno longer synthesizes a default image from host paths.\n\nResolve Firecracker from PATH or firecracker_bin, make SSH keys config-owned\nwith an auto-managed XDG default, replace the external name generator and\npackage manifests with Go code, and keep the vsock helper as a companion\nbinary instead of a user-managed runtime asset.\n\nUpdate the manual scripts, web/CLI forms, config surface, and docs around\nthe new build/manual flow and explicit image registration semantics.\n\nValidation: GOCACHE=/tmp/banger-gocache go test ./..., bash -n scripts/*.sh,\nand make build.
This commit is contained in:
parent
01c7cb5e65
commit
572bf32424
44 changed files with 1194 additions and 3456 deletions
|
|
@ -1,38 +1,29 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
toml "github.com/pelletier/go-toml"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"banger/internal/model"
|
||||
"banger/internal/paths"
|
||||
"banger/internal/runtimebundle"
|
||||
"banger/internal/system"
|
||||
)
|
||||
|
||||
type fileConfig struct {
|
||||
RuntimeDir string `toml:"runtime_dir"`
|
||||
RepoRoot string `toml:"repo_root"`
|
||||
LogLevel string `toml:"log_level"`
|
||||
WebListenAddr *string `toml:"web_listen_addr"`
|
||||
FirecrackerBin string `toml:"firecracker_bin"`
|
||||
SSHKeyPath string `toml:"ssh_key_path"`
|
||||
NamegenPath string `toml:"namegen_path"`
|
||||
CustomizeScript string `toml:"customize_script"`
|
||||
VSockAgent string `toml:"vsock_agent_path"`
|
||||
VSockPingHelper string `toml:"vsock_ping_helper_path"`
|
||||
DefaultWorkSeed string `toml:"default_work_seed"`
|
||||
DefaultImageName string `toml:"default_image_name"`
|
||||
DefaultRootfs string `toml:"default_rootfs"`
|
||||
DefaultBaseRootfs string `toml:"default_base_rootfs"`
|
||||
DefaultKernel string `toml:"default_kernel"`
|
||||
DefaultInitrd string `toml:"default_initrd"`
|
||||
DefaultModulesDir string `toml:"default_modules_dir"`
|
||||
DefaultPackages string `toml:"default_packages_file"`
|
||||
AutoStopStaleAfter string `toml:"auto_stop_stale_after"`
|
||||
StatsPollInterval string `toml:"stats_poll_interval"`
|
||||
MetricsPoll string `toml:"metrics_poll_interval"`
|
||||
|
|
@ -58,202 +49,130 @@ func Load(layout paths.Layout) (model.DaemonConfig, error) {
|
|||
DefaultImageName: "default",
|
||||
}
|
||||
|
||||
path := filepath.Join(layout.ConfigDir, "config.toml")
|
||||
info, err := os.Stat(path)
|
||||
var file fileConfig
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return cfg, err
|
||||
}
|
||||
} else if !info.IsDir() {
|
||||
data, err := os.ReadFile(path)
|
||||
configPath := filepath.Join(layout.ConfigDir, "config.toml")
|
||||
if info, err := os.Stat(configPath); err == nil && !info.IsDir() {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
if err := toml.Unmarshal(data, &file); err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
}
|
||||
|
||||
cfg.RuntimeDir = paths.ResolveRuntimeDir(file.RuntimeDir, file.RepoRoot)
|
||||
if err := applyRuntimeDefaults(&cfg); err != nil {
|
||||
} else if err != nil && !os.IsNotExist(err) {
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
if file.FirecrackerBin != "" {
|
||||
cfg.FirecrackerBin = file.FirecrackerBin
|
||||
}
|
||||
if file.LogLevel != "" {
|
||||
cfg.LogLevel = file.LogLevel
|
||||
if value := strings.TrimSpace(file.LogLevel); value != "" {
|
||||
cfg.LogLevel = value
|
||||
}
|
||||
if file.WebListenAddr != nil {
|
||||
cfg.WebListenAddr = strings.TrimSpace(*file.WebListenAddr)
|
||||
}
|
||||
if file.NamegenPath != "" {
|
||||
cfg.NamegenPath = file.NamegenPath
|
||||
if value := strings.TrimSpace(file.FirecrackerBin); value != "" {
|
||||
cfg.FirecrackerBin = value
|
||||
} else if path, err := system.LookupExecutable("firecracker"); err == nil {
|
||||
cfg.FirecrackerBin = path
|
||||
}
|
||||
if file.CustomizeScript != "" {
|
||||
cfg.CustomizeScript = file.CustomizeScript
|
||||
if value := strings.TrimSpace(file.DefaultImageName); value != "" {
|
||||
cfg.DefaultImageName = value
|
||||
}
|
||||
if file.VSockAgent != "" {
|
||||
cfg.VSockAgentPath = file.VSockAgent
|
||||
} else if file.VSockPingHelper != "" {
|
||||
cfg.VSockAgentPath = file.VSockPingHelper
|
||||
if value := strings.TrimSpace(file.BridgeName); value != "" {
|
||||
cfg.BridgeName = value
|
||||
}
|
||||
if file.DefaultWorkSeed != "" {
|
||||
cfg.DefaultWorkSeed = file.DefaultWorkSeed
|
||||
if value := strings.TrimSpace(file.BridgeIP); value != "" {
|
||||
cfg.BridgeIP = value
|
||||
}
|
||||
if file.DefaultImageName != "" {
|
||||
cfg.DefaultImageName = file.DefaultImageName
|
||||
}
|
||||
if file.DefaultRootfs != "" {
|
||||
cfg.DefaultRootfs = file.DefaultRootfs
|
||||
}
|
||||
if file.DefaultBaseRootfs != "" {
|
||||
cfg.DefaultBaseRootfs = file.DefaultBaseRootfs
|
||||
}
|
||||
if file.DefaultKernel != "" {
|
||||
cfg.DefaultKernel = file.DefaultKernel
|
||||
}
|
||||
if file.DefaultInitrd != "" {
|
||||
cfg.DefaultInitrd = file.DefaultInitrd
|
||||
}
|
||||
if file.DefaultModulesDir != "" {
|
||||
cfg.DefaultModulesDir = file.DefaultModulesDir
|
||||
}
|
||||
if file.DefaultPackages != "" {
|
||||
cfg.DefaultPackagesFile = file.DefaultPackages
|
||||
}
|
||||
if file.BridgeName != "" {
|
||||
cfg.BridgeName = file.BridgeName
|
||||
}
|
||||
if file.BridgeIP != "" {
|
||||
cfg.BridgeIP = file.BridgeIP
|
||||
}
|
||||
if file.CIDR != "" {
|
||||
cfg.CIDR = file.CIDR
|
||||
if value := strings.TrimSpace(file.CIDR); value != "" {
|
||||
cfg.CIDR = value
|
||||
}
|
||||
if file.TapPoolSize > 0 {
|
||||
cfg.TapPoolSize = file.TapPoolSize
|
||||
}
|
||||
if file.DefaultDNS != "" {
|
||||
cfg.DefaultDNS = file.DefaultDNS
|
||||
if value := strings.TrimSpace(file.DefaultDNS); value != "" {
|
||||
cfg.DefaultDNS = value
|
||||
}
|
||||
if file.AutoStopStaleAfter != "" {
|
||||
duration, err := time.ParseDuration(file.AutoStopStaleAfter)
|
||||
if value := strings.TrimSpace(file.AutoStopStaleAfter); value != "" {
|
||||
duration, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
cfg.AutoStopStaleAfter = duration
|
||||
}
|
||||
if file.StatsPollInterval != "" {
|
||||
duration, err := time.ParseDuration(file.StatsPollInterval)
|
||||
if value := strings.TrimSpace(file.StatsPollInterval); value != "" {
|
||||
duration, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
cfg.StatsPollInterval = duration
|
||||
}
|
||||
if file.MetricsPoll != "" {
|
||||
duration, err := time.ParseDuration(file.MetricsPoll)
|
||||
if value := strings.TrimSpace(file.MetricsPoll); value != "" {
|
||||
duration, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
cfg.MetricsPollInterval = duration
|
||||
}
|
||||
if value := os.Getenv("BANGER_LOG_LEVEL"); value != "" {
|
||||
if value := strings.TrimSpace(os.Getenv("BANGER_LOG_LEVEL")); value != "" {
|
||||
cfg.LogLevel = value
|
||||
}
|
||||
|
||||
sshKeyPath, err := resolveSSHKeyPath(layout, file.SSHKeyPath)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
cfg.SSHKeyPath = sshKeyPath
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func applyRuntimeDefaults(cfg *model.DaemonConfig) error {
|
||||
if cfg.RuntimeDir == "" {
|
||||
return nil
|
||||
func resolveSSHKeyPath(layout paths.Layout, configured string) (string, error) {
|
||||
configured = strings.TrimSpace(configured)
|
||||
if configured != "" {
|
||||
return configured, nil
|
||||
}
|
||||
meta, err := runtimebundle.LoadBundleMetadata(cfg.RuntimeDir)
|
||||
switch {
|
||||
case err == nil:
|
||||
applyBundleMetadataDefaults(cfg, cfg.RuntimeDir, meta)
|
||||
case errors.Is(err, os.ErrNotExist):
|
||||
applyLegacyRuntimeDefaults(cfg)
|
||||
default:
|
||||
return ensureDefaultSSHKey(filepath.Join(layout.ConfigDir, "ssh", "id_ed25519"))
|
||||
}
|
||||
|
||||
func ensureDefaultSSHKey(path string) (string, error) {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
if err := ensurePublicKeyFile(path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
} else if !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pkcs8, err := x509.MarshalPKCS8PrivateKey(privateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
privatePEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8})
|
||||
if err := os.WriteFile(path, privatePEM, 0o600); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := ensurePublicKeyFile(path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func ensurePublicKeyFile(privateKeyPath string) error {
|
||||
data, err := os.ReadFile(privateKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cfg.DefaultRootfs == "" {
|
||||
cfg.DefaultRootfs = firstExistingRuntimePath(
|
||||
filepath.Join(cfg.RuntimeDir, "rootfs-docker.ext4"),
|
||||
filepath.Join(cfg.RuntimeDir, "rootfs.ext4"),
|
||||
)
|
||||
signer, err := ssh.ParsePrivateKey(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cfg.DefaultBaseRootfs == "" {
|
||||
cfg.DefaultBaseRootfs = firstExistingRuntimePath(
|
||||
filepath.Join(cfg.RuntimeDir, "rootfs.ext4"),
|
||||
cfg.DefaultRootfs,
|
||||
)
|
||||
}
|
||||
if cfg.DefaultWorkSeed == "" && cfg.DefaultRootfs != "" {
|
||||
cfg.DefaultWorkSeed = firstExistingRuntimePath(associatedWorkSeedPath(cfg.DefaultRootfs))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyBundleMetadataDefaults(cfg *model.DaemonConfig, runtimeDir string, meta runtimebundle.BundleMetadata) {
|
||||
cfg.FirecrackerBin = defaultRuntimePath(cfg.FirecrackerBin, runtimeDir, meta.FirecrackerBin)
|
||||
cfg.SSHKeyPath = defaultRuntimePath(cfg.SSHKeyPath, runtimeDir, meta.SSHKeyPath)
|
||||
cfg.NamegenPath = defaultRuntimePath(cfg.NamegenPath, runtimeDir, meta.NamegenPath)
|
||||
cfg.CustomizeScript = defaultRuntimePath(cfg.CustomizeScript, runtimeDir, meta.CustomizeScript)
|
||||
cfg.VSockAgentPath = defaultRuntimePath(cfg.VSockAgentPath, runtimeDir, meta.VSockAgentPath)
|
||||
cfg.DefaultWorkSeed = defaultRuntimePath(cfg.DefaultWorkSeed, runtimeDir, meta.DefaultWorkSeed)
|
||||
cfg.DefaultKernel = defaultRuntimePath(cfg.DefaultKernel, runtimeDir, meta.DefaultKernel)
|
||||
cfg.DefaultInitrd = defaultRuntimePath(cfg.DefaultInitrd, runtimeDir, meta.DefaultInitrd)
|
||||
cfg.DefaultModulesDir = defaultRuntimePath(cfg.DefaultModulesDir, runtimeDir, meta.DefaultModulesDir)
|
||||
cfg.DefaultPackagesFile = defaultRuntimePath(cfg.DefaultPackagesFile, runtimeDir, meta.DefaultPackages)
|
||||
cfg.DefaultRootfs = defaultRuntimePath(cfg.DefaultRootfs, runtimeDir, meta.DefaultRootfs)
|
||||
cfg.DefaultBaseRootfs = defaultRuntimePath(cfg.DefaultBaseRootfs, runtimeDir, meta.DefaultBaseRootfs)
|
||||
}
|
||||
|
||||
func applyLegacyRuntimeDefaults(cfg *model.DaemonConfig) {
|
||||
cfg.FirecrackerBin = defaultRuntimePath(cfg.FirecrackerBin, cfg.RuntimeDir, "firecracker")
|
||||
cfg.SSHKeyPath = defaultRuntimePath(cfg.SSHKeyPath, cfg.RuntimeDir, "id_ed25519")
|
||||
cfg.NamegenPath = defaultRuntimePath(cfg.NamegenPath, cfg.RuntimeDir, "namegen")
|
||||
cfg.CustomizeScript = defaultRuntimePath(cfg.CustomizeScript, cfg.RuntimeDir, "customize.sh")
|
||||
cfg.VSockAgentPath = firstExistingRuntimePath(
|
||||
defaultRuntimePath(cfg.VSockAgentPath, cfg.RuntimeDir, "banger-vsock-agent"),
|
||||
filepath.Join(cfg.RuntimeDir, "banger-vsock-pingd"),
|
||||
)
|
||||
cfg.DefaultWorkSeed = defaultRuntimePath(cfg.DefaultWorkSeed, cfg.RuntimeDir, "rootfs-docker.work-seed.ext4")
|
||||
cfg.DefaultKernel = defaultRuntimePath(cfg.DefaultKernel, cfg.RuntimeDir, "wtf/root/boot/vmlinux-6.8.0-94-generic")
|
||||
cfg.DefaultInitrd = defaultRuntimePath(cfg.DefaultInitrd, cfg.RuntimeDir, "wtf/root/boot/initrd.img-6.8.0-94-generic")
|
||||
cfg.DefaultModulesDir = defaultRuntimePath(cfg.DefaultModulesDir, cfg.RuntimeDir, "wtf/root/lib/modules/6.8.0-94-generic")
|
||||
cfg.DefaultPackagesFile = defaultRuntimePath(cfg.DefaultPackagesFile, cfg.RuntimeDir, "packages.apt")
|
||||
}
|
||||
|
||||
func defaultRuntimePath(current, runtimeDir, relative string) string {
|
||||
if current != "" || relative == "" {
|
||||
return current
|
||||
}
|
||||
return filepath.Join(runtimeDir, relative)
|
||||
}
|
||||
|
||||
func firstExistingRuntimePath(paths ...string) string {
|
||||
for _, candidate := range paths {
|
||||
if candidate == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := os.Stat(candidate); err == nil {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func associatedWorkSeedPath(rootfsPath string) string {
|
||||
rootfsPath = strings.TrimSpace(rootfsPath)
|
||||
if rootfsPath == "" {
|
||||
return ""
|
||||
}
|
||||
if strings.HasSuffix(rootfsPath, ".ext4") {
|
||||
return strings.TrimSuffix(rootfsPath, ".ext4") + ".work-seed.ext4"
|
||||
}
|
||||
return rootfsPath + ".work-seed"
|
||||
publicKey := ssh.MarshalAuthorizedKey(signer.PublicKey())
|
||||
return os.WriteFile(privateKeyPath+".pub", publicKey, 0o644)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,154 +1,70 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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)
|
||||
func TestLoadDefaultsResolveFirecrackerAndGenerateSSHKey(t *testing.T) {
|
||||
configDir := t.TempDir()
|
||||
binDir := t.TempDir()
|
||||
firecrackerPath := filepath.Join(binDir, "firecracker")
|
||||
if err := os.WriteFile(firecrackerPath, []byte("#!/bin/sh\nexit 0\n"), 0o755); err != nil {
|
||||
t.Fatalf("write firecracker: %v", err)
|
||||
}
|
||||
t.Setenv("PATH", binDir)
|
||||
|
||||
t.Setenv("BANGER_RUNTIME_DIR", runtimeDir)
|
||||
cfg, err := Load(paths.Layout{ConfigDir: t.TempDir()})
|
||||
cfg, err := Load(paths.Layout{ConfigDir: configDir})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
|
||||
if cfg.RuntimeDir != runtimeDir {
|
||||
t.Fatalf("RuntimeDir = %q, want %q", cfg.RuntimeDir, runtimeDir)
|
||||
if cfg.FirecrackerBin != firecrackerPath {
|
||||
t.Fatalf("FirecrackerBin = %q, want %q", cfg.FirecrackerBin, firecrackerPath)
|
||||
}
|
||||
if cfg.FirecrackerBin != filepath.Join(runtimeDir, meta.FirecrackerBin) {
|
||||
t.Fatalf("FirecrackerBin = %q", cfg.FirecrackerBin)
|
||||
wantKey := filepath.Join(configDir, "ssh", "id_ed25519")
|
||||
if cfg.SSHKeyPath != wantKey {
|
||||
t.Fatalf("SSHKeyPath = %q, want %q", cfg.SSHKeyPath, wantKey)
|
||||
}
|
||||
if cfg.SSHKeyPath != filepath.Join(runtimeDir, meta.SSHKeyPath) {
|
||||
t.Fatalf("SSHKeyPath = %q", cfg.SSHKeyPath)
|
||||
for _, path := range []string{wantKey, wantKey + ".pub"} {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
t.Fatalf("stat %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
if cfg.NamegenPath != filepath.Join(runtimeDir, meta.NamegenPath) {
|
||||
t.Fatalf("NamegenPath = %q", cfg.NamegenPath)
|
||||
if cfg.DefaultImageName != "default" {
|
||||
t.Fatalf("DefaultImageName = %q, want default", cfg.DefaultImageName)
|
||||
}
|
||||
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)
|
||||
if cfg.WebListenAddr != "127.0.0.1:7777" {
|
||||
t.Fatalf("WebListenAddr = %q", cfg.WebListenAddr)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
func TestLoadAppliesConfigOverrides(t *testing.T) {
|
||||
configDir := t.TempDir()
|
||||
data := []byte(`
|
||||
log_level = "debug"
|
||||
web_listen_addr = ""
|
||||
firecracker_bin = "/opt/firecracker"
|
||||
ssh_key_path = "/tmp/custom-key"
|
||||
default_image_name = "void-exp"
|
||||
auto_stop_stale_after = "1h"
|
||||
stats_poll_interval = "15s"
|
||||
metrics_poll_interval = "30s"
|
||||
bridge_name = "br-test"
|
||||
bridge_ip = "10.0.0.1"
|
||||
cidr = "25"
|
||||
tap_pool_size = 8
|
||||
default_dns = "9.9.9.9"
|
||||
`)
|
||||
if err := os.WriteFile(filepath.Join(configDir, "config.toml"), data, 0o644); err != nil {
|
||||
t.Fatalf("write config.toml: %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.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()})
|
||||
cfg, err := Load(paths.Layout{ConfigDir: configDir})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
|
|
@ -156,158 +72,46 @@ func TestLoadAppliesLogLevelEnvOverride(t *testing.T) {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadWebListenAddrDefaultsAndAllowsDisable(t *testing.T) {
|
||||
cfg, err := Load(paths.Layout{ConfigDir: t.TempDir()})
|
||||
if err != nil {
|
||||
t.Fatalf("Load default config: %v", err)
|
||||
}
|
||||
if cfg.WebListenAddr != "127.0.0.1:7777" {
|
||||
t.Fatalf("WebListenAddr = %q, want default 127.0.0.1:7777", cfg.WebListenAddr)
|
||||
}
|
||||
|
||||
configDir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(configDir, "config.toml"), []byte("web_listen_addr = \"\"\n"), 0o644); err != nil {
|
||||
t.Fatalf("write config.toml: %v", err)
|
||||
}
|
||||
cfg, err = Load(paths.Layout{ConfigDir: configDir})
|
||||
if err != nil {
|
||||
t.Fatalf("Load disabled config: %v", err)
|
||||
}
|
||||
if cfg.WebListenAddr != "" {
|
||||
t.Fatalf("WebListenAddr = %q, want disabled empty string", cfg.WebListenAddr)
|
||||
t.Fatalf("WebListenAddr = %q, want empty", cfg.WebListenAddr)
|
||||
}
|
||||
if cfg.FirecrackerBin != "/opt/firecracker" {
|
||||
t.Fatalf("FirecrackerBin = %q", cfg.FirecrackerBin)
|
||||
}
|
||||
if cfg.SSHKeyPath != "/tmp/custom-key" {
|
||||
t.Fatalf("SSHKeyPath = %q", cfg.SSHKeyPath)
|
||||
}
|
||||
if cfg.DefaultImageName != "void-exp" {
|
||||
t.Fatalf("DefaultImageName = %q", cfg.DefaultImageName)
|
||||
}
|
||||
if cfg.AutoStopStaleAfter != time.Hour {
|
||||
t.Fatalf("AutoStopStaleAfter = %s", cfg.AutoStopStaleAfter)
|
||||
}
|
||||
if cfg.StatsPollInterval != 15*time.Second {
|
||||
t.Fatalf("StatsPollInterval = %s", cfg.StatsPollInterval)
|
||||
}
|
||||
if cfg.MetricsPollInterval != 30*time.Second {
|
||||
t.Fatalf("MetricsPollInterval = %s", cfg.MetricsPollInterval)
|
||||
}
|
||||
if cfg.BridgeName != "br-test" || cfg.BridgeIP != "10.0.0.1" || cfg.CIDR != "25" {
|
||||
t.Fatalf("bridge config = %+v", cfg)
|
||||
}
|
||||
if cfg.TapPoolSize != 8 {
|
||||
t.Fatalf("TapPoolSize = %d", cfg.TapPoolSize)
|
||||
}
|
||||
if cfg.DefaultDNS != "9.9.9.9" {
|
||||
t.Fatalf("DefaultDNS = %q", cfg.DefaultDNS)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAppliesLogLevelEnvOverride(t *testing.T) {
|
||||
t.Setenv("BANGER_LOG_LEVEL", "warn")
|
||||
|
||||
cfg, err := Load(paths.Layout{ConfigDir: t.TempDir()})
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.LogLevel != "warn" {
|
||||
t.Fatalf("LogLevel = %q, want warn", cfg.LogLevel)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue