Teach VM listing Docker-style aliases and filters

Make `banger ps` a true alias of `banger vm list` and add `banger vm ls`
and `banger vm ps` so the common listing entrypoints all share one path.

Default the shared list command to running VMs only, add `--all` to include
stopped entries, `--latest` to keep only the newest matching VM, and `--quiet`
to print full VM IDs without the table renderer.

Cover the alias wiring plus the running/latest/quiet helpers in CLI tests.
Validation: go test ./internal/cli; GOCACHE=/tmp/banger-gocache go test ./...;
make build; ./build/bin/banger ps --help; ./build/bin/banger vm ls --help.
This commit is contained in:
Thales Maciel 2026-03-31 13:03:12 -03:00
parent 671723a0ef
commit dbc70643c3
No known key found for this signature in database
GPG key ID: 33112E6833C34679
2 changed files with 164 additions and 19 deletions

View file

@ -150,7 +150,7 @@ func NewBangerCommand() *cobra.Command {
RunE: helpNoArgs,
}
root.CompletionOptions.DisableDefaultCmd = true
root.AddCommand(newDaemonCommand(), newDoctorCommand(), newImageCommand(), newInternalCommand(), newVersionCommand(), newVMCommand())
root.AddCommand(newDaemonCommand(), newDoctorCommand(), newImageCommand(), newInternalCommand(), newVersionCommand(), newPSCommand(), newVMCommand())
return root
}
@ -626,27 +626,79 @@ func newVMCreateCommand() *cobra.Command {
return cmd
}
type vmListOptions struct {
showAll bool
latest bool
quiet bool
}
func newPSCommand() *cobra.Command {
return newVMListLikeCommand("ps", nil, "usage: banger ps")
}
func newVMListCommand() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List VMs",
Args: noArgsUsage("usage: banger vm list"),
return newVMListLikeCommand("list", []string{"ls", "ps"}, "usage: banger vm list")
}
func newVMListLikeCommand(use string, aliases []string, usage string) *cobra.Command {
var opts vmListOptions
cmd := &cobra.Command{
Use: use,
Aliases: aliases,
Short: "List VMs",
Args: noArgsUsage(usage),
RunE: func(cmd *cobra.Command, args []string) error {
layout, _, err := ensureDaemon(cmd.Context())
if err != nil {
return err
}
result, err := rpc.Call[api.VMListResult](cmd.Context(), layout.SocketPath, "vm.list", api.Empty{})
if err != nil {
return err
}
images, err := rpc.Call[api.ImageListResult](cmd.Context(), layout.SocketPath, "image.list", api.Empty{})
if err != nil {
return err
}
return printVMListTable(cmd.OutOrStdout(), result.VMs, imageNameIndex(images.Images))
return runVMList(cmd, opts)
},
}
cmd.Flags().BoolVarP(&opts.showAll, "all", "a", false, "show all VMs")
cmd.Flags().BoolVarP(&opts.latest, "latest", "l", false, "show only the latest VM")
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "only show VM IDs")
return cmd
}
func runVMList(cmd *cobra.Command, opts vmListOptions) error {
layout, _, err := ensureDaemon(cmd.Context())
if err != nil {
return err
}
result, err := rpc.Call[api.VMListResult](cmd.Context(), layout.SocketPath, "vm.list", api.Empty{})
if err != nil {
return err
}
vms := selectVMListVMs(result.VMs, opts.showAll, opts.latest)
if opts.quiet {
return printVMIDList(cmd.OutOrStdout(), vms)
}
images, err := rpc.Call[api.ImageListResult](cmd.Context(), layout.SocketPath, "image.list", api.Empty{})
if err != nil {
return err
}
return printVMListTable(cmd.OutOrStdout(), vms, imageNameIndex(images.Images))
}
func selectVMListVMs(vms []model.VMRecord, showAll, latest bool) []model.VMRecord {
filtered := make([]model.VMRecord, 0, len(vms))
for _, vm := range vms {
if !showAll && vm.State != model.VMStateRunning {
continue
}
filtered = append(filtered, vm)
}
if !latest || len(filtered) <= 1 {
return filtered
}
latestVM := filtered[0]
for _, vm := range filtered[1:] {
if vm.CreatedAt.After(latestVM.CreatedAt) {
latestVM = vm
continue
}
if vm.CreatedAt.Equal(latestVM.CreatedAt) && vm.UpdatedAt.After(latestVM.UpdatedAt) {
latestVM = vm
}
}
return []model.VMRecord{latestVM}
}
func newVMShowCommand() *cobra.Command {
@ -2054,6 +2106,15 @@ func printVMSummary(out anyWriter, vm model.VMRecord) error {
return err
}
func printVMIDList(out anyWriter, vms []model.VMRecord) error {
for _, vm := range vms {
if _, err := fmt.Fprintln(out, vm.ID); err != nil {
return err
}
}
return nil
}
func printVMListTable(out anyWriter, vms []model.VMRecord, imageNames map[string]string) error {
w := tabwriter.NewWriter(out, 0, 8, 2, ' ', 0)
if _, err := fmt.Fprintln(w, "ID\tNAME\tSTATE\tIMAGE\tIP\tVCPU\tMEM\tDISK\tCREATED"); err != nil {