Skip to content

Commit e7beaf5

Browse files
adrianreberRadostin Stoyanov
authored andcommitted
Replace olekukonko/tablewriter with text/tabwriter
Drop external tablewriter dependency in favor of Go's built-in text/tabwriter package to reduce dependencies and simplify maintenance. Changes: - Replace tablewriter.NewWriter() with tabwriter.NewWriter() in all table display functions - Update table formatting logic to use tab-separated output with headers and separator lines - Remove olekukonko/tablewriter and related dependencies from go.mod - Update test expectations to match new table output format - Fix test line number references after table format changes All tests pass with the new implementation. Assisted-by: Claude AI for dependency replacement and test updates Signed-off-by: Adrian Reber <areber@redhat.com> Signed-off-by: Radostin Stoyanov <rstoyano@redhat.com>
1 parent 6e6a8c6 commit e7beaf5

48 files changed

Lines changed: 127 additions & 19243 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,25 @@ To display an overview of a checkpoint archive you can just use
3030
```console
3131
$ checkpointctl show /tmp/dump.tar
3232

33-
+-----------------+------------------------------------------+--------------+---------+----------------------+--------+------------+-------------------+
34-
| CONTAINER | IMAGE | ID | RUNTIME | CREATED | ENGINE | CHKPT SIZE | ROOT FS DIFF SIZE |
35-
+-----------------+------------------------------------------+--------------+---------+----------------------+--------+------------+-------------------+
36-
| magical_murdock | quay.io/adrianreber/wildfly-hello:latest | f11d11844af0 | crun | 2023-02-28T09:43:52Z | Podman | 338.2 MiB | 177.0 KiB |
37-
+-----------------+------------------------------------------+--------------+---------+----------------------+--------+------------+-------------------+
33+
Displaying container checkpoint data from /root/dump.tar
34+
35+
CONTAINER IMAGE ID RUNTIME CREATED ENGINE CHKPT SIZE ROOT FS DIFF SIZE
36+
--------- ----- -- ------- ------- ------ ---------- -----------------
37+
looper docker.io/library/busybox:latest 8b5c2ca15082 crun 2021-09-28T10:03:56Z Podman 130.8 KiB 204 B
3838
```
3939

4040
For a checkpoint archive created by Kubernetes with *CRI-O* the output would
4141
look like this:
4242

4343
```console
44-
$ checkpointctl show /var/lib/kubelet/checkpoints/checkpoint-counters_default-counter-2023-02-13T16\:20\:09Z.tar
44+
$ checkpointctl show /var/lib/kubelet/checkpoints/checkpoint-counters_default-counter-2025-05-22T14\:31\:35Z.tar
45+
46+
Displaying container checkpoint data from /var/lib/kubelet/checkpoints/checkpoint-counters_default-counter-2025-05-22T14:31:35Z.tar
47+
48+
CONTAINER IMAGE ID RUNTIME CREATED ENGINE IP CHKPT SIZE ROOT FS DIFF SIZE
49+
--------- ----- -- ------- ------- ------ -- ---------- -----------------
50+
counter quay.io/adrianreber/counter:latest 29ed106ef467 runc 2025-05-22T14:31:24.818422898Z CRI-O 10.0.0.70 9.2 MiB 2.0 KiB
4551

46-
+-----------+------------------------------------+--------------+---------+--------------------------------+--------+------------+------------+
47-
| CONTAINER | IMAGE | ID | RUNTIME | CREATED | ENGINE | IP | CHKPT SIZE |
48-
+-----------+------------------------------------+--------------+---------+--------------------------------+--------+------------+------------+
49-
| counter | quay.io/adrianreber/counter:latest | 7eb9680287f1 | runc | 2023-02-13T16:12:25.843774934Z | CRI-O | 10.88.0.24 | 8.5 MiB |
50-
+-----------+------------------------------------+--------------+---------+--------------------------------+--------+------------+------------+
5152
```
5253

5354
### `inspect` sub-command
@@ -88,7 +89,7 @@ $ checkpointctl memparse /tmp/jira.tar.gz --pid=1 | less
8889

8990
Displaying memory pages content for Process ID 1 from checkpoint: /tmp/jira.tar.gz
9091

91-
Address Hexadecimal ASCII
92+
ADDRESS HEXADECIMAL ASCII
9293
-------------------------------------------------------------------------------------
9394
00005633bb080000 f3 0f 1e fa 48 83 ec 08 48 8b 05 d1 4f 00 00 48 |....H...H...O..H|
9495
00005633bb080010 85 c0 74 02 ff d0 48 83 c4 08 c3 00 00 00 00 00 |..t...H.........|
@@ -145,13 +146,10 @@ $ sudo checkpointctl memparse /tmp/jira.tar.gz
145146

146147
Displaying processes memory sizes from /tmp/jira.tar.gz
147148

148-
+-----+--------------+-------------+
149-
| PID | PROCESS NAME | MEMORY SIZE |
150-
+-----+--------------+-------------+
151-
| 1 | tini | 100.0 KiB |
152-
+-----+--------------+-------------+
153-
| 2 | java | 553.5 MiB |
154-
+-----+--------------+-------------+
149+
PID PROCESS NAME MEMORY SIZE SHARED MEMORY SIZE
150+
--- ------------ ----------- ------------------
151+
1 tini 100.0 KiB 0 B
152+
2 java 553.5 MiB 0 B
155153
```
156154

157155
In this example, given the large size of the java process, it is better to write its output to a file.

cmd/list.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"time"
1313

1414
"github.com/checkpoint-restore/checkpointctl/internal"
15-
"github.com/olekukonko/tablewriter"
1615
"github.com/spf13/cobra"
1716
)
1817

@@ -38,7 +37,7 @@ func list(cmd *cobra.Command, args []string) error {
3837
}()
3938
showTable := false
4039

41-
table := tablewriter.NewWriter(os.Stdout)
40+
w := internal.GetNewTabWriter(os.Stdout)
4241
header := []string{
4342
"Namespace",
4443
"Pod",
@@ -48,9 +47,7 @@ func list(cmd *cobra.Command, args []string) error {
4847
"Checkpoint Name",
4948
}
5049

51-
table.SetHeader(header)
52-
table.SetAutoMergeCells(false)
53-
table.SetRowLine(true)
50+
var rows [][]string
5451

5552
for _, checkpointPath := range allPaths {
5653
files, err := filepath.Glob(filepath.Join(checkpointPath, "checkpoint-*"))
@@ -81,7 +78,7 @@ func list(cmd *cobra.Command, args []string) error {
8178
filepath.Base(file),
8279
}
8380

84-
table.Append(row)
81+
rows = append(rows, row)
8582
}
8683
}
8784

@@ -90,6 +87,9 @@ func list(cmd *cobra.Command, args []string) error {
9087
return nil
9188
}
9289

93-
table.Render()
90+
internal.WriteTableHeader(w, header)
91+
internal.WriteTableRows(w, rows)
92+
93+
w.Flush()
9494
return nil
9595
}

cmd/memparse.go

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"github.com/checkpoint-restore/checkpointctl/internal"
1515
metadata "github.com/checkpoint-restore/checkpointctl/lib"
1616
"github.com/checkpoint-restore/go-criu/v7/crit"
17-
"github.com/olekukonko/tablewriter"
1817
"github.com/spf13/cobra"
1918
)
2019

@@ -116,21 +115,16 @@ func memparse(cmd *cobra.Command, args []string) error {
116115

117116
// Display processes memory sizes within the given container checkpoints.
118117
func showProcessMemorySizeTables(tasks []internal.Task) error {
119-
// Initialize the table
120-
table := tablewriter.NewWriter(os.Stdout)
121118
header := []string{
122119
"PID",
123120
"Process name",
124121
"Memory size",
125122
"Shared memory size",
126123
}
127-
table.SetHeader(header)
128-
table.SetAutoMergeCells(false)
129-
table.SetRowLine(true)
130124

131125
// Function to recursively traverse the process tree and populate the table rows
132-
var traverseTree func(*crit.PsTree, string) error
133-
traverseTree = func(root *crit.PsTree, checkpointOutputDir string) error {
126+
var traverseTree func(*crit.PsTree, string, *[][]string) error
127+
traverseTree = func(root *crit.PsTree, checkpointOutputDir string, rows *[][]string) error {
134128
memReader, err := crit.NewMemoryReader(
135129
filepath.Join(checkpointOutputDir, metadata.CheckpointDirectory),
136130
root.PID, pageSize,
@@ -152,24 +146,25 @@ func showProcessMemorySizeTables(tasks []internal.Task) error {
152146
return err
153147
}
154148

155-
table.Append([]string{
149+
row := []string{
156150
fmt.Sprintf("%d", root.PID),
157151
root.Comm,
158152
metadata.ByteToString(memSize),
159153
metadata.ByteToString(shmemSize),
160-
})
154+
}
155+
*rows = append(*rows, row)
161156

162157
for _, child := range root.Children {
163-
if err := traverseTree(child, checkpointOutputDir); err != nil {
158+
if err := traverseTree(child, checkpointOutputDir, rows); err != nil {
164159
return err
165160
}
166161
}
167162
return nil
168163
}
169164

170165
for _, task := range tasks {
171-
// Clear the table before processing each checkpoint task
172-
table.ClearRows()
166+
w := internal.GetNewTabWriter(os.Stdout)
167+
var rows [][]string
173168

174169
c := crit.New(nil, nil, filepath.Join(task.OutputDir, "checkpoint"), false, false)
175170
psTree, err := c.ExplorePs()
@@ -178,12 +173,16 @@ func showProcessMemorySizeTables(tasks []internal.Task) error {
178173
}
179174

180175
// Populate the table rows
181-
if err := traverseTree(psTree, task.OutputDir); err != nil {
176+
if err := traverseTree(psTree, task.OutputDir, &rows); err != nil {
182177
return err
183178
}
184179

185180
fmt.Printf("\nDisplaying processes memory sizes from %s\n\n", task.CheckpointFilePath)
186-
table.Render()
181+
182+
internal.WriteTableHeader(w, header)
183+
internal.WriteTableRows(w, rows)
184+
185+
w.Flush()
187186
}
188187

189188
return nil
@@ -348,21 +347,24 @@ func printMemorySearchResultForPID(task internal.Task) error {
348347
return nil
349348
}
350349

351-
table := tablewriter.NewWriter(os.Stdout)
352-
table.SetHeader([]string{"Address", "Match", "Instance"})
353-
table.SetAutoMergeCells(false)
354-
table.SetRowLine(true)
350+
w := internal.GetNewTabWriter(os.Stdout)
351+
header := []string{"Address", "Match", "Instance"}
355352

353+
internal.WriteTableHeader(w, header)
354+
355+
// Build rows
356+
var rows [][]string
356357
for i, result := range results {
357-
table.Append([]string{
358-
fmt.Sprintf(
359-
"%016x", result.Vaddr),
358+
row := []string{
359+
fmt.Sprintf("%016x", result.Vaddr),
360360
result.Match,
361361
fmt.Sprintf("%d", i+1),
362-
})
362+
}
363+
rows = append(rows, row)
363364
}
364365

365-
table.Render()
366+
internal.WriteTableRows(w, rows)
366367

368+
w.Flush()
367369
return nil
368370
}

go.mod

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ toolchain go1.24.2
77
require (
88
github.com/checkpoint-restore/go-criu/v7 v7.2.0
99
github.com/containers/storage v1.58.0
10-
github.com/olekukonko/tablewriter v0.0.5
1110
github.com/opencontainers/runtime-spec v1.2.1
1211
github.com/spf13/cobra v1.9.1
1312
github.com/xlab/treeprint v1.2.0
@@ -18,11 +17,9 @@ require (
1817
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1918
github.com/klauspost/compress v1.18.0 // indirect
2019
github.com/klauspost/pgzip v1.2.6 // indirect
21-
github.com/mattn/go-runewidth v0.0.16 // indirect
2220
github.com/moby/sys/capability v0.4.0 // indirect
2321
github.com/moby/sys/mountinfo v0.7.2 // indirect
2422
github.com/moby/sys/user v0.4.0 // indirect
25-
github.com/rivo/uniseg v0.4.7 // indirect
2623
github.com/sirupsen/logrus v1.9.3 // indirect
2724
github.com/spf13/pflag v1.0.6 // indirect
2825
github.com/ulikunitz/xz v0.5.12 // indirect

go.sum

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,16 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt
1616
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
1717
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
1818
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
19-
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
20-
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
21-
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
2219
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
2320
github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
2421
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
2522
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
2623
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
2724
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
28-
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
29-
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
3025
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
3126
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
3227
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
3328
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
34-
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
35-
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
36-
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
3729
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
3830
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
3931
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=

internal/container.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
metadata "github.com/checkpoint-restore/checkpointctl/lib"
1919
"github.com/checkpoint-restore/go-criu/v7/crit"
2020
"github.com/containers/storage/pkg/archive"
21-
"github.com/olekukonko/tablewriter"
2221
spec "github.com/opencontainers/runtime-spec/specs-go"
2322
)
2423

@@ -107,7 +106,8 @@ func getCheckpointInfo(task Task) (*checkpointInfo, error) {
107106
}
108107

109108
func ShowContainerCheckpoints(tasks []Task) error {
110-
table := tablewriter.NewWriter(os.Stdout)
109+
w := GetNewTabWriter(os.Stdout)
110+
111111
header := []string{
112112
"Container",
113113
"Image",
@@ -121,6 +121,8 @@ func ShowContainerCheckpoints(tasks []Task) error {
121121
header = append(header, "IP", "MAC", "CHKPT Size", "Root Fs Diff Size")
122122
}
123123

124+
var rows [][]string
125+
124126
for _, task := range tasks {
125127
info, err := getCheckpointInfo(task)
126128
if err != nil {
@@ -167,14 +169,13 @@ func ShowContainerCheckpoints(tasks []Task) error {
167169
row = append(row, metadata.ByteToString(info.archiveSizes.rootFsDiffTarSize))
168170
}
169171

170-
table.Append(row)
172+
rows = append(rows, row)
171173
}
172174

173-
table.SetHeader(header)
174-
table.SetAutoMergeCells(false)
175-
table.SetRowLine(true)
176-
table.Render()
175+
WriteTableHeader(w, header)
176+
WriteTableRows(w, rows)
177177

178+
w.Flush()
178179
return nil
179180
}
180181

internal/utils.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package internal
22

33
import (
44
"fmt"
5+
"io"
56
"os"
67
"strings"
8+
"text/tabwriter"
79
"time"
810

911
metadata "github.com/checkpoint-restore/checkpointctl/lib"
@@ -82,3 +84,52 @@ func CleanupTasks(tasks []Task) {
8284
}
8385
}
8486
}
87+
88+
// Constant values are based on kubectl's tabwriter settings:
89+
// https://github.com/kubernetes/cli-runtime/blob/master/pkg/printers/tabwriter.go
90+
const (
91+
tabwriterMinWidth = 6
92+
tabwriterWidth = 4
93+
tabwriterPadding = 3
94+
tabwriterPadChar = ' '
95+
tabwriterFlags = 0
96+
)
97+
98+
// GetNewTabWriter returns a tabwriter that translates tabbed columns in input into properly aligned text.
99+
func GetNewTabWriter(output io.Writer) *tabwriter.Writer {
100+
return tabwriter.NewWriter(output, tabwriterMinWidth, tabwriterWidth, tabwriterPadding, tabwriterPadChar, tabwriterFlags)
101+
}
102+
103+
// WriteTableHeader writes the header row and separator line for a table
104+
func WriteTableHeader(w *tabwriter.Writer, header []string) {
105+
// Print header
106+
for i, h := range header {
107+
if i > 0 {
108+
fmt.Fprint(w, "\t")
109+
}
110+
fmt.Fprint(w, strings.ToUpper(h))
111+
}
112+
fmt.Fprintln(w)
113+
114+
// Print separator line
115+
for i := range header {
116+
if i > 0 {
117+
fmt.Fprint(w, "\t")
118+
}
119+
fmt.Fprint(w, strings.Repeat("-", len(header[i])))
120+
}
121+
fmt.Fprintln(w)
122+
}
123+
124+
// WriteTableRows writes the data rows for a table
125+
func WriteTableRows(w *tabwriter.Writer, rows [][]string) {
126+
for _, row := range rows {
127+
for i, cell := range row {
128+
if i > 0 {
129+
fmt.Fprint(w, "\t")
130+
}
131+
fmt.Fprint(w, cell)
132+
}
133+
fmt.Fprintln(w)
134+
}
135+
}

0 commit comments

Comments
 (0)