From e75535d30b60455e95cb8a249b29160e24d27ec8 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Thu, 23 Feb 2023 20:13:19 +0100 Subject: [PATCH 01/12] Refactor status functions and add first tests --- client/cmd/status.go | 291 +++++++++++++++++++++----------------- client/cmd/status_test.go | 134 ++++++++++++++++++ 2 files changed, 295 insertions(+), 130 deletions(-) create mode 100644 client/cmd/status_test.go diff --git a/client/cmd/status.go b/client/cmd/status.go index 1582e8b90..78179c72b 100644 --- a/client/cmd/status.go +++ b/client/cmd/status.go @@ -2,7 +2,9 @@ package cmd import ( "context" + "encoding/json" "fmt" + yaml2 "gopkg.in/yaml.v2" "net" "net/netip" "sort" @@ -21,6 +23,8 @@ import ( var ( detailFlag bool ipv4Flag bool + jsonFlag bool + yamlFlag bool ipsFilter []string statusFilter string ipsFilterMap map[string]struct{} @@ -29,67 +33,92 @@ var ( var statusCmd = &cobra.Command{ Use: "status", Short: "status of the Netbird Service", - RunE: func(cmd *cobra.Command, args []string) error { - SetFlagsFromEnvVars(rootCmd) - - cmd.SetOut(cmd.OutOrStdout()) - - err := parseFilters() - if err != nil { - return err - } - - err = util.InitLog(logLevel, "console") - if err != nil { - return fmt.Errorf("failed initializing log %v", err) - } - - ctx := internal.CtxInitState(context.Background()) - - conn, err := DialClientGRPCServer(ctx, daemonAddr) - if err != nil { - return fmt.Errorf("failed to connect to daemon error: %v\n"+ - "If the daemon is not running please run: "+ - "\nnetbird service install \nnetbird service start\n", err) - } - defer conn.Close() - - resp, err := proto.NewDaemonServiceClient(conn).Status(cmd.Context(), &proto.StatusRequest{GetFullPeerStatus: true}) - if err != nil { - return fmt.Errorf("status failed: %v", status.Convert(err).Message()) - } - - daemonStatus := fmt.Sprintf("Daemon status: %s\n", resp.GetStatus()) - if resp.GetStatus() == string(internal.StatusNeedsLogin) || resp.GetStatus() == string(internal.StatusLoginFailed) { - - cmd.Printf("%s\n"+ - "Run UP command to log in with SSO (interactive login):\n\n"+ - " netbird up \n\n"+ - "If you are running a self-hosted version and no SSO provider has been configured in your Management Server,\n"+ - "you can use a setup-key:\n\n netbird up --management-url --setup-key \n\n"+ - "More info: https://www.netbird.io/docs/overview/setup-keys\n\n", - daemonStatus, - ) - return nil - } - - pbFullStatus := resp.GetFullStatus() - fullStatus := fromProtoFullStatus(pbFullStatus) - - cmd.Print(parseFullStatus(fullStatus, detailFlag, daemonStatus, resp.GetDaemonVersion(), ipv4Flag)) - - return nil - }, + RunE: statusFunc, } func init() { ipsFilterMap = make(map[string]struct{}) - statusCmd.PersistentFlags().BoolVarP(&detailFlag, "detail", "d", false, "display detailed status information") + statusCmd.PersistentFlags().BoolVarP(&detailFlag, "detail", "d", false, "display detailed status information in human-readable format") + statusCmd.PersistentFlags().BoolVar(&jsonFlag, "json", false, "display detailed status information in json format") + statusCmd.PersistentFlags().BoolVar(&yamlFlag, "yaml", false, "display detailed status information in yaml format") statusCmd.PersistentFlags().BoolVar(&ipv4Flag, "ipv4", false, "display only NetBird IPv4 of this peer, e.g., --ipv4 will output 100.64.0.33") + statusCmd.MarkFlagsMutuallyExclusive("detail", "json", "yaml", "ipv4") statusCmd.PersistentFlags().StringSliceVar(&ipsFilter, "filter-by-ips", []string{}, "filters the detailed output by a list of one or more IPs, e.g., --filter-by-ips 100.64.0.100,100.64.0.200") statusCmd.PersistentFlags().StringVar(&statusFilter, "filter-by-status", "", "filters the detailed output by connection status(connected|disconnected), e.g., --filter-by-status connected") } +func statusFunc(cmd *cobra.Command, args []string) error { + SetFlagsFromEnvVars(rootCmd) + + cmd.SetOut(cmd.OutOrStdout()) + + err := parseFilters() + if err != nil { + return err + } + + err = util.InitLog(logLevel, "console") + if err != nil { + return fmt.Errorf("failed initializing log %v", err) + } + + ctx := internal.CtxInitState(context.Background()) + + conn, err := DialClientGRPCServer(ctx, daemonAddr) + if err != nil { + return fmt.Errorf("failed to connect to daemon error: %v\n"+ + "If the daemon is not running please run: "+ + "\nnetbird service install \nnetbird service start\n", err) + } + defer conn.Close() + + resp, err := proto.NewDaemonServiceClient(conn).Status(cmd.Context(), &proto.StatusRequest{GetFullPeerStatus: true}) + if err != nil { + return fmt.Errorf("status failed: %v", status.Convert(err).Message()) + } + + daemonStatus := fmt.Sprintf("Daemon status: %s\n", resp.GetStatus()) + if resp.GetStatus() == string(internal.StatusNeedsLogin) || resp.GetStatus() == string(internal.StatusLoginFailed) { + + cmd.Printf("%s\n"+ + "Run UP command to log in with SSO (interactive login):\n\n"+ + " netbird up \n\n"+ + "If you are running a self-hosted version and no SSO provider has been configured in your Management Server,\n"+ + "you can use a setup-key:\n\n netbird up --management-url --setup-key \n\n"+ + "More info: https://www.netbird.io/docs/overview/setup-keys\n\n", + daemonStatus, + ) + return nil + } + + pbFullStatus := resp.GetFullStatus() + fullStatus := fromProtoFullStatus(pbFullStatus) + + statusOutputString := "" + if detailFlag { + statusOutputString = parseToHumanReadable(fullStatus, daemonStatus, resp.GetDaemonVersion()) + } + if jsonFlag { + statusOutputString, err = parseToJson(fullStatus) + if err != nil { + return fmt.Errorf("json marshal failed") + } + } + if yamlFlag { + statusOutputString, err = parseToYaml(fullStatus) + if err != nil { + return fmt.Errorf("yaml marshal failed") + } + } + if ipv4Flag { + statusOutputString = parseInterfaceIP(fullStatus.LocalPeerState.IP) + } + + cmd.Print(statusOutputString) + + return nil +} + func parseFilters() error { switch strings.ToLower(statusFilter) { case "", "disconnected", "connected": @@ -148,40 +177,51 @@ func fromProtoFullStatus(pbFullStatus *proto.FullStatus) nbStatus.FullStatus { return fullStatus } -func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonStatus string, daemonVersion string, flag bool) string { - - interfaceIP := fullStatus.LocalPeerState.IP - +func parseInterfaceIP(interfaceIP string) string { ip, _, err := net.ParseCIDR(interfaceIP) if err != nil { return "" } + return fmt.Sprintf("%s\n", ip) +} - if ipv4Flag { - return fmt.Sprintf("%s\n", ip) +func parseToJson(fullStatus nbStatus.FullStatus) (string, error) { + jsonBytes, err := json.Marshal(fullStatus) + return string(jsonBytes), err +} + +func parseToYaml(fullStatus nbStatus.FullStatus) (string, error) { + yamlBytes, err := yaml2.Marshal(fullStatus) + return string(yamlBytes), err +} + +func countConnectedPeers(peers []nbStatus.PeerState) int { + peersConnected := 0 + for _, peerState := range peers { + if peerState.ConnStatus == peer.StatusConnected.String() { + peersConnected = peersConnected + 1 + } } + return peersConnected +} - var ( - managementStatusURL = "" - signalStatusURL = "" - managementConnString = "Disconnected" - signalConnString = "Disconnected" - interfaceTypeString = "Userspace" - ) +func parseGeneralSummary(fullStatus nbStatus.FullStatus, daemonStatus string, daemonVersion string) string { - if printDetail { - managementStatusURL = fmt.Sprintf(" to %s", fullStatus.ManagementState.URL) - signalStatusURL = fmt.Sprintf(" to %s", fullStatus.SignalState.URL) - } + managementStatusURL := fmt.Sprintf(" to %s", fullStatus.ManagementState.URL) + signalStatusURL := fmt.Sprintf(" to %s", fullStatus.SignalState.URL) + managementConnString := "Disconnected" if fullStatus.ManagementState.Connected { managementConnString = "Connected" } + signalConnString := "Disconnected" if fullStatus.SignalState.Connected { signalConnString = "Connected" } + interfaceTypeString := "Userspace" + interfaceIP := "" if fullStatus.LocalPeerState.KernelInterface { interfaceTypeString = "Kernel" } else if fullStatus.LocalPeerState.IP == "" { @@ -189,8 +229,7 @@ func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonSta interfaceIP = "N/A" } - parsedPeersString, peersConnected := parsePeers(fullStatus.Peers, printDetail) - + peersConnected := countConnectedPeers(fullStatus.Peers) peersCountString := fmt.Sprintf("%d/%d Connected", peersConnected, len(fullStatus.Peers)) summary := fmt.Sprintf( @@ -215,23 +254,25 @@ func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonSta interfaceTypeString, peersCountString, ) - - if printDetail { - return fmt.Sprintf( - "Peers detail:"+ - "%s\n"+ - "%s", - parsedPeersString, - summary, - ) - } return summary } -func parsePeers(peers []nbStatus.PeerState, printDetail bool) (string, int) { +func parseToHumanReadable(fullStatus nbStatus.FullStatus, daemonStatus string, daemonVersion string) string { + parsedPeersString := parsePeers(fullStatus.Peers) + summary := parseGeneralSummary(fullStatus, daemonStatus, daemonVersion) + + return fmt.Sprintf( + "Peers detail:"+ + "%s\n"+ + "%s", + parsedPeersString, + summary, + ) +} + +func parsePeers(peers []nbStatus.PeerState) string { var ( - peersString = "" - peersConnected = 0 + peersString = "" ) if len(peers) > 0 { @@ -242,59 +283,49 @@ func parsePeers(peers []nbStatus.PeerState, printDetail bool) (string, int) { }) } - connectedStatusString := peer.StatusConnected.String() - for _, peerState := range peers { - peerConnectionStatus := false - if peerState.ConnStatus == connectedStatusString { - peersConnected = peersConnected + 1 - peerConnectionStatus = true + peerConnectionStatus := peerState.ConnStatus == peer.StatusConnected.String() + if skipDetailByFilters(peerState, peerConnectionStatus) { + continue } - if printDetail { + localICE := "-" + remoteICE := "-" + connType := "-" - if skipDetailByFilters(peerState, peerConnectionStatus) { - continue + if peerConnectionStatus { + localICE = peerState.LocalIceCandidateType + remoteICE = peerState.RemoteIceCandidateType + connType = "P2P" + if peerState.Relayed { + connType = "Relayed" } - - localICE := "-" - remoteICE := "-" - connType := "-" - - if peerConnectionStatus { - localICE = peerState.LocalIceCandidateType - remoteICE = peerState.RemoteIceCandidateType - connType = "P2P" - if peerState.Relayed { - connType = "Relayed" - } - } - - peerString := fmt.Sprintf( - "\n %s:\n"+ - " NetBird IP: %s\n"+ - " Public key: %s\n"+ - " Status: %s\n"+ - " -- detail --\n"+ - " Connection type: %s\n"+ - " Direct: %t\n"+ - " ICE candidate (Local/Remote): %s/%s\n"+ - " Last connection update: %s\n", - peerState.FQDN, - peerState.IP, - peerState.PubKey, - peerState.ConnStatus, - connType, - peerState.Direct, - localICE, - remoteICE, - peerState.ConnStatusUpdate.Format("2006-01-02 15:04:05"), - ) - - peersString = peersString + peerString } + + peerString := fmt.Sprintf( + "\n %s:\n"+ + " NetBird IP: %s\n"+ + " Public key: %s\n"+ + " Status: %s\n"+ + " -- detail --\n"+ + " Connection type: %s\n"+ + " Direct: %t\n"+ + " ICE candidate (Local/Remote): %s/%s\n"+ + " Last connection update: %s\n", + peerState.FQDN, + peerState.IP, + peerState.PubKey, + peerState.ConnStatus, + connType, + peerState.Direct, + localICE, + remoteICE, + peerState.ConnStatusUpdate.Format("2006-01-02 15:04:05"), + ) + + peersString = peersString + peerString } - return peersString, peersConnected + return peersString } func skipDetailByFilters(peerState nbStatus.PeerState, isConnected bool) bool { diff --git a/client/cmd/status_test.go b/client/cmd/status_test.go new file mode 100644 index 000000000..ea94282c0 --- /dev/null +++ b/client/cmd/status_test.go @@ -0,0 +1,134 @@ +package cmd + +import ( + nbStatus "github.com/netbirdio/netbird/client/status" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +var fullStatus = nbStatus.FullStatus{ + Peers: []nbStatus.PeerState{ + { + IP: "192.168.178.101", + PubKey: "Pubkey1", + FQDN: "peer-1.awesome-domain.com", + ConnStatus: "Connected", + ConnStatusUpdate: time.Date(2001, time.Month(1), 1, 1, 1, 1, 0, time.UTC), + Relayed: false, + LocalIceCandidateType: "-", + RemoteIceCandidateType: "-", + }, + { + IP: "192.168.178.102", + PubKey: "Pubkey2", + FQDN: "peer-2.awesome-domain.com", + ConnStatus: "Connected", + ConnStatusUpdate: time.Date(2002, time.Month(2), 2, 2, 2, 2, 0, time.UTC), + Relayed: false, + LocalIceCandidateType: "-", + RemoteIceCandidateType: "-", + }, + }, + ManagementState: nbStatus.ManagementState{ + URL: "my-awesome-management.com:443", + Connected: true, + }, + SignalState: nbStatus.SignalState{ + URL: "my-awesome-signal.com:443", + Connected: true, + }, + LocalPeerState: nbStatus.LocalPeerState{ + IP: "192.168.178.2", + PubKey: "Some-Pub-Key", + KernelInterface: false, + FQDN: "some-localhost.awesome-domain.com", + }, +} + +// @formatter:off +func TestParsingToJson(t *testing.T) { + json, _ := parseToJson(fullStatus) + + expectedJson := "{" + + "\"Peers\":" + + "[" + + "{" + + "\"IP\":\"192.168.178.101\"," + + "\"PubKey\":\"Pubkey1\"," + + "\"FQDN\":\"peer-1.awesome-domain.com\"," + + "\"ConnStatus\":\"Connected\"," + + "\"ConnStatusUpdate\":\"2001-01-01T01:01:01Z\"," + + "\"Relayed\":false," + + "\"Direct\":false," + + "\"LocalIceCandidateType\":\"-\"," + + "\"RemoteIceCandidateType\":\"-\"" + + "}," + + "{" + + "\"IP\":\"192.168.178.102\"," + + "\"PubKey\":\"Pubkey2\"," + + "\"FQDN\":\"peer-2.awesome-domain.com\"," + + "\"ConnStatus\":\"Connected\"," + + "\"ConnStatusUpdate\":\"2002-02-02T02:02:02Z\"," + + "\"Relayed\":false," + + "\"Direct\":false," + + "\"LocalIceCandidateType\":\"-\"," + + "\"RemoteIceCandidateType\":\"-\"" + + "}" + + "]," + + "\"ManagementState\":" + + "{" + + "\"URL\":\"my-awesome-management.com:443\"," + + "\"Connected\":true" + + "}," + + "\"SignalState\":" + + "{" + + "\"URL\":\"my-awesome-signal.com:443\"," + + "\"Connected\":true" + + "}," + + "\"LocalPeerState\":" + + "{" + + "\"IP\":\"192.168.178.2\"," + + "\"PubKey\":\"Some-Pub-Key\"," + + "\"KernelInterface\":false," + + "\"FQDN\":\"some-localhost.awesome-domain.com\"" + + "}" + + "}" + assert.Equal(t, expectedJson, json) +} + +func TestParsingToYaml(t *testing.T) { + yaml, _ := parseToYaml(fullStatus) + + expectedYaml := "peers:\n" + + "- ip: 192.168.178.101\n" + + " pubkey: Pubkey1\n" + + " fqdn: peer-1.awesome-domain.com\n" + + " connstatus: Connected\n" + + " connstatusupdate: 2001-01-01T01:01:01Z\n" + + " relayed: false\n" + + " direct: false\n" + + " localicecandidatetype: '-'\n" + + " remoteicecandidatetype: '-'\n" + + "- ip: 192.168.178.102\n" + + " pubkey: Pubkey2\n" + + " fqdn: peer-2.awesome-domain.com\n" + + " connstatus: Connected\n" + + " connstatusupdate: 2002-02-02T02:02:02Z\n" + + " relayed: false\n" + + " direct: false\n" + + " localicecandidatetype: '-'\n" + + " remoteicecandidatetype: '-'\n" + + "managementstate:\n" + + " url: my-awesome-management.com:443\n" + + " connected: true\n" + + "signalstate:\n" + + " url: my-awesome-signal.com:443\n" + + " connected: true\n" + + "localpeerstate:\n" + + " ip: 192.168.178.2\n" + + " pubkey: Some-Pub-Key\n" + + " kernelinterface: false\n" + + " fqdn: some-localhost.awesome-domain.com\n" + assert.Equal(t, expectedYaml, yaml) +} From 78c6231c0129ec4e461bbd630233718cadac8f54 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Fri, 24 Feb 2023 19:01:54 +0100 Subject: [PATCH 02/12] Added Output struct to properly name json and yaml attr's and add missing tests --- client/cmd/status.go | 356 +++++++++++++++++++++++--------------- client/cmd/status_test.go | 342 ++++++++++++++++++++++++++---------- 2 files changed, 464 insertions(+), 234 deletions(-) diff --git a/client/cmd/status.go b/client/cmd/status.go index 78179c72b..3d7337eb8 100644 --- a/client/cmd/status.go +++ b/client/cmd/status.go @@ -4,22 +4,64 @@ import ( "context" "encoding/json" "fmt" - yaml2 "gopkg.in/yaml.v2" "net" "net/netip" "sort" "strings" + "time" + + "github.com/spf13/cobra" + "google.golang.org/grpc/status" + "gopkg.in/yaml.v2" "github.com/netbirdio/netbird/client/internal" "github.com/netbirdio/netbird/client/internal/peer" "github.com/netbirdio/netbird/client/proto" - nbStatus "github.com/netbirdio/netbird/client/status" "github.com/netbirdio/netbird/client/system" "github.com/netbirdio/netbird/util" - "github.com/spf13/cobra" - "google.golang.org/grpc/status" ) +type peerStateDetailOutput struct { + IP string `json:"ip" yaml:"ip"` + PubKey string `json:"publicKey" yaml:"publicKey"` + FQDN string `json:"fqdn" yaml:"fqdn"` + ConnStatus string `json:"connectionStatus" yaml:"connectionStatus"` + ConnStatusUpdate time.Time `json:"connectionStatusUpdate" yaml:"connectionStatusUpdate"` + ConnType string `json:"connectionType" yaml:"connectionType"` + Direct bool `json:"direct" yaml:"direct"` + LocalIceCandidateType string `json:"localIceCandidateType" yaml:"localIceCandidateType"` + RemoteIceCandidateType string `json:"remoteIceCandidateType" yaml:"remoteIceCandidateType"` +} + +type peersStateOutput struct { + Total int `json:"total" yaml:"total"` + Connected int `json:"connected" yaml:"connected"` + Details []peerStateDetailOutput `json:"details" yaml:"details"` +} + +type signalStateOutput struct { + URL string `json:"url" yaml:"url"` + Connected bool `json:"connected" yaml:"connected"` +} + +type managementStateOutput struct { + URL string `json:"url" yaml:"url"` + Connected bool `json:"connected" yaml:"connected"` +} + +type statusOutputOverview struct { + Peers peersStateOutput `json:"peers" yaml:"peers"` + CliVersion string `json:"cliVersion" yaml:"cliVersion"` + DaemonVersion string `json:"daemonVersion" yaml:"daemonVersion"` + DaemonStatus string `json:"daemonStatus" yaml:"daemonStatus"` + ManagementState managementStateOutput `json:"management" yaml:"management"` + SignalState signalStateOutput `json:"signal" yaml:"signal"` + IP string `json:"ip" yaml:"ip"` + PubKey string `json:"publicKey" yaml:"publicKey"` + KernelInterface string `json:"interfaceType" yaml:"interfaceType"` + FQDN string `json:"domain" yaml:"domain"` +} + var ( detailFlag bool ipv4Flag bool @@ -64,9 +106,56 @@ func statusFunc(cmd *cobra.Command, args []string) error { ctx := internal.CtxInitState(context.Background()) + resp, _ := getStatus(ctx, cmd) + if err != nil { + return nil + } + + if resp.GetStatus() == string(internal.StatusNeedsLogin) || resp.GetStatus() == string(internal.StatusLoginFailed) { + cmd.Printf("Daemon status: %s\n\n"+ + "Run UP command to log in with SSO (interactive login):\n\n"+ + " netbird up \n\n"+ + "If you are running a self-hosted version and no SSO provider has been configured in your Management Server,\n"+ + "you can use a setup-key:\n\n netbird up --management-url --setup-key \n\n"+ + "More info: https://www.netbird.io/docs/overview/setup-keys\n\n", + resp.GetStatus(), + ) + return nil + } + + if ipv4Flag { + cmd.Print(parseInterfaceIP(resp.GetFullStatus().GetLocalPeerState().GetIP())) + return nil + } + + statusOutputOverview := convertToStatusOutputOverview(resp) + + statusOutputString := "" + if detailFlag { + statusOutputString = parseToFullDetailSummary(statusOutputOverview) + } else if jsonFlag { + statusOutputString, err = parseToJson(statusOutputOverview) + if err != nil { + return err + } + } else if yamlFlag { + statusOutputString, err = parseToYaml(statusOutputOverview) + if err != nil { + return err + } + } else { + statusOutputString = parseGeneralSummary(statusOutputOverview, false) + } + + cmd.Print(statusOutputString) + + return nil +} + +func getStatus(ctx context.Context, cmd *cobra.Command) (*proto.StatusResponse, error) { conn, err := DialClientGRPCServer(ctx, daemonAddr) if err != nil { - return fmt.Errorf("failed to connect to daemon error: %v\n"+ + return nil, fmt.Errorf("failed to connect to daemon error: %v\n"+ "If the daemon is not running please run: "+ "\nnetbird service install \nnetbird service start\n", err) } @@ -74,49 +163,10 @@ func statusFunc(cmd *cobra.Command, args []string) error { resp, err := proto.NewDaemonServiceClient(conn).Status(cmd.Context(), &proto.StatusRequest{GetFullPeerStatus: true}) if err != nil { - return fmt.Errorf("status failed: %v", status.Convert(err).Message()) + return nil, fmt.Errorf("status failed: %v", status.Convert(err).Message()) } - daemonStatus := fmt.Sprintf("Daemon status: %s\n", resp.GetStatus()) - if resp.GetStatus() == string(internal.StatusNeedsLogin) || resp.GetStatus() == string(internal.StatusLoginFailed) { - - cmd.Printf("%s\n"+ - "Run UP command to log in with SSO (interactive login):\n\n"+ - " netbird up \n\n"+ - "If you are running a self-hosted version and no SSO provider has been configured in your Management Server,\n"+ - "you can use a setup-key:\n\n netbird up --management-url --setup-key \n\n"+ - "More info: https://www.netbird.io/docs/overview/setup-keys\n\n", - daemonStatus, - ) - return nil - } - - pbFullStatus := resp.GetFullStatus() - fullStatus := fromProtoFullStatus(pbFullStatus) - - statusOutputString := "" - if detailFlag { - statusOutputString = parseToHumanReadable(fullStatus, daemonStatus, resp.GetDaemonVersion()) - } - if jsonFlag { - statusOutputString, err = parseToJson(fullStatus) - if err != nil { - return fmt.Errorf("json marshal failed") - } - } - if yamlFlag { - statusOutputString, err = parseToYaml(fullStatus) - if err != nil { - return fmt.Errorf("yaml marshal failed") - } - } - if ipv4Flag { - statusOutputString = parseInterfaceIP(fullStatus.LocalPeerState.IP) - } - - cmd.Print(statusOutputString) - - return nil + return resp, nil } func parseFilters() error { @@ -138,43 +188,104 @@ func parseFilters() error { return nil } -func fromProtoFullStatus(pbFullStatus *proto.FullStatus) nbStatus.FullStatus { - var fullStatus nbStatus.FullStatus +func convertToStatusOutputOverview(resp *proto.StatusResponse) statusOutputOverview { + pbFullStatus := resp.GetFullStatus() + managementState := pbFullStatus.GetManagementState() - fullStatus.ManagementState.URL = managementState.GetURL() - fullStatus.ManagementState.Connected = managementState.GetConnected() + managementOverview := managementStateOutput{ + URL: managementState.GetURL(), + Connected: managementState.GetConnected(), + } signalState := pbFullStatus.GetSignalState() - fullStatus.SignalState.URL = signalState.GetURL() - fullStatus.SignalState.Connected = signalState.GetConnected() + signalOverview := signalStateOutput{ + URL: signalState.GetURL(), + Connected: signalState.GetConnected(), + } - localPeerState := pbFullStatus.GetLocalPeerState() - fullStatus.LocalPeerState.IP = localPeerState.GetIP() - fullStatus.LocalPeerState.PubKey = localPeerState.GetPubKey() - fullStatus.LocalPeerState.KernelInterface = localPeerState.GetKernelInterface() - fullStatus.LocalPeerState.FQDN = localPeerState.GetFqdn() + peersOverview := mapPeers(resp.GetFullStatus().GetPeers()) - var peersState []nbStatus.PeerState + interfaceTypeString := "Userspace" + interfaceIP := pbFullStatus.GetLocalPeerState().GetIP() + if pbFullStatus.LocalPeerState.KernelInterface { + interfaceTypeString = "Kernel" + } else if pbFullStatus.LocalPeerState.IP == "" { + interfaceTypeString = "N/A" + interfaceIP = "N/A" + } + + overview := statusOutputOverview{ + Peers: peersOverview, + CliVersion: system.NetbirdVersion(), + DaemonVersion: resp.GetDaemonVersion(), + DaemonStatus: resp.GetStatus(), + ManagementState: managementOverview, + SignalState: signalOverview, + IP: interfaceIP, + PubKey: pbFullStatus.GetLocalPeerState().GetPubKey(), + KernelInterface: interfaceTypeString, + FQDN: pbFullStatus.GetLocalPeerState().GetFqdn(), + } + + return overview +} + +func mapPeers(peers []*proto.PeerState) peersStateOutput { + var peersStateDetail []peerStateDetailOutput + localICE := "-" + remoteICE := "-" + connType := "-" + peersConnected := 0 + for _, pbPeerState := range peers { + isPeerConnected := pbPeerState.ConnStatus == peer.StatusConnected.String() + if skipDetailByFilters(pbPeerState, isPeerConnected) { + continue + } + if isPeerConnected { + peersConnected = peersConnected + 1 + + localICE = pbPeerState.GetLocalIceCandidateType() + remoteICE = pbPeerState.GetRemoteIceCandidateType() + connType = "P2P" + if pbPeerState.Relayed { + connType = "Relayed" + } + } - for _, pbPeerState := range pbFullStatus.GetPeers() { timeLocal := pbPeerState.GetConnStatusUpdate().AsTime().Local() - peerState := nbStatus.PeerState{ + peerState := peerStateDetailOutput{ IP: pbPeerState.GetIP(), PubKey: pbPeerState.GetPubKey(), ConnStatus: pbPeerState.GetConnStatus(), - ConnStatusUpdate: timeLocal, - Relayed: pbPeerState.GetRelayed(), + ConnStatusUpdate: timeLocal.UTC(), + ConnType: connType, Direct: pbPeerState.GetDirect(), - LocalIceCandidateType: pbPeerState.GetLocalIceCandidateType(), - RemoteIceCandidateType: pbPeerState.GetRemoteIceCandidateType(), + LocalIceCandidateType: localICE, + RemoteIceCandidateType: remoteICE, FQDN: pbPeerState.GetFqdn(), } - peersState = append(peersState, peerState) + + peersStateDetail = append(peersStateDetail, peerState) } - fullStatus.Peers = peersState + sortPeersByIp(peersStateDetail) - return fullStatus + peersOverview := peersStateOutput{ + Total: len(peersStateDetail), + Connected: peersConnected, + Details: peersStateDetail, + } + return peersOverview +} + +func sortPeersByIp(peersStateDetail []peerStateDetailOutput) { + if len(peersStateDetail) > 0 { + sort.SliceStable(peersStateDetail, func(i, j int) bool { + iAddr, _ := netip.ParseAddr(peersStateDetail[i].IP) + jAddr, _ := netip.ParseAddr(peersStateDetail[j].IP) + return iAddr.Compare(jAddr) == -1 + }) + } } func parseInterfaceIP(interfaceIP string) string { @@ -185,81 +296,68 @@ func parseInterfaceIP(interfaceIP string) string { return fmt.Sprintf("%s\n", ip) } -func parseToJson(fullStatus nbStatus.FullStatus) (string, error) { - jsonBytes, err := json.Marshal(fullStatus) +func parseToJson(overview statusOutputOverview) (string, error) { + jsonBytes, err := json.Marshal(overview) + if err != nil { + return "", fmt.Errorf("json marshal failed") + } return string(jsonBytes), err } -func parseToYaml(fullStatus nbStatus.FullStatus) (string, error) { - yamlBytes, err := yaml2.Marshal(fullStatus) - return string(yamlBytes), err -} - -func countConnectedPeers(peers []nbStatus.PeerState) int { - peersConnected := 0 - for _, peerState := range peers { - if peerState.ConnStatus == peer.StatusConnected.String() { - peersConnected = peersConnected + 1 - } +func parseToYaml(overview statusOutputOverview) (string, error) { + yamlBytes, err := yaml.Marshal(overview) + if err != nil { + return "", fmt.Errorf("yaml marshal failed") } - return peersConnected + return string(yamlBytes), nil } -func parseGeneralSummary(fullStatus nbStatus.FullStatus, daemonStatus string, daemonVersion string) string { - - managementStatusURL := fmt.Sprintf(" to %s", fullStatus.ManagementState.URL) - signalStatusURL := fmt.Sprintf(" to %s", fullStatus.SignalState.URL) +func parseGeneralSummary(overview statusOutputOverview, showUrl bool) string { managementConnString := "Disconnected" - if fullStatus.ManagementState.Connected { + if overview.ManagementState.Connected { managementConnString = "Connected" + if showUrl { + managementConnString = fmt.Sprintf("%s to %s", managementConnString, overview.ManagementState.URL) + } } signalConnString := "Disconnected" - if fullStatus.SignalState.Connected { + if overview.SignalState.Connected { signalConnString = "Connected" + if showUrl { + signalConnString = fmt.Sprintf("%s to %s", signalConnString, overview.SignalState.URL) + } } - interfaceTypeString := "Userspace" - interfaceIP := "" - if fullStatus.LocalPeerState.KernelInterface { - interfaceTypeString = "Kernel" - } else if fullStatus.LocalPeerState.IP == "" { - interfaceTypeString = "N/A" - interfaceIP = "N/A" - } - - peersConnected := countConnectedPeers(fullStatus.Peers) - peersCountString := fmt.Sprintf("%d/%d Connected", peersConnected, len(fullStatus.Peers)) + peersCountString := fmt.Sprintf("%d/%d Connected", overview.Peers.Connected, overview.Peers.Total) summary := fmt.Sprintf( "Daemon version: %s\n"+ "CLI version: %s\n"+ "%s"+ // daemon status - "Management: %s%s\n"+ - "Signal: %s%s\n"+ + "Management: %s\n"+ + "Signal: %s\n"+ "Domain: %s\n"+ "NetBird IP: %s\n"+ "Interface type: %s\n"+ "Peers count: %s\n", - daemonVersion, + overview.DaemonVersion, system.NetbirdVersion(), - daemonStatus, + overview.DaemonStatus, managementConnString, - managementStatusURL, signalConnString, - signalStatusURL, - fullStatus.LocalPeerState.FQDN, - interfaceIP, - interfaceTypeString, + overview.FQDN, + overview.IP, + overview.KernelInterface, peersCountString, ) return summary } -func parseToHumanReadable(fullStatus nbStatus.FullStatus, daemonStatus string, daemonVersion string) string { - parsedPeersString := parsePeers(fullStatus.Peers) - summary := parseGeneralSummary(fullStatus, daemonStatus, daemonVersion) +func parseToFullDetailSummary(overview statusOutputOverview) string { + parsedPeersString := parsePeers(overview.Peers) + summary := parseGeneralSummary(overview, true) return fmt.Sprintf( "Peers detail:"+ @@ -270,38 +368,12 @@ func parseToHumanReadable(fullStatus nbStatus.FullStatus, daemonStatus string, d ) } -func parsePeers(peers []nbStatus.PeerState) string { +func parsePeers(peers peersStateOutput) string { var ( peersString = "" ) - if len(peers) > 0 { - sort.SliceStable(peers, func(i, j int) bool { - iAddr, _ := netip.ParseAddr(peers[i].IP) - jAddr, _ := netip.ParseAddr(peers[j].IP) - return iAddr.Compare(jAddr) == -1 - }) - } - - for _, peerState := range peers { - peerConnectionStatus := peerState.ConnStatus == peer.StatusConnected.String() - if skipDetailByFilters(peerState, peerConnectionStatus) { - continue - } - - localICE := "-" - remoteICE := "-" - connType := "-" - - if peerConnectionStatus { - localICE = peerState.LocalIceCandidateType - remoteICE = peerState.RemoteIceCandidateType - connType = "P2P" - if peerState.Relayed { - connType = "Relayed" - } - } - + for _, peerState := range peers.Details { peerString := fmt.Sprintf( "\n %s:\n"+ " NetBird IP: %s\n"+ @@ -316,10 +388,10 @@ func parsePeers(peers []nbStatus.PeerState) string { peerState.IP, peerState.PubKey, peerState.ConnStatus, - connType, + peerState.ConnType, peerState.Direct, - localICE, - remoteICE, + peerState.LocalIceCandidateType, + peerState.RemoteIceCandidateType, peerState.ConnStatusUpdate.Format("2006-01-02 15:04:05"), ) @@ -328,7 +400,7 @@ func parsePeers(peers []nbStatus.PeerState) string { return peersString } -func skipDetailByFilters(peerState nbStatus.PeerState, isConnected bool) bool { +func skipDetailByFilters(peerState *proto.PeerState, isConnected bool) bool { statusEval := false ipEval := false diff --git a/client/cmd/status_test.go b/client/cmd/status_test.go index ea94282c0..bc55c0e85 100644 --- a/client/cmd/status_test.go +++ b/client/cmd/status_test.go @@ -1,134 +1,292 @@ package cmd import ( - nbStatus "github.com/netbirdio/netbird/client/status" - "github.com/stretchr/testify/assert" "testing" "time" + + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/netbirdio/netbird/client/proto" + "github.com/netbirdio/netbird/client/system" ) -var fullStatus = nbStatus.FullStatus{ - Peers: []nbStatus.PeerState{ - { - IP: "192.168.178.101", - PubKey: "Pubkey1", - FQDN: "peer-1.awesome-domain.com", - ConnStatus: "Connected", - ConnStatusUpdate: time.Date(2001, time.Month(1), 1, 1, 1, 1, 0, time.UTC), - Relayed: false, - LocalIceCandidateType: "-", - RemoteIceCandidateType: "-", +var resp = &proto.StatusResponse{ + Status: "Connected", + FullStatus: &proto.FullStatus{ + Peers: []*proto.PeerState{ + { + IP: "192.168.178.101", + PubKey: "Pubkey1", + Fqdn: "peer-1.awesome-domain.com", + ConnStatus: "Connected", + ConnStatusUpdate: timestamppb.New(time.Date(2001, time.Month(1), 1, 1, 1, 1, 0, time.UTC)), + Relayed: false, + Direct: true, + LocalIceCandidateType: "-", + RemoteIceCandidateType: "-", + }, + { + IP: "192.168.178.102", + PubKey: "Pubkey2", + Fqdn: "peer-2.awesome-domain.com", + ConnStatus: "Connected", + ConnStatusUpdate: timestamppb.New(time.Date(2002, time.Month(2), 2, 2, 2, 2, 0, time.UTC)), + Relayed: true, + Direct: false, + LocalIceCandidateType: "-", + RemoteIceCandidateType: "-", + }, }, - { - IP: "192.168.178.102", - PubKey: "Pubkey2", - FQDN: "peer-2.awesome-domain.com", - ConnStatus: "Connected", - ConnStatusUpdate: time.Date(2002, time.Month(2), 2, 2, 2, 2, 0, time.UTC), - Relayed: false, - LocalIceCandidateType: "-", - RemoteIceCandidateType: "-", + ManagementState: &proto.ManagementState{ + URL: "my-awesome-management.com:443", + Connected: true, + }, + SignalState: &proto.SignalState{ + URL: "my-awesome-signal.com:443", + Connected: true, + }, + LocalPeerState: &proto.LocalPeerState{ + IP: "192.168.178.100/16", + PubKey: "Some-Pub-Key", + KernelInterface: true, + Fqdn: "some-localhost.awesome-domain.com", }, }, - ManagementState: nbStatus.ManagementState{ + DaemonVersion: "0.14.1", +} + +var overview = statusOutputOverview{ + Peers: peersStateOutput{ + Total: 2, + Connected: 2, + Details: []peerStateDetailOutput{ + { + IP: "192.168.178.101", + PubKey: "Pubkey1", + FQDN: "peer-1.awesome-domain.com", + ConnStatus: "Connected", + ConnStatusUpdate: time.Date(2001, 1, 1, 1, 1, 1, 0, time.UTC), + ConnType: "P2P", + Direct: true, + LocalIceCandidateType: "-", + RemoteIceCandidateType: "-", + }, + { + IP: "192.168.178.102", + PubKey: "Pubkey2", + FQDN: "peer-2.awesome-domain.com", + ConnStatus: "Connected", + ConnStatusUpdate: time.Date(2002, 2, 2, 2, 2, 2, 0, time.UTC), + ConnType: "Relayed", + Direct: false, + LocalIceCandidateType: "-", + RemoteIceCandidateType: "-", + }, + }, + }, + CliVersion: system.NetbirdVersion(), + DaemonVersion: "0.14.1", + DaemonStatus: "Connected", + ManagementState: managementStateOutput{ URL: "my-awesome-management.com:443", Connected: true, }, - SignalState: nbStatus.SignalState{ + SignalState: signalStateOutput{ URL: "my-awesome-signal.com:443", Connected: true, }, - LocalPeerState: nbStatus.LocalPeerState{ - IP: "192.168.178.2", - PubKey: "Some-Pub-Key", - KernelInterface: false, - FQDN: "some-localhost.awesome-domain.com", - }, + IP: "192.168.178.100/16", + PubKey: "Some-Pub-Key", + KernelInterface: "Kernel", + FQDN: "some-localhost.awesome-domain.com", } -// @formatter:off -func TestParsingToJson(t *testing.T) { - json, _ := parseToJson(fullStatus) +func TestConversionFromFullStatusToOutputOverview(t *testing.T) { + convertedResult := convertToStatusOutputOverview(resp) + assert.Equal(t, overview, convertedResult) +} + +func TestSortingOfPeers(t *testing.T) { + peers := []peerStateDetailOutput{ + { + IP: "192.168.178.104", + }, + { + IP: "192.168.178.102", + }, + { + IP: "192.168.178.101", + }, + { + IP: "192.168.178.105", + }, + { + IP: "192.168.178.103", + }, + } + + sortPeersByIp(peers) + + assert.Equal(t, peers[3].IP, "192.168.178.104") +} + +func TestParsingToJson(t *testing.T) { + json, _ := parseToJson(overview) + + // @formatter:off expectedJson := "{" + - "\"Peers\":" + + "\"peers\":" + + "{" + + "\"total\":2," + + "\"connected\":2," + + "\"details\":" + "[" + "{" + - "\"IP\":\"192.168.178.101\"," + - "\"PubKey\":\"Pubkey1\"," + - "\"FQDN\":\"peer-1.awesome-domain.com\"," + - "\"ConnStatus\":\"Connected\"," + - "\"ConnStatusUpdate\":\"2001-01-01T01:01:01Z\"," + - "\"Relayed\":false," + - "\"Direct\":false," + - "\"LocalIceCandidateType\":\"-\"," + - "\"RemoteIceCandidateType\":\"-\"" + + "\"ip\":\"192.168.178.101\"," + + "\"publicKey\":\"Pubkey1\"," + + "\"fqdn\":\"peer-1.awesome-domain.com\"," + + "\"connectionStatus\":\"Connected\"" + + ",\"connectionStatusUpdate\":\"2001-01-01T01:01:01Z\"," + + "\"connectionType\":\"P2P\"," + + "\"direct\":true," + + "\"localIceCandidateType\":\"-\"," + + "\"remoteIceCandidateType\":\"-\"" + "}," + "{" + - "\"IP\":\"192.168.178.102\"," + - "\"PubKey\":\"Pubkey2\"," + - "\"FQDN\":\"peer-2.awesome-domain.com\"," + - "\"ConnStatus\":\"Connected\"," + - "\"ConnStatusUpdate\":\"2002-02-02T02:02:02Z\"," + - "\"Relayed\":false," + - "\"Direct\":false," + - "\"LocalIceCandidateType\":\"-\"," + - "\"RemoteIceCandidateType\":\"-\"" + + "\"ip\":\"192.168.178.102\"," + + "\"publicKey\":\"Pubkey2\"," + + "\"fqdn\":\"peer-2.awesome-domain.com\"," + + "\"connectionStatus\":\"Connected\"," + + "\"connectionStatusUpdate\":\"2002-02-02T02:02:02Z\"," + + "\"connectionType\":\"Relayed\"," + + "\"direct\":false," + + "\"localIceCandidateType\":\"-\"," + + "\"remoteIceCandidateType\":\"-\"" + "}" + - "]," + - "\"ManagementState\":" + - "{" + - "\"URL\":\"my-awesome-management.com:443\"," + - "\"Connected\":true" + + "]" + "}," + - "\"SignalState\":" + + "\"cliVersion\":\"development\"," + + "\"daemonVersion\":\"0.14.1\"," + + "\"daemonStatus\":\"Connected\"," + + "\"management\":" + "{" + - "\"URL\":\"my-awesome-signal.com:443\"," + - "\"Connected\":true" + + "\"url\":\"my-awesome-management.com:443\"," + + "\"connected\":true" + "}," + - "\"LocalPeerState\":" + + "\"signal\":" + "{" + - "\"IP\":\"192.168.178.2\"," + - "\"PubKey\":\"Some-Pub-Key\"," + - "\"KernelInterface\":false," + - "\"FQDN\":\"some-localhost.awesome-domain.com\"" + - "}" + + "\"url\":\"my-awesome-signal.com:443\"," + + "\"connected\":true" + + "}," + + "\"ip\":\"192.168.178.100/16\"," + + "\"publicKey\":\"Some-Pub-Key\"," + + "\"interfaceType\":\"Kernel\"," + + "\"domain\":\"some-localhost.awesome-domain.com\"" + "}" + // @formatter:on + assert.Equal(t, expectedJson, json) } func TestParsingToYaml(t *testing.T) { - yaml, _ := parseToYaml(fullStatus) + yaml, _ := parseToYaml(overview) expectedYaml := "peers:\n" + - "- ip: 192.168.178.101\n" + - " pubkey: Pubkey1\n" + - " fqdn: peer-1.awesome-domain.com\n" + - " connstatus: Connected\n" + - " connstatusupdate: 2001-01-01T01:01:01Z\n" + - " relayed: false\n" + - " direct: false\n" + - " localicecandidatetype: '-'\n" + - " remoteicecandidatetype: '-'\n" + - "- ip: 192.168.178.102\n" + - " pubkey: Pubkey2\n" + - " fqdn: peer-2.awesome-domain.com\n" + - " connstatus: Connected\n" + - " connstatusupdate: 2002-02-02T02:02:02Z\n" + - " relayed: false\n" + - " direct: false\n" + - " localicecandidatetype: '-'\n" + - " remoteicecandidatetype: '-'\n" + - "managementstate:\n" + + " total: 2\n" + + " connected: 2\n" + + " details:\n" + + " - ip: 192.168.178.101\n" + + " publicKey: Pubkey1\n" + + " fqdn: peer-1.awesome-domain.com\n" + + " connectionStatus: Connected\n" + + " connectionStatusUpdate: 2001-01-01T01:01:01Z\n" + + " connectionType: P2P\n" + + " direct: true\n" + + " localIceCandidateType: '-'\n" + + " remoteIceCandidateType: '-'\n" + + " - ip: 192.168.178.102\n" + + " publicKey: Pubkey2\n" + + " fqdn: peer-2.awesome-domain.com\n" + + " connectionStatus: Connected\n" + + " connectionStatusUpdate: 2002-02-02T02:02:02Z\n" + + " connectionType: Relayed\n" + + " direct: false\n" + + " localIceCandidateType: '-'\n" + + " remoteIceCandidateType: '-'\n" + + "cliVersion: development\n" + + "daemonVersion: 0.14.1\n" + + "daemonStatus: Connected\n" + + "management:\n" + " url: my-awesome-management.com:443\n" + " connected: true\n" + - "signalstate:\n" + + "signal:\n" + " url: my-awesome-signal.com:443\n" + " connected: true\n" + - "localpeerstate:\n" + - " ip: 192.168.178.2\n" + - " pubkey: Some-Pub-Key\n" + - " kernelinterface: false\n" + - " fqdn: some-localhost.awesome-domain.com\n" + "ip: 192.168.178.100/16\n" + + "publicKey: Some-Pub-Key\n" + + "interfaceType: Kernel\n" + + "domain: some-localhost.awesome-domain.com\n" + assert.Equal(t, expectedYaml, yaml) } + +func TestParsingToDetail(t *testing.T) { + detail := parseToFullDetailSummary(overview) + + expectedDetail := "Peers detail:\n" + + " peer-1.awesome-domain.com:\n" + + " NetBird IP: 192.168.178.101\n" + + " Public key: Pubkey1\n" + + " Status: Connected\n" + + " -- detail --\n" + + " Connection type: P2P\n" + + " Direct: true\n" + + " ICE candidate (Local/Remote): -/-\n" + + " Last connection update: 2001-01-01 01:01:01\n" + + "\n" + + " peer-2.awesome-domain.com:\n" + + " NetBird IP: 192.168.178.102\n" + + " Public key: Pubkey2\n" + + " Status: Connected\n" + + " -- detail --\n" + + " Connection type: Relayed\n" + + " Direct: false\n" + + " ICE candidate (Local/Remote): -/-\n" + + " Last connection update: 2002-02-02 02:02:02\n" + + "\n" + + "Daemon version: 0.14.1\n" + + "CLI version: development\n" + + "ConnectedManagement: Connected to my-awesome-management.com:443\n" + + "Signal: Connected to my-awesome-signal.com:443\n" + + "Domain: some-localhost.awesome-domain.com\n" + + "NetBird IP: 192.168.178.100/16\n" + + "Interface type: Kernel\n" + + "Peers count: 2/2 Connected\n" + + assert.Equal(t, expectedDetail, detail) +} + +func TestParsingToShortVersion(t *testing.T) { + shortVersion := parseGeneralSummary(overview, false) + + expectedString := "Daemon version: 0.14.1\n" + + "CLI version: development\n" + + "ConnectedManagement: Connected\n" + + "Signal: Connected\n" + + "Domain: some-localhost.awesome-domain.com\n" + + "NetBird IP: 192.168.178.100/16\n" + + "Interface type: Kernel\n" + + "Peers count: 2/2 Connected\n" + + assert.Equal(t, expectedString, shortVersion) +} + +func TestParsingOfIp(t *testing.T) { + InterfaceIp := "192.168.178.123/16" + + parsedId := parseInterfaceIP(InterfaceIp) + + assert.Equal(t, "192.168.178.123\n", parsedId) +} From f36869e97d0082afaed08b5423fcde65313c979d Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Fri, 24 Feb 2023 19:14:22 +0100 Subject: [PATCH 03/12] use yaml v3 --- client/cmd/status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/cmd/status.go b/client/cmd/status.go index 3d7337eb8..17c3dc092 100644 --- a/client/cmd/status.go +++ b/client/cmd/status.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/cobra" "google.golang.org/grpc/status" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" "github.com/netbirdio/netbird/client/internal" "github.com/netbirdio/netbird/client/internal/peer" From 014f1b841f6f3702dc58a20edece217b4f386f3a Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 27 Feb 2023 11:04:53 +0100 Subject: [PATCH 04/12] fix indention in test for yaml output --- client/cmd/status_test.go | 100 ++++++++++++++------------------------ 1 file changed, 36 insertions(+), 64 deletions(-) diff --git a/client/cmd/status_test.go b/client/cmd/status_test.go index bc55c0e85..b3e8847db 100644 --- a/client/cmd/status_test.go +++ b/client/cmd/status_test.go @@ -193,41 +193,7 @@ func TestParsingToJson(t *testing.T) { func TestParsingToYaml(t *testing.T) { yaml, _ := parseToYaml(overview) - expectedYaml := "peers:\n" + - " total: 2\n" + - " connected: 2\n" + - " details:\n" + - " - ip: 192.168.178.101\n" + - " publicKey: Pubkey1\n" + - " fqdn: peer-1.awesome-domain.com\n" + - " connectionStatus: Connected\n" + - " connectionStatusUpdate: 2001-01-01T01:01:01Z\n" + - " connectionType: P2P\n" + - " direct: true\n" + - " localIceCandidateType: '-'\n" + - " remoteIceCandidateType: '-'\n" + - " - ip: 192.168.178.102\n" + - " publicKey: Pubkey2\n" + - " fqdn: peer-2.awesome-domain.com\n" + - " connectionStatus: Connected\n" + - " connectionStatusUpdate: 2002-02-02T02:02:02Z\n" + - " connectionType: Relayed\n" + - " direct: false\n" + - " localIceCandidateType: '-'\n" + - " remoteIceCandidateType: '-'\n" + - "cliVersion: development\n" + - "daemonVersion: 0.14.1\n" + - "daemonStatus: Connected\n" + - "management:\n" + - " url: my-awesome-management.com:443\n" + - " connected: true\n" + - "signal:\n" + - " url: my-awesome-signal.com:443\n" + - " connected: true\n" + - "ip: 192.168.178.100/16\n" + - "publicKey: Some-Pub-Key\n" + - "interfaceType: Kernel\n" + - "domain: some-localhost.awesome-domain.com\n" + expectedYaml := "peers:\n total: 2\n connected: 2\n details:\n - ip: 192.168.178.101\n publicKey: Pubkey1\n fqdn: peer-1.awesome-domain.com\n connectionStatus: Connected\n connectionStatusUpdate: 2001-01-01T01:01:01Z\n connectionType: P2P\n direct: true\n localIceCandidateType: '-'\n remoteIceCandidateType: '-'\n - ip: 192.168.178.102\n publicKey: Pubkey2\n fqdn: peer-2.awesome-domain.com\n connectionStatus: Connected\n connectionStatusUpdate: 2002-02-02T02:02:02Z\n connectionType: Relayed\n direct: false\n localIceCandidateType: '-'\n remoteIceCandidateType: '-'\ncliVersion: development\ndaemonVersion: 0.14.1\ndaemonStatus: Connected\nmanagement:\n url: my-awesome-management.com:443\n connected: true\nsignal:\n url: my-awesome-signal.com:443\n connected: true\nip: 192.168.178.100/16\npublicKey: Some-Pub-Key\ninterfaceType: Kernel\ndomain: some-localhost.awesome-domain.com\n" assert.Equal(t, expectedYaml, yaml) } @@ -235,35 +201,41 @@ func TestParsingToYaml(t *testing.T) { func TestParsingToDetail(t *testing.T) { detail := parseToFullDetailSummary(overview) - expectedDetail := "Peers detail:\n" + - " peer-1.awesome-domain.com:\n" + - " NetBird IP: 192.168.178.101\n" + - " Public key: Pubkey1\n" + - " Status: Connected\n" + - " -- detail --\n" + - " Connection type: P2P\n" + - " Direct: true\n" + - " ICE candidate (Local/Remote): -/-\n" + - " Last connection update: 2001-01-01 01:01:01\n" + - "\n" + - " peer-2.awesome-domain.com:\n" + - " NetBird IP: 192.168.178.102\n" + - " Public key: Pubkey2\n" + - " Status: Connected\n" + - " -- detail --\n" + - " Connection type: Relayed\n" + - " Direct: false\n" + - " ICE candidate (Local/Remote): -/-\n" + - " Last connection update: 2002-02-02 02:02:02\n" + - "\n" + - "Daemon version: 0.14.1\n" + - "CLI version: development\n" + - "ConnectedManagement: Connected to my-awesome-management.com:443\n" + - "Signal: Connected to my-awesome-signal.com:443\n" + - "Domain: some-localhost.awesome-domain.com\n" + - "NetBird IP: 192.168.178.100/16\n" + - "Interface type: Kernel\n" + - "Peers count: 2/2 Connected\n" + expectedDetail := "peers:\n" + + " total: 2\n" + + " connected: 2\n" + + " details:\n" + + " - ip: 192.168.178.101\n" + + " publicKey: Pubkey1\n" + + " fqdn: peer-1.awesome-domain.com\n" + + " connectionStatus: Connected\n" + + " connectionStatusUpdate: 2001-01-01T01:01:01Z\n" + + " connectionType: P2P\n" + + " direct: true\n" + + " localIceCandidateType: '-'\n" + + " remoteIceCandidateType: '-'\n" + + " - ip: 192.168.178.102\n" + + " publicKey: Pubkey2\n" + + " fqdn: peer-2.awesome-domain.com\n" + + " connectionStatus: Connected\n" + + " connectionStatusUpdate: 2002-02-02T02:02:02Z\n" + + " connectionType: Relayed\n" + + " direct: false\n" + + " localIceCandidateType: '-'\n" + + " remoteIceCandidateType: '-'\n" + + "cliVersion: development\n" + + "daemonVersion: 0.14.1\n" + + "daemonStatus: Connected\n" + + "management:\n" + + " url: my-awesome-management.com:443\n" + + " connected: true\n" + + "signal:\n" + + " url: my-awesome-signal.com:443\n" + + " connected: true\n" + + "ip: 192.168.178.100/16\n" + + "publicKey: Some-Pub-Key\n" + + "interfaceType: Kernel\n" + + "domain: some-localhost.awesome-domain.com\n" assert.Equal(t, expectedDetail, detail) } From 6539b591b6802b9c863350f884c61c9887380536 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 27 Feb 2023 11:23:34 +0100 Subject: [PATCH 05/12] fix indention in test for detail output --- client/cmd/status_test.go | 46 +++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/client/cmd/status_test.go b/client/cmd/status_test.go index b3e8847db..a271554ac 100644 --- a/client/cmd/status_test.go +++ b/client/cmd/status_test.go @@ -193,15 +193,7 @@ func TestParsingToJson(t *testing.T) { func TestParsingToYaml(t *testing.T) { yaml, _ := parseToYaml(overview) - expectedYaml := "peers:\n total: 2\n connected: 2\n details:\n - ip: 192.168.178.101\n publicKey: Pubkey1\n fqdn: peer-1.awesome-domain.com\n connectionStatus: Connected\n connectionStatusUpdate: 2001-01-01T01:01:01Z\n connectionType: P2P\n direct: true\n localIceCandidateType: '-'\n remoteIceCandidateType: '-'\n - ip: 192.168.178.102\n publicKey: Pubkey2\n fqdn: peer-2.awesome-domain.com\n connectionStatus: Connected\n connectionStatusUpdate: 2002-02-02T02:02:02Z\n connectionType: Relayed\n direct: false\n localIceCandidateType: '-'\n remoteIceCandidateType: '-'\ncliVersion: development\ndaemonVersion: 0.14.1\ndaemonStatus: Connected\nmanagement:\n url: my-awesome-management.com:443\n connected: true\nsignal:\n url: my-awesome-signal.com:443\n connected: true\nip: 192.168.178.100/16\npublicKey: Some-Pub-Key\ninterfaceType: Kernel\ndomain: some-localhost.awesome-domain.com\n" - - assert.Equal(t, expectedYaml, yaml) -} - -func TestParsingToDetail(t *testing.T) { - detail := parseToFullDetailSummary(overview) - - expectedDetail := "peers:\n" + + expectedYaml := "peers:\n" + " total: 2\n" + " connected: 2\n" + " details:\n" + @@ -237,6 +229,42 @@ func TestParsingToDetail(t *testing.T) { "interfaceType: Kernel\n" + "domain: some-localhost.awesome-domain.com\n" + assert.Equal(t, expectedYaml, yaml) +} + +func TestParsingToDetail(t *testing.T) { + detail := parseToFullDetailSummary(overview) + + expectedDetail := "Peers detail:\n" + + " peer-1.awesome-domain.com:\n" + + " NetBird IP: 192.168.178.101\n" + + " Public key: Pubkey1\n" + + " Status: Connected\n" + + " -- detail --\n" + + " Connection type: P2P\n" + + " Direct: true\n" + + " ICE candidate (Local/Remote): -/-\n" + + " Last connection update: 2001-01-01 01:01:01\n" + + "\n" + + " peer-2.awesome-domain.com:\n" + + " NetBird IP: 192.168.178.102\n" + + " Public key: Pubkey2\n" + + " Status: Connected\n" + + " -- detail --\n" + + " Connection type: Relayed\n" + + " Direct: false\n" + + " ICE candidate (Local/Remote): -/-\n" + + " Last connection update: 2002-02-02 02:02:02\n" + + "\n" + + "Daemon version: 0.14.1\n" + + "CLI version: development\n" + + "ConnectedManagement: Connected to my-awesome-management.com:443\n" + + "Signal: Connected to my-awesome-signal.com:443\n" + + "Domain: some-localhost.awesome-domain.com\n" + + "NetBird IP: 192.168.178.100/16\n" + + "Interface type: Kernel\n" + + "Peers count: 2/2 Connected\n" + assert.Equal(t, expectedDetail, detail) } From 8276e0908a7d1d8f1e24b74fa6b9796c1b44cebc Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 27 Feb 2023 11:33:12 +0100 Subject: [PATCH 06/12] clean go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 059e8b120..84c2f7c62 100644 --- a/go.mod +++ b/go.mod @@ -53,6 +53,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v0.33.0 golang.org/x/net v0.0.0-20220630215102-69896b714898 golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -125,7 +126,6 @@ require ( gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.2.2 // indirect k8s.io/apimachinery v0.23.5 // indirect ) From 0350faf75d657b08dcce5913c5170ac80ba7e198 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 27 Feb 2023 15:14:41 +0100 Subject: [PATCH 07/12] return empty strings for not applicable values --- client/cmd/status.go | 49 +++++++++++++++++++++------------- client/cmd/status_test.go | 56 ++++++++++++++++++++++++--------------- 2 files changed, 64 insertions(+), 41 deletions(-) diff --git a/client/cmd/status.go b/client/cmd/status.go index 17c3dc092..9310b898c 100644 --- a/client/cmd/status.go +++ b/client/cmd/status.go @@ -58,7 +58,7 @@ type statusOutputOverview struct { SignalState signalStateOutput `json:"signal" yaml:"signal"` IP string `json:"ip" yaml:"ip"` PubKey string `json:"publicKey" yaml:"publicKey"` - KernelInterface string `json:"interfaceType" yaml:"interfaceType"` + KernelInterface bool `json:"usesKernelInterface" yaml:"usesKernelInterface"` FQDN string `json:"domain" yaml:"domain"` } @@ -205,15 +205,6 @@ func convertToStatusOutputOverview(resp *proto.StatusResponse) statusOutputOverv peersOverview := mapPeers(resp.GetFullStatus().GetPeers()) - interfaceTypeString := "Userspace" - interfaceIP := pbFullStatus.GetLocalPeerState().GetIP() - if pbFullStatus.LocalPeerState.KernelInterface { - interfaceTypeString = "Kernel" - } else if pbFullStatus.LocalPeerState.IP == "" { - interfaceTypeString = "N/A" - interfaceIP = "N/A" - } - overview := statusOutputOverview{ Peers: peersOverview, CliVersion: system.NetbirdVersion(), @@ -221,9 +212,9 @@ func convertToStatusOutputOverview(resp *proto.StatusResponse) statusOutputOverv DaemonStatus: resp.GetStatus(), ManagementState: managementOverview, SignalState: signalOverview, - IP: interfaceIP, + IP: pbFullStatus.GetLocalPeerState().GetIP(), PubKey: pbFullStatus.GetLocalPeerState().GetPubKey(), - KernelInterface: interfaceTypeString, + KernelInterface: pbFullStatus.GetLocalPeerState().GetKernelInterface(), FQDN: pbFullStatus.GetLocalPeerState().GetFqdn(), } @@ -232,9 +223,9 @@ func convertToStatusOutputOverview(resp *proto.StatusResponse) statusOutputOverv func mapPeers(peers []*proto.PeerState) peersStateOutput { var peersStateDetail []peerStateDetailOutput - localICE := "-" - remoteICE := "-" - connType := "-" + localICE := "" + remoteICE := "" + connType := "" peersConnected := 0 for _, pbPeerState := range peers { isPeerConnected := pbPeerState.ConnStatus == peer.StatusConnected.String() @@ -330,6 +321,15 @@ func parseGeneralSummary(overview statusOutputOverview, showUrl bool) string { } } + interfaceTypeString := "Userspace" + interfaceIP := overview.IP + if overview.KernelInterface { + interfaceTypeString = "Kernel" + } else if overview.IP == "" { + interfaceTypeString = "N/A" + interfaceIP = "N/A" + } + peersCountString := fmt.Sprintf("%d/%d Connected", overview.Peers.Connected, overview.Peers.Total) summary := fmt.Sprintf( @@ -348,8 +348,8 @@ func parseGeneralSummary(overview statusOutputOverview, showUrl bool) string { managementConnString, signalConnString, overview.FQDN, - overview.IP, - overview.KernelInterface, + interfaceIP, + interfaceTypeString, peersCountString, ) return summary @@ -374,6 +374,17 @@ func parsePeers(peers peersStateOutput) string { ) for _, peerState := range peers.Details { + + localICE := "-" + if peerState.LocalIceCandidateType != "" { + localICE = peerState.LocalIceCandidateType + } + + remoteICE := "-" + if peerState.RemoteIceCandidateType != "" { + remoteICE = peerState.RemoteIceCandidateType + } + peerString := fmt.Sprintf( "\n %s:\n"+ " NetBird IP: %s\n"+ @@ -390,8 +401,8 @@ func parsePeers(peers peersStateOutput) string { peerState.ConnStatus, peerState.ConnType, peerState.Direct, - peerState.LocalIceCandidateType, - peerState.RemoteIceCandidateType, + localICE, + remoteICE, peerState.ConnStatusUpdate.Format("2006-01-02 15:04:05"), ) diff --git a/client/cmd/status_test.go b/client/cmd/status_test.go index a271554ac..b33d07c49 100644 --- a/client/cmd/status_test.go +++ b/client/cmd/status_test.go @@ -23,8 +23,8 @@ var resp = &proto.StatusResponse{ ConnStatusUpdate: timestamppb.New(time.Date(2001, time.Month(1), 1, 1, 1, 1, 0, time.UTC)), Relayed: false, Direct: true, - LocalIceCandidateType: "-", - RemoteIceCandidateType: "-", + LocalIceCandidateType: "", + RemoteIceCandidateType: "", }, { IP: "192.168.178.102", @@ -34,8 +34,8 @@ var resp = &proto.StatusResponse{ ConnStatusUpdate: timestamppb.New(time.Date(2002, time.Month(2), 2, 2, 2, 2, 0, time.UTC)), Relayed: true, Direct: false, - LocalIceCandidateType: "-", - RemoteIceCandidateType: "-", + LocalIceCandidateType: "relay", + RemoteIceCandidateType: "prflx", }, }, ManagementState: &proto.ManagementState{ @@ -69,8 +69,8 @@ var overview = statusOutputOverview{ ConnStatusUpdate: time.Date(2001, 1, 1, 1, 1, 1, 0, time.UTC), ConnType: "P2P", Direct: true, - LocalIceCandidateType: "-", - RemoteIceCandidateType: "-", + LocalIceCandidateType: "", + RemoteIceCandidateType: "", }, { IP: "192.168.178.102", @@ -80,8 +80,8 @@ var overview = statusOutputOverview{ ConnStatusUpdate: time.Date(2002, 2, 2, 2, 2, 2, 0, time.UTC), ConnType: "Relayed", Direct: false, - LocalIceCandidateType: "-", - RemoteIceCandidateType: "-", + LocalIceCandidateType: "relay", + RemoteIceCandidateType: "prflx", }, }, }, @@ -98,7 +98,7 @@ var overview = statusOutputOverview{ }, IP: "192.168.178.100/16", PubKey: "Some-Pub-Key", - KernelInterface: "Kernel", + KernelInterface: true, FQDN: "some-localhost.awesome-domain.com", } @@ -108,6 +108,18 @@ func TestConversionFromFullStatusToOutputOverview(t *testing.T) { assert.Equal(t, overview, convertedResult) } +func TestForErrorOnMultipleOutputFlags(t *testing.T) { + rootCmd.SetArgs([]string{ + "status", + "--yaml", + "--json", + }) + if err := rootCmd.Execute(); err != nil { + return + } + t.Errorf("expected error while running status command with 2 output flags") +} + func TestSortingOfPeers(t *testing.T) { peers := []peerStateDetailOutput{ { @@ -147,12 +159,12 @@ func TestParsingToJson(t *testing.T) { "\"ip\":\"192.168.178.101\"," + "\"publicKey\":\"Pubkey1\"," + "\"fqdn\":\"peer-1.awesome-domain.com\"," + - "\"connectionStatus\":\"Connected\"" + - ",\"connectionStatusUpdate\":\"2001-01-01T01:01:01Z\"," + + "\"connectionStatus\":\"Connected\"," + + "\"connectionStatusUpdate\":\"2001-01-01T01:01:01Z\"," + "\"connectionType\":\"P2P\"," + "\"direct\":true," + - "\"localIceCandidateType\":\"-\"," + - "\"remoteIceCandidateType\":\"-\"" + + "\"localIceCandidateType\":\"\"," + + "\"remoteIceCandidateType\":\"\"" + "}," + "{" + "\"ip\":\"192.168.178.102\"," + @@ -162,8 +174,8 @@ func TestParsingToJson(t *testing.T) { "\"connectionStatusUpdate\":\"2002-02-02T02:02:02Z\"," + "\"connectionType\":\"Relayed\"," + "\"direct\":false," + - "\"localIceCandidateType\":\"-\"," + - "\"remoteIceCandidateType\":\"-\"" + + "\"localIceCandidateType\":\"relay\"," + + "\"remoteIceCandidateType\":\"prflx\"" + "}" + "]" + "}," + @@ -182,7 +194,7 @@ func TestParsingToJson(t *testing.T) { "}," + "\"ip\":\"192.168.178.100/16\"," + "\"publicKey\":\"Some-Pub-Key\"," + - "\"interfaceType\":\"Kernel\"," + + "\"usesKernelInterface\":true," + "\"domain\":\"some-localhost.awesome-domain.com\"" + "}" // @formatter:on @@ -204,8 +216,8 @@ func TestParsingToYaml(t *testing.T) { " connectionStatusUpdate: 2001-01-01T01:01:01Z\n" + " connectionType: P2P\n" + " direct: true\n" + - " localIceCandidateType: '-'\n" + - " remoteIceCandidateType: '-'\n" + + " localIceCandidateType: \"\"\n" + + " remoteIceCandidateType: \"\"\n" + " - ip: 192.168.178.102\n" + " publicKey: Pubkey2\n" + " fqdn: peer-2.awesome-domain.com\n" + @@ -213,8 +225,8 @@ func TestParsingToYaml(t *testing.T) { " connectionStatusUpdate: 2002-02-02T02:02:02Z\n" + " connectionType: Relayed\n" + " direct: false\n" + - " localIceCandidateType: '-'\n" + - " remoteIceCandidateType: '-'\n" + + " localIceCandidateType: relay\n" + + " remoteIceCandidateType: prflx\n" + "cliVersion: development\n" + "daemonVersion: 0.14.1\n" + "daemonStatus: Connected\n" + @@ -226,7 +238,7 @@ func TestParsingToYaml(t *testing.T) { " connected: true\n" + "ip: 192.168.178.100/16\n" + "publicKey: Some-Pub-Key\n" + - "interfaceType: Kernel\n" + + "usesKernelInterface: true\n" + "domain: some-localhost.awesome-domain.com\n" assert.Equal(t, expectedYaml, yaml) @@ -253,7 +265,7 @@ func TestParsingToDetail(t *testing.T) { " -- detail --\n" + " Connection type: Relayed\n" + " Direct: false\n" + - " ICE candidate (Local/Remote): -/-\n" + + " ICE candidate (Local/Remote): relay/prflx\n" + " Last connection update: 2002-02-02 02:02:02\n" + "\n" + "Daemon version: 0.14.1\n" + From a0f2b5f591ee335a519b85995f2bcf694fc166d1 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 27 Feb 2023 15:34:17 +0100 Subject: [PATCH 08/12] fix codacy --- client/cmd/status.go | 18 +++++++++--------- client/cmd/status_test.go | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/client/cmd/status.go b/client/cmd/status.go index 9310b898c..cb98aabf2 100644 --- a/client/cmd/status.go +++ b/client/cmd/status.go @@ -134,12 +134,12 @@ func statusFunc(cmd *cobra.Command, args []string) error { if detailFlag { statusOutputString = parseToFullDetailSummary(statusOutputOverview) } else if jsonFlag { - statusOutputString, err = parseToJson(statusOutputOverview) + statusOutputString, err = parseToJSON(statusOutputOverview) if err != nil { return err } } else if yamlFlag { - statusOutputString, err = parseToYaml(statusOutputOverview) + statusOutputString, err = parseToYAML(statusOutputOverview) if err != nil { return err } @@ -259,7 +259,7 @@ func mapPeers(peers []*proto.PeerState) peersStateOutput { peersStateDetail = append(peersStateDetail, peerState) } - sortPeersByIp(peersStateDetail) + sortPeersByIP(peersStateDetail) peersOverview := peersStateOutput{ Total: len(peersStateDetail), @@ -269,7 +269,7 @@ func mapPeers(peers []*proto.PeerState) peersStateOutput { return peersOverview } -func sortPeersByIp(peersStateDetail []peerStateDetailOutput) { +func sortPeersByIP(peersStateDetail []peerStateDetailOutput) { if len(peersStateDetail) > 0 { sort.SliceStable(peersStateDetail, func(i, j int) bool { iAddr, _ := netip.ParseAddr(peersStateDetail[i].IP) @@ -287,7 +287,7 @@ func parseInterfaceIP(interfaceIP string) string { return fmt.Sprintf("%s\n", ip) } -func parseToJson(overview statusOutputOverview) (string, error) { +func parseToJSON(overview statusOutputOverview) (string, error) { jsonBytes, err := json.Marshal(overview) if err != nil { return "", fmt.Errorf("json marshal failed") @@ -295,7 +295,7 @@ func parseToJson(overview statusOutputOverview) (string, error) { return string(jsonBytes), err } -func parseToYaml(overview statusOutputOverview) (string, error) { +func parseToYAML(overview statusOutputOverview) (string, error) { yamlBytes, err := yaml.Marshal(overview) if err != nil { return "", fmt.Errorf("yaml marshal failed") @@ -303,12 +303,12 @@ func parseToYaml(overview statusOutputOverview) (string, error) { return string(yamlBytes), nil } -func parseGeneralSummary(overview statusOutputOverview, showUrl bool) string { +func parseGeneralSummary(overview statusOutputOverview, showURL bool) string { managementConnString := "Disconnected" if overview.ManagementState.Connected { managementConnString = "Connected" - if showUrl { + if showURL { managementConnString = fmt.Sprintf("%s to %s", managementConnString, overview.ManagementState.URL) } } @@ -316,7 +316,7 @@ func parseGeneralSummary(overview statusOutputOverview, showUrl bool) string { signalConnString := "Disconnected" if overview.SignalState.Connected { signalConnString = "Connected" - if showUrl { + if showURL { signalConnString = fmt.Sprintf("%s to %s", signalConnString, overview.SignalState.URL) } } diff --git a/client/cmd/status_test.go b/client/cmd/status_test.go index b33d07c49..872e800b0 100644 --- a/client/cmd/status_test.go +++ b/client/cmd/status_test.go @@ -139,13 +139,13 @@ func TestSortingOfPeers(t *testing.T) { }, } - sortPeersByIp(peers) + sortPeersByIP(peers) assert.Equal(t, peers[3].IP, "192.168.178.104") } -func TestParsingToJson(t *testing.T) { - json, _ := parseToJson(overview) +func TestParsingToJSON(t *testing.T) { + json, _ := parseToJSON(overview) // @formatter:off expectedJson := "{" + @@ -202,8 +202,8 @@ func TestParsingToJson(t *testing.T) { assert.Equal(t, expectedJson, json) } -func TestParsingToYaml(t *testing.T) { - yaml, _ := parseToYaml(overview) +func TestParsingToYAML(t *testing.T) { + yaml, _ := parseToYAML(overview) expectedYaml := "peers:\n" + " total: 2\n" + @@ -295,10 +295,10 @@ func TestParsingToShortVersion(t *testing.T) { assert.Equal(t, expectedString, shortVersion) } -func TestParsingOfIp(t *testing.T) { +func TestParsingOfIP(t *testing.T) { InterfaceIp := "192.168.178.123/16" - parsedId := parseInterfaceIP(InterfaceIp) + parsedIP := parseInterfaceIP(InterfaceIp) - assert.Equal(t, "192.168.178.123\n", parsedId) + assert.Equal(t, "192.168.178.123\n", parsedIP) } From 5782496287b8e466b220d693e1d72aaa9cc40f9f Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 27 Feb 2023 15:52:46 +0100 Subject: [PATCH 09/12] fix codacy --- client/cmd/status_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/cmd/status_test.go b/client/cmd/status_test.go index 872e800b0..ff62c438e 100644 --- a/client/cmd/status_test.go +++ b/client/cmd/status_test.go @@ -148,7 +148,7 @@ func TestParsingToJSON(t *testing.T) { json, _ := parseToJSON(overview) // @formatter:off - expectedJson := "{" + + expectedJSON := "{" + "\"peers\":" + "{" + "\"total\":2," + @@ -199,13 +199,13 @@ func TestParsingToJSON(t *testing.T) { "}" // @formatter:on - assert.Equal(t, expectedJson, json) + assert.Equal(t, expectedJSON, json) } func TestParsingToYAML(t *testing.T) { yaml, _ := parseToYAML(overview) - expectedYaml := "peers:\n" + + expectedYAML := "peers:\n" + " total: 2\n" + " connected: 2\n" + " details:\n" + @@ -241,7 +241,7 @@ func TestParsingToYAML(t *testing.T) { "usesKernelInterface: true\n" + "domain: some-localhost.awesome-domain.com\n" - assert.Equal(t, expectedYaml, yaml) + assert.Equal(t, expectedYAML, yaml) } func TestParsingToDetail(t *testing.T) { @@ -296,9 +296,9 @@ func TestParsingToShortVersion(t *testing.T) { } func TestParsingOfIP(t *testing.T) { - InterfaceIp := "192.168.178.123/16" + InterfaceIP := "192.168.178.123/16" - parsedIP := parseInterfaceIP(InterfaceIp) + parsedIP := parseInterfaceIP(InterfaceIP) assert.Equal(t, "192.168.178.123\n", parsedIP) } From 23610db7273d0ed823ccc1618717bdfec710f460 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 27 Feb 2023 17:06:20 +0100 Subject: [PATCH 10/12] apply first set of review comments (mostly reorder and naming) --- client/cmd/status.go | 89 ++++++++++++++-------------- client/cmd/status_test.go | 118 +++++++++++++++++++++----------------- 2 files changed, 112 insertions(+), 95 deletions(-) diff --git a/client/cmd/status.go b/client/cmd/status.go index cb98aabf2..44ba24251 100644 --- a/client/cmd/status.go +++ b/client/cmd/status.go @@ -22,15 +22,14 @@ import ( ) type peerStateDetailOutput struct { - IP string `json:"ip" yaml:"ip"` - PubKey string `json:"publicKey" yaml:"publicKey"` - FQDN string `json:"fqdn" yaml:"fqdn"` - ConnStatus string `json:"connectionStatus" yaml:"connectionStatus"` - ConnStatusUpdate time.Time `json:"connectionStatusUpdate" yaml:"connectionStatusUpdate"` - ConnType string `json:"connectionType" yaml:"connectionType"` - Direct bool `json:"direct" yaml:"direct"` - LocalIceCandidateType string `json:"localIceCandidateType" yaml:"localIceCandidateType"` - RemoteIceCandidateType string `json:"remoteIceCandidateType" yaml:"remoteIceCandidateType"` + FQDN string `json:"fqdn" yaml:"fqdn"` + IP string `json:"netbirdIp" yaml:"netbirdIp"` + PubKey string `json:"publicKey" yaml:"publicKey"` + Status string `json:"status" yaml:"status"` + LastStatusUpdate time.Time `json:"lastStatusUpdate" yaml:"lastStatusUpdate"` + ConnType string `json:"connectionType" yaml:"connectionType"` + Direct bool `json:"direct" yaml:"direct"` + IceCandidateType iceCandidateType `json:"iceCandidateType" yaml:"iceCandidateType"` } type peersStateOutput struct { @@ -49,6 +48,11 @@ type managementStateOutput struct { Connected bool `json:"connected" yaml:"connected"` } +type iceCandidateType struct { + Local string `json:"local" yaml:"local"` + Remote string `json:"remote" yaml:"remote"` +} + type statusOutputOverview struct { Peers peersStateOutput `json:"peers" yaml:"peers"` CliVersion string `json:"cliVersion" yaml:"cliVersion"` @@ -56,10 +60,10 @@ type statusOutputOverview struct { DaemonStatus string `json:"daemonStatus" yaml:"daemonStatus"` ManagementState managementStateOutput `json:"management" yaml:"management"` SignalState signalStateOutput `json:"signal" yaml:"signal"` - IP string `json:"ip" yaml:"ip"` + IP string `json:"netbirdIp" yaml:"netbirdIp"` PubKey string `json:"publicKey" yaml:"publicKey"` KernelInterface bool `json:"usesKernelInterface" yaml:"usesKernelInterface"` - FQDN string `json:"domain" yaml:"domain"` + FQDN string `json:"fqdn" yaml:"fqdn"` } var ( @@ -128,23 +132,22 @@ func statusFunc(cmd *cobra.Command, args []string) error { return nil } - statusOutputOverview := convertToStatusOutputOverview(resp) + outputInformationHolder := convertToStatusOutputOverview(resp) statusOutputString := "" - if detailFlag { - statusOutputString = parseToFullDetailSummary(statusOutputOverview) - } else if jsonFlag { - statusOutputString, err = parseToJSON(statusOutputOverview) - if err != nil { - return err - } - } else if yamlFlag { - statusOutputString, err = parseToYAML(statusOutputOverview) - if err != nil { - return err - } - } else { - statusOutputString = parseGeneralSummary(statusOutputOverview, false) + switch { + case detailFlag: + statusOutputString = parseToFullDetailSummary(outputInformationHolder) + case jsonFlag: + statusOutputString, err = parseToJSON(outputInformationHolder) + case yamlFlag: + statusOutputString, err = parseToYAML(outputInformationHolder) + default: + statusOutputString = parseGeneralSummary(outputInformationHolder, false) + } + + if err != nil { + return err } cmd.Print(statusOutputString) @@ -245,15 +248,17 @@ func mapPeers(peers []*proto.PeerState) peersStateOutput { timeLocal := pbPeerState.GetConnStatusUpdate().AsTime().Local() peerState := peerStateDetailOutput{ - IP: pbPeerState.GetIP(), - PubKey: pbPeerState.GetPubKey(), - ConnStatus: pbPeerState.GetConnStatus(), - ConnStatusUpdate: timeLocal.UTC(), - ConnType: connType, - Direct: pbPeerState.GetDirect(), - LocalIceCandidateType: localICE, - RemoteIceCandidateType: remoteICE, - FQDN: pbPeerState.GetFqdn(), + IP: pbPeerState.GetIP(), + PubKey: pbPeerState.GetPubKey(), + Status: pbPeerState.GetConnStatus(), + LastStatusUpdate: timeLocal.UTC(), + ConnType: connType, + Direct: pbPeerState.GetDirect(), + IceCandidateType: iceCandidateType{ + Local: localICE, + Remote: remoteICE, + }, + FQDN: pbPeerState.GetFqdn(), } peersStateDetail = append(peersStateDetail, peerState) @@ -338,7 +343,7 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool) string { "%s"+ // daemon status "Management: %s\n"+ "Signal: %s\n"+ - "Domain: %s\n"+ + "FQDN: %s\n"+ "NetBird IP: %s\n"+ "Interface type: %s\n"+ "Peers count: %s\n", @@ -376,13 +381,13 @@ func parsePeers(peers peersStateOutput) string { for _, peerState := range peers.Details { localICE := "-" - if peerState.LocalIceCandidateType != "" { - localICE = peerState.LocalIceCandidateType + if peerState.IceCandidateType.Local != "" { + localICE = peerState.IceCandidateType.Local } remoteICE := "-" - if peerState.RemoteIceCandidateType != "" { - remoteICE = peerState.RemoteIceCandidateType + if peerState.IceCandidateType.Remote != "" { + remoteICE = peerState.IceCandidateType.Remote } peerString := fmt.Sprintf( @@ -398,12 +403,12 @@ func parsePeers(peers peersStateOutput) string { peerState.FQDN, peerState.IP, peerState.PubKey, - peerState.ConnStatus, + peerState.Status, peerState.ConnType, peerState.Direct, localICE, remoteICE, - peerState.ConnStatusUpdate.Format("2006-01-02 15:04:05"), + peerState.LastStatusUpdate.Format("2006-01-02 15:04:05"), ) peersString = peersString + peerString diff --git a/client/cmd/status_test.go b/client/cmd/status_test.go index ff62c438e..348ba537a 100644 --- a/client/cmd/status_test.go +++ b/client/cmd/status_test.go @@ -62,26 +62,30 @@ var overview = statusOutputOverview{ Connected: 2, Details: []peerStateDetailOutput{ { - IP: "192.168.178.101", - PubKey: "Pubkey1", - FQDN: "peer-1.awesome-domain.com", - ConnStatus: "Connected", - ConnStatusUpdate: time.Date(2001, 1, 1, 1, 1, 1, 0, time.UTC), - ConnType: "P2P", - Direct: true, - LocalIceCandidateType: "", - RemoteIceCandidateType: "", + IP: "192.168.178.101", + PubKey: "Pubkey1", + FQDN: "peer-1.awesome-domain.com", + Status: "Connected", + LastStatusUpdate: time.Date(2001, 1, 1, 1, 1, 1, 0, time.UTC), + ConnType: "P2P", + Direct: true, + IceCandidateType: iceCandidateType{ + Local: "", + Remote: "", + }, }, { - IP: "192.168.178.102", - PubKey: "Pubkey2", - FQDN: "peer-2.awesome-domain.com", - ConnStatus: "Connected", - ConnStatusUpdate: time.Date(2002, 2, 2, 2, 2, 2, 0, time.UTC), - ConnType: "Relayed", - Direct: false, - LocalIceCandidateType: "relay", - RemoteIceCandidateType: "prflx", + IP: "192.168.178.102", + PubKey: "Pubkey2", + FQDN: "peer-2.awesome-domain.com", + Status: "Connected", + LastStatusUpdate: time.Date(2002, 2, 2, 2, 2, 2, 0, time.UTC), + ConnType: "Relayed", + Direct: false, + IceCandidateType: iceCandidateType{ + Local: "relay", + Remote: "prflx", + }, }, }, }, @@ -147,35 +151,41 @@ func TestSortingOfPeers(t *testing.T) { func TestParsingToJSON(t *testing.T) { json, _ := parseToJSON(overview) - // @formatter:off - expectedJSON := "{" + - "\"peers\":" + + //@formatter:off + expectedJSON := "{\"" + + "peers\":" + "{" + "\"total\":2," + "\"connected\":2," + "\"details\":" + "[" + "{" + - "\"ip\":\"192.168.178.101\"," + - "\"publicKey\":\"Pubkey1\"," + "\"fqdn\":\"peer-1.awesome-domain.com\"," + - "\"connectionStatus\":\"Connected\"," + - "\"connectionStatusUpdate\":\"2001-01-01T01:01:01Z\"," + + "\"netbirdIp\":\"192.168.178.101\"," + + "\"publicKey\":\"Pubkey1\"," + + "\"status\":\"Connected\"," + + "\"lastStatusUpdate\":\"2001-01-01T01:01:01Z\"," + "\"connectionType\":\"P2P\"," + "\"direct\":true," + - "\"localIceCandidateType\":\"\"," + - "\"remoteIceCandidateType\":\"\"" + + "\"iceCandidateType\":" + + "{" + + "\"local\":\"\"," + + "\"remote\":\"\"" + + "}" + "}," + "{" + - "\"ip\":\"192.168.178.102\"," + - "\"publicKey\":\"Pubkey2\"," + "\"fqdn\":\"peer-2.awesome-domain.com\"," + - "\"connectionStatus\":\"Connected\"," + - "\"connectionStatusUpdate\":\"2002-02-02T02:02:02Z\"," + + "\"netbirdIp\":\"192.168.178.102\"," + + "\"publicKey\":\"Pubkey2\"," + + "\"status\":\"Connected\"," + + "\"lastStatusUpdate\":\"2002-02-02T02:02:02Z\"," + "\"connectionType\":\"Relayed\"," + "\"direct\":false," + - "\"localIceCandidateType\":\"relay\"," + - "\"remoteIceCandidateType\":\"prflx\"" + + "\"iceCandidateType\":" + + "{" + + "\"local\":\"relay\"," + + "\"remote\":\"prflx\"" + + "}" + "}" + "]" + "}," + @@ -188,14 +198,14 @@ func TestParsingToJSON(t *testing.T) { "\"connected\":true" + "}," + "\"signal\":" + - "{" + - "\"url\":\"my-awesome-signal.com:443\"," + + "{\"" + + "url\":\"my-awesome-signal.com:443\"," + "\"connected\":true" + "}," + - "\"ip\":\"192.168.178.100/16\"," + + "\"netbirdIp\":\"192.168.178.100/16\"," + "\"publicKey\":\"Some-Pub-Key\"," + "\"usesKernelInterface\":true," + - "\"domain\":\"some-localhost.awesome-domain.com\"" + + "\"fqdn\":\"some-localhost.awesome-domain.com\"" + "}" // @formatter:on @@ -209,24 +219,26 @@ func TestParsingToYAML(t *testing.T) { " total: 2\n" + " connected: 2\n" + " details:\n" + - " - ip: 192.168.178.101\n" + + " - fqdn: peer-1.awesome-domain.com\n" + + " netbirdIp: 192.168.178.101\n" + " publicKey: Pubkey1\n" + - " fqdn: peer-1.awesome-domain.com\n" + - " connectionStatus: Connected\n" + - " connectionStatusUpdate: 2001-01-01T01:01:01Z\n" + + " status: Connected\n" + + " lastStatusUpdate: 2001-01-01T01:01:01Z\n" + " connectionType: P2P\n" + " direct: true\n" + - " localIceCandidateType: \"\"\n" + - " remoteIceCandidateType: \"\"\n" + - " - ip: 192.168.178.102\n" + + " iceCandidateType:\n" + + " local: \"\"\n" + + " remote: \"\"\n" + + " - fqdn: peer-2.awesome-domain.com\n" + + " netbirdIp: 192.168.178.102\n" + " publicKey: Pubkey2\n" + - " fqdn: peer-2.awesome-domain.com\n" + - " connectionStatus: Connected\n" + - " connectionStatusUpdate: 2002-02-02T02:02:02Z\n" + + " status: Connected\n" + + " lastStatusUpdate: 2002-02-02T02:02:02Z\n" + " connectionType: Relayed\n" + " direct: false\n" + - " localIceCandidateType: relay\n" + - " remoteIceCandidateType: prflx\n" + + " iceCandidateType:\n" + + " local: relay\n" + + " remote: prflx\n" + "cliVersion: development\n" + "daemonVersion: 0.14.1\n" + "daemonStatus: Connected\n" + @@ -236,10 +248,10 @@ func TestParsingToYAML(t *testing.T) { "signal:\n" + " url: my-awesome-signal.com:443\n" + " connected: true\n" + - "ip: 192.168.178.100/16\n" + + "netbirdIp: 192.168.178.100/16\n" + "publicKey: Some-Pub-Key\n" + "usesKernelInterface: true\n" + - "domain: some-localhost.awesome-domain.com\n" + "fqdn: some-localhost.awesome-domain.com\n" assert.Equal(t, expectedYAML, yaml) } @@ -272,7 +284,7 @@ func TestParsingToDetail(t *testing.T) { "CLI version: development\n" + "ConnectedManagement: Connected to my-awesome-management.com:443\n" + "Signal: Connected to my-awesome-signal.com:443\n" + - "Domain: some-localhost.awesome-domain.com\n" + + "FQDN: some-localhost.awesome-domain.com\n" + "NetBird IP: 192.168.178.100/16\n" + "Interface type: Kernel\n" + "Peers count: 2/2 Connected\n" @@ -287,7 +299,7 @@ func TestParsingToShortVersion(t *testing.T) { "CLI version: development\n" + "ConnectedManagement: Connected\n" + "Signal: Connected\n" + - "Domain: some-localhost.awesome-domain.com\n" + + "FQDN: some-localhost.awesome-domain.com\n" + "NetBird IP: 192.168.178.100/16\n" + "Interface type: Kernel\n" + "Peers count: 2/2 Connected\n" From 82059df3247d2efff50cf99e92ccd1a46602227c Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 27 Feb 2023 17:12:34 +0100 Subject: [PATCH 11/12] remove daemon status from output --- client/cmd/status.go | 4 ---- client/cmd/status_test.go | 7 ++----- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/client/cmd/status.go b/client/cmd/status.go index 44ba24251..5a667e6ee 100644 --- a/client/cmd/status.go +++ b/client/cmd/status.go @@ -57,7 +57,6 @@ type statusOutputOverview struct { Peers peersStateOutput `json:"peers" yaml:"peers"` CliVersion string `json:"cliVersion" yaml:"cliVersion"` DaemonVersion string `json:"daemonVersion" yaml:"daemonVersion"` - DaemonStatus string `json:"daemonStatus" yaml:"daemonStatus"` ManagementState managementStateOutput `json:"management" yaml:"management"` SignalState signalStateOutput `json:"signal" yaml:"signal"` IP string `json:"netbirdIp" yaml:"netbirdIp"` @@ -212,7 +211,6 @@ func convertToStatusOutputOverview(resp *proto.StatusResponse) statusOutputOverv Peers: peersOverview, CliVersion: system.NetbirdVersion(), DaemonVersion: resp.GetDaemonVersion(), - DaemonStatus: resp.GetStatus(), ManagementState: managementOverview, SignalState: signalOverview, IP: pbFullStatus.GetLocalPeerState().GetIP(), @@ -340,7 +338,6 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool) string { summary := fmt.Sprintf( "Daemon version: %s\n"+ "CLI version: %s\n"+ - "%s"+ // daemon status "Management: %s\n"+ "Signal: %s\n"+ "FQDN: %s\n"+ @@ -349,7 +346,6 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool) string { "Peers count: %s\n", overview.DaemonVersion, system.NetbirdVersion(), - overview.DaemonStatus, managementConnString, signalConnString, overview.FQDN, diff --git a/client/cmd/status_test.go b/client/cmd/status_test.go index 348ba537a..d7a71bc3b 100644 --- a/client/cmd/status_test.go +++ b/client/cmd/status_test.go @@ -91,7 +91,6 @@ var overview = statusOutputOverview{ }, CliVersion: system.NetbirdVersion(), DaemonVersion: "0.14.1", - DaemonStatus: "Connected", ManagementState: managementStateOutput{ URL: "my-awesome-management.com:443", Connected: true, @@ -191,7 +190,6 @@ func TestParsingToJSON(t *testing.T) { "}," + "\"cliVersion\":\"development\"," + "\"daemonVersion\":\"0.14.1\"," + - "\"daemonStatus\":\"Connected\"," + "\"management\":" + "{" + "\"url\":\"my-awesome-management.com:443\"," + @@ -241,7 +239,6 @@ func TestParsingToYAML(t *testing.T) { " remote: prflx\n" + "cliVersion: development\n" + "daemonVersion: 0.14.1\n" + - "daemonStatus: Connected\n" + "management:\n" + " url: my-awesome-management.com:443\n" + " connected: true\n" + @@ -282,7 +279,7 @@ func TestParsingToDetail(t *testing.T) { "\n" + "Daemon version: 0.14.1\n" + "CLI version: development\n" + - "ConnectedManagement: Connected to my-awesome-management.com:443\n" + + "Management: Connected to my-awesome-management.com:443\n" + "Signal: Connected to my-awesome-signal.com:443\n" + "FQDN: some-localhost.awesome-domain.com\n" + "NetBird IP: 192.168.178.100/16\n" + @@ -297,7 +294,7 @@ func TestParsingToShortVersion(t *testing.T) { expectedString := "Daemon version: 0.14.1\n" + "CLI version: development\n" + - "ConnectedManagement: Connected\n" + + "Management: Connected\n" + "Signal: Connected\n" + "FQDN: some-localhost.awesome-domain.com\n" + "NetBird IP: 192.168.178.100/16\n" + From 8026c84c95c8f2f204eb6cedd343a9ef617d62b1 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Mon, 27 Feb 2023 17:45:02 +0100 Subject: [PATCH 12/12] remove flag test --- client/cmd/status_test.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/client/cmd/status_test.go b/client/cmd/status_test.go index d7a71bc3b..7281a1f12 100644 --- a/client/cmd/status_test.go +++ b/client/cmd/status_test.go @@ -111,18 +111,6 @@ func TestConversionFromFullStatusToOutputOverview(t *testing.T) { assert.Equal(t, overview, convertedResult) } -func TestForErrorOnMultipleOutputFlags(t *testing.T) { - rootCmd.SetArgs([]string{ - "status", - "--yaml", - "--json", - }) - if err := rootCmd.Execute(); err != nil { - return - } - t.Errorf("expected error while running status command with 2 output flags") -} - func TestSortingOfPeers(t *testing.T) { peers := []peerStateDetailOutput{ {