From b9053e525b37b51502046b8a1da9c14ee9d3b397 Mon Sep 17 00:00:00 2001 From: Jonas Tingeborn <134889+jojje@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:24:33 +0100 Subject: [PATCH 1/3] Refactor extract table specific rendering Prepare for supporting multiple output formats --- cmd/pod/getPod.go | 92 ++++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/cmd/pod/getPod.go b/cmd/pod/getPod.go index 4d4ee81..27c6f31 100644 --- a/cmd/pod/getPod.go +++ b/cmd/pod/getPod.go @@ -23,55 +23,59 @@ var GetPodCmd = &cobra.Command{ pods, err := api.GetPods() cobra.CheckErr(err) - data := make([][]string, len(pods)) - for i, p := range pods { - if len(args) == 1 && p.Id != strings.ToLower(args[0]) { - continue - } - row := []string{p.Id, p.Name, fmt.Sprintf("%d %s", p.GpuCount, p.Machine.GpuDisplayName), p.ImageName, p.DesiredStatus} - if AllFields { + toTable(pods, args) + }, +} - var portEntries int = 0 - if p.Runtime != nil && p.Runtime.Ports != nil { - portEntries = len(p.Runtime.Ports) - } - ports := make([]string, portEntries) - for j := range ports { - var privpub string = "prv" - if p.Runtime.Ports[j].IsIpPublic { - privpub = "pub" - } - ports[j] = fmt.Sprintf("%s:%d->%d\u00A0(%s,%s)", p.Runtime.Ports[j].Ip, p.Runtime.Ports[j].PublicPort, p.Runtime.Ports[j].PrivatePort, privpub, p.Runtime.Ports[j].PortType) - } +func init() { + GetPodCmd.Flags().BoolVarP(&AllFields, "allfields", "a", false, "include all fields in output") +} - row = append( - row, - p.PodType, - fmt.Sprintf("%d", p.VcpuCount), - fmt.Sprintf("%d", p.MemoryInGb), - fmt.Sprintf("%d", p.ContainerDiskInGb), - fmt.Sprintf("%d", p.VolumeInGb), - fmt.Sprintf("%s", p.Machine.Location), - fmt.Sprintf("%.3f", p.CostPerHr), - fmt.Sprintf("%s", strings.Join(ports[:], ",")), - ) - } - data[i] = row +func toTable(pods []*api.Pod, args []string) { + data := make([][]string, len(pods)) + for i, p := range pods { + if len(args) == 1 && p.Id != strings.ToLower(args[0]) { + continue } - - header := []string{"ID", "Name", "GPU", "Image Name", "Status"} + row := []string{p.Id, p.Name, fmt.Sprintf("%d %s", p.GpuCount, p.Machine.GpuDisplayName), p.ImageName, p.DesiredStatus} if AllFields { - header = append(header, "Pod Type", "vCPU", "Mem", "Container Disk", "Volume Disk", "Location", "$/hr", "Ports") + + var portEntries int = 0 + if p.Runtime != nil && p.Runtime.Ports != nil { + portEntries = len(p.Runtime.Ports) + } + ports := make([]string, portEntries) + for j := range ports { + var privpub string = "prv" + if p.Runtime.Ports[j].IsIpPublic { + privpub = "pub" + } + ports[j] = fmt.Sprintf("%s:%d->%d\u00A0(%s,%s)", p.Runtime.Ports[j].Ip, p.Runtime.Ports[j].PublicPort, p.Runtime.Ports[j].PrivatePort, privpub, p.Runtime.Ports[j].PortType) + } + + row = append( + row, + p.PodType, + fmt.Sprintf("%d", p.VcpuCount), + fmt.Sprintf("%d", p.MemoryInGb), + fmt.Sprintf("%d", p.ContainerDiskInGb), + fmt.Sprintf("%d", p.VolumeInGb), + fmt.Sprintf("%s", p.Machine.Location), + fmt.Sprintf("%.3f", p.CostPerHr), + fmt.Sprintf("%s", strings.Join(ports[:], ",")), + ) } + data[i] = row + } - tb := tablewriter.NewWriter(os.Stdout) - tb.SetHeader(header) - tb.AppendBulk(data) - format.TableDefaults(tb) - tb.Render() - }, -} + header := []string{"ID", "Name", "GPU", "Image Name", "Status"} + if AllFields { + header = append(header, "Pod Type", "vCPU", "Mem", "Container Disk", "Volume Disk", "Location", "$/hr", "Ports") + } -func init() { - GetPodCmd.Flags().BoolVarP(&AllFields, "allfields", "a", false, "include all fields in output") + tb := tablewriter.NewWriter(os.Stdout) + tb.SetHeader(header) + tb.AppendBulk(data) + format.TableDefaults(tb) + tb.Render() } From 9150c67c90d7d1801c3fa291f396722b42e53c2a Mon Sep 17 00:00:00 2001 From: Jonas Tingeborn <134889+jojje@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:44:58 +0100 Subject: [PATCH 2/3] Refactor extract pod filtering Decompose rendering and filtering to enable consistent pod filtering irrespective of output rendering format. --- cmd/pod/getPod.go | 17 +++++++++++++---- cmd/pod/getPod_test.go | 43 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 cmd/pod/getPod_test.go diff --git a/cmd/pod/getPod.go b/cmd/pod/getPod.go index 27c6f31..021a405 100644 --- a/cmd/pod/getPod.go +++ b/cmd/pod/getPod.go @@ -23,7 +23,8 @@ var GetPodCmd = &cobra.Command{ pods, err := api.GetPods() cobra.CheckErr(err) - toTable(pods, args) + pods = filter(pods, args) + toTable(pods) }, } @@ -31,12 +32,20 @@ func init() { GetPodCmd.Flags().BoolVarP(&AllFields, "allfields", "a", false, "include all fields in output") } -func toTable(pods []*api.Pod, args []string) { - data := make([][]string, len(pods)) - for i, p := range pods { +func filter(pods []*api.Pod, args []string) []*api.Pod { + filtered := make([]*api.Pod, 0) // ensures [] instead of "null" if no pods when mashalling + for _, p := range pods { if len(args) == 1 && p.Id != strings.ToLower(args[0]) { continue } + filtered = append(filtered, p) + } + return filtered +} + +func toTable(pods []*api.Pod) { + data := make([][]string, len(pods)) + for i, p := range pods { row := []string{p.Id, p.Name, fmt.Sprintf("%d %s", p.GpuCount, p.Machine.GpuDisplayName), p.ImageName, p.DesiredStatus} if AllFields { diff --git a/cmd/pod/getPod_test.go b/cmd/pod/getPod_test.go new file mode 100644 index 0000000..28750fe --- /dev/null +++ b/cmd/pod/getPod_test.go @@ -0,0 +1,43 @@ +package pod + +import ( + "reflect" + "testing" + + "github.com/runpod/runpodctl/api" +) + +func TestGetPod(t *testing.T) { + pods := []*api.Pod{ + {Id: "p1"}, + {Id: "p2"}, + {Id: "p3"}, + } + + t.Run("filter pods", func(t *testing.T) { + + t.Run("without argument", func(t *testing.T) { + got := filter(pods, nil) + assertEqualIDs(t, getIDs(pods), got) + }) + + t.Run("with argument", func(t *testing.T) { + got := filter(pods, []string{"p2"}) + assertEqualIDs(t, []string{"p2"}, got) + }) + }) +} + +func assertEqualIDs(t *testing.T, want []string, got []*api.Pod) { + if !reflect.DeepEqual(want, getIDs(got)) { + t.Errorf("got %v, want %v", getIDs(got), want) + } +} + +func getIDs(pods []*api.Pod) []string { + out := make([]string, len(pods)) + for i, p := range pods { + out[i] = p.Id + } + return out +} From 1ce79c1961069698cdec2bf76701a4abc28882ae Mon Sep 17 00:00:00 2001 From: Jonas Tingeborn <134889+jojje@users.noreply.github.com> Date: Thu, 22 Jan 2026 14:14:08 +0100 Subject: [PATCH 3/3] feat: get pod json output (#148) --- cmd/pod/getPod.go | 21 ++++++++++++++++++++- cmd/pod/getPod_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/cmd/pod/getPod.go b/cmd/pod/getPod.go index 021a405..ec0e88c 100644 --- a/cmd/pod/getPod.go +++ b/cmd/pod/getPod.go @@ -1,7 +1,9 @@ package pod import ( + "encoding/json" "fmt" + "io" "os" "strings" @@ -13,6 +15,7 @@ import ( ) var AllFields bool +var outputJSON bool var GetPodCmd = &cobra.Command{ Use: "pod [podId]", @@ -24,12 +27,18 @@ var GetPodCmd = &cobra.Command{ cobra.CheckErr(err) pods = filter(pods, args) - toTable(pods) + + if outputJSON { + cobra.CheckErr(toJSON(os.Stdout, pods)) + } else { + toTable(pods) + } }, } func init() { GetPodCmd.Flags().BoolVarP(&AllFields, "allfields", "a", false, "include all fields in output") + GetPodCmd.Flags().BoolVarP(&outputJSON, "json", "j", false, "use json as output format") } func filter(pods []*api.Pod, args []string) []*api.Pod { @@ -88,3 +97,13 @@ func toTable(pods []*api.Pod) { format.TableDefaults(tb) tb.Render() } + +func toJSON(w io.Writer, pods []*api.Pod) error { + b, err := json.MarshalIndent(pods, "", " ") + if err != nil { + return err + } + + _, err = w.Write(b) + return err +} diff --git a/cmd/pod/getPod_test.go b/cmd/pod/getPod_test.go index 28750fe..3374f3f 100644 --- a/cmd/pod/getPod_test.go +++ b/cmd/pod/getPod_test.go @@ -1,6 +1,8 @@ package pod import ( + "bytes" + "encoding/json" "reflect" "testing" @@ -26,6 +28,31 @@ func TestGetPod(t *testing.T) { assertEqualIDs(t, []string{"p2"}, got) }) }) + + t.Run("output json", func(t *testing.T) { + t.Run("with results", func(t *testing.T) { + var buf bytes.Buffer + + check(t, toJSON(&buf, pods)) + + var got []*api.Pod + check(t, json.NewDecoder(bytes.NewReader(buf.Bytes())).Decode(&got)) + + assertEqualIDs(t, []string{"p1", "p2", "p3"}, got) + }) + + t.Run("empty output", func(t *testing.T) { + var buf bytes.Buffer + + _ = toJSON(&buf, []*api.Pod{}) + got := string(buf.Bytes()) + + if got != "[]" { + t.Errorf("got %v, want %v", got, "[]") + } + }) + }) + } func assertEqualIDs(t *testing.T, want []string, got []*api.Pod) { @@ -34,6 +61,12 @@ func assertEqualIDs(t *testing.T, want []string, got []*api.Pod) { } } +func check(t *testing.T, err error) { + if err != nil { + t.Fatal(err) + } +} + func getIDs(pods []*api.Pod) []string { out := make([]string, len(pods)) for i, p := range pods {