From a761fe55160a6c969cbf359fd65f7f5a4b50ce00 Mon Sep 17 00:00:00 2001 From: Ilja Rotar Date: Mon, 19 Jan 2026 09:46:07 +0100 Subject: [PATCH 1/3] wip --- cmd/admin/v1/commands.go | 1 + cmd/admin/v1/switch.go | 84 ++++++++++++++++++++++ cmd/completion/switch.go | 29 ++++++++ cmd/tableprinters/common.go | 9 +++ cmd/tableprinters/switch.go | 137 ++++++++++++++++++++++++++++++++++++ go.mod | 10 +-- go.sum | 22 +++--- 7 files changed, 275 insertions(+), 17 deletions(-) create mode 100644 cmd/admin/v1/switch.go create mode 100644 cmd/completion/switch.go create mode 100644 cmd/tableprinters/switch.go diff --git a/cmd/admin/v1/commands.go b/cmd/admin/v1/commands.go index 52d9c49..00f2523 100644 --- a/cmd/admin/v1/commands.go +++ b/cmd/admin/v1/commands.go @@ -18,6 +18,7 @@ func AddCmds(cmd *cobra.Command, c *config.Config) { adminCmd.AddCommand(newTenantCmd(c)) adminCmd.AddCommand(newTokenCmd(c)) adminCmd.AddCommand(newProjectCmd(c)) + adminCmd.AddCommand(newSwitchCmd(c)) cmd.AddCommand(adminCmd) } diff --git a/cmd/admin/v1/switch.go b/cmd/admin/v1/switch.go new file mode 100644 index 0000000..b1997d7 --- /dev/null +++ b/cmd/admin/v1/switch.go @@ -0,0 +1,84 @@ +package v1 + +import ( + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/metal-lib/pkg/genericcli" + "github.com/metal-stack/metal-lib/pkg/genericcli/printers" + "github.com/metal-stack/metal-lib/pkg/multisort" + "github.com/spf13/cobra" +) + +type switchCmd struct { + c *config.Config +} + +func newSwitchCmd(c *config.Config) *cobra.Command { + sw := &switchCmd{ + c: c, + } + + cmdsConfig := &genericcli.CmdsConfig[any, *adminv2.SwitchServiceUpdateRequest, *apiv2.Switch]{ + GenericCLI: genericcli.NewGenericCLI(sw).WithFS(c.Fs), + OnlyCmds: genericcli.OnlyCmds( + genericcli.DescribeCmd, + genericcli.ListCmd, + genericcli.UpdateCmd, + genericcli.DeleteCmd, + genericcli.EditCmd, + ), + BinaryName: config.BinaryName, + Singular: "switch", + Plural: "switches", + Description: "view and manage network switches", + Aliases: []string{"sw"}, + DescribePrinter: func() printers.Printer { return c.DescribePrinter }, + ListPrinter: func() printers.Printer { return c.ListPrinter }, + Sorter: &multisort.Sorter[*apiv2.Switch]{}, + ListCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().String("id", "", "ID of the switch.") + cmd.Flags().String("name", "", "Name of the switch.") + cmd.Flags().String("os-vendor", "", "OS vendor of this switch.") + cmd.Flags().String("os-version", "", "OS version of this switch.") + cmd.Flags().String("partition", "", "Partition of this switch.") + cmd.Flags().String("rack", "", "Rack of this switch.") + + genericcli.Must(cmd.RegisterFlagCompletionFunc("id", c.Completion.SwitchListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("name", c.Completion.SwitchNameListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("partition", c.Completion.PartitionListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("rack", c.Completion.SwitchRackListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("os-vendor", c.Completion.SwitchOSVendorListCompletion)) + genericcli.Must(cmd.RegisterFlagCompletionFunc("os-version", c.Completion.SwitchOSVersionListCompletion)) + }, + DeleteCmdMutateFn: func(cmd *cobra.Command) { + cmd.Flags().Bool("force", false, "forcefully delete the switch accepting the risk that it still has machines connected to it") + }, + } + + return genericcli.NewCmds(cmdsConfig) +} + +func (c *switchCmd) Get(id string) (*apiv2.Switch, error) { + panic("unimplemented") +} + +func (c *switchCmd) List() ([]*apiv2.Switch, error) { + panic("unimplemented") +} + +func (c *switchCmd) Create(rq any) (*apiv2.Switch, error) { + panic("unimplemented") +} + +func (c *switchCmd) Delete(id string) (*apiv2.Switch, error) { + panic("unimplemented") +} + +func (c *switchCmd) Convert(sw *apiv2.Switch) (string, any, *adminv2.SwitchServiceUpdateRequest, error) { + panic("unimplemented") +} + +func (c *switchCmd) Update(rq *adminv2.SwitchServiceUpdateRequest) (*apiv2.Switch, error) { + panic("unimplemented") +} diff --git a/cmd/completion/switch.go b/cmd/completion/switch.go new file mode 100644 index 0000000..bd87ca8 --- /dev/null +++ b/cmd/completion/switch.go @@ -0,0 +1,29 @@ +package completion + +import ( + "github.com/spf13/cobra" +) + +func (c *Completion) SwitchListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + panic("unimplemented") +} + +func (c *Completion) SwitchNameListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + panic("unimplemented") +} + +func (c *Completion) PartitionListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + panic("unimplemented") +} + +func (c *Completion) SwitchRackListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + panic("unimplemented") +} + +func (c *Completion) SwitchOSVendorListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + panic("unimplemented") +} + +func (c *Completion) SwitchOSVersionListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + panic("unimplemented") +} diff --git a/cmd/tableprinters/common.go b/cmd/tableprinters/common.go index 19c30e7..075faff 100644 --- a/cmd/tableprinters/common.go +++ b/cmd/tableprinters/common.go @@ -12,6 +12,15 @@ import ( "github.com/metal-stack/metal-lib/pkg/pointer" ) +const ( + dot = "●" + halfpie = "◒" + threequarterpie = "◕" + nbr = " " + poweron = "⏻" + powersleep = "⏾" +) + type TablePrinter struct { t *printers.TablePrinter } diff --git a/cmd/tableprinters/switch.go b/cmd/tableprinters/switch.go new file mode 100644 index 0000000..5fcf9f2 --- /dev/null +++ b/cmd/tableprinters/switch.go @@ -0,0 +1,137 @@ +package tableprinters + +import ( + "fmt" + "strings" + "time" + + "github.com/fatih/color" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/metal-lib/pkg/pointer" +) + +func (t *TablePrinter) SwitchTable(data []*apiv2.Switch, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + ) + + header := []string{"ID", "Partition", "Rack", "OS", "Status", "Last Sync"} + if wide { + header = []string{"ID", "Partition", "Rack", "OS", "Metalcore", "IP", "Mode", "Last Sync", "Sync Duration", "Last Error"} + + t.t.DisableAutoWrap(true) + } + + for _, s := range data { + var ( + id = s.Id + partition = s.Partition + rack = pointer.SafeDeref(s.Rack) + + syncTime time.Time + syncLast = "" + syncDurStr = "" + lastError = "" + shortStatus = nbr + allUp = true + ) + + for _, c := range s.MachineConnections { + if c.Nic == nil { + continue + } + + if c.Nic.State == nil { + allUp = false + lastError = fmt.Sprintf("port status of %q is unknown", c.Nic.Name) + break + } + + desired := c.Nic.State.Desired + actual := c.Nic.State.Actual + allUp = allUp && actual == apiv2.SwitchPortStatus_SWITCH_PORT_STATUS_UP + + if desired != nil && actual != *desired { + lastError = fmt.Sprintf("%q is %s but should be %s", c.Nic.Name, c.Nic.State.Actual, desired) + break + } + + if !allUp { + lastError = fmt.Sprintf("%q is %s", c.Nic.Name, c.Nic.State.Actual) + break + } + } + + // FIXME: nil pointer checks and refactor + if s.LastSync != nil { + syncTime = s.LastSync.Time.AsTime() + syncAge := time.Since(syncTime) + syncDur := s.LastSync.Duration.AsDuration().Round(time.Millisecond) + + if syncAge >= time.Minute*10 || syncDur >= 30*time.Second { + shortStatus = color.RedString(dot) + } else if syncAge >= time.Minute*1 || syncDur >= 20*time.Second { + shortStatus = color.YellowString(dot) + } else { + shortStatus = color.GreenString(dot) + if !allUp { + shortStatus = color.YellowString(dot) + } + } + + syncLast = humanizeDuration(syncAge) + " ago" + syncDurStr = fmt.Sprintf("%v", syncDur) + } + + // FIXME: nil pointer checks and refactor + if s.LastSyncError != nil { + errorTime := s.LastSyncError.Time.AsTime() + // after 7 days we do not show sync errors anymore + if !errorTime.IsZero() && time.Since(errorTime) < 7*24*time.Hour { + lastError = fmt.Sprintf("%s ago: %s", humanizeDuration(time.Since(errorTime)), s.LastSyncError.Error) + + if errorTime.After(s.LastSync.Time.AsTime()) { + shortStatus = color.RedString(dot) + } + } + } + + var mode string + switch s.ReplaceMode { + case apiv2.SwitchReplaceMode_SWITCH_REPLACE_MODE_REPLACE: + shortStatus = nbr + color.RedString(dot) + mode = "replace" + default: + mode = "operational" + } + + os := "" + osIcon := "" + metalCore := "" + if s.Os != nil { + switch s.Os.Vendor { + case apiv2.SwitchOSVendor_SWITCH_OS_VENDOR_CUMULUS: + osIcon = "🐢" + case apiv2.SwitchOSVendor_SWITCH_OS_VENDOR_SONIC: + osIcon = "🦔" + default: + osIcon = s.Os.Vendor.String() + } + + os = s.Os.Vendor.String() + if s.Os.Version != "" { + os = fmt.Sprintf("%s (%s)", os, s.Os.Version) + } + // metal core version is very long: v0.9.1 (1d5e42ea), tags/v0.9.1-0-g1d5e42e, go1.20.5 + metalCore = strings.Split(s.Os.MetalCoreVersion, ",")[0] + } + + if wide { + rows = append(rows, []string{id, partition, rack, os, metalCore, s.ManagementIp, mode, syncLast, syncDurStr, lastError}) + } else { + rows = append(rows, []string{id, partition, rack, osIcon, shortStatus, syncLast}) + } + } + + return header, rows, nil +} diff --git a/go.mod b/go.mod index 9399bc5..cd30b20 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.18.0 github.com/google/go-cmp v0.7.0 - github.com/metal-stack/api v0.0.35 + github.com/metal-stack/api v0.0.38-0.20260114100931-81fb1b7d4b93 github.com/metal-stack/metal-lib v0.23.5 github.com/metal-stack/v v1.0.3 github.com/spf13/afero v1.15.0 @@ -15,12 +15,12 @@ require ( github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 golang.org/x/net v0.46.0 - google.golang.org/protobuf v1.36.10 + google.golang.org/protobuf v1.36.11 sigs.k8s.io/yaml v1.6.0 ) require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 // indirect connectrpc.com/connect v1.19.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/clipperhouse/uax29/v2 v2.2.0 // indirect @@ -35,7 +35,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.18.1 // indirect + github.com/klauspost/compress v1.18.2 // indirect github.com/klauspost/connect-compress/v2 v2.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -58,7 +58,7 @@ require ( go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/sys v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/text v0.32.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apimachinery v0.34.1 // indirect diff --git a/go.sum b/go.sum index cbca136..87ad89c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 h1:31on4W/yPcV4nZHL4+UCiCvLPsMqe/vJcNg8Rci0scc= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1/go.mod h1:fUl8CEN/6ZAMk6bP8ahBJPUJw7rbp+j4x+wCcYi2IG4= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 h1:j9yeqTWEFrtimt8Nng2MIeRrpoCvQzM9/g25XTvqUGg= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -39,8 +39,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= -github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/connect-compress/v2 v2.1.0 h1:8fM8QrVeHT69e5VVSh4yjDaQASYIvOp2uMZq7nVLj2U= github.com/klauspost/connect-compress/v2 v2.1.0/go.mod h1:Ayurh2wscMMx3AwdGGVL+ylSR5316WfApREDgsqHyH8= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -53,10 +53,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/metal-stack/api v0.0.35-0.20251124101516-a3076941d0f8 h1:PGBiFwASqhtmI6dGzVo0IEVQiBTIsQ2eo3pMriPViNY= -github.com/metal-stack/api v0.0.35-0.20251124101516-a3076941d0f8/go.mod h1:EBwS/oZr5tIcnV6hM7iK4aBQrw4wlU7vF5p+O1p3YIU= -github.com/metal-stack/api v0.0.35 h1:XxxYKTscSeYJg/ftL519nY3FAZ01atPeyD7+Zz/amQQ= -github.com/metal-stack/api v0.0.35/go.mod h1:EBwS/oZr5tIcnV6hM7iK4aBQrw4wlU7vF5p+O1p3YIU= +github.com/metal-stack/api v0.0.38-0.20260114100931-81fb1b7d4b93 h1:GfDGUyn3KA7LI/NyO6smxFs2xkW6bKQxce77cDylgJ4= +github.com/metal-stack/api v0.0.38-0.20260114100931-81fb1b7d4b93/go.mod h1:lVDIha/gViLpYuJi+OhQIQCeh6XYdzGxrtbtJTJ94eI= github.com/metal-stack/metal-lib v0.23.5 h1:ozrkB3DNr3Cqn8nkBvmzc/KKpYqC1j1mv2OVOj8i7Ac= github.com/metal-stack/metal-lib v0.23.5/go.mod h1:7uyHIrE19dkLwCZyeh2jmd7IEq5pEpzrzUGLoMN1eqY= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= @@ -113,10 +111,10 @@ golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 82b4e3afcac2e11305cb82fb19f168c0f2b92d48 Mon Sep 17 00:00:00 2001 From: Ilja Rotar Date: Mon, 19 Jan 2026 09:56:39 +0100 Subject: [PATCH 2/3] rename package --- cmd/admin/v2/switch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/admin/v2/switch.go b/cmd/admin/v2/switch.go index b1997d7..9aaa4ee 100644 --- a/cmd/admin/v2/switch.go +++ b/cmd/admin/v2/switch.go @@ -1,4 +1,4 @@ -package v1 +package v2 import ( adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" From ee2a5b6c3553e771b13bd13e48093f3dec8a4056 Mon Sep 17 00:00:00 2001 From: Ilja Rotar Date: Mon, 19 Jan 2026 11:48:32 +0100 Subject: [PATCH 3/3] refactor switch table --- cmd/tableprinters/common.go | 8 ++---- cmd/tableprinters/switch.go | 55 +++++++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/cmd/tableprinters/common.go b/cmd/tableprinters/common.go index 075faff..5a519ca 100644 --- a/cmd/tableprinters/common.go +++ b/cmd/tableprinters/common.go @@ -13,12 +13,8 @@ import ( ) const ( - dot = "●" - halfpie = "◒" - threequarterpie = "◕" - nbr = " " - poweron = "⏻" - powersleep = "⏾" + dot = "●" + nbr = " " ) type TablePrinter struct { diff --git a/cmd/tableprinters/switch.go b/cmd/tableprinters/switch.go index 5fcf9f2..2ca9c27 100644 --- a/cmd/tableprinters/switch.go +++ b/cmd/tableprinters/switch.go @@ -10,7 +10,7 @@ import ( "github.com/metal-stack/metal-lib/pkg/pointer" ) -func (t *TablePrinter) SwitchTable(data []*apiv2.Switch, wide bool) ([]string, [][]string, error) { +func (t *TablePrinter) SwitchTable(switches []*apiv2.Switch, wide bool) ([]string, [][]string, error) { var ( rows [][]string ) @@ -22,7 +22,7 @@ func (t *TablePrinter) SwitchTable(data []*apiv2.Switch, wide bool) ([]string, [ t.t.DisableAutoWrap(true) } - for _, s := range data { + for _, s := range switches { var ( id = s.Id partition = s.Partition @@ -62,35 +62,54 @@ func (t *TablePrinter) SwitchTable(data []*apiv2.Switch, wide bool) ([]string, [ } } - // FIXME: nil pointer checks and refactor if s.LastSync != nil { - syncTime = s.LastSync.Time.AsTime() - syncAge := time.Since(syncTime) - syncDur := s.LastSync.Duration.AsDuration().Round(time.Millisecond) + var ( + syncAge time.Duration + syncDur time.Duration + ) + + if s.LastSync.Time != nil && !s.LastSync.Time.AsTime().IsZero() { + syncTime = s.LastSync.Time.AsTime() + syncAge = time.Since(syncTime) + } + if s.LastSync.Duration != nil { + syncDur = s.LastSync.Duration.AsDuration().Round(time.Millisecond) + } - if syncAge >= time.Minute*10 || syncDur >= 30*time.Second { + switch { + case syncAge >= 10*time.Minute, syncDur >= 30*time.Second: shortStatus = color.RedString(dot) - } else if syncAge >= time.Minute*1 || syncDur >= 20*time.Second { + case syncAge >= time.Minute, syncDur >= 20*time.Second, !allUp: shortStatus = color.YellowString(dot) - } else { + default: shortStatus = color.GreenString(dot) - if !allUp { - shortStatus = color.YellowString(dot) - } } - syncLast = humanizeDuration(syncAge) + " ago" - syncDurStr = fmt.Sprintf("%v", syncDur) + if syncAge > 0 { + syncLast = humanizeDuration(syncAge) + " ago" + } + if syncDur > 0 { + syncDurStr = fmt.Sprintf("%v", syncDur) + } } - // FIXME: nil pointer checks and refactor if s.LastSyncError != nil { - errorTime := s.LastSyncError.Time.AsTime() + var ( + errorTime time.Time + error string + ) + + if s.LastSyncError.Time != nil { + errorTime = s.LastSyncError.Time.AsTime() + } + if s.LastSyncError.Error != nil { + error = *s.LastSyncError.Error + } // after 7 days we do not show sync errors anymore if !errorTime.IsZero() && time.Since(errorTime) < 7*24*time.Hour { - lastError = fmt.Sprintf("%s ago: %s", humanizeDuration(time.Since(errorTime)), s.LastSyncError.Error) + lastError = fmt.Sprintf("%s ago: %s", humanizeDuration(time.Since(errorTime)), error) - if errorTime.After(s.LastSync.Time.AsTime()) { + if errorTime.After(syncTime) { shortStatus = color.RedString(dot) } }