Remove the banger TUI
Hard cut the terminal UI so the supported management surface is the daemon-backed CLI only. Drop the tui subcommand, delete the Bubble Tea implementation and its tests, and keep a regression check that the legacy command is rejected. Prune the Charmbracelet dependencies with go mod tidy and remove the stale README and AGENTS references. Validated with go test ./... and GOCACHE=/tmp/banger-gocache go test ./internal/cli.
This commit is contained in:
parent
7667249b47
commit
0c80d03081
8 changed files with 13 additions and 2290 deletions
|
|
@ -21,7 +21,6 @@
|
|||
- `./banger vm stop vm-a vm-b vm-c` and `./banger vm set --nat web-1 web-2` are supported; multi-VM lifecycle and `set` actions fan out concurrently through the CLI.
|
||||
- `./banger doctor` reports runtime bundle, host tool, feature, and image-build readiness from the same Go checks used by the daemon.
|
||||
- `./banger image register --name local --rootfs /abs/path/rootfs.ext4` creates or updates an unmanaged image record without changing the default image config; use it for experimental guest iteration paths such as Void.
|
||||
- `./banger tui` launches the terminal UI.
|
||||
- `make test` runs `go test ./...`.
|
||||
- `./verify.sh` runs the smoke test for the Go VM workflow.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# banger
|
||||
|
||||
Persistent Firecracker development VMs managed through a Go daemon, CLI, and TUI.
|
||||
Persistent Firecracker development VMs managed through a Go daemon and CLI.
|
||||
|
||||
## Requirements
|
||||
- Linux host with KVM (`/dev/kvm` access)
|
||||
|
|
@ -137,11 +137,6 @@ banger vm kill --signal KILL aa12bb34 cc56dd78
|
|||
banger vm set --nat web-1 web-2 web-3
|
||||
```
|
||||
|
||||
Launch the TUI:
|
||||
```bash
|
||||
banger tui
|
||||
```
|
||||
|
||||
## Daemon
|
||||
The CLI auto-starts `bangerd` when needed.
|
||||
|
||||
|
|
|
|||
15
go.mod
15
go.mod
|
|
@ -3,11 +3,7 @@ module banger
|
|||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/bubbles v0.14.0
|
||||
github.com/charmbracelet/bubbletea v0.21.1-0.20220623121936-ca32c4c62873
|
||||
github.com/charmbracelet/lipgloss v0.5.1-0.20220407020210-a86f21a0ae43
|
||||
github.com/firecracker-microvm/firecracker-go-sdk v1.0.0
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/miekg/dns v1.1.72
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
|
|
@ -21,8 +17,6 @@ require (
|
|||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/containerd/console v1.0.3 // indirect
|
||||
github.com/containerd/fifo v1.0.0 // indirect
|
||||
github.com/containernetworking/cni v1.0.1 // indirect
|
||||
github.com/containernetworking/plugins v1.0.1 // indirect
|
||||
|
|
@ -43,22 +37,16 @@ require (
|
|||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mdlayher/socket v0.2.0 // indirect
|
||||
github.com/mdlayher/vsock v1.1.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
||||
github.com/muesli/cancelreader v0.2.1 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
|
||||
|
|
@ -67,7 +55,6 @@ require (
|
|||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/term v0.38.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
|
|
|||
38
go.sum
38
go.sum
|
|
@ -72,8 +72,6 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l
|
|||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
|
|
@ -93,15 +91,6 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3k
|
|||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og=
|
||||
github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
|
||||
github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4=
|
||||
github.com/charmbracelet/bubbletea v0.21.1-0.20220623121936-ca32c4c62873 h1:ti/1QRoSzanYHPW4jLgIjCkfJ3beXh2h1nr6nEkWOig=
|
||||
github.com/charmbracelet/bubbletea v0.21.1-0.20220623121936-ca32c4c62873/go.mod h1:aoVIwlNlr5wbCB26KhxfrqAn0bMp4YpJcoOelbxApjs=
|
||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
|
||||
github.com/charmbracelet/lipgloss v0.5.1-0.20220407020210-a86f21a0ae43 h1:xO5Bh21Ii+0p3EYp1GdFEF/Iax7VhBgMbBVCOFBZ2/Q=
|
||||
github.com/charmbracelet/lipgloss v0.5.1-0.20220407020210-a86f21a0ae43/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
|
||||
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
|
|
@ -132,8 +121,6 @@ github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on
|
|||
github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
|
||||
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
|
||||
github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
|
||||
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
|
|
@ -487,9 +474,6 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
|
|
@ -502,14 +486,9 @@ github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN
|
|||
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
|
|
@ -540,17 +519,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
|||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/cancelreader v0.2.1 h1:Xzd1B4U5bWQOuSKuN398MyynIGTNT89dxzpEDsalXZs=
|
||||
github.com/muesli/cancelreader v0.2.1/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI=
|
||||
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
|
|
@ -651,9 +619,6 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
|
|||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
|
|
@ -662,7 +627,6 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
|||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
||||
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
|
|
@ -952,12 +916,10 @@ golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ func NewBangerCommand() *cobra.Command {
|
|||
RunE: helpNoArgs,
|
||||
}
|
||||
root.CompletionOptions.DisableDefaultCmd = true
|
||||
root.AddCommand(newDaemonCommand(), newDoctorCommand(), newVMCommand(), newImageCommand(), newTUICommand(), newInternalCommand())
|
||||
root.AddCommand(newDaemonCommand(), newDoctorCommand(), newVMCommand(), newImageCommand(), newInternalCommand())
|
||||
return root
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,12 +25,21 @@ func TestNewBangerCommandHasExpectedSubcommands(t *testing.T) {
|
|||
for _, sub := range cmd.Commands() {
|
||||
names = append(names, sub.Name())
|
||||
}
|
||||
want := []string{"daemon", "doctor", "image", "internal", "tui", "vm"}
|
||||
want := []string{"daemon", "doctor", "image", "internal", "vm"}
|
||||
if !reflect.DeepEqual(names, want) {
|
||||
t.Fatalf("subcommands = %v, want %v", names, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegacyRemovedCommandIsRejected(t *testing.T) {
|
||||
cmd := NewBangerCommand()
|
||||
cmd.SetArgs([]string{"tui"})
|
||||
err := cmd.Execute()
|
||||
if err == nil || !strings.Contains(err.Error(), "unknown command \"tui\"") {
|
||||
t.Fatalf("Execute() error = %v, want unknown legacy command", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoctorCommandPrintsReportAndFailsOnHardFailures(t *testing.T) {
|
||||
original := doctorFunc
|
||||
t.Cleanup(func() {
|
||||
|
|
|
|||
1833
internal/cli/tui.go
1833
internal/cli/tui.go
File diff suppressed because it is too large
Load diff
|
|
@ -1,396 +0,0 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"banger/internal/api"
|
||||
"banger/internal/model"
|
||||
"banger/internal/paths"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func TestCreateVMFormSubmit(t *testing.T) {
|
||||
form := newCreateVMForm([]model.Image{{Name: "default"}}, model.DaemonConfig{DefaultImageName: "default"})
|
||||
form.fields[0].input.SetValue("devbox")
|
||||
form.fields[2].input.SetValue("4")
|
||||
form.fields[3].input.SetValue("2048")
|
||||
form.fields[4].input.SetValue("12G")
|
||||
form.fields[5].input.SetValue("24G")
|
||||
form.fields[6].index = 1
|
||||
|
||||
action, err := form.submit()
|
||||
if err != nil {
|
||||
t.Fatalf("submit: %v", err)
|
||||
}
|
||||
if action.kind != actionCreate {
|
||||
t.Fatalf("kind = %s, want %s", action.kind, actionCreate)
|
||||
}
|
||||
if action.create.Name != "devbox" || action.create.ImageName != "default" {
|
||||
t.Fatalf("unexpected create params: %+v", action.create)
|
||||
}
|
||||
if action.create.VCPUCount == nil || *action.create.VCPUCount != 4 || action.create.MemoryMiB == nil || *action.create.MemoryMiB != 2048 {
|
||||
t.Fatalf("unexpected cpu/memory: %+v", action.create)
|
||||
}
|
||||
if action.create.SystemOverlaySize != "12G" || action.create.WorkDiskSize != "24G" {
|
||||
t.Fatalf("unexpected disk sizes: %+v", action.create)
|
||||
}
|
||||
if !action.create.NATEnabled {
|
||||
t.Fatalf("expected NAT enabled: %+v", action.create)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditVMFormSubmit(t *testing.T) {
|
||||
form := newEditVMForm(model.VMRecord{
|
||||
ID: "vm-1",
|
||||
Spec: model.VMSpec{
|
||||
VCPUCount: 2,
|
||||
MemoryMiB: 1024,
|
||||
WorkDiskSizeBytes: 16 * 1024 * 1024 * 1024,
|
||||
NATEnabled: false,
|
||||
},
|
||||
})
|
||||
form.fields[0].input.SetValue("6")
|
||||
form.fields[1].input.SetValue("4096")
|
||||
form.fields[2].input.SetValue("32G")
|
||||
form.fields[3].index = 1
|
||||
|
||||
action, err := form.submit()
|
||||
if err != nil {
|
||||
t.Fatalf("submit: %v", err)
|
||||
}
|
||||
if action.kind != actionEdit {
|
||||
t.Fatalf("kind = %s, want %s", action.kind, actionEdit)
|
||||
}
|
||||
if action.set.IDOrName != "vm-1" {
|
||||
t.Fatalf("unexpected vm id: %+v", action.set)
|
||||
}
|
||||
if action.set.VCPUCount == nil || *action.set.VCPUCount != 6 {
|
||||
t.Fatalf("unexpected vcpu: %+v", action.set)
|
||||
}
|
||||
if action.set.MemoryMiB == nil || *action.set.MemoryMiB != 4096 {
|
||||
t.Fatalf("unexpected memory: %+v", action.set)
|
||||
}
|
||||
if action.set.WorkDiskSize != "32G" {
|
||||
t.Fatalf("unexpected disk size: %+v", action.set)
|
||||
}
|
||||
if action.set.NATEnabled == nil || !*action.set.NATEnabled {
|
||||
t.Fatalf("expected nat enabled: %+v", action.set)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveSelectedID(t *testing.T) {
|
||||
vms := []model.VMRecord{{ID: "one"}, {ID: "two"}}
|
||||
if got := resolveSelectedID("two", vms); got != "two" {
|
||||
t.Fatalf("resolveSelectedID existing = %q, want %q", got, "two")
|
||||
}
|
||||
if got := resolveSelectedID("missing", vms); got != "one" {
|
||||
t.Fatalf("resolveSelectedID fallback = %q, want %q", got, "one")
|
||||
}
|
||||
if got := resolveSelectedID("anything", nil); got != "" {
|
||||
t.Fatalf("resolveSelectedID empty = %q, want empty", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTUICommandStartsProgramWithoutEnsuringDaemon(t *testing.T) {
|
||||
origEnsure := tuiEnsureDaemonFunc
|
||||
origRunner := tuiProgramRunner
|
||||
origTerminal := tuiIsTerminal
|
||||
t.Cleanup(func() {
|
||||
tuiEnsureDaemonFunc = origEnsure
|
||||
tuiProgramRunner = origRunner
|
||||
tuiIsTerminal = origTerminal
|
||||
})
|
||||
|
||||
ensureCalled := false
|
||||
tuiEnsureDaemonFunc = func(ctx context.Context) (paths.Layout, model.DaemonConfig, error) {
|
||||
ensureCalled = true
|
||||
return paths.Layout{}, model.DaemonConfig{}, nil
|
||||
}
|
||||
tuiProgramRunner = func(model tuiModel) error {
|
||||
if ensureCalled {
|
||||
t.Fatal("ensureDaemon should not run before the TUI starts")
|
||||
}
|
||||
if !model.daemonPending || !model.loading {
|
||||
t.Fatalf("startup model = %+v, want pending daemon startup", model)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
tuiIsTerminal = func(fd uintptr) bool { return true }
|
||||
|
||||
cmd := NewBangerCommand()
|
||||
cmd.SetArgs([]string{"tui"})
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("Execute: %v", err)
|
||||
}
|
||||
if ensureCalled {
|
||||
t.Fatal("ensureDaemon should not have been called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTUIViewRendersLayoutImmediately(t *testing.T) {
|
||||
m := newTUIModel(paths.Layout{}, model.DaemonConfig{})
|
||||
view := m.View()
|
||||
if strings.Contains(view, "Loading...") {
|
||||
t.Fatalf("view = %q, want full layout instead of one-line loading", view)
|
||||
}
|
||||
if !strings.Contains(view, "Starting daemon") {
|
||||
t.Fatalf("view = %q, want startup placeholder", view)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTUIVMLoadCanCompleteBeforeImages(t *testing.T) {
|
||||
now := time.Date(2026, time.March, 18, 12, 0, 0, 0, time.UTC)
|
||||
initial := newTUIModel(paths.Layout{}, model.DaemonConfig{})
|
||||
|
||||
updated, _ := initial.Update(daemonReadyMsg{
|
||||
generation: initial.loadGeneration,
|
||||
layout: paths.Layout{SocketPath: "/tmp/bangerd.sock"},
|
||||
cfg: model.DaemonConfig{DefaultImageName: "default"},
|
||||
duration: 2400 * time.Millisecond,
|
||||
})
|
||||
m := updated.(tuiModel)
|
||||
if !m.daemonReady || !m.vmListPending || !m.imagePending {
|
||||
t.Fatalf("model after daemonReady = %+v, want pending vm/image loads", m)
|
||||
}
|
||||
|
||||
vm := model.VMRecord{
|
||||
ID: "vm-1",
|
||||
Name: "devbox",
|
||||
State: model.VMStateRunning,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
LastTouchedAt: now,
|
||||
Spec: model.VMSpec{
|
||||
VCPUCount: 2,
|
||||
MemoryMiB: 1024,
|
||||
WorkDiskSizeBytes: 16 * 1024 * 1024 * 1024,
|
||||
},
|
||||
Runtime: model.VMRuntime{
|
||||
GuestIP: "172.16.0.2",
|
||||
DNSName: "devbox.vm",
|
||||
},
|
||||
}
|
||||
updated, _ = m.Update(vmListLoadedMsg{
|
||||
generation: m.loadGeneration,
|
||||
vms: []model.VMRecord{vm},
|
||||
duration: 20 * time.Millisecond,
|
||||
})
|
||||
m = updated.(tuiModel)
|
||||
if len(m.vms) != 1 || m.selectedID != vm.ID {
|
||||
t.Fatalf("model after vmListLoaded = %+v, want selected vm", m)
|
||||
}
|
||||
if !m.imagePending {
|
||||
t.Fatalf("image load should still be pending: %+v", m)
|
||||
}
|
||||
if strings.Contains(m.View(), "No VMs") {
|
||||
t.Fatalf("view should render the loaded VM while images are pending: %q", m.View())
|
||||
}
|
||||
if !strings.Contains(m.View(), "devbox") {
|
||||
t.Fatalf("view = %q, want loaded VM name", m.View())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTUICreateBlockedWhileImagesLoad(t *testing.T) {
|
||||
m := newTUIModel(paths.Layout{}, model.DaemonConfig{})
|
||||
m.daemonPending = false
|
||||
m.daemonReady = true
|
||||
m.imagePending = true
|
||||
m.loading = true
|
||||
|
||||
updated, _ := m.updateBrowse(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'c'}})
|
||||
if updated.mode != tuiModeBrowse {
|
||||
t.Fatalf("mode = %v, want browse", updated.mode)
|
||||
}
|
||||
if updated.statusText != "Images are still loading" {
|
||||
t.Fatalf("status = %q, want image loading warning", updated.statusText)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTUIStatusIncludesStageDurationsAfterInitialLoad(t *testing.T) {
|
||||
initial := newTUIModel(paths.Layout{}, model.DaemonConfig{})
|
||||
updated, _ := initial.Update(daemonReadyMsg{
|
||||
generation: initial.loadGeneration,
|
||||
layout: paths.Layout{SocketPath: "/tmp/bangerd.sock"},
|
||||
duration: 2400 * time.Millisecond,
|
||||
})
|
||||
m := updated.(tuiModel)
|
||||
updated, _ = m.Update(vmListLoadedMsg{
|
||||
generation: m.loadGeneration,
|
||||
vms: []model.VMRecord{},
|
||||
duration: 20 * time.Millisecond,
|
||||
})
|
||||
m = updated.(tuiModel)
|
||||
updated, _ = m.Update(imageListLoadedMsg{
|
||||
generation: m.loadGeneration,
|
||||
images: []model.Image{{Name: "default"}},
|
||||
duration: 15 * time.Millisecond,
|
||||
})
|
||||
m = updated.(tuiModel)
|
||||
if !strings.Contains(m.statusText, "daemon 2.4s") || !strings.Contains(m.statusText, "vm list 20ms") || !strings.Contains(m.statusText, "image list 15ms") {
|
||||
t.Fatalf("statusText = %q, want stage timings", m.statusText)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHDoneMsgShowsReminderWhenHealthCheckPasses(t *testing.T) {
|
||||
origHealth := vmHealthFunc
|
||||
t.Cleanup(func() {
|
||||
vmHealthFunc = origHealth
|
||||
})
|
||||
vmHealthFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMHealthResult, error) {
|
||||
return api.VMHealthResult{Name: "devbox", Healthy: true}, nil
|
||||
}
|
||||
|
||||
msg := sshDoneMsg(paths.Layout{SocketPath: "/tmp/bangerd.sock"}, actionRequest{id: "devbox", name: "devbox"}, "devbox", nil)
|
||||
result, ok := msg.(actionResultMsg)
|
||||
if !ok {
|
||||
t.Fatalf("msg = %T, want actionResultMsg", msg)
|
||||
}
|
||||
if !strings.Contains(result.status, "devbox is still running") {
|
||||
t.Fatalf("status = %q, want reminder", result.status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHDoneMsgShowsWarningWhenHealthCheckFails(t *testing.T) {
|
||||
origHealth := vmHealthFunc
|
||||
t.Cleanup(func() {
|
||||
vmHealthFunc = origHealth
|
||||
})
|
||||
vmHealthFunc = func(ctx context.Context, socketPath, idOrName string) (api.VMHealthResult, error) {
|
||||
return api.VMHealthResult{}, errors.New("dial failed")
|
||||
}
|
||||
|
||||
msg := sshDoneMsg(paths.Layout{SocketPath: "/tmp/bangerd.sock"}, actionRequest{id: "devbox", name: "devbox"}, "devbox", nil)
|
||||
result := msg.(actionResultMsg)
|
||||
if !strings.Contains(result.status, "failed to check whether devbox is still running") {
|
||||
t.Fatalf("status = %q, want warning", result.status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateRunningVMResources(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
running, vcpus, memoryBytes := aggregateRunningVMResources([]model.VMRecord{
|
||||
{
|
||||
State: model.VMStateRunning,
|
||||
Spec: model.VMSpec{
|
||||
VCPUCount: 2,
|
||||
MemoryMiB: 1024,
|
||||
},
|
||||
},
|
||||
{
|
||||
State: model.VMStateStopped,
|
||||
Spec: model.VMSpec{
|
||||
VCPUCount: 8,
|
||||
MemoryMiB: 8192,
|
||||
},
|
||||
},
|
||||
{
|
||||
State: model.VMStateRunning,
|
||||
Spec: model.VMSpec{
|
||||
VCPUCount: 4,
|
||||
MemoryMiB: 2048,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if running != 2 || vcpus != 6 || memoryBytes != 3*1024*1024*1024 {
|
||||
t.Fatalf("aggregateRunningVMResources = (%d, %d, %d), want (2, 6, %d)", running, vcpus, memoryBytes, int64(3*1024*1024*1024))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTUIViewShowsResourceBar(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := newTUIModel(paths.Layout{}, model.DaemonConfig{})
|
||||
m.hostCPUCount = 32
|
||||
m.hostMemoryBytes = 125 * 1024 * 1024 * 1024
|
||||
m.hostDiskBytes = 200 * 1024 * 1024 * 1024
|
||||
m.daemonPending = false
|
||||
m.loading = false
|
||||
stateDir := t.TempDir()
|
||||
overlayPath := filepath.Join(stateDir, "system.cow")
|
||||
workDiskPath := filepath.Join(stateDir, "root.ext4")
|
||||
if err := os.WriteFile(overlayPath, make([]byte, 1024), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile overlay: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(workDiskPath, make([]byte, 2048), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile work disk: %v", err)
|
||||
}
|
||||
m.vms = []model.VMRecord{
|
||||
{
|
||||
ID: "vm-1",
|
||||
Name: "devbox",
|
||||
State: model.VMStateRunning,
|
||||
Spec: model.VMSpec{
|
||||
VCPUCount: 2,
|
||||
MemoryMiB: 1024,
|
||||
WorkDiskSizeBytes: 16 * 1024 * 1024 * 1024,
|
||||
},
|
||||
Runtime: model.VMRuntime{
|
||||
SystemOverlay: overlayPath,
|
||||
WorkDiskPath: workDiskPath,
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "vm-2",
|
||||
Name: "db",
|
||||
State: model.VMStateStopped,
|
||||
Spec: model.VMSpec{
|
||||
VCPUCount: 4,
|
||||
MemoryMiB: 4096,
|
||||
WorkDiskSizeBytes: 32 * 1024 * 1024 * 1024,
|
||||
},
|
||||
},
|
||||
}
|
||||
m.selectedID = "vm-1"
|
||||
m.rebuildTable()
|
||||
m.refreshDetail()
|
||||
|
||||
view := m.View()
|
||||
if !strings.Contains(view, "VMs") || !strings.Contains(view, "1/2") {
|
||||
t.Fatalf("view = %q, want running VM count", view)
|
||||
}
|
||||
if !strings.Contains(view, "CPU") || !strings.Contains(view, "2/32") {
|
||||
t.Fatalf("view = %q, want vcpu aggregate", view)
|
||||
}
|
||||
if !strings.Contains(view, "RAM") || !strings.Contains(view, "1.0G/125.0G") {
|
||||
t.Fatalf("view = %q, want memory aggregate", view)
|
||||
}
|
||||
if !strings.Contains(view, "Disk") {
|
||||
t.Fatalf("view = %q, want disk aggregate", view)
|
||||
}
|
||||
if !strings.Contains(view, "█") || !strings.Contains(view, "░") {
|
||||
t.Fatalf("view = %q, want visual progress bars", view)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateVMDiskUsage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
overlayPath := filepath.Join(dir, "system.cow")
|
||||
workDiskPath := filepath.Join(dir, "root.ext4")
|
||||
if err := os.WriteFile(overlayPath, make([]byte, 4096), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile overlay: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(workDiskPath, make([]byte, 8192), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile work disk: %v", err)
|
||||
}
|
||||
|
||||
total := aggregateVMDiskUsage([]model.VMRecord{{
|
||||
Runtime: model.VMRuntime{
|
||||
SystemOverlay: overlayPath,
|
||||
WorkDiskPath: workDiskPath,
|
||||
},
|
||||
}})
|
||||
if total <= 0 {
|
||||
t.Fatalf("aggregateVMDiskUsage = %d, want positive allocated bytes", total)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue