From 75214223d728fb9235709f92053e9eed983e8749 Mon Sep 17 00:00:00 2001 From: TechHutTV Date: Tue, 19 May 2026 16:45:19 -0700 Subject: [PATCH] JSON deregister, login, networks select, debug bundle --- client/cmd/debug.go | 35 +++++++++++++-- client/cmd/login.go | 37 +++++++++++++--- client/cmd/logout.go | 25 ++++++++++- client/cmd/networks.go | 43 +++++++++++++++--- client/status/status.go | 98 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 222 insertions(+), 16 deletions(-) diff --git a/client/cmd/debug.go b/client/cmd/debug.go index 2a8cdc887..6c9e0bf8c 100644 --- a/client/cmd/debug.go +++ b/client/cmd/debug.go @@ -17,6 +17,7 @@ import ( "github.com/netbirdio/netbird/client/internal/profilemanager" "github.com/netbirdio/netbird/client/proto" "github.com/netbirdio/netbird/client/server" + nbstatus "github.com/netbirdio/netbird/client/status" mgmProto "github.com/netbirdio/netbird/shared/management/proto" "github.com/netbirdio/netbird/upload-server/types" ) @@ -108,16 +109,41 @@ func debugBundle(cmd *cobra.Command, _ []string) error { if err != nil { return fmt.Errorf("failed to bundle debug: %v", status.Convert(err).Message()) } - cmd.Printf("Local file:\n%s\n", resp.GetPath()) if resp.GetUploadFailureReason() != "" { return fmt.Errorf("upload failed: %s", resp.GetUploadFailureReason()) } - if uploadBundleFlag { - cmd.Printf("Upload file key:\n%s\n", resp.GetUploadedKey()) + return emitDebugBundle(cmd, resp.GetPath(), resp.GetUploadedKey()) +} + +func emitDebugBundle(cmd *cobra.Command, path, uploadedKey string) error { + if !jsonFlag && !yamlFlag { + cmd.Printf("Local file:\n%s\n", path) + if uploadBundleFlag { + cmd.Printf("Upload file key:\n%s\n", uploadedKey) + } + return nil } + out := &nbstatus.DebugBundleOutput{Path: path} + if uploadBundleFlag { + out.UploadedKey = uploadedKey + } + + 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 } @@ -451,6 +477,9 @@ func init() { debugBundleCmd.Flags().BoolVarP(&systemInfoFlag, "system-info", "S", true, "Adds system information to the debug bundle") debugBundleCmd.Flags().BoolVarP(&uploadBundleFlag, "upload-bundle", "U", false, "Uploads the debug bundle to a server") debugBundleCmd.Flags().StringVar(&uploadBundleURLFlag, "upload-bundle-url", types.DefaultBundleURL, "Service URL to get an URL to upload the debug bundle") + debugBundleCmd.PersistentFlags().BoolVarP(&jsonFlag, "json", "j", false, "display command result in json format") + debugBundleCmd.PersistentFlags().BoolVarP(&yamlFlag, "yaml", "y", false, "display command result in yaml format") + debugBundleCmd.MarkFlagsMutuallyExclusive("json", "yaml") forCmd.Flags().Uint32VarP(&logFileCount, "log-file-count", "C", 1, "Number of rotated log files to include in debug bundle") forCmd.Flags().BoolVarP(&systemInfoFlag, "system-info", "S", true, "Adds system information to the debug bundle") diff --git a/client/cmd/login.go b/client/cmd/login.go index e7d75c27c..1887a7651 100644 --- a/client/cmd/login.go +++ b/client/cmd/login.go @@ -28,6 +28,10 @@ func init() { loginCmd.PersistentFlags().BoolVar(&showQR, showQRFlag, false, showQRDesc) loginCmd.PersistentFlags().StringVar(&profileName, profileNameFlag, "", profileNameDesc) loginCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "(DEPRECATED) Netbird config file location") + + loginCmd.PersistentFlags().BoolVarP(&jsonFlag, "json", "j", false, "display command result in json format") + loginCmd.PersistentFlags().BoolVarP(&yamlFlag, "yaml", "y", false, "display command result in yaml format") + loginCmd.MarkFlagsMutuallyExclusive("json", "yaml") } var loginCmd = &cobra.Command{ @@ -74,12 +78,36 @@ var loginCmd = &cobra.Command{ return fmt.Errorf("daemon login failed: %v", err) } - cmd.Println("Logging successfully") - - return nil + return emitLoginOutput(cmd, activeProf.Name) }, } +// emitLoginOutput writes the result of a successful login in the format +// requested by the user (json, yaml, or human-readable text fallback). +func emitLoginOutput(cmd *cobra.Command, profileName string) error { + out := &nbstatus.LoginOutput{ + Status: "logged_in", + ProfileName: profileName, + } + switch { + case jsonFlag: + s, err := out.JSON() + if err != nil { + return err + } + cmd.Println(s) + case yamlFlag: + s, err := out.YAML() + if err != nil { + return err + } + cmd.Print(s) + default: + cmd.Println("Logging successfully") + } + return nil +} + func doDaemonLogin(ctx context.Context, cmd *cobra.Command, providedSetupKey string, activeProf *profilemanager.Profile, username string, pm *profilemanager.ProfileManager) error { conn, err := DialClientGRPCServer(ctx, daemonAddr) if err != nil { @@ -254,8 +282,7 @@ func doForegroundLogin(ctx context.Context, cmd *cobra.Command, setupKey string, if err != nil { return fmt.Errorf("foreground login failed: %v", err) } - cmd.Println("Logging successfully") - return nil + return emitLoginOutput(cmd, activeProf.Name) } func handleSSOLogin(ctx context.Context, cmd *cobra.Command, loginResp *proto.LoginResponse, client proto.DaemonServiceClient, pm *profilemanager.ProfileManager) error { diff --git a/client/cmd/logout.go b/client/cmd/logout.go index 1a5281acb..4d675ff29 100644 --- a/client/cmd/logout.go +++ b/client/cmd/logout.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" "github.com/netbirdio/netbird/client/proto" + nbstatus "github.com/netbirdio/netbird/client/status" ) var logoutCmd = &cobra.Command{ @@ -49,11 +50,33 @@ var logoutCmd = &cobra.Command{ return fmt.Errorf("deregister: %v", err) } - cmd.Println("Deregistered successfully") + out := &nbstatus.DeregisterOutput{ + Status: "deregistered", + ProfileName: profileName, + } + switch { + case jsonFlag: + s, err := out.JSON() + if err != nil { + return err + } + cmd.Println(s) + case yamlFlag: + s, err := out.YAML() + if err != nil { + return err + } + cmd.Print(s) + default: + cmd.Println("Deregistered successfully") + } return nil }, } func init() { logoutCmd.PersistentFlags().StringVar(&profileName, profileNameFlag, "", profileNameDesc) + logoutCmd.PersistentFlags().BoolVarP(&jsonFlag, "json", "j", false, "display command result in json format") + logoutCmd.PersistentFlags().BoolVarP(&yamlFlag, "yaml", "y", false, "display command result in yaml format") + logoutCmd.MarkFlagsMutuallyExclusive("json", "yaml") } diff --git a/client/cmd/networks.go b/client/cmd/networks.go index 2ca9f07bd..d9d4e3371 100644 --- a/client/cmd/networks.go +++ b/client/cmd/networks.go @@ -50,9 +50,11 @@ 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") + for _, c := range []*cobra.Command{routesListCmd, routesSelectCmd, routesDeselectCmd} { + c.PersistentFlags().BoolVarP(&jsonFlag, "json", "j", false, "display command result in json format") + c.PersistentFlags().BoolVarP(&yamlFlag, "yaml", "y", false, "display command result in yaml format") + c.MarkFlagsMutuallyExclusive("json", "yaml") + } } func networksList(cmd *cobra.Command, _ []string) error { @@ -185,11 +187,10 @@ func networksSelect(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to select networks: %v", status.Convert(err).Message()) } - cmd.Println("Networks selected successfully.") - - return nil + return emitNetworksMutation(cmd, "selected", req, "Networks selected successfully.") } + func networksDeselect(cmd *cobra.Command, args []string) error { conn, err := getClient(cmd) if err != nil { @@ -210,7 +211,35 @@ func networksDeselect(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to deselect networks: %v", status.Convert(err).Message()) } - cmd.Println("Networks deselected successfully.") + return emitNetworksMutation(cmd, "deselected", req, "Networks deselected successfully.") +} +func emitNetworksMutation(cmd *cobra.Command, action string, req *proto.SelectNetworksRequest, textFallback string) error { + if !jsonFlag && !yamlFlag { + cmd.Println(textFallback) + return nil + } + + out := &nbstatus.NetworksMutationOutput{ + Status: action, + All: req.GetAll(), + } + if !req.GetAll() { + out.Networks = req.GetNetworkIDs() + } + + 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 } diff --git a/client/status/status.go b/client/status/status.go index 06b8ddf4e..134b44446 100644 --- a/client/status/status.go +++ b/client/status/status.go @@ -564,6 +564,104 @@ func (o *StateListOutput) YAML() (string, error) { return string(yamlBytes), nil } +// DeregisterOutput is the structured result of `netbird deregister`. +type DeregisterOutput struct { + Status string `json:"status" yaml:"status"` // "deregistered" + ProfileName string `json:"profileName,omitempty" yaml:"profileName,omitempty"` +} + +// JSON returns the DeregisterOutput as a JSON string. +func (o *DeregisterOutput) JSON() (string, error) { + jsonBytes, err := json.Marshal(o) + if err != nil { + return "", fmt.Errorf("json marshal failed") + } + return string(jsonBytes), err +} + +// YAML returns the DeregisterOutput as a YAML string. +func (o *DeregisterOutput) YAML() (string, error) { + yamlBytes, err := yaml.Marshal(o) + if err != nil { + return "", fmt.Errorf("yaml marshal failed") + } + return string(yamlBytes), nil +} + +// LoginOutput is the structured result of `netbird login` after successful auth. +type LoginOutput struct { + Status string `json:"status" yaml:"status"` // "logged_in" + ProfileName string `json:"profileName,omitempty" yaml:"profileName,omitempty"` +} + +// JSON returns the LoginOutput as a JSON string. +func (o *LoginOutput) JSON() (string, error) { + jsonBytes, err := json.Marshal(o) + if err != nil { + return "", fmt.Errorf("json marshal failed") + } + return string(jsonBytes), err +} + +// YAML returns the LoginOutput as a YAML string. +func (o *LoginOutput) YAML() (string, error) { + yamlBytes, err := yaml.Marshal(o) + if err != nil { + return "", fmt.Errorf("yaml marshal failed") + } + return string(yamlBytes), nil +} + +// NetworksMutationOutput is the structured result of `netbird networks select` +// or `netbird networks deselect`. +type NetworksMutationOutput struct { + Status string `json:"status" yaml:"status"` // "selected" | "deselected" + Networks []string `json:"networks,omitempty" yaml:"networks,omitempty"` + All bool `json:"all,omitempty" yaml:"all,omitempty"` +} + +// JSON returns the NetworksMutationOutput as a JSON string. +func (o *NetworksMutationOutput) JSON() (string, error) { + jsonBytes, err := json.Marshal(o) + if err != nil { + return "", fmt.Errorf("json marshal failed") + } + return string(jsonBytes), err +} + +// YAML returns the NetworksMutationOutput as a YAML string. +func (o *NetworksMutationOutput) YAML() (string, error) { + yamlBytes, err := yaml.Marshal(o) + if err != nil { + return "", fmt.Errorf("yaml marshal failed") + } + return string(yamlBytes), nil +} + +// DebugBundleOutput is the structured result of `netbird debug bundle`. +type DebugBundleOutput struct { + Path string `json:"path" yaml:"path"` + UploadedKey string `json:"uploadedKey,omitempty" yaml:"uploadedKey,omitempty"` +} + +// JSON returns the DebugBundleOutput as a JSON string. +func (o *DebugBundleOutput) JSON() (string, error) { + jsonBytes, err := json.Marshal(o) + if err != nil { + return "", fmt.Errorf("json marshal failed") + } + return string(jsonBytes), err +} + +// YAML returns the DebugBundleOutput as a YAML string. +func (o *DebugBundleOutput) 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"`