diff --git a/internal/cli/banger.go b/internal/cli/banger.go index f0ff81f..39601ac 100644 --- a/internal/cli/banger.go +++ b/internal/cli/banger.go @@ -809,12 +809,7 @@ func newImageListCommand() *cobra.Command { if err != nil { return err } - w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 8, 2, ' ', 0) - fmt.Fprintln(w, "ID\tNAME\tMANAGED\tROOTFS\tCREATED") - for _, image := range result.Images { - fmt.Fprintf(w, "%s\t%s\t%t\t%s\t%s\n", shortID(image.ID), image.Name, image.Managed, image.RootfsPath, relativeTime(image.CreatedAt)) - } - return w.Flush() + return printImageListTable(cmd.OutOrStdout(), result.Images) }, } } @@ -1318,6 +1313,38 @@ func printImageSummary(out anyWriter, image model.Image) error { return err } +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()) +} + func printVMPortsTable(out anyWriter, result api.VMPortsResult) error { type portRow struct { Proto string diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index 6482353..a515757 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -474,6 +474,51 @@ func TestAbsolutizeImageRegisterPaths(t *testing.T) { } } +func TestPrintImageListTableShowsRootfsSizes(t *testing.T) { + rootfs := filepath.Join(t.TempDir(), "rootfs.ext4") + if err := os.WriteFile(rootfs, nil, 0o644); err != nil { + t.Fatalf("WriteFile(%s): %v", rootfs, err) + } + if err := os.Truncate(rootfs, 8*1024); err != nil { + t.Fatalf("Truncate(%s): %v", rootfs, err) + } + + var out bytes.Buffer + err := printImageListTable(&out, []model.Image{ + { + ID: "0123456789abcdef", + Name: "alpine", + Managed: true, + RootfsPath: rootfs, + CreatedAt: time.Now().Add(-1 * time.Hour), + }, + { + ID: "fedcba9876543210", + Name: "missing", + Managed: false, + RootfsPath: filepath.Join(t.TempDir(), "missing.ext4"), + CreatedAt: time.Now().Add(-2 * time.Hour), + }, + }) + if err != nil { + t.Fatalf("printImageListTable() error = %v", err) + } + + output := out.String() + if !strings.Contains(output, "ROOTFS SIZE") { + t.Fatalf("output = %q, want rootfs size header", output) + } + if !strings.Contains(output, "alpine") || !strings.Contains(output, "8K") { + t.Fatalf("output = %q, want alpine row with 8K size", output) + } + if strings.Contains(output, rootfs) { + t.Fatalf("output = %q, should not include rootfs path", output) + } + if !strings.Contains(output, "missing") || !strings.Contains(output, "-") { + t.Fatalf("output = %q, want fallback size for missing image", output) + } +} + func TestPrintVMPortsTableSortsAndRendersURLEndpoints(t *testing.T) { result := api.VMPortsResult{ Name: "alpha",