diff --git a/client/cmd/forwarding_rules.go b/client/cmd/forwarding_rules.go index b3052746a..fe24dd6c1 100644 --- a/client/cmd/forwarding_rules.go +++ b/client/cmd/forwarding_rules.go @@ -8,6 +8,7 @@ import ( "google.golang.org/grpc/status" "github.com/netbirdio/netbird/client/proto" + nbstatus "github.com/netbirdio/netbird/client/status" ) var forwardingRulesCmd = &cobra.Command{ @@ -25,6 +26,12 @@ var forwardingRulesListCmd = &cobra.Command{ RunE: listForwardingRules, } +func init() { + forwardingRulesListCmd.PersistentFlags().BoolVarP(&jsonFlag, "json", "j", false, "display command result in json format") + forwardingRulesListCmd.PersistentFlags().BoolVarP(&yamlFlag, "yaml", "y", false, "display command result in yaml format") + forwardingRulesListCmd.MarkFlagsMutuallyExclusive("json", "yaml") +} + func listForwardingRules(cmd *cobra.Command, _ []string) error { conn, err := getClient(cmd) if err != nil { @@ -38,19 +45,23 @@ func listForwardingRules(cmd *cobra.Command, _ []string) error { return fmt.Errorf("failed to list network: %v", status.Convert(err).Message()) } - if len(resp.GetRules()) == 0 { + rules := resp.GetRules() + sortForwardingRules(rules) + + if jsonFlag || yamlFlag { + return emitForwardingList(cmd, rules) + } + + if len(rules) == 0 { cmd.Println("No forwarding rules available.") return nil } - printForwardingRules(cmd, resp.GetRules()) + printForwardingRules(cmd, rules) return nil } -func printForwardingRules(cmd *cobra.Command, rules []*proto.ForwardingRule) { - cmd.Println("Available forwarding rules:") - - // Sort rules by translated address +func sortForwardingRules(rules []*proto.ForwardingRule) { sort.Slice(rules, func(i, j int) bool { if rules[i].GetTranslatedAddress() != rules[j].GetTranslatedAddress() { return rules[i].GetTranslatedAddress() < rules[j].GetTranslatedAddress() @@ -58,9 +69,40 @@ func printForwardingRules(cmd *cobra.Command, rules []*proto.ForwardingRule) { if rules[i].GetProtocol() != rules[j].GetProtocol() { return rules[i].GetProtocol() < rules[j].GetProtocol() } - return getFirstPort(rules[i].GetDestinationPort()) < getFirstPort(rules[j].GetDestinationPort()) }) +} + +func emitForwardingList(cmd *cobra.Command, rules []*proto.ForwardingRule) error { + out := &nbstatus.ForwardingListOutput{Rules: make([]nbstatus.ForwardingRuleOutput, 0, len(rules))} + for _, rule := range rules { + out.Rules = append(out.Rules, nbstatus.ForwardingRuleOutput{ + TranslatedAddress: rule.GetTranslatedAddress(), + TranslatedHostname: rule.GetTranslatedHostname(), + Protocol: rule.GetProtocol(), + DestinationPort: portToString(rule.GetDestinationPort()), + TranslatedPort: portToString(rule.GetTranslatedPort()), + }) + } + + if jsonFlag { + s, err := out.JSON() + if err != nil { + return err + } + cmd.Println(s) + return nil + } + s, err := out.YAML() + if err != nil { + return err + } + cmd.Print(s) + return nil +} + +func printForwardingRules(cmd *cobra.Command, rules []*proto.ForwardingRule) { + cmd.Println("Available forwarding rules:") var lastIP string for _, rule := range rules { diff --git a/client/cmd/networks.go b/client/cmd/networks.go index 05823b8bb..2ca9f07bd 100644 --- a/client/cmd/networks.go +++ b/client/cmd/networks.go @@ -8,6 +8,7 @@ import ( "google.golang.org/grpc/status" "github.com/netbirdio/netbird/client/proto" + nbstatus "github.com/netbirdio/netbird/client/status" ) var appendFlag bool @@ -48,6 +49,10 @@ var routesDeselectCmd = &cobra.Command{ func init() { routesSelectCmd.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "Append to current network selection instead of replacing") + + routesListCmd.PersistentFlags().BoolVarP(&jsonFlag, "json", "j", false, "display command result in json format") + routesListCmd.PersistentFlags().BoolVarP(&yamlFlag, "yaml", "y", false, "display command result in yaml format") + routesListCmd.MarkFlagsMutuallyExclusive("json", "yaml") } func networksList(cmd *cobra.Command, _ []string) error { @@ -63,6 +68,10 @@ func networksList(cmd *cobra.Command, _ []string) error { return fmt.Errorf("failed to list network: %v", status.Convert(err).Message()) } + if jsonFlag || yamlFlag { + return emitNetworksList(cmd, resp) + } + if len(resp.Routes) == 0 { cmd.Println("No networks available.") return nil @@ -73,6 +82,40 @@ func networksList(cmd *cobra.Command, _ []string) error { return nil } +func emitNetworksList(cmd *cobra.Command, resp *proto.ListNetworksResponse) error { + out := &nbstatus.NetworksListOutput{Networks: make([]nbstatus.NetworkOutput, 0, len(resp.GetRoutes()))} + for _, route := range resp.GetRoutes() { + row := nbstatus.NetworkOutput{ + ID: route.GetID(), + Range: route.GetRange(), + Domains: route.GetDomains(), + Selected: route.GetSelected(), + } + if resolved := route.GetResolvedIPs(); len(resolved) > 0 { + row.ResolvedIPs = make(map[string][]string, len(resolved)) + for d, ipList := range resolved { + row.ResolvedIPs[d] = ipList.GetIps() + } + } + out.Networks = append(out.Networks, row) + } + + if jsonFlag { + s, err := out.JSON() + if err != nil { + return err + } + cmd.Println(s) + return nil + } + s, err := out.YAML() + if err != nil { + return err + } + cmd.Print(s) + return nil +} + func printNetworks(cmd *cobra.Command, resp *proto.ListNetworksResponse) { cmd.Println("Available Networks:") for _, route := range resp.Routes { diff --git a/client/cmd/profile.go b/client/cmd/profile.go index d6e81760f..d963e80bc 100644 --- a/client/cmd/profile.go +++ b/client/cmd/profile.go @@ -11,9 +11,16 @@ import ( "github.com/netbirdio/netbird/client/internal" "github.com/netbirdio/netbird/client/internal/profilemanager" "github.com/netbirdio/netbird/client/proto" + nbstatus "github.com/netbirdio/netbird/client/status" "github.com/netbirdio/netbird/util" ) +func init() { + profileListCmd.PersistentFlags().BoolVarP(&jsonFlag, "json", "j", false, "display command result in json format") + profileListCmd.PersistentFlags().BoolVarP(&yamlFlag, "yaml", "y", false, "display command result in yaml format") + profileListCmd.MarkFlagsMutuallyExclusive("json", "yaml") +} + var profileCmd = &cobra.Command{ Use: "profile", Short: "Manage NetBird client profiles", @@ -90,6 +97,10 @@ func listProfilesFunc(cmd *cobra.Command, _ []string) error { return err } + if jsonFlag || yamlFlag { + return emitProfileList(cmd, profiles.GetProfiles()) + } + // list profiles, add a tick if the profile is active cmd.Println("Found", len(profiles.Profiles), "profiles:") for _, profile := range profiles.Profiles { @@ -104,6 +115,31 @@ func listProfilesFunc(cmd *cobra.Command, _ []string) error { return nil } +func emitProfileList(cmd *cobra.Command, profiles []*proto.Profile) error { + out := &nbstatus.ProfileListOutput{Profiles: make([]nbstatus.ProfileOutput, 0, len(profiles))} + for _, p := range profiles { + out.Profiles = append(out.Profiles, nbstatus.ProfileOutput{ + Name: p.GetName(), + Active: p.GetIsActive(), + }) + } + + if jsonFlag { + s, err := out.JSON() + if err != nil { + return err + } + cmd.Println(s) + return nil + } + s, err := out.YAML() + if err != nil { + return err + } + cmd.Print(s) + return nil +} + func addProfileFunc(cmd *cobra.Command, args []string) error { if err := setupCmd(cmd); err != nil { return err diff --git a/client/cmd/state.go b/client/cmd/state.go index b4612e601..4fdcfb263 100644 --- a/client/cmd/state.go +++ b/client/cmd/state.go @@ -8,6 +8,7 @@ import ( "google.golang.org/grpc/status" "github.com/netbirdio/netbird/client/proto" + nbstatus "github.com/netbirdio/netbird/client/status" ) var ( @@ -75,6 +76,10 @@ func init() { stateCleanCmd.Flags().BoolVarP(&allFlag, "all", "a", false, "Clean all states") stateDeleteCmd.Flags().BoolVarP(&allFlag, "all", "a", false, "Delete all states") + + stateListCmd.PersistentFlags().BoolVarP(&jsonFlag, "json", "j", false, "display command result in json format") + stateListCmd.PersistentFlags().BoolVarP(&yamlFlag, "yaml", "y", false, "display command result in yaml format") + stateListCmd.MarkFlagsMutuallyExclusive("json", "yaml") } func stateList(cmd *cobra.Command, _ []string) error { @@ -94,6 +99,10 @@ func stateList(cmd *cobra.Command, _ []string) error { return fmt.Errorf("failed to list states: %v", status.Convert(err).Message()) } + if jsonFlag || yamlFlag { + return emitStateList(cmd, resp.GetStates()) + } + cmd.Printf("\nStored states:\n\n") for _, state := range resp.States { cmd.Printf("- %s\n", state.Name) @@ -102,6 +111,28 @@ func stateList(cmd *cobra.Command, _ []string) error { return nil } +func emitStateList(cmd *cobra.Command, states []*proto.State) error { + out := &nbstatus.StateListOutput{States: make([]nbstatus.StateOutput, 0, len(states))} + for _, s := range states { + out.States = append(out.States, nbstatus.StateOutput{Name: s.GetName()}) + } + + if jsonFlag { + s, err := out.JSON() + if err != nil { + return err + } + cmd.Println(s) + return nil + } + s, err := out.YAML() + if err != nil { + return err + } + cmd.Print(s) + return nil +} + func stateClean(cmd *cobra.Command, args []string) error { var stateName string if !allFlag { diff --git a/client/cmd/version.go b/client/cmd/version.go index 249854444..06c74ba8d 100644 --- a/client/cmd/version.go +++ b/client/cmd/version.go @@ -3,6 +3,7 @@ package cmd import ( "github.com/spf13/cobra" + nbstatus "github.com/netbirdio/netbird/client/status" "github.com/netbirdio/netbird/version" ) @@ -10,9 +11,35 @@ var ( versionCmd = &cobra.Command{ Use: "version", Short: "Print the NetBird's client application version", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { cmd.SetOut(cmd.OutOrStdout()) - cmd.Println(version.NetbirdVersion()) + v := version.NetbirdVersion() + + switch { + case jsonFlag: + out := &nbstatus.VersionOutput{Version: v} + s, err := out.JSON() + if err != nil { + return err + } + cmd.Println(s) + case yamlFlag: + out := &nbstatus.VersionOutput{Version: v} + s, err := out.YAML() + if err != nil { + return err + } + cmd.Print(s) + default: + cmd.Println(v) + } + return nil }, } ) + +func init() { + versionCmd.PersistentFlags().BoolVarP(&jsonFlag, "json", "j", false, "display command result in json format") + versionCmd.PersistentFlags().BoolVarP(&yamlFlag, "yaml", "y", false, "display command result in yaml format") + versionCmd.MarkFlagsMutuallyExclusive("json", "yaml") +} diff --git a/client/status/status.go b/client/status/status.go index 1b54c9bfa..b122e68d0 100644 --- a/client/status/status.go +++ b/client/status/status.go @@ -440,6 +440,150 @@ func (e *SSOEvent) YAML() (string, error) { return string(yamlBytes), nil } +// NetworkOutput is one row of a networks-list response. +type NetworkOutput struct { + ID string `json:"id" yaml:"id"` + Range string `json:"range,omitempty" yaml:"range,omitempty"` + Domains []string `json:"domains,omitempty" yaml:"domains,omitempty"` + ResolvedIPs map[string][]string `json:"resolvedIps,omitempty" yaml:"resolvedIps,omitempty"` + Selected bool `json:"selected" yaml:"selected"` +} + +// NetworksListOutput is the structured result of `netbird networks list`. +type NetworksListOutput struct { + Networks []NetworkOutput `json:"networks" yaml:"networks"` +} + +// JSON returns the NetworksListOutput as a JSON string. +func (o *NetworksListOutput) JSON() (string, error) { + jsonBytes, err := json.Marshal(o) + if err != nil { + return "", fmt.Errorf("json marshal failed") + } + return string(jsonBytes), err +} + +// YAML returns the NetworksListOutput as a YAML string. +func (o *NetworksListOutput) YAML() (string, error) { + yamlBytes, err := yaml.Marshal(o) + if err != nil { + return "", fmt.Errorf("yaml marshal failed") + } + return string(yamlBytes), nil +} + +// ForwardingRuleOutput is one row of a forwarding-list response. +type ForwardingRuleOutput struct { + TranslatedAddress string `json:"translatedAddress" yaml:"translatedAddress"` + TranslatedHostname string `json:"translatedHostname,omitempty" yaml:"translatedHostname,omitempty"` + Protocol string `json:"protocol" yaml:"protocol"` + DestinationPort string `json:"destinationPort" yaml:"destinationPort"` + TranslatedPort string `json:"translatedPort" yaml:"translatedPort"` +} + +// ForwardingListOutput is the structured result of `netbird forwarding list`. +type ForwardingListOutput struct { + Rules []ForwardingRuleOutput `json:"rules" yaml:"rules"` +} + +// JSON returns the ForwardingListOutput as a JSON string. +func (o *ForwardingListOutput) JSON() (string, error) { + jsonBytes, err := json.Marshal(o) + if err != nil { + return "", fmt.Errorf("json marshal failed") + } + return string(jsonBytes), err +} + +// YAML returns the ForwardingListOutput as a YAML string. +func (o *ForwardingListOutput) YAML() (string, error) { + yamlBytes, err := yaml.Marshal(o) + if err != nil { + return "", fmt.Errorf("yaml marshal failed") + } + return string(yamlBytes), nil +} + +// ProfileOutput is one row of a profile-list response. +type ProfileOutput struct { + Name string `json:"name" yaml:"name"` + Active bool `json:"active" yaml:"active"` +} + +// ProfileListOutput is the structured result of `netbird profile list`. +type ProfileListOutput struct { + Profiles []ProfileOutput `json:"profiles" yaml:"profiles"` +} + +// JSON returns the ProfileListOutput as a JSON string. +func (o *ProfileListOutput) JSON() (string, error) { + jsonBytes, err := json.Marshal(o) + if err != nil { + return "", fmt.Errorf("json marshal failed") + } + return string(jsonBytes), err +} + +// YAML returns the ProfileListOutput as a YAML string. +func (o *ProfileListOutput) YAML() (string, error) { + yamlBytes, err := yaml.Marshal(o) + if err != nil { + return "", fmt.Errorf("yaml marshal failed") + } + return string(yamlBytes), nil +} + +// StateOutput is one row of a state-list response. +type StateOutput struct { + Name string `json:"name" yaml:"name"` +} + +// StateListOutput is the structured result of `netbird state list`. +type StateListOutput struct { + States []StateOutput `json:"states" yaml:"states"` +} + +// JSON returns the StateListOutput as a JSON string. +func (o *StateListOutput) JSON() (string, error) { + jsonBytes, err := json.Marshal(o) + if err != nil { + return "", fmt.Errorf("json marshal failed") + } + return string(jsonBytes), err +} + +// YAML returns the StateListOutput as a YAML string. +func (o *StateListOutput) YAML() (string, error) { + yamlBytes, err := yaml.Marshal(o) + if err != nil { + return "", fmt.Errorf("yaml marshal failed") + } + return string(yamlBytes), nil +} + +// VersionOutput is the structured result of `netbird version`. +type VersionOutput struct { + Version string `json:"version" yaml:"version"` +} + +// JSON returns the VersionOutput as a JSON string. +func (o *VersionOutput) JSON() (string, error) { + jsonBytes, err := json.Marshal(o) + if err != nil { + return "", fmt.Errorf("json marshal failed") + } + return string(jsonBytes), err +} + +// YAML returns the VersionOutput as a YAML string. +func (o *VersionOutput) YAML() (string, error) { + yamlBytes, err := yaml.Marshal(o) + if err != nil { + return "", fmt.Errorf("yaml marshal failed") + } + return string(yamlBytes), nil +} + // JSON returns the DownOutput as a JSON string. func (o *DownOutput) JSON() (string, error) { jsonBytes, err := json.Marshal(o)