vm defaults: host-aware sizing + spec line on spawn + doctor check

Replaces the static model.Default* constants that drove --vcpu / --memory
/ --disk-size with a three-layer resolver:

  1. [vm_defaults] in ~/.config/banger/config.toml (if set)
  2. host-derived heuristics (cpus/4 capped at 4; ram/8 capped at 8 GiB)
  3. baked-in constants (floor)

Visibility:

- Every `vm run` / `vm create` prints a `spec:` line before progress
  begins: `spec: 4 vcpu · 8192 MiB · 8G disk`. Matches the VM that
  actually gets created because the CLI is now the single source of
  truth — it resolves, populates the flag defaults, and forwards the
  explicit values to the daemon.
- `banger doctor` adds a "vm defaults" check showing per-field
  provenance (config|auto|builtin) and the config file path for
  overrides.
- `--help` shows the resolved defaults (e.g. `--vcpu int (default 4)`
  on an 8-core host).

No `banger config init` command, no first-run side effects, no writes
to the user's filesystem behind their back. Users who want explicit
control set the keys; everyone else gets sensible numbers that track
their hardware.
This commit is contained in:
Thales Maciel 2026-04-19 13:06:51 -03:00
parent 78ff482bfa
commit 21b74639d8
No known key found for this signature in database
GPG key ID: 33112E6833C34679
10 changed files with 594 additions and 56 deletions

View file

@ -0,0 +1,53 @@
package cli
import (
"bytes"
"strings"
"testing"
"banger/internal/api"
)
func TestPrintVMSpecLineWithAllFields(t *testing.T) {
vcpu, mem := 2, 2048
params := api.VMCreateParams{
VCPUCount: &vcpu,
MemoryMiB: &mem,
WorkDiskSize: "8G",
}
var buf bytes.Buffer
printVMSpecLine(&buf, params)
got := buf.String()
for _, want := range []string{"spec:", "2 vcpu", "2048 MiB", "8G"} {
if !strings.Contains(got, want) {
t.Errorf("output missing %q:\n%s", want, got)
}
}
if !strings.HasSuffix(got, "\n") {
t.Error("spec line should terminate with newline")
}
}
func TestPrintVMSpecLineFallsBackToBuiltinsOnNilFields(t *testing.T) {
// Empty params — the printer reaches for DefaultVCPUCount /
// DefaultMemoryMiB / DefaultWorkDiskSize so output is still sane.
var buf bytes.Buffer
printVMSpecLine(&buf, api.VMCreateParams{})
got := buf.String()
// Not asserting exact values — just that it produced a plausible
// line with the three labels.
for _, want := range []string{"spec:", "vcpu", "MiB", "disk"} {
if !strings.Contains(got, want) {
t.Errorf("output missing %q:\n%s", want, got)
}
}
}
func TestPrintVMSpecLineIgnoresUnparseableDiskSize(t *testing.T) {
// Falls back to builtin default; must not panic or print garbage.
var buf bytes.Buffer
printVMSpecLine(&buf, api.VMCreateParams{WorkDiskSize: "not-a-size"})
if !strings.Contains(buf.String(), "spec:") {
t.Errorf("expected spec line even with bad input, got %q", buf.String())
}
}