util/prompt: add a default and take default in non-interactive cases

The Tailscale CLI is the primary configuration interface and as such it
is used in scripts, container setups, and many other places that do not
have a terminal available and should not be made to respond to prompts.

The default is set to false where the "risky" API is being used by the
CLI and true otherwise, this means that the `--yes` flags are only
required under interactive runs and scripts do not need to be concerned
with prompts or extra flags.

Updates #19445

Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
James Tucker 2025-09-12 12:33:46 -07:00 committed by James Tucker
parent 442a3a779d
commit b9cdef18c0
5 changed files with 21 additions and 6 deletions

View File

@ -384,7 +384,7 @@ Removal of a signing key(s) without resigning nodes (--re-sign=false)
will cause any nodes signed by the the given key(s) to be locked out
of the Tailscale network. Proceed with caution.
`)
if !prompt.YesNo("Are you sure you want to remove the signing key(s)?") {
if !prompt.YesNo("Are you sure you want to remove the signing key(s)?", true) {
fmt.Printf("aborting removal of signing key(s)\n")
os.Exit(0)
}

View File

@ -66,7 +66,7 @@ func presentRiskToUser(riskType, riskMessage, acceptedRisks string) error {
outln(riskMessage)
printf("To skip this warning, use --accept-risk=%s\n", riskType)
if prompt.YesNo("Continue?") {
if prompt.YesNo("Continue?", false) {
return nil
}

View File

@ -1086,7 +1086,7 @@ func (e *serveEnv) removeWebServe(sc *ipn.ServeConfig, dnsName string, srvPort u
if len(mounts) > 1 {
msg := fmt.Sprintf("Are you sure you want to delete %d handlers under port %s?", len(mounts), portStr)
if !e.yes && !prompt.YesNo(msg) {
if !e.yes && !prompt.YesNo(msg, true) {
return nil
}
}

View File

@ -87,5 +87,5 @@ func confirmUpdate(ver string) bool {
}
msg := fmt.Sprintf("This will update Tailscale from %v to %v. Continue?", version.Short(), ver)
return prompt.YesNo(msg)
return prompt.YesNo(msg, true)
}

View File

@ -6,19 +6,34 @@ package prompt
import (
"fmt"
"os"
"strings"
"github.com/mattn/go-isatty"
)
// YesNo takes a question and prompts the user to answer the
// question with a yes or no. It appends a [y/n] to the message.
func YesNo(msg string) bool {
fmt.Print(msg + " [y/n] ")
//
// If there is no TTY on both Stdin and Stdout, assume that we're in a script
// and return the dflt result.
func YesNo(msg string, dflt bool) bool {
if !(isatty.IsTerminal(os.Stdin.Fd()) && isatty.IsTerminal(os.Stdout.Fd())) {
return dflt
}
if dflt {
fmt.Print(msg + " [Y/n] ")
} else {
fmt.Print(msg + " [y/N] ")
}
var resp string
fmt.Scanln(&resp)
resp = strings.ToLower(resp)
switch resp {
case "y", "yes", "sure":
return true
case "":
return dflt
}
return false
}