Skip to content

Commit

Permalink
replace table writer with go-pretty #2292 (#2296)
Browse files Browse the repository at this point in the history
* replaced tablewriter with go-pretty

* variable renames

* adjusts smoke test to account for different border char

* add center alignment for headers

* remove container ID, collapse v4/v6 addresses

* squash header to 1 line

* removed number col, bolded headers

* combine kind/image and strip prefixlen from ip

* fix ip stripping func and use the latest go-pretty to use the align functionality in the headers

* topo path -> topology

* added bold color

* use the shortest file path when displaying the inspect --all (relative vs absolute)

* fix "Ensure "inspect all" outputs IP addresses" test

* wait longer for the tests running on arm/macos

* do not check if nf_tables kernel module is available in /proc/modules

this file might not be available on some VMs like OrbStack on macos, and the error will anyhow be detected when setting up a connection with the netlink socket

* fix 02-destroy test suite

* fix 05* test

* fix ext container tests

* change style for the netem table

---------

Co-authored-by: Roman Dodin <[email protected]>
  • Loading branch information
hyposcaler-bot and hellt authored Nov 25, 2024
1 parent 8c0b4b3 commit 2269834
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 89 deletions.
110 changes: 84 additions & 26 deletions cmd/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import (
"path/filepath"
"slices"
"sort"
"strings"

"github.com/olekukonko/tablewriter"
tableWriter "github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/srl-labs/containerlab/clab"
Expand Down Expand Up @@ -130,12 +132,12 @@ func inspectFn(_ *cobra.Command, _ []string) error {
return err
}

func toTableData(det []types.ContainerDetails) [][]string {
tabData := make([][]string, 0, len(det))
for i := range det {
d := &det[i]
func toTableData(contDetails []types.ContainerDetails) []tableWriter.Row {
tabData := make([]tableWriter.Row, 0, len(contDetails))
for i := range contDetails {
d := &contDetails[i]

tabRow := []string{fmt.Sprintf("%d", i+1)}
tabRow := tableWriter.Row{}

if all {
tabRow = append(tabRow, d.LabPath, d.LabName)
Expand All @@ -147,22 +149,54 @@ func toTableData(det []types.ContainerDetails) [][]string {
}

// Common fields
tabRow = append(tabRow, d.Name, d.ContainerID, d.Image, d.Kind, d.State, d.IPv4Address, d.IPv6Address)
tabRow = append(tabRow,
d.Name,
fmt.Sprintf("%s\n%s", d.Kind, d.Image),
d.State,
fmt.Sprintf("%s\n%s",
ipWithoutPrefix(d.IPv4Address),
ipWithoutPrefix(d.IPv6Address)))

tabData = append(tabData, tabRow)
}
return tabData
}

// getTopologyPath returns the relative path to the topology file
// if the relative path is shorted than the absolute path.
func getTopologyPath(p string) (string, error) {
if p == "" {
return "", nil
}

// get topo file path relative of the cwd
cwd, err := os.Getwd()
if err != nil {
return "", err
}

relPath, err := filepath.Rel(cwd, p)
if err != nil {
return "", err
}

if len(relPath) < len(p) {
return relPath, nil
}

return p, nil
}

func printContainerInspect(containers []runtime.GenericContainer, format string) error {
contDetails := make([]types.ContainerDetails, 0, len(containers))

// Gather details of each container
for _, cont := range containers {

// get topo file path relative of the cwd
cwd, _ := os.Getwd()
path, _ := filepath.Rel(cwd, cont.Labels[labels.TopoFile])
path, err := getTopologyPath(cont.Labels[labels.TopoFile])
if err != nil {
return fmt.Errorf("failed to get topology path: %v", err)
}

cdet := &types.ContainerDetails{
LabName: cont.Labels[labels.Containerlab],
Expand Down Expand Up @@ -213,35 +247,46 @@ func printContainerInspect(containers []runtime.GenericContainer, format string)

case "table":
tabData := toTableData(contDetails)
table := tablewriter.NewWriter(os.Stdout)
header := []string{
table := tableWriter.NewWriter()
table.SetOutputMirror(os.Stdout)
table.SetStyle(tableWriter.StyleRounded)
table.Style().Format.Header = text.FormatTitle
table.Style().Format.HeaderAlign = text.AlignCenter
table.Style().Options.SeparateRows = true
table.Style().Color = tableWriter.ColorOptions{
Header: text.Colors{text.Bold},
}

header := tableWriter.Row{
"Lab Name",
"Name",
"Container ID",
"Image",
"Kind",
"Kind/Image",
"State",
"IPv4 Address",
"IPv6 Address",
}
"IPv4/6 Address"}

if wide {
header = slices.Insert(header, 1, "Owner")
}

if all {
table.SetHeader(append([]string{"#", "Topo Path"}, header...))
// merge cells with topo file path and lab name when in all mode
table.SetColumnConfigs([]tableWriter.ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: true},
})
table.AppendHeader(append(tableWriter.Row{"Topology"}, header...))
} else {
table.SetHeader(append([]string{"#"}, header[1:]...))
table.AppendHeader(append(tableWriter.Row{}, header[1:]...))
}
table.SetAutoFormatHeaders(false)
table.SetAutoWrapText(false)
// merge cells with lab name and topo file path
table.SetAutoMergeCellsByColumnIndex([]int{1, 2})

if wide {
table.SetAutoMergeCellsByColumnIndex([]int{1, 2, 3})
table.SetColumnConfigs([]tableWriter.ColumnConfig{
{Number: 2, AutoMerge: true},
})
}
table.AppendBulk(tabData)

table.AppendRows(tabData)

table.Render()

return nil
Expand All @@ -253,3 +298,16 @@ type TokenFileResults struct {
File string
Labname string
}

func ipWithoutPrefix(ip string) string {
if strings.Contains(ip, "N/A") {
return ip
}

ipParts := strings.Split(ip, "/")
if len(ipParts) != 2 {
return ip
}

return ipParts[0]
}
29 changes: 18 additions & 11 deletions cmd/tools_netem.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import (

"github.com/containernetworking/plugins/pkg/ns"
gotc "github.com/florianl/go-tc"
"github.com/olekukonko/tablewriter"
tableWriter "github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/srl-labs/containerlab/clab"
Expand Down Expand Up @@ -164,8 +165,16 @@ func validateInput(_ *cobra.Command, _ []string) error {
}

func printImpairments(qdiscs []gotc.Object) {
table := tablewriter.NewWriter(os.Stdout)
header := []string{
table := tableWriter.NewWriter()
table.SetOutputMirror(os.Stdout)
table.SetStyle(tableWriter.StyleRounded)
table.Style().Format.Header = text.FormatTitle
table.Style().Format.HeaderAlign = text.AlignCenter
table.Style().Color = tableWriter.ColorOptions{
Header: text.Colors{text.Bold},
}

header := tableWriter.Row{
"Interface",
"Delay",
"Jitter",
Expand All @@ -174,21 +183,19 @@ func printImpairments(qdiscs []gotc.Object) {
"Corruption",
}

table.SetHeader(header)
table.SetAutoFormatHeaders(false)
table.SetAutoWrapText(false)
table.AppendHeader(header)

var rows [][]string
var rows []tableWriter.Row

for _, qdisc := range qdiscs {
rows = append(rows, qdiscToTableData(qdisc))
}

table.AppendBulk(rows)
table.AppendRows(rows)
table.Render()
}

func qdiscToTableData(qdisc gotc.Object) []string {
func qdiscToTableData(qdisc gotc.Object) tableWriter.Row {
link, err := netlink.LinkByIndex(int(qdisc.Ifindex))
if err != nil {
log.Errorf("could not get netlink interface by index: %v", err)
Expand All @@ -204,7 +211,7 @@ func qdiscToTableData(qdisc gotc.Object) []string {
// return N/A values when netem is not set
// which is the case when qdisc is not set for an interface
if qdisc.Netem == nil {
return []string{
return tableWriter.Row{
ifDisplayName,
"N/A", // delay
"N/A", // jitter
Expand All @@ -226,7 +233,7 @@ func qdiscToTableData(qdisc gotc.Object) []string {
rate = strconv.Itoa(int(qdisc.Netem.Rate.Rate * 8 / 1000))
corruption = strconv.FormatFloat(float64(qdisc.Netem.Corrupt.Probability)/float64(math.MaxUint32)*100, 'f', 2, 64) + "%"

return []string{
return tableWriter.Row{
ifDisplayName,
delay,
jitter,
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ require (
github.com/h2non/gock v1.2.0
github.com/hairyhenderson/gomplate/v3 v3.11.8
github.com/hashicorp/go-version v1.7.0
github.com/jedib0t/go-pretty/v6 v6.6.2-0.20241117161227-ada750040aae
github.com/joho/godotenv v1.5.1
github.com/jsimonetti/rtnetlink v1.4.2
github.com/kellerza/template v0.0.6
github.com/klauspost/cpuid/v2 v2.2.9
github.com/mackerelio/go-osstat v0.2.5
github.com/mitchellh/go-homedir v1.1.0
github.com/olekukonko/tablewriter v0.0.5
github.com/opencontainers/runtime-spec v1.2.0
github.com/pkg/errors v0.9.1
github.com/pmorjan/kmod v1.1.1
Expand Down
7 changes: 4 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,10 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jedib0t/go-pretty/v6 v6.6.1 h1:iJ65Xjb680rHcikRj6DSIbzCex2huitmc7bDtxYVWyc=
github.com/jedib0t/go-pretty/v6 v6.6.1/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
github.com/jedib0t/go-pretty/v6 v6.6.2-0.20241117161227-ada750040aae h1:OwQkOZM+eaPuzQAe7ELAhJYGGUxEi/XZYfcC+AjnhIQ=
github.com/jedib0t/go-pretty/v6 v6.6.2-0.20241117161227-ada750040aae/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
Expand Down Expand Up @@ -876,7 +880,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
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.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
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-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
Expand Down Expand Up @@ -973,8 +976,6 @@ github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JX
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.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
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 v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
Expand Down
2 changes: 1 addition & 1 deletion runtime/docker/firewall/definitions/definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package definitions

import "errors"

var ErrNotAvailabel = errors.New("not available")
var ErrNotAvailable = errors.New("not available")

const (
DockerFWUserChain = "DOCKER-USER"
Expand Down
2 changes: 1 addition & 1 deletion runtime/docker/firewall/iptables/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func NewIpTablesClient(bridgeName string) (*IpTablesClient, error) {
if !loaded {
log.Debug("ip_tables kernel module not available")
// module is not loaded
return nil, definitions.ErrNotAvailabel
return nil, definitions.ErrNotAvailable
}

return &IpTablesClient{
Expand Down
13 changes: 1 addition & 12 deletions runtime/docker/firewall/nftables/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,6 @@ type NftablesClient struct {

// NewNftablesClient returns a new NftablesClient.
func NewNftablesClient(bridgeName string) (*NftablesClient, error) {
loaded, err := utils.IsKernelModuleLoaded("nf_tables")
if err != nil {
return nil, err
}

if !loaded {
log.Debug("nf_tables kernel module not available")
// module is not loaded
return nil, definitions.ErrNotAvailabel
}

// setup netlink connection with nftables
nftConn, err := nftables.New(nftables.AsLasting())
if err != nil {
Expand All @@ -51,7 +40,7 @@ func NewNftablesClient(bridgeName string) (*NftablesClient, error) {
}
if len(chains) == 0 {
log.Debugf("nftables does not seem to be in use, no %s chain found.", definitions.DockerFWUserChain)
return nil, definitions.ErrNotAvailabel
return nil, definitions.ErrNotAvailable
}

return nftC, nil
Expand Down
33 changes: 23 additions & 10 deletions tests/01-smoke/01-basic-flow.robot
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ ${runtime} docker
${runtime-cli-exec-cmd} sudo docker exec
${n2-ipv4} 172.20.20.100/24
${n2-ipv6} 3fff:172:20:20::100/64

${table-delimit}

*** Test Cases ***
Verify number of Hosts entries before deploy
Expand Down Expand Up @@ -190,17 +190,30 @@ Ensure "inspect all" outputs IP addresses
... sudo -E ${CLAB_BIN} --runtime ${runtime} inspect --all
Log ${output}
Should Be Equal As Integers ${rc} 0
# get a 3rd line from the bottom of the inspect cmd.
# this relates to the l2 node
${line} = String.Get Line ${output} -3

# get a 4th line from the bottom of the inspect cmd.
# this relates to the l2 node ipv4
${line} = String.Get Line ${output} -6
Log ${line}
@{data} = Split String ${line} |

@{data} = Split String ${line} ${table-delimit}
Log ${data}

# verify ipv4 address
${ipv4} = String.Strip String ${data}[9]
Should Match Regexp ${ipv4} ^[\\d\\.]+/\\d{1,2}$
${ipv4} = String.Strip String ${data}[6]
Should Match Regexp ${ipv4} ^[\\d\\.]+$

# get a 3rd line from the bottom of the inspect cmd.
# this relates to the l2 node ipv6
${line} = String.Get Line ${output} -5
Log ${line}

@{data} = Split String ${line} ${table-delimit}
Log ${data}

# verify ipv6 address
Run Keyword Match IPv6 Address ${data}[10]
${ipv6} = String.Strip String ${data}[6]
Run Keyword Match IPv6 Address ${ipv6}

Verify bind mount in l1 node
${rc} ${output} = Run And Return Rc And Output
Expand Down Expand Up @@ -334,7 +347,7 @@ Verify Exec rc != 0 on no containers match
Verify l1 node is healthy
[Documentation] Checking if l1 node is healthy after the lab is deployed
Sleep 3s
Sleep 10s

${output} = Process.Run Process
... sudo ${runtime} inspect clab-${lab-name}-l1 -f ''{{.State.Health.Status}}''
Expand Down Expand Up @@ -380,4 +393,4 @@ Match IPv6 Address
[Arguments]
... ${address}=${None}
${ipv6} = String.Strip String ${address}
Should Match Regexp ${ipv6} ^[\\d:abcdef]+/\\d{1,2}$
Should Match Regexp ${ipv6} ^[\\d:abcdef]+$
Loading

0 comments on commit 2269834

Please sign in to comment.