From 2372827f40a09297aa8cca4a8b23dc14fbf687c7 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 12 Sep 2025 16:15:05 +0200 Subject: [PATCH] cmd: replace prompt with one that respect stdout redir Signed-off-by: Kristoffer Dalby --- cmd/headscale/cli/nodes.go | 62 +++---------------------------------- cmd/headscale/cli/policy.go | 31 +++++++++++++------ cmd/headscale/cli/users.go | 16 +++------- cmd/headscale/cli/utils.go | 9 +++++- 4 files changed, 39 insertions(+), 79 deletions(-) diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index 6d6476fb..e1b8e7b3 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -9,7 +9,6 @@ import ( "strings" "time" - survey "github.com/AlecAivazis/survey/v2" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/juanfont/headscale/hscontrol/util" "github.com/pterm/pterm" @@ -222,8 +221,6 @@ var listNodeRoutesCmd = &cobra.Command{ fmt.Sprintf("Error converting ID to integer: %s", err), output, ) - - return } ctx, client, conn, cancel := newHeadscaleCLIWithConfig() @@ -290,8 +287,6 @@ var expireNodeCmd = &cobra.Command{ fmt.Sprintf("Error converting ID to integer: %s", err), output, ) - - return } ctx, client, conn, cancel := newHeadscaleCLIWithConfig() @@ -312,8 +307,6 @@ var expireNodeCmd = &cobra.Command{ ), output, ) - - return } SuccessOutput(response.GetNode(), "Node expired", output) @@ -333,8 +326,6 @@ var renameNodeCmd = &cobra.Command{ fmt.Sprintf("Error converting ID to integer: %s", err), output, ) - - return } ctx, client, conn, cancel := newHeadscaleCLIWithConfig() @@ -360,8 +351,6 @@ var renameNodeCmd = &cobra.Command{ ), output, ) - - return } SuccessOutput(response.GetNode(), "Node renamed", output) @@ -382,8 +371,6 @@ var deleteNodeCmd = &cobra.Command{ fmt.Sprintf("Error converting ID to integer: %s", err), output, ) - - return } ctx, client, conn, cancel := newHeadscaleCLIWithConfig() @@ -401,8 +388,6 @@ var deleteNodeCmd = &cobra.Command{ "Error getting node node: "+status.Convert(err).Message(), output, ) - - return } deleteRequest := &v1.DeleteNodeRequest{ @@ -412,16 +397,10 @@ var deleteNodeCmd = &cobra.Command{ confirm := false force, _ := cmd.Flags().GetBool("force") if !force { - prompt := &survey.Confirm{ - Message: fmt.Sprintf( - "Do you want to remove the node %s?", - getResponse.GetNode().GetName(), - ), - } - err = survey.AskOne(prompt, &confirm) - if err != nil { - return - } + confirm = util.YesNo(fmt.Sprintf( + "Do you want to remove the node %s?", + getResponse.GetNode().GetName(), + )) } if confirm || force { @@ -437,8 +416,6 @@ var deleteNodeCmd = &cobra.Command{ "Error deleting node: "+status.Convert(err).Message(), output, ) - - return } SuccessOutput( map[string]string{"Result": "Node deleted"}, @@ -465,8 +442,6 @@ var moveNodeCmd = &cobra.Command{ fmt.Sprintf("Error converting ID to integer: %s", err), output, ) - - return } user, err := cmd.Flags().GetUint64("user") @@ -476,8 +451,6 @@ var moveNodeCmd = &cobra.Command{ fmt.Sprintf("Error getting user: %s", err), output, ) - - return } ctx, client, conn, cancel := newHeadscaleCLIWithConfig() @@ -495,8 +468,6 @@ var moveNodeCmd = &cobra.Command{ "Error getting node: "+status.Convert(err).Message(), output, ) - - return } moveRequest := &v1.MoveNodeRequest{ @@ -511,8 +482,6 @@ var moveNodeCmd = &cobra.Command{ "Error moving node: "+status.Convert(err).Message(), output, ) - - return } SuccessOutput(moveResponse.GetNode(), "Node moved to another user", output) @@ -535,20 +504,13 @@ If you remove IPv4 or IPv6 prefixes from the config, it can be run to remove the IPs that should no longer be assigned to nodes.`, Run: func(cmd *cobra.Command, args []string) { - var err error output, _ := cmd.Flags().GetString("output") confirm := false force, _ := cmd.Flags().GetBool("force") if !force { - prompt := &survey.Confirm{ - Message: "Are you sure that you want to assign/remove IPs to/from nodes?", - } - err = survey.AskOne(prompt, &confirm) - if err != nil { - return - } + confirm = util.YesNo("Are you sure that you want to assign/remove IPs to/from nodes?") } if confirm || force { @@ -563,8 +525,6 @@ be assigned to nodes.`, "Error backfilling IPs: "+status.Convert(err).Message(), output, ) - - return } SuccessOutput(changes, "Node IPs backfilled successfully", output) @@ -763,8 +723,6 @@ var tagCmd = &cobra.Command{ fmt.Sprintf("Error converting ID to integer: %s", err), output, ) - - return } tagsToSet, err := cmd.Flags().GetStringSlice("tags") if err != nil { @@ -773,8 +731,6 @@ var tagCmd = &cobra.Command{ fmt.Sprintf("Error retrieving list of tags to add to node, %v", err), output, ) - - return } // Sending tags to node @@ -789,8 +745,6 @@ var tagCmd = &cobra.Command{ fmt.Sprintf("Error while sending tags to headscale: %s", err), output, ) - - return } if resp != nil { @@ -820,8 +774,6 @@ var approveRoutesCmd = &cobra.Command{ fmt.Sprintf("Error converting ID to integer: %s", err), output, ) - - return } routes, err := cmd.Flags().GetStringSlice("routes") if err != nil { @@ -830,8 +782,6 @@ var approveRoutesCmd = &cobra.Command{ fmt.Sprintf("Error retrieving list of routes to add to node, %v", err), output, ) - - return } // Sending routes to node @@ -846,8 +796,6 @@ var approveRoutesCmd = &cobra.Command{ fmt.Sprintf("Error while sending routes to headscale: %s", err), output, ) - - return } if resp != nil { diff --git a/cmd/headscale/cli/policy.go b/cmd/headscale/cli/policy.go index a03f7f8b..b8a9a2ad 100644 --- a/cmd/headscale/cli/policy.go +++ b/cmd/headscale/cli/policy.go @@ -9,10 +9,10 @@ import ( "github.com/juanfont/headscale/hscontrol/db" "github.com/juanfont/headscale/hscontrol/policy" "github.com/juanfont/headscale/hscontrol/types" + "github.com/juanfont/headscale/hscontrol/util" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "tailscale.com/types/views" - "tailscale.com/util/prompt" ) const ( @@ -52,7 +52,13 @@ var getPolicy = &cobra.Command{ output, _ := cmd.Flags().GetString("output") var policy string if bypass, _ := cmd.Flags().GetBool(bypassFlag); bypass { - if !prompt.YesNo("DO NOT run this command if an instance of headscale is running, are you sure headscale is not running?") { + confirm := false + force, _ := cmd.Flags().GetBool("force") + if !force { + confirm = util.YesNo("DO NOT run this command if an instance of headscale is running, are you sure headscale is not running?") + } + + if !confirm && !force { ErrorOutput(nil, "Aborting command", output) return } @@ -60,7 +66,6 @@ var getPolicy = &cobra.Command{ cfg, err := types.LoadServerConfig() if err != nil { ErrorOutput(err, fmt.Sprintf("Failed loading config: %s", err), output) - return } d, err := db.NewHeadscaleDatabase( @@ -70,13 +75,11 @@ var getPolicy = &cobra.Command{ ) if err != nil { ErrorOutput(err, fmt.Sprintf("Failed to open database: %s", err), output) - return } pol, err := d.GetPolicy() if err != nil { ErrorOutput(err, fmt.Sprintf("Failed loading Policy from database: %s", err), output) - return } policy = pol.Data @@ -124,8 +127,20 @@ var setPolicy = &cobra.Command{ ErrorOutput(err, fmt.Sprintf("Error reading the policy file: %s", err), output) } + _, err = policy.NewPolicyManager(policyBytes, nil, views.Slice[types.NodeView]{}) + if err != nil { + ErrorOutput(err, fmt.Sprintf("Error parsing the policy file: %s", err), output) + return + } + if bypass, _ := cmd.Flags().GetBool(bypassFlag); bypass { - if !prompt.YesNo("DO NOT run this command if an instance of headscale is running, are you sure headscale is not running?") { + confirm := false + force, _ := cmd.Flags().GetBool("force") + if !force { + confirm = util.YesNo("DO NOT run this command if an instance of headscale is running, are you sure headscale is not running?") + } + + if !confirm && !force { ErrorOutput(nil, "Aborting command", output) return } @@ -133,7 +148,6 @@ var setPolicy = &cobra.Command{ cfg, err := types.LoadServerConfig() if err != nil { ErrorOutput(err, fmt.Sprintf("Failed loading config: %s", err), output) - return } d, err := db.NewHeadscaleDatabase( @@ -143,13 +157,11 @@ var setPolicy = &cobra.Command{ ) if err != nil { ErrorOutput(err, fmt.Sprintf("Failed to open database: %s", err), output) - return } _, err = d.SetPolicy(string(policyBytes)) if err != nil { ErrorOutput(err, fmt.Sprintf("Failed to set ACL Policy: %s", err), output) - return } } else { request := &v1.SetPolicyRequest{Policy: string(policyBytes)} @@ -160,7 +172,6 @@ var setPolicy = &cobra.Command{ if _, err := client.SetPolicy(ctx, request); err != nil { ErrorOutput(err, fmt.Sprintf("Failed to set ACL Policy: %s", err), output) - return } } diff --git a/cmd/headscale/cli/users.go b/cmd/headscale/cli/users.go index 8b32d935..9a816c78 100644 --- a/cmd/headscale/cli/users.go +++ b/cmd/headscale/cli/users.go @@ -6,8 +6,8 @@ import ( "net/url" "strconv" - survey "github.com/AlecAivazis/survey/v2" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + "github.com/juanfont/headscale/hscontrol/util" "github.com/pterm/pterm" "github.com/rs/zerolog/log" "github.com/spf13/cobra" @@ -161,16 +161,10 @@ var destroyUserCmd = &cobra.Command{ confirm := false force, _ := cmd.Flags().GetBool("force") if !force { - prompt := &survey.Confirm{ - Message: fmt.Sprintf( - "Do you want to remove the user %q (%d) and any associated preauthkeys?", - user.GetName(), user.GetId(), - ), - } - err := survey.AskOne(prompt, &confirm) - if err != nil { - return - } + confirm = util.YesNo(fmt.Sprintf( + "Do you want to remove the user %q (%d) and any associated preauthkeys?", + user.GetName(), user.GetId(), + )) } if confirm || force { diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 0347c0a9..f6b5f71a 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -169,7 +169,14 @@ func ErrorOutput(errResult error, override string, outputFormat string) { Error string `json:"error"` } - fmt.Fprintf(os.Stderr, "%s\n", output(errOutput{errResult.Error()}, override, outputFormat)) + var errorMessage string + if errResult != nil { + errorMessage = errResult.Error() + } else { + errorMessage = override + } + + fmt.Fprintf(os.Stderr, "%s\n", output(errOutput{errorMessage}, override, outputFormat)) os.Exit(1) }