diff --git a/CHANGELOG.md b/CHANGELOG.md index 02aedd6a..ba938ebb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Remove a 3-second sleep during iPXE boot. #1500 - Don't package the API in RPM packages by default. #1493 - Update default `warewulfd` port to match shipped configuration. #1448 +- Replace `olekukonko/tablewriter` with `cheynewallace/tabby`. #1497, #1498 ### Removed diff --git a/go.mod b/go.mod index b73c7111..1725510a 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( dario.cat/mergo v1.0.0 github.com/Masterminds/sprig/v3 v3.2.3 + github.com/cheynewallace/tabby v1.1.1 github.com/containers/image/v5 v5.30.1 github.com/containers/storage v1.55.0 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e @@ -17,7 +18,6 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 github.com/hashicorp/go-version v1.7.0 github.com/manifoldco/promptui v0.9.0 - github.com/olekukonko/tablewriter v0.0.5 github.com/opencontainers/image-spec v1.1.0 github.com/opencontainers/umoci v0.4.7 github.com/pkg/errors v0.9.1 @@ -91,7 +91,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect diff --git a/go.sum b/go.sum index e2574b4b..a3f46b2d 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheynewallace/tabby v1.1.1 h1:JvUR8waht4Y0S3JF17G6Vhyt+FRhnqVCkk8l4YrOU54= +github.com/cheynewallace/tabby v1.1.1/go.mod h1:Pba/6cUL8uYqvOc9RkyvFbHGrQ9wShyrn6/S/1OYVys= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= @@ -301,9 +303,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= @@ -340,8 +341,6 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= diff --git a/internal/app/wwctl/container/list/main.go b/internal/app/wwctl/container/list/main.go index 8e91e1f3..de4b4816 100644 --- a/internal/app/wwctl/container/list/main.go +++ b/internal/app/wwctl/container/list/main.go @@ -5,17 +5,18 @@ import ( "time" "github.com/spf13/cobra" - "github.com/warewulf/warewulf/internal/app/wwctl/helper" + + "github.com/warewulf/warewulf/internal/app/wwctl/table" apicontainer "github.com/warewulf/warewulf/internal/pkg/api/container" "github.com/warewulf/warewulf/internal/pkg/container" "github.com/warewulf/warewulf/internal/pkg/util" - "github.com/warewulf/warewulf/internal/pkg/wwlog" ) var containerList = apicontainer.ContainerList func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) { return func(cmd *cobra.Command, args []string) (err error) { + t := table.New(cmd.OutOrStdout()) showSize := vars.size || vars.chroot || vars.compressed if showSize || vars.full || vars.kernel { containerInfo, err := containerList() @@ -23,7 +24,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err return err } if vars.full { - ph := helper.New([]string{"CONTAINER NAME", "NODES", "KERNEL VERSION", "CREATION TIME", "MODIFICATION TIME", "SIZE"}) + t.AddHeader("CONTAINER NAME", "NODES", "KERNEL VERSION", "CREATION TIME", "MODIFICATION TIME", "SIZE") for i := 0; i < len(containerInfo); i++ { createTime := time.Unix(int64(containerInfo[i].CreateDate), 0) modTime := time.Unix(int64(containerInfo[i].ModDate), 0) @@ -34,30 +35,26 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err if vars.chroot { sz = util.ByteToString(int64(containerInfo[i].Size)) } - ph.Append([]string{ + t.AddLine( containerInfo[i].Name, strconv.FormatUint(uint64(containerInfo[i].NodeCount), 10), containerInfo[i].KernelVersion, createTime.Format(time.RFC822), modTime.Format(time.RFC822), sz, - }) + ) } - ph.Render() - wwlog.Info("%s", ph.String()) } else if vars.kernel { - ph := helper.New([]string{"CONTAINER NAME", "NODES", "KERNEL VERSION"}) + t.AddHeader("CONTAINER NAME", "NODES", "KERNEL VERSION") for i := 0; i < len(containerInfo); i++ { - ph.Append([]string{ + t.AddLine( containerInfo[i].Name, strconv.FormatUint(uint64(containerInfo[i].NodeCount), 10), containerInfo[i].KernelVersion, - }) + ) } - ph.Render() - wwlog.Info("%s", ph.String()) } else if showSize { - ph := helper.New([]string{"CONTAINER NAME", "NODES", "SIZE"}) + t.AddHeader("CONTAINER NAME", "NODES", "SIZE") for i := 0; i < len(containerInfo); i++ { sz := util.ByteToString(int64(containerInfo[i].ImgSize)) if vars.compressed { @@ -67,24 +64,21 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err sz = util.ByteToString(int64(containerInfo[i].Size)) } - ph.Append([]string{ + t.AddLine( containerInfo[i].Name, strconv.FormatUint(uint64(containerInfo[i].NodeCount), 10), sz, - }) + ) } - ph.Render() - wwlog.Info("%s", ph.String()) } } else { - ph := helper.New([]string{"CONTAINER NAME"}) + t.AddHeader("CONTAINER NAME") list, _ := container.ListSources() for _, cont := range list { - ph.Append([]string{cont}) + t.AddLine(cont) } - ph.Render() - wwlog.Info("%s", ph.String()) } + t.Print() return } } diff --git a/internal/app/wwctl/container/list/main_test.go b/internal/app/wwctl/container/list/main_test.go index 07c7ba8a..1e841128 100644 --- a/internal/app/wwctl/container/list/main_test.go +++ b/internal/app/wwctl/container/list/main_test.go @@ -24,8 +24,11 @@ func Test_List(t *testing.T) { { name: "container list test", args: []string{"-l"}, - stdout: ` CONTAINER NAME NODES KERNEL VERSION CREATION TIME MODIFICATION TIME SIZE - test 1 kernel`, + stdout: ` +CONTAINER NAME NODES KERNEL VERSION CREATION TIME MODIFICATION TIME SIZE +-------------- ----- -------------- ------------- ----------------- ---- +test 1 kernel 01 Jan 70 00:00 UTC 01 Jan 70 00:00 UTC 0 B +`, inDb: `WW_INTERNAL: 43 nodeprofiles: default: {} @@ -61,14 +64,12 @@ nodes: buf := new(bytes.Buffer) baseCmd := GetCommand() baseCmd.SetArgs(tt.args) - baseCmd.SetOut(nil) - baseCmd.SetErr(nil) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) wwlog.SetLogWriter(buf) err := baseCmd.Execute() assert.NoError(t, err) - assert.Contains(t, - strings.Join(strings.Fields(buf.String()), ""), - strings.Join(strings.Fields(tt.stdout), "")) + assert.Equal(t, strings.TrimSpace(tt.stdout), strings.TrimSpace(buf.String())) }) } } diff --git a/internal/app/wwctl/helper/printhelper.go b/internal/app/wwctl/helper/printhelper.go deleted file mode 100644 index ada9d5fd..00000000 --- a/internal/app/wwctl/helper/printhelper.go +++ /dev/null @@ -1,36 +0,0 @@ -package helper - -import ( - "regexp" - "strings" - - "github.com/olekukonko/tablewriter" -) - -type PrintHelper struct { - *tablewriter.Table - sb *strings.Builder -} - -func New(header []string) *PrintHelper { - sb := &strings.Builder{} - tb := tablewriter.NewWriter(sb) - tb.SetHeader(header) - tb.SetAutoWrapText(false) - tb.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - tb.SetAlignment(tablewriter.ALIGN_LEFT) - tb.SetCenterSeparator("") - tb.SetColumnSeparator("") - tb.SetRowSeparator("") - tb.SetHeaderLine(false) - tb.SetBorder(false) - return &PrintHelper{ - Table: tb, - sb: sb, - } -} - -func (p *PrintHelper) String() string { - exp := regexp.MustCompile("(?m) *$") - return string(exp.ReplaceAll([]byte(p.sb.String()), []byte(""))) -} diff --git a/internal/app/wwctl/node/list/main.go b/internal/app/wwctl/node/list/main.go index fc84c39f..80b0a3d5 100644 --- a/internal/app/wwctl/node/list/main.go +++ b/internal/app/wwctl/node/list/main.go @@ -4,7 +4,8 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/warewulf/warewulf/internal/app/wwctl/helper" + + "github.com/warewulf/warewulf/internal/app/wwctl/table" apinode "github.com/warewulf/warewulf/internal/pkg/api/node" "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" "github.com/warewulf/warewulf/internal/pkg/wwlog" @@ -38,12 +39,12 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err if req.Type == wwapiv1.GetNodeList_YAML || req.Type == wwapiv1.GetNodeList_JSON { wwlog.Info(nodeInfo.Output[0]) } else { - ph := helper.New(strings.Split(nodeInfo.Output[0], ":=:")) + t := table.New(cmd.OutOrStdout()) + t.AddHeader(table.Prep(strings.Split(nodeInfo.Output[0], ":=:"))...) for _, val := range nodeInfo.Output[1:] { - ph.Append(strings.Split(val, ":=:")) + t.AddLine(table.Prep(strings.Split(val, ":=:"))...) } - ph.Render() - wwlog.Info("%s", ph.String()) + t.Print() } } return diff --git a/internal/app/wwctl/node/list/main_test.go b/internal/app/wwctl/node/list/main_test.go index d413a9e7..c787c0a8 100644 --- a/internal/app/wwctl/node/list/main_test.go +++ b/internal/app/wwctl/node/list/main_test.go @@ -28,8 +28,10 @@ func Test_List(t *testing.T) { name: "single node list", args: []string{}, wantErr: false, - stdout: ` NODE NAME PROFILES NETWORK - n01 [default] + stdout: ` +NODE NAME PROFILES NETWORK +--------- -------- ------- +n01 [default] -- `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -44,9 +46,11 @@ nodes: name: "multiple nodes list", args: []string{}, wantErr: false, - stdout: ` NODE NAME PROFILES NETWORK - n01 [default] - n02 [default] + stdout: ` +NODE NAME PROFILES NETWORK +--------- -------- ------- +n01 [default] -- +n02 [default] -- `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -64,9 +68,11 @@ nodes: name: "node list returns multiple nodes", args: []string{"n01,n02"}, wantErr: false, - stdout: ` NODE NAME PROFILES NETWORK - n01 [default] - n02 [default] + stdout: ` +NODE NAME PROFILES NETWORK +--------- -------- ------- +n01 [default] -- +n02 [default] -- `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -84,9 +90,11 @@ nodes: name: "node list returns multiple nodes (case 2)", args: []string{"n01,n03"}, wantErr: false, - stdout: ` NODE NAME PROFILES NETWORK - n01 [default] - n03 [default] + stdout: ` +NODE NAME PROFILES NETWORK +--------- -------- ------- +n01 [default] -- +n03 [default] -- `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -113,8 +121,10 @@ nodes: name: "node list returns one node", args: []string{"n01,"}, wantErr: false, - stdout: ` NODE NAME PROFILES NETWORK - n01 [default] + stdout: ` +NODE NAME PROFILES NETWORK +--------- -------- ------- +n01 [default] -- `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -132,8 +142,10 @@ nodes: name: "node list profile with network", args: []string{}, wantErr: false, - stdout: ` NODE NAME PROFILES NETWORK - n01 [default] default + stdout: ` +NODE NAME PROFILES NETWORK +--------- -------- ------- +n01 [default] default `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -150,9 +162,11 @@ nodes: name: "node list profile with comment", args: []string{"-a"}, wantErr: false, - stdout: ` NODE FIELD PROFILE VALUE - n01 Comment default profilecomment - n01 Profiles default + stdout: ` +NODE FIELD PROFILE VALUE +---- ----- ------- ----- +n01 Comment default profilecomment +n01 Profiles -- default `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -167,9 +181,11 @@ nodes: name: "node list profile with comment superseded", args: []string{"-a"}, wantErr: false, - stdout: ` NODE FIELD PROFILE VALUE - n01 Comment SUPERSEDED nodecomment - n01 Profiles default + stdout: ` +NODE FIELD PROFILE VALUE +---- ----- ------- ----- +n01 Comment SUPERSEDED nodecomment +n01 Profiles -- default `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -185,8 +201,10 @@ nodes: name: "node list profile with ipmi user", args: []string{"-i"}, wantErr: false, - stdout: ` NODE IPMI IPADDR IPMI PORT IPMI USERNAME IPMI INTERFACE - n01 admin + stdout: ` +NODE IPMI IPADDR IPMI PORT IPMI USERNAME IPMI INTERFACE +---- ----------- --------- ------------- -------------- +n01 -- admin -- `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -202,8 +220,10 @@ nodes: name: "node list profile with ipmi user superseded", args: []string{"-i"}, wantErr: false, - stdout: ` NODE IPMI IPADDR IPMI PORT IPMI USERNAME IPMI INTERFACE - n01 user + stdout: ` +NODE IPMI IPADDR IPMI PORT IPMI USERNAME IPMI INTERFACE +---- ----------- --------- ------------- -------------- +n01 -- user -- `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -221,8 +241,10 @@ nodes: name: "multiple profiles list", args: []string{}, wantErr: false, - stdout: ` NODE NAME PROFILES NETWORK - n01 [p1 p2] + stdout: ` +NODE NAME PROFILES NETWORK +--------- -------- ------- +n01 [p1 p2] -- `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -238,8 +260,10 @@ nodes: name: "multiple profiles list all", args: []string{"-a"}, wantErr: false, - stdout: ` NODE FIELD PROFILE VALUE - n01 Profiles p1,p2 + stdout: ` +NODE FIELD PROFILE VALUE +---- ----- ------- ----- +n01 Profiles -- p1,p2 `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -255,8 +279,10 @@ nodes: name: "multiple overlays list long with negation", args: []string{"-l"}, wantErr: false, - stdout: ` NODE NAME KERNEL OVERRIDE CONTAINER OVERLAYS (S/R) - n01 /rop1,rop2 + stdout: ` +NODE NAME KERNEL OVERRIDE CONTAINER OVERLAYS (S/R) +--------- --------------- --------- -------------- +n01 -- -- /rop1,rop2 `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -273,8 +299,10 @@ nodes: name: "multiple overlays list long", args: []string{"-l"}, wantErr: false, - stdout: ` NODE NAME KERNEL OVERRIDE CONTAINER OVERLAYS (S/R) - n01 sop1/nop1,~rop1,rop1,rop2 + stdout: ` +NODE NAME KERNEL OVERRIDE CONTAINER OVERLAYS (S/R) +--------- --------------- --------- -------------- +n01 -- -- sop1/nop1,~rop1,rop1,rop2 `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -296,10 +324,12 @@ nodes: name: "multiple overlays list all with negation", args: []string{"-a"}, wantErr: false, - stdout: ` NODE FIELD PROFILE VALUE - n01 Profiles p1 - n01 RuntimeOverlay p1+ nop1,~rop1,rop1,rop2 - n01 SystemOverlay p1 sop1 + stdout: ` +NODE FIELD PROFILE VALUE +---- ----- ------- ----- +n01 Profiles -- p1 +n01 RuntimeOverlay p1+ nop1,~rop1,rop1,rop2 +n01 SystemOverlay p1 sop1 `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -321,9 +351,11 @@ nodes: name: "multiple overlays list all", args: []string{"-a"}, wantErr: false, - stdout: ` NODE FIELD PROFILE VALUE - n01 Profiles p1 - n01 RuntimeOverlay p1+ nop1,rop1,rop2 + stdout: ` +NODE FIELD PROFILE VALUE +---- ----- ------- ----- +n01 Profiles -- p1 +n01 RuntimeOverlay p1+ nop1,rop1,rop2 `, inDb: `WW_INTERNAL: 45 nodeprofiles: @@ -342,8 +374,10 @@ nodes: name: "network onboot", args: []string{"-a"}, wantErr: false, - stdout: ` NODE FIELD PROFILE VALUE - n1 NetDevs[default].OnBoot true + stdout: ` +NODE FIELD PROFILE VALUE +---- ----- ------- ----- +n1 NetDevs[default].OnBoot -- true `, inDb: `WW_INTERNAL: 45 nodes: @@ -356,7 +390,9 @@ nodes: name: "empty network device", args: []string{"-a"}, wantErr: false, - stdout: ` NODE FIELD PROFILE VALUE + stdout: ` +NODE FIELD PROFILE VALUE +---- ----- ------- ----- `, inDb: `WW_INTERNAL: 46 nodes: @@ -393,13 +429,14 @@ nodes: baseCmd := GetCommand() baseCmd.SetArgs(tt.args) buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) wwlog.SetLogWriter(buf) wwlog.SetLogWriterErr(buf) wwlog.SetLogWriterInfo(buf) err := baseCmd.Execute() assert.NoError(t, err) - assert.NotEmpty(t, buf, "output should not be empty") - assert.Equal(t, tt.stdout, buf.String()) + assert.Equal(t, strings.TrimSpace(tt.stdout), strings.TrimSpace(buf.String())) }) } @@ -492,9 +529,7 @@ nodes: } else { assert.NoError(t, err) } - assert.Contains(t, - strings.Join(strings.Fields(tt.stdout), ""), - strings.Join(strings.Fields(buf.String()), "")) + assert.Equal(t, strings.TrimSpace(buf.String()), strings.TrimSpace(tt.stdout)) }) } } diff --git a/internal/app/wwctl/profile/list/main.go b/internal/app/wwctl/profile/list/main.go index 05396a40..86cd1736 100644 --- a/internal/app/wwctl/profile/list/main.go +++ b/internal/app/wwctl/profile/list/main.go @@ -3,12 +3,12 @@ package list import ( "strings" - "github.com/warewulf/warewulf/internal/app/wwctl/helper" - apiprofile "github.com/warewulf/warewulf/internal/pkg/api/profile" - "github.com/warewulf/warewulf/internal/pkg/wwlog" - "github.com/spf13/cobra" + + "github.com/warewulf/warewulf/internal/app/wwctl/table" + apiprofile "github.com/warewulf/warewulf/internal/pkg/api/profile" "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" + "github.com/warewulf/warewulf/internal/pkg/wwlog" ) func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) { @@ -30,12 +30,12 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err if vars.showYaml || vars.showJson { wwlog.Info(profileInfo.Output[0]) } else { - ph := helper.New(strings.Split(profileInfo.Output[0], ":=:")) + t := table.New(cmd.OutOrStdout()) + t.AddHeader(table.Prep(strings.Split(profileInfo.Output[0], ":=:"))...) for _, val := range profileInfo.Output[1:] { - ph.Append(strings.Split(val, ":=:")) + t.AddLine(table.Prep(strings.Split(val, ":=:"))...) } - ph.Render() - wwlog.Info("%s", ph.String()) + t.Print() } } return diff --git a/internal/app/wwctl/profile/list/main_test.go b/internal/app/wwctl/profile/list/main_test.go index a919c5c1..fa400283 100644 --- a/internal/app/wwctl/profile/list/main_test.go +++ b/internal/app/wwctl/profile/list/main_test.go @@ -4,9 +4,9 @@ import ( "bytes" "io" "os" + "strings" "testing" - "github.com/spf13/cobra" "github.com/stretchr/testify/assert" warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" "github.com/warewulf/warewulf/internal/pkg/node" @@ -24,8 +24,11 @@ func Test_List(t *testing.T) { { name: "profile list test", args: []string{}, - stdout: `PROFILE NAME COMMENT/DESCRIPTION - default`, + stdout: ` +PROFILE NAME COMMENT/DESCRIPTION +------------ ------------------- +default -- +`, inDb: `WW_INTERNAL: 45 nodeprofiles: default: {} @@ -38,9 +41,12 @@ nodes: { name: "profile list returns multiple profiles", args: []string{"default,test"}, - stdout: `PROFILE NAME COMMENT/DESCRIPTION - default - test`, + stdout: ` +PROFILE NAME COMMENT/DESCRIPTION +------------ ------------------- +default -- +test -- +`, inDb: `WW_INTERNAL: 45 nodeprofiles: default: {} @@ -54,8 +60,11 @@ nodes: { name: "profile list returns one profile", args: []string{"test,"}, - stdout: `PROFILE NAME COMMENT/DESCRIPTION - test`, + stdout: ` +PROFILE NAME COMMENT/DESCRIPTION +------------ ------------------- +test -- +`, inDb: `WW_INTERNAL: 45 nodeprofiles: default: {} @@ -69,9 +78,12 @@ nodes: { name: "profile list returns all profiles", args: []string{","}, - stdout: `PROFILE NAME COMMENT/DESCRIPTION - default - test`, + stdout: ` +PROFILE NAME COMMENT/DESCRIPTION +------------ ------------------- +default -- +test -- +`, inDb: `WW_INTERNAL: 45 nodeprofiles: default: {} @@ -111,7 +123,26 @@ nodes: t.Run(tt.name, func(t *testing.T) { baseCmd := GetCommand() baseCmd.SetArgs(tt.args) - verifyOutput(t, baseCmd, tt.stdout) + stdoutR, stdoutW, _ := os.Pipe() + oriout := os.Stdout + os.Stdout = stdoutW + wwlog.SetLogWriter(os.Stdout) + baseCmd.SetOut(os.Stdout) + baseCmd.SetErr(os.Stdout) + err := baseCmd.Execute() + assert.NoError(t, err) + + stdoutC := make(chan string) + go func() { + var buf bytes.Buffer + _, _ = io.Copy(&buf, stdoutR) + stdoutC <- buf.String() + }() + stdoutW.Close() + os.Stdout = oriout + + stdout := <-stdoutC + assert.Equal(t, strings.TrimSpace(tt.stdout), strings.TrimSpace(stdout)) }) } } @@ -219,27 +250,3 @@ nodes: }) } } - -func verifyOutput(t *testing.T, baseCmd *cobra.Command, content string) { - stdoutR, stdoutW, _ := os.Pipe() - oriout := os.Stdout - os.Stdout = stdoutW - wwlog.SetLogWriter(os.Stdout) - baseCmd.SetOut(os.Stdout) - baseCmd.SetErr(os.Stdout) - err := baseCmd.Execute() - assert.NoError(t, err) - - stdoutC := make(chan string) - go func() { - var buf bytes.Buffer - _, _ = io.Copy(&buf, stdoutR) - stdoutC <- buf.String() - }() - stdoutW.Close() - os.Stdout = oriout - - stdout := <-stdoutC - assert.NotEmpty(t, stdout) - assert.Contains(t, stdout, content) -} diff --git a/internal/app/wwctl/table/table.go b/internal/app/wwctl/table/table.go new file mode 100644 index 00000000..aba80548 --- /dev/null +++ b/internal/app/wwctl/table/table.go @@ -0,0 +1,24 @@ +package table + +import ( + "io" + "text/tabwriter" + + "github.com/cheynewallace/tabby" +) + +func Prep(parts []string) []interface{} { + args := make([]interface{}, len(parts)) + for i, v := range parts { + if v == "" { + args[i] = "--" + } else { + args[i] = v + } + } + return args +} + +func New(writer io.Writer) *tabby.Tabby { + return tabby.NewCustom(tabwriter.NewWriter(writer, 0, 0, 2, ' ', 0)) +} diff --git a/internal/pkg/api/node/list.go b/internal/pkg/api/node/list.go index 6e4a5c2e..3f177fa9 100644 --- a/internal/pkg/api/node/list.go +++ b/internal/pkg/api/node/list.go @@ -64,12 +64,12 @@ func NodeList(nodeGet *wwapiv1.GetNodeList) (nodeList wwapiv1.NodeList, err erro fmt.Sprintf("%s:=:%s:=:%s:=:%s:=:%s", "NODE", "IPMI IPADDR", "IPMI PORT", "IPMI USERNAME", "IPMI INTERFACE")) for _, n := range node.FilterNodeListByName(nodes, nodeGet.Nodes) { nodeList.Output = append(nodeList.Output, - fmt.Sprintf("%s:=:%s:=:%s:=:%s:=:%s:=:%s", n.Id(), + fmt.Sprintf("%s:=:%s:=:%s:=:%s:=:%s", + n.Id(), n.Ipmi.Ipaddr.String(), n.Ipmi.Port, n.Ipmi.UserName, - n.Ipmi.Interface, - n.Ipmi.EscapeChar)) + n.Ipmi.Interface)) } } else if nodeGet.Type == wwapiv1.GetNodeList_Long { nodeList.Output = append(nodeList.Output, @@ -82,9 +82,9 @@ func NodeList(nodeGet *wwapiv1.GetNodeList) (nodeList wwapiv1.NodeList, err erro strings.Join(n.SystemOverlay, ",")+"/"+strings.Join(n.RuntimeOverlay, ","))) } } else if nodeGet.Type == wwapiv1.GetNodeList_All { + nodeList.Output = append(nodeList.Output, + fmt.Sprintf("%s:=:%s:=:%s:=:%s", "NODE", "FIELD", "PROFILE", "VALUE")) for _, n := range node.FilterNodeListByName(nodes, nodeGet.Nodes) { - nodeList.Output = append(nodeList.Output, - fmt.Sprintf("%s:=:%s:=:%s:=:%s", "NODE", "FIELD", "PROFILE", "VALUE")) fields := nodeDB.GetFields(n) for _, f := range fields { nodeList.Output = append(nodeList.Output, diff --git a/internal/pkg/api/profile/list.go b/internal/pkg/api/profile/list.go index b6afdbdc..5d027943 100644 --- a/internal/pkg/api/profile/list.go +++ b/internal/pkg/api/profile/list.go @@ -28,9 +28,9 @@ func ProfileList(ShowOpt *wwapiv1.GetProfileList) (profileList wwapiv1.ProfileLi return profiles[i].Id() < profiles[j].Id() }) if ShowOpt.ShowAll { + profileList.Output = append(profileList.Output, + fmt.Sprintf("%s:=:%s:=:%s", "PROFILE", "FIELD", "VALUE")) for _, p := range profiles { - profileList.Output = append(profileList.Output, - fmt.Sprintf("%s:=:%s:=:%s", "PROFILE", "FIELD", "VALUE")) fields := nodeDB.GetFieldsProfile(p) for _, f := range fields { profileList.Output = append(profileList.Output,