Pure code motion — banger.go 3508→240 LOC, same-package decomposition keeps all identifiers visible without export changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
318 lines
7.5 KiB
Go
318 lines
7.5 KiB
Go
package cli
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"banger/internal/api"
|
|
"banger/internal/model"
|
|
"banger/internal/system"
|
|
)
|
|
|
|
// anyWriter is the minimal writer surface every printer needs. Split
|
|
// out from io.Writer because some of our callers already hold a
|
|
// tabwriter/bytes.Buffer by value.
|
|
type anyWriter interface {
|
|
Write(p []byte) (n int, err error)
|
|
}
|
|
|
|
// -- small helpers --------------------------------------------------
|
|
|
|
func humanSize(bytes int64) string {
|
|
if bytes <= 0 {
|
|
return "-"
|
|
}
|
|
const (
|
|
kib = 1024
|
|
mib = 1024 * kib
|
|
gib = 1024 * mib
|
|
)
|
|
switch {
|
|
case bytes >= gib:
|
|
return fmt.Sprintf("%.1fGiB", float64(bytes)/float64(gib))
|
|
case bytes >= mib:
|
|
return fmt.Sprintf("%.1fMiB", float64(bytes)/float64(mib))
|
|
case bytes >= kib:
|
|
return fmt.Sprintf("%.1fKiB", float64(bytes)/float64(kib))
|
|
default:
|
|
return fmt.Sprintf("%dB", bytes)
|
|
}
|
|
}
|
|
|
|
func dashIfEmpty(s string) string {
|
|
if strings.TrimSpace(s) == "" {
|
|
return "-"
|
|
}
|
|
return s
|
|
}
|
|
|
|
func emptyDash(value string) string {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return "-"
|
|
}
|
|
return value
|
|
}
|
|
|
|
// -- generic printers -----------------------------------------------
|
|
|
|
func printJSON(out anyWriter, v any) error {
|
|
data, err := json.MarshalIndent(v, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = fmt.Fprintln(out, string(data))
|
|
return err
|
|
}
|
|
|
|
// -- VM printers ----------------------------------------------------
|
|
|
|
func printVMSummary(out anyWriter, vm model.VMRecord) error {
|
|
_, err := fmt.Fprintf(
|
|
out,
|
|
"%s\t%s\t%s\t%s\t%s\t%s\n",
|
|
shortID(vm.ID),
|
|
vm.Name,
|
|
vm.State,
|
|
vm.Runtime.GuestIP,
|
|
model.FormatSizeBytes(vm.Spec.WorkDiskSizeBytes),
|
|
vm.Runtime.DNSName,
|
|
)
|
|
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 {
|
|
return err
|
|
}
|
|
for _, vm := range vms {
|
|
if _, err := fmt.Fprintf(
|
|
w,
|
|
"%s\t%s\t%s\t%s\t%s\t%d\t%d MiB\t%s\t%s\n",
|
|
shortID(vm.ID),
|
|
vm.Name,
|
|
vm.State,
|
|
vmImageLabel(vm.ImageID, imageNames),
|
|
vm.Runtime.GuestIP,
|
|
vm.Spec.VCPUCount,
|
|
vm.Spec.MemoryMiB,
|
|
model.FormatSizeBytes(vm.Spec.WorkDiskSizeBytes),
|
|
relativeTime(vm.CreatedAt),
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return w.Flush()
|
|
}
|
|
|
|
func printVMPortsTable(out anyWriter, result api.VMPortsResult) error {
|
|
type portRow struct {
|
|
Proto string
|
|
Endpoint string
|
|
Process string
|
|
Command string
|
|
Port int
|
|
}
|
|
rows := make([]portRow, 0, len(result.Ports))
|
|
for _, port := range result.Ports {
|
|
rows = append(rows, portRow{
|
|
Proto: port.Proto,
|
|
Endpoint: port.Endpoint,
|
|
Process: port.Process,
|
|
Command: port.Command,
|
|
Port: port.Port,
|
|
})
|
|
}
|
|
sort.Slice(rows, func(i, j int) bool {
|
|
if rows[i].Proto != rows[j].Proto {
|
|
return rows[i].Proto < rows[j].Proto
|
|
}
|
|
if rows[i].Port != rows[j].Port {
|
|
return rows[i].Port < rows[j].Port
|
|
}
|
|
if rows[i].Process != rows[j].Process {
|
|
return rows[i].Process < rows[j].Process
|
|
}
|
|
return rows[i].Command < rows[j].Command
|
|
})
|
|
if len(rows) == 0 {
|
|
return nil
|
|
}
|
|
|
|
w := tabwriter.NewWriter(out, 0, 8, 2, ' ', 0)
|
|
if _, err := fmt.Fprintln(w, "PROTO\tENDPOINT\tPROCESS\tCOMMAND"); err != nil {
|
|
return err
|
|
}
|
|
for _, row := range rows {
|
|
if _, err := fmt.Fprintf(
|
|
w,
|
|
"%s\t%s\t%s\t%s\n",
|
|
row.Proto,
|
|
emptyDash(row.Endpoint),
|
|
emptyDash(row.Process),
|
|
emptyDash(row.Command),
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return w.Flush()
|
|
}
|
|
|
|
// -- image printers -------------------------------------------------
|
|
|
|
func printImageSummary(out anyWriter, image model.Image) error {
|
|
_, err := fmt.Fprintf(out, "%s\t%s\t%t\t%s\n", shortID(image.ID), image.Name, image.Managed, image.RootfsPath)
|
|
return err
|
|
}
|
|
|
|
func imageNameIndex(images []model.Image) map[string]string {
|
|
index := make(map[string]string, len(images))
|
|
for _, image := range images {
|
|
index[image.ID] = image.Name
|
|
}
|
|
return index
|
|
}
|
|
|
|
func vmImageLabel(imageID string, imageNames map[string]string) string {
|
|
if name := strings.TrimSpace(imageNames[imageID]); name != "" {
|
|
return name
|
|
}
|
|
return shortID(imageID)
|
|
}
|
|
|
|
func printImageListTable(out anyWriter, images []model.Image) error {
|
|
w := tabwriter.NewWriter(out, 0, 8, 2, ' ', 0)
|
|
if _, err := fmt.Fprintln(w, "ID\tNAME\tMANAGED\tROOTFS SIZE\tCREATED"); err != nil {
|
|
return err
|
|
}
|
|
for _, image := range images {
|
|
if _, err := fmt.Fprintf(
|
|
w,
|
|
"%s\t%s\t%t\t%s\t%s\n",
|
|
shortID(image.ID),
|
|
image.Name,
|
|
image.Managed,
|
|
rootfsSizeLabel(image.RootfsPath),
|
|
relativeTime(image.CreatedAt),
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return w.Flush()
|
|
}
|
|
|
|
func rootfsSizeLabel(path string) string {
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
return "-"
|
|
}
|
|
if info.Size() <= 0 {
|
|
return "0"
|
|
}
|
|
return model.FormatSizeBytes(info.Size())
|
|
}
|
|
|
|
// -- kernel printers ------------------------------------------------
|
|
|
|
func printKernelListTable(out anyWriter, entries []api.KernelEntry) error {
|
|
w := tabwriter.NewWriter(out, 0, 8, 2, ' ', 0)
|
|
if _, err := fmt.Fprintln(w, "NAME\tDISTRO\tARCH\tKERNEL\tIMPORTED"); err != nil {
|
|
return err
|
|
}
|
|
for _, entry := range entries {
|
|
if _, err := fmt.Fprintf(
|
|
w,
|
|
"%s\t%s\t%s\t%s\t%s\n",
|
|
entry.Name,
|
|
dashIfEmpty(entry.Distro),
|
|
dashIfEmpty(entry.Arch),
|
|
dashIfEmpty(entry.KernelVersion),
|
|
dashIfEmpty(entry.ImportedAt),
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return w.Flush()
|
|
}
|
|
|
|
func printKernelCatalogTable(out anyWriter, entries []api.KernelCatalogEntry) error {
|
|
w := tabwriter.NewWriter(out, 0, 8, 2, ' ', 0)
|
|
if _, err := fmt.Fprintln(w, "NAME\tDISTRO\tARCH\tKERNEL\tSIZE\tSTATE"); err != nil {
|
|
return err
|
|
}
|
|
for _, entry := range entries {
|
|
state := "available"
|
|
if entry.Pulled {
|
|
state = "pulled"
|
|
}
|
|
if _, err := fmt.Fprintf(
|
|
w,
|
|
"%s\t%s\t%s\t%s\t%s\t%s\n",
|
|
entry.Name,
|
|
dashIfEmpty(entry.Distro),
|
|
dashIfEmpty(entry.Arch),
|
|
dashIfEmpty(entry.KernelVersion),
|
|
humanSize(entry.SizeBytes),
|
|
state,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return w.Flush()
|
|
}
|
|
|
|
// -- guest session printers -----------------------------------------
|
|
|
|
func printGuestSessionSummary(out anyWriter, session model.GuestSession) error {
|
|
_, err := fmt.Fprintf(out, "%s\t%s\t%s\t%s\t%s\n", session.ID, session.Name, session.Status, session.Command, session.CWD)
|
|
return err
|
|
}
|
|
|
|
func printGuestSessionTable(out io.Writer, sessions []model.GuestSession) error {
|
|
tw := tabwriter.NewWriter(out, 0, 0, 2, ' ', 0)
|
|
if _, err := fmt.Fprintln(tw, "ID\tNAME\tSTATUS\tATTACH\tCOMMAND\tCWD"); err != nil {
|
|
return err
|
|
}
|
|
for _, session := range sessions {
|
|
attach := "no"
|
|
if session.Attachable {
|
|
attach = "yes"
|
|
}
|
|
if _, err := fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\n", shortID(session.ID), session.Name, session.Status, attach, session.Command, session.CWD); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return tw.Flush()
|
|
}
|
|
|
|
// -- doctor printer -------------------------------------------------
|
|
|
|
func printDoctorReport(out anyWriter, report system.Report) error {
|
|
for _, check := range report.Checks {
|
|
status := strings.ToUpper(string(check.Status))
|
|
if _, err := fmt.Fprintf(out, "%s\t%s\n", status, check.Name); err != nil {
|
|
return err
|
|
}
|
|
for _, detail := range check.Details {
|
|
if _, err := fmt.Fprintf(out, " - %s\n", detail); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|