summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2021-04-26 14:39:49 -0700
committerBrad Fitzpatrick <bradfitz@tailscale.com>2021-04-26 20:44:30 -0700
commit47f10f80925df83602045520f13799000cd22ecf (patch)
tree5c98f41c21c223aa1f2bc66f7907e0304b433d46
parentffe6c8e3356c5170ee352c6c38006047d77bf690 (diff)
downloadtailscale-bradfitz/cli_pretty.tar.xz
tailscale-bradfitz/cli_pretty.zip
cmd/tailscale: make the new 'up' errors prettier and more helpfulbradfitz/cli_pretty
Fixes #1746 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
-rw-r--r--cmd/tailscale/cli/cli_test.go70
-rw-r--r--cmd/tailscale/cli/up.go74
-rw-r--r--cmd/tailscale/depaware.txt2
-rw-r--r--go.mod1
-rw-r--r--go.sum2
5 files changed, 130 insertions, 19 deletions
diff --git a/cmd/tailscale/cli/cli_test.go b/cmd/tailscale/cli/cli_test.go
index 70b163824..fbfa4cffc 100644
--- a/cmd/tailscale/cli/cli_test.go
+++ b/cmd/tailscale/cli/cli_test.go
@@ -79,7 +79,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
WantRunningSet: true,
CorpDNSSet: true,
},
- want: `'tailscale up' without --reset requires all preferences with changing values to be explicitly mentioned; --hostname is not specified but its default value of "" differs from current value "foo"`,
+ want: accidentalUpPrefix + " --accept-dns --hostname=foo",
},
{
name: "hostname_changing_explicitly",
@@ -163,7 +163,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
},
ControlURLSet: true,
},
- want: `'tailscale up' without --reset requires all preferences with changing values to be explicitly mentioned; --operator is not specified but its default value of "" differs from current value "alice"`,
+ want: accidentalUpPrefix + " --hostname= --operator=alice",
},
{
name: "implicit_operator_matches_shell_user",
@@ -201,7 +201,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
},
AdvertiseRoutesSet: true,
},
- want: "'tailscale up' without --reset requires all preferences with changing values to be explicitly mentioned; --advertise-exit-node flag not mentioned but currently advertised routes are an exit node",
+ want: accidentalUpPrefix + " --advertise-routes=10.0.42.0/24 --advertise-exit-node",
},
{
name: "advertised_routes_exit_node_removed",
@@ -270,7 +270,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
},
AdvertiseRoutesSet: true,
},
- want: "'tailscale up' without --reset requires all preferences with changing values to be explicitly mentioned; --advertise-exit-node flag not mentioned but currently advertised routes are an exit node",
+ want: accidentalUpPrefix + " --advertise-routes=11.1.43.0/24,0.0.0.0/0 --advertise-exit-node",
},
{
name: "exit_node_clearing", // Issue 1777
@@ -288,6 +288,66 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
},
want: "",
},
+ {
+ name: "remove_all_implicit",
+ flagSet: f("force-reauth"),
+ curPrefs: &ipn.Prefs{
+ WantRunning: true,
+ ControlURL: ipn.DefaultControlURL,
+ RouteAll: true,
+ AllowSingleHosts: false,
+ ExitNodeIP: netaddr.MustParseIP("100.64.5.6"),
+ CorpDNS: true,
+ ShieldsUp: true,
+ AdvertiseTags: []string{"tag:foo", "tag:bar"},
+ Hostname: "myhostname",
+ ForceDaemon: true,
+ AdvertiseRoutes: []netaddr.IPPrefix{
+ netaddr.MustParseIPPrefix("10.0.0.0/16"),
+ },
+ NetfilterMode: preftype.NetfilterNoDivert,
+ OperatorUser: "alice",
+ },
+ curUser: "eve",
+ mp: &ipn.MaskedPrefs{
+ Prefs: ipn.Prefs{
+ ControlURL: ipn.DefaultControlURL,
+ WantRunning: true,
+ },
+ },
+ want: accidentalUpPrefix + " --accept-routes --exit-node=100.64.5.6 --accept-dns --shields-up --advertise-tags=tag:foo,tag:bar --hostname=myhostname --unattended --advertise-routes=10.0.0.0/16 --netfilter-mode=nodivert --operator=alice",
+ },
+ {
+ name: "remove_all_implicit_except_hostname",
+ flagSet: f("hostname"),
+ curPrefs: &ipn.Prefs{
+ WantRunning: true,
+ ControlURL: ipn.DefaultControlURL,
+ RouteAll: true,
+ AllowSingleHosts: false,
+ ExitNodeIP: netaddr.MustParseIP("100.64.5.6"),
+ CorpDNS: true,
+ ShieldsUp: true,
+ AdvertiseTags: []string{"tag:foo", "tag:bar"},
+ Hostname: "myhostname",
+ ForceDaemon: true,
+ AdvertiseRoutes: []netaddr.IPPrefix{
+ netaddr.MustParseIPPrefix("10.0.0.0/16"),
+ },
+ NetfilterMode: preftype.NetfilterNoDivert,
+ OperatorUser: "alice",
+ },
+ curUser: "eve",
+ mp: &ipn.MaskedPrefs{
+ Prefs: ipn.Prefs{
+ ControlURL: ipn.DefaultControlURL,
+ WantRunning: true,
+ Hostname: "newhostname",
+ },
+ HostnameSet: true,
+ },
+ want: accidentalUpPrefix + " --hostname=newhostname --accept-routes --exit-node=100.64.5.6 --accept-dns --shields-up --advertise-tags=tag:foo,tag:bar --unattended --advertise-routes=10.0.0.0/16 --netfilter-mode=nodivert --operator=alice",
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -295,7 +355,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
if err := checkForAccidentalSettingReverts(tt.flagSet, tt.curPrefs, tt.mp, tt.curUser); err != nil {
got = err.Error()
}
- if got != tt.want {
+ if strings.TrimSpace(got) != tt.want {
t.Errorf("unexpected result\n got: %s\nwant: %s\n", got, tt.want)
}
})
diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go
index bdfde2d3c..c53ce0946 100644
--- a/cmd/tailscale/cli/up.go
+++ b/cmd/tailscale/cli/up.go
@@ -17,7 +17,7 @@ import (
"strings"
"sync"
- "github.com/go-multierror/multierror"
+ shellquote "github.com/kballard/go-shellquote"
"github.com/peterbourgon/ff/v2/ffcli"
"inet.af/netaddr"
"tailscale.com/client/tailscale"
@@ -508,6 +508,12 @@ func updateMaskedPrefsFromUpFlag(mp *ipn.MaskedPrefs, flagName string) {
}
}
+const accidentalUpPrefix = "Error: changing settings via 'tailscale up' requires mentioning all\n" +
+ "non-default flags. To proceed, either re-run your command with --reset or\n" +
+ "specify use the command below to explicitly mention the current value of\n" +
+ "all non-default settings:\n\n" +
+ "\ttailscale up"
+
// checkForAccidentalSettingReverts checks for people running
// "tailscale up" with a subset of the flags they originally ran it
// with.
@@ -541,8 +547,9 @@ func checkForAccidentalSettingReverts(flagSet map[string]bool, curPrefs *ipn.Pre
ev := reflect.ValueOf(curWithExplicitEdits).Elem()
// Implicit values (what we'd get if we replaced everything with flag defaults):
iv := reflect.ValueOf(&mp.Prefs).Elem()
- var errs []error
- var didExitNodeErr bool
+
+ var missing []string
+ flagExplicitValue := map[string]interface{}{} // e.g. "accept-dns" => true (from flagSet)
for i := 0; i < prefType.NumField(); i++ {
prefName := prefType.Field(i).Name
if prefName == "Persist" {
@@ -558,10 +565,11 @@ func checkForAccidentalSettingReverts(flagSet map[string]bool, curPrefs *ipn.Pre
hasExitNodeRoutes(curPrefs.AdvertiseRoutes) &&
!hasExitNodeRoutes(curWithExplicitEdits.AdvertiseRoutes) &&
!flagSet["advertise-exit-node"] {
- errs = append(errs, errors.New("'tailscale up' without --reset requires all preferences with changing values to be explicitly mentioned; --advertise-exit-node flag not mentioned but currently advertised routes are an exit node"))
+ missing = append(missing, "--advertise-exit-node")
}
if hasFlag && flagSet[flagName] {
+ flagExplicitValue[flagName] = ev.Field(i).Interface()
continue
}
// Get explicit value and implicit value
@@ -585,28 +593,68 @@ func checkForAccidentalSettingReverts(flagSet map[string]bool, curPrefs *ipn.Pre
}
switch flagName {
case "":
- errs = append(errs, fmt.Errorf("'tailscale up' without --reset requires all preferences with changing values to be explicitly mentioned; this command would change the value of flagless pref %q", prefName))
+ return fmt.Errorf("'tailscale up' without --reset requires all preferences with changing values to be explicitly mentioned; this command would change the value of flagless pref %q", prefName)
case "exit-node":
- if !didExitNodeErr {
- didExitNodeErr = true
- errs = append(errs, errors.New("'tailscale up' without --reset requires all preferences with changing values to be explicitly mentioned; --exit-node is not specified but an exit node is currently configured"))
+ if prefName == "ExitNodeIP" {
+ missing = append(missing, fmtFlagValueArg("exit-node", fmtSettingVal(exi)))
}
default:
- errs = append(errs, fmt.Errorf("'tailscale up' without --reset requires all preferences with changing values to be explicitly mentioned; --%s is not specified but its default value of %v differs from current value %v",
- flagName, fmtSettingVal(imi), fmtSettingVal(exi)))
+ missing = append(missing, fmtFlagValueArg(flagName, fmtSettingVal(exi)))
+ }
+ }
+ if len(missing) == 0 {
+ return nil
+ }
+ var sb strings.Builder
+ sb.WriteString(accidentalUpPrefix)
+
+ var flagSetSorted []string
+ for f := range flagSet {
+ flagSetSorted = append(flagSetSorted, f)
+ }
+ sort.Strings(flagSetSorted)
+ for _, flagName := range flagSetSorted {
+ if ev, ok := flagExplicitValue[flagName]; ok {
+ fmt.Fprintf(&sb, " %s", fmtFlagValueArg(flagName, fmtSettingVal(ev)))
}
}
- return multierror.New(errs)
+ for _, a := range missing {
+ fmt.Fprintf(&sb, " %s", a)
+ }
+ sb.WriteString("\n\n")
+ return errors.New(sb.String())
+}
+
+func fmtFlagValueArg(flagName, val string) string {
+ if val == "true" {
+ // TODO: check flagName's type to see if its Pref is of type bool
+ return "--" + flagName
+ }
+ if val == "" {
+ return "--" + flagName + "="
+ }
+ return fmt.Sprintf("--%s=%v", flagName, shellquote.Join(val))
}
func fmtSettingVal(v interface{}) string {
switch v := v.(type) {
case bool:
return strconv.FormatBool(v)
- case string, preftype.NetfilterMode:
- return fmt.Sprintf("%q", v)
+ case string:
+ return v
+ case preftype.NetfilterMode:
+ return v.String()
case []string:
return strings.Join(v, ",")
+ case []netaddr.IPPrefix:
+ var sb strings.Builder
+ for i, r := range v {
+ if i > 0 {
+ sb.WriteByte(',')
+ }
+ sb.WriteString(r.String())
+ }
+ return sb.String()
}
return fmt.Sprint(v)
}
diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt
index 2b2fa40ee..c95c3b495 100644
--- a/cmd/tailscale/depaware.txt
+++ b/cmd/tailscale/depaware.txt
@@ -2,7 +2,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
- github.com/go-multierror/multierror from tailscale.com/cmd/tailscale/cli
+ github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
github.com/peterbourgon/ff/v2 from github.com/peterbourgon/ff/v2/ffcli
github.com/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
diff --git a/go.mod b/go.mod
index 0c20cbe70..2653e203b 100644
--- a/go.mod
+++ b/go.mod
@@ -15,6 +15,7 @@ require (
github.com/google/go-cmp v0.5.4
github.com/goreleaser/nfpm v1.1.10
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b
+ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.10.10
github.com/kr/pty v1.1.8
github.com/mdlayher/netlink v1.3.2
diff --git a/go.sum b/go.sum
index 5d8b47883..7718353be 100644
--- a/go.sum
+++ b/go.sum
@@ -61,6 +61,8 @@ github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=