1
0
mirror of https://github.com/juanfont/headscale.git synced 2025-09-25 17:51:11 +02:00

cmd: replace prompt with one that respect stdout redir

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
Kristoffer Dalby 2025-09-12 16:15:05 +02:00
parent 07d8da580f
commit 2372827f40
No known key found for this signature in database
4 changed files with 39 additions and 79 deletions

View File

@ -9,7 +9,6 @@ import (
"strings" "strings"
"time" "time"
survey "github.com/AlecAivazis/survey/v2"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1" v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util"
"github.com/pterm/pterm" "github.com/pterm/pterm"
@ -222,8 +221,6 @@ var listNodeRoutesCmd = &cobra.Command{
fmt.Sprintf("Error converting ID to integer: %s", err), fmt.Sprintf("Error converting ID to integer: %s", err),
output, output,
) )
return
} }
ctx, client, conn, cancel := newHeadscaleCLIWithConfig() ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
@ -290,8 +287,6 @@ var expireNodeCmd = &cobra.Command{
fmt.Sprintf("Error converting ID to integer: %s", err), fmt.Sprintf("Error converting ID to integer: %s", err),
output, output,
) )
return
} }
ctx, client, conn, cancel := newHeadscaleCLIWithConfig() ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
@ -312,8 +307,6 @@ var expireNodeCmd = &cobra.Command{
), ),
output, output,
) )
return
} }
SuccessOutput(response.GetNode(), "Node expired", output) SuccessOutput(response.GetNode(), "Node expired", output)
@ -333,8 +326,6 @@ var renameNodeCmd = &cobra.Command{
fmt.Sprintf("Error converting ID to integer: %s", err), fmt.Sprintf("Error converting ID to integer: %s", err),
output, output,
) )
return
} }
ctx, client, conn, cancel := newHeadscaleCLIWithConfig() ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
@ -360,8 +351,6 @@ var renameNodeCmd = &cobra.Command{
), ),
output, output,
) )
return
} }
SuccessOutput(response.GetNode(), "Node renamed", output) SuccessOutput(response.GetNode(), "Node renamed", output)
@ -382,8 +371,6 @@ var deleteNodeCmd = &cobra.Command{
fmt.Sprintf("Error converting ID to integer: %s", err), fmt.Sprintf("Error converting ID to integer: %s", err),
output, output,
) )
return
} }
ctx, client, conn, cancel := newHeadscaleCLIWithConfig() ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
@ -401,8 +388,6 @@ var deleteNodeCmd = &cobra.Command{
"Error getting node node: "+status.Convert(err).Message(), "Error getting node node: "+status.Convert(err).Message(),
output, output,
) )
return
} }
deleteRequest := &v1.DeleteNodeRequest{ deleteRequest := &v1.DeleteNodeRequest{
@ -412,16 +397,10 @@ var deleteNodeCmd = &cobra.Command{
confirm := false confirm := false
force, _ := cmd.Flags().GetBool("force") force, _ := cmd.Flags().GetBool("force")
if !force { if !force {
prompt := &survey.Confirm{ confirm = util.YesNo(fmt.Sprintf(
Message: fmt.Sprintf( "Do you want to remove the node %s?",
"Do you want to remove the node %s?", getResponse.GetNode().GetName(),
getResponse.GetNode().GetName(), ))
),
}
err = survey.AskOne(prompt, &confirm)
if err != nil {
return
}
} }
if confirm || force { if confirm || force {
@ -437,8 +416,6 @@ var deleteNodeCmd = &cobra.Command{
"Error deleting node: "+status.Convert(err).Message(), "Error deleting node: "+status.Convert(err).Message(),
output, output,
) )
return
} }
SuccessOutput( SuccessOutput(
map[string]string{"Result": "Node deleted"}, map[string]string{"Result": "Node deleted"},
@ -465,8 +442,6 @@ var moveNodeCmd = &cobra.Command{
fmt.Sprintf("Error converting ID to integer: %s", err), fmt.Sprintf("Error converting ID to integer: %s", err),
output, output,
) )
return
} }
user, err := cmd.Flags().GetUint64("user") user, err := cmd.Flags().GetUint64("user")
@ -476,8 +451,6 @@ var moveNodeCmd = &cobra.Command{
fmt.Sprintf("Error getting user: %s", err), fmt.Sprintf("Error getting user: %s", err),
output, output,
) )
return
} }
ctx, client, conn, cancel := newHeadscaleCLIWithConfig() ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
@ -495,8 +468,6 @@ var moveNodeCmd = &cobra.Command{
"Error getting node: "+status.Convert(err).Message(), "Error getting node: "+status.Convert(err).Message(),
output, output,
) )
return
} }
moveRequest := &v1.MoveNodeRequest{ moveRequest := &v1.MoveNodeRequest{
@ -511,8 +482,6 @@ var moveNodeCmd = &cobra.Command{
"Error moving node: "+status.Convert(err).Message(), "Error moving node: "+status.Convert(err).Message(),
output, output,
) )
return
} }
SuccessOutput(moveResponse.GetNode(), "Node moved to another user", output) 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 it can be run to remove the IPs that should no longer
be assigned to nodes.`, be assigned to nodes.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
var err error
output, _ := cmd.Flags().GetString("output") output, _ := cmd.Flags().GetString("output")
confirm := false confirm := false
force, _ := cmd.Flags().GetBool("force") force, _ := cmd.Flags().GetBool("force")
if !force { if !force {
prompt := &survey.Confirm{ confirm = util.YesNo("Are you sure that you want to assign/remove IPs to/from nodes?")
Message: "Are you sure that you want to assign/remove IPs to/from nodes?",
}
err = survey.AskOne(prompt, &confirm)
if err != nil {
return
}
} }
if confirm || force { if confirm || force {
@ -563,8 +525,6 @@ be assigned to nodes.`,
"Error backfilling IPs: "+status.Convert(err).Message(), "Error backfilling IPs: "+status.Convert(err).Message(),
output, output,
) )
return
} }
SuccessOutput(changes, "Node IPs backfilled successfully", output) SuccessOutput(changes, "Node IPs backfilled successfully", output)
@ -763,8 +723,6 @@ var tagCmd = &cobra.Command{
fmt.Sprintf("Error converting ID to integer: %s", err), fmt.Sprintf("Error converting ID to integer: %s", err),
output, output,
) )
return
} }
tagsToSet, err := cmd.Flags().GetStringSlice("tags") tagsToSet, err := cmd.Flags().GetStringSlice("tags")
if err != nil { if err != nil {
@ -773,8 +731,6 @@ var tagCmd = &cobra.Command{
fmt.Sprintf("Error retrieving list of tags to add to node, %v", err), fmt.Sprintf("Error retrieving list of tags to add to node, %v", err),
output, output,
) )
return
} }
// Sending tags to node // Sending tags to node
@ -789,8 +745,6 @@ var tagCmd = &cobra.Command{
fmt.Sprintf("Error while sending tags to headscale: %s", err), fmt.Sprintf("Error while sending tags to headscale: %s", err),
output, output,
) )
return
} }
if resp != nil { if resp != nil {
@ -820,8 +774,6 @@ var approveRoutesCmd = &cobra.Command{
fmt.Sprintf("Error converting ID to integer: %s", err), fmt.Sprintf("Error converting ID to integer: %s", err),
output, output,
) )
return
} }
routes, err := cmd.Flags().GetStringSlice("routes") routes, err := cmd.Flags().GetStringSlice("routes")
if err != nil { if err != nil {
@ -830,8 +782,6 @@ var approveRoutesCmd = &cobra.Command{
fmt.Sprintf("Error retrieving list of routes to add to node, %v", err), fmt.Sprintf("Error retrieving list of routes to add to node, %v", err),
output, output,
) )
return
} }
// Sending routes to node // Sending routes to node
@ -846,8 +796,6 @@ var approveRoutesCmd = &cobra.Command{
fmt.Sprintf("Error while sending routes to headscale: %s", err), fmt.Sprintf("Error while sending routes to headscale: %s", err),
output, output,
) )
return
} }
if resp != nil { if resp != nil {

View File

@ -9,10 +9,10 @@ import (
"github.com/juanfont/headscale/hscontrol/db" "github.com/juanfont/headscale/hscontrol/db"
"github.com/juanfont/headscale/hscontrol/policy" "github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"tailscale.com/types/views" "tailscale.com/types/views"
"tailscale.com/util/prompt"
) )
const ( const (
@ -52,7 +52,13 @@ var getPolicy = &cobra.Command{
output, _ := cmd.Flags().GetString("output") output, _ := cmd.Flags().GetString("output")
var policy string var policy string
if bypass, _ := cmd.Flags().GetBool(bypassFlag); bypass { 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) ErrorOutput(nil, "Aborting command", output)
return return
} }
@ -60,7 +66,6 @@ var getPolicy = &cobra.Command{
cfg, err := types.LoadServerConfig() cfg, err := types.LoadServerConfig()
if err != nil { if err != nil {
ErrorOutput(err, fmt.Sprintf("Failed loading config: %s", err), output) ErrorOutput(err, fmt.Sprintf("Failed loading config: %s", err), output)
return
} }
d, err := db.NewHeadscaleDatabase( d, err := db.NewHeadscaleDatabase(
@ -70,13 +75,11 @@ var getPolicy = &cobra.Command{
) )
if err != nil { if err != nil {
ErrorOutput(err, fmt.Sprintf("Failed to open database: %s", err), output) ErrorOutput(err, fmt.Sprintf("Failed to open database: %s", err), output)
return
} }
pol, err := d.GetPolicy() pol, err := d.GetPolicy()
if err != nil { if err != nil {
ErrorOutput(err, fmt.Sprintf("Failed loading Policy from database: %s", err), output) ErrorOutput(err, fmt.Sprintf("Failed loading Policy from database: %s", err), output)
return
} }
policy = pol.Data policy = pol.Data
@ -124,8 +127,20 @@ var setPolicy = &cobra.Command{
ErrorOutput(err, fmt.Sprintf("Error reading the policy file: %s", err), output) 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 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) ErrorOutput(nil, "Aborting command", output)
return return
} }
@ -133,7 +148,6 @@ var setPolicy = &cobra.Command{
cfg, err := types.LoadServerConfig() cfg, err := types.LoadServerConfig()
if err != nil { if err != nil {
ErrorOutput(err, fmt.Sprintf("Failed loading config: %s", err), output) ErrorOutput(err, fmt.Sprintf("Failed loading config: %s", err), output)
return
} }
d, err := db.NewHeadscaleDatabase( d, err := db.NewHeadscaleDatabase(
@ -143,13 +157,11 @@ var setPolicy = &cobra.Command{
) )
if err != nil { if err != nil {
ErrorOutput(err, fmt.Sprintf("Failed to open database: %s", err), output) ErrorOutput(err, fmt.Sprintf("Failed to open database: %s", err), output)
return
} }
_, err = d.SetPolicy(string(policyBytes)) _, err = d.SetPolicy(string(policyBytes))
if err != nil { if err != nil {
ErrorOutput(err, fmt.Sprintf("Failed to set ACL Policy: %s", err), output) ErrorOutput(err, fmt.Sprintf("Failed to set ACL Policy: %s", err), output)
return
} }
} else { } else {
request := &v1.SetPolicyRequest{Policy: string(policyBytes)} request := &v1.SetPolicyRequest{Policy: string(policyBytes)}
@ -160,7 +172,6 @@ var setPolicy = &cobra.Command{
if _, err := client.SetPolicy(ctx, request); err != nil { if _, err := client.SetPolicy(ctx, request); err != nil {
ErrorOutput(err, fmt.Sprintf("Failed to set ACL Policy: %s", err), output) ErrorOutput(err, fmt.Sprintf("Failed to set ACL Policy: %s", err), output)
return
} }
} }

View File

@ -6,8 +6,8 @@ import (
"net/url" "net/url"
"strconv" "strconv"
survey "github.com/AlecAivazis/survey/v2"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1" v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/pterm/pterm" "github.com/pterm/pterm"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -161,16 +161,10 @@ var destroyUserCmd = &cobra.Command{
confirm := false confirm := false
force, _ := cmd.Flags().GetBool("force") force, _ := cmd.Flags().GetBool("force")
if !force { if !force {
prompt := &survey.Confirm{ confirm = util.YesNo(fmt.Sprintf(
Message: fmt.Sprintf( "Do you want to remove the user %q (%d) and any associated preauthkeys?",
"Do you want to remove the user %q (%d) and any associated preauthkeys?", user.GetName(), user.GetId(),
user.GetName(), user.GetId(), ))
),
}
err := survey.AskOne(prompt, &confirm)
if err != nil {
return
}
} }
if confirm || force { if confirm || force {

View File

@ -169,7 +169,14 @@ func ErrorOutput(errResult error, override string, outputFormat string) {
Error string `json:"error"` 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) os.Exit(1)
} }