mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-16 03:31:39 +00:00
cmd/tailscale: make the new 'up' errors prettier and more helpful
Fixes #1746 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
744de615f1
commit
5ecc7c7200
@ -79,7 +79,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
|
|||||||
WantRunningSet: true,
|
WantRunningSet: true,
|
||||||
CorpDNSSet: 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",
|
name: "hostname_changing_explicitly",
|
||||||
@ -163,7 +163,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ControlURLSet: true,
|
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",
|
name: "implicit_operator_matches_shell_user",
|
||||||
@ -201,7 +201,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
|
|||||||
},
|
},
|
||||||
AdvertiseRoutesSet: true,
|
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",
|
name: "advertised_routes_exit_node_removed",
|
||||||
@ -270,7 +270,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
|
|||||||
},
|
},
|
||||||
AdvertiseRoutesSet: true,
|
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
|
name: "exit_node_clearing", // Issue 1777
|
||||||
@ -288,6 +288,66 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: "",
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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 {
|
if err := checkForAccidentalSettingReverts(tt.flagSet, tt.curPrefs, tt.mp, tt.curUser); err != nil {
|
||||||
got = err.Error()
|
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)
|
t.Errorf("unexpected result\n got: %s\nwant: %s\n", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -17,7 +17,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-multierror/multierror"
|
shellquote "github.com/kballard/go-shellquote"
|
||||||
"github.com/peterbourgon/ff/v2/ffcli"
|
"github.com/peterbourgon/ff/v2/ffcli"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/client/tailscale"
|
"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
|
// checkForAccidentalSettingReverts checks for people running
|
||||||
// "tailscale up" with a subset of the flags they originally ran it
|
// "tailscale up" with a subset of the flags they originally ran it
|
||||||
// with.
|
// with.
|
||||||
@ -541,8 +547,9 @@ func checkForAccidentalSettingReverts(flagSet map[string]bool, curPrefs *ipn.Pre
|
|||||||
ev := reflect.ValueOf(curWithExplicitEdits).Elem()
|
ev := reflect.ValueOf(curWithExplicitEdits).Elem()
|
||||||
// Implicit values (what we'd get if we replaced everything with flag defaults):
|
// Implicit values (what we'd get if we replaced everything with flag defaults):
|
||||||
iv := reflect.ValueOf(&mp.Prefs).Elem()
|
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++ {
|
for i := 0; i < prefType.NumField(); i++ {
|
||||||
prefName := prefType.Field(i).Name
|
prefName := prefType.Field(i).Name
|
||||||
if prefName == "Persist" {
|
if prefName == "Persist" {
|
||||||
@ -558,10 +565,11 @@ func checkForAccidentalSettingReverts(flagSet map[string]bool, curPrefs *ipn.Pre
|
|||||||
hasExitNodeRoutes(curPrefs.AdvertiseRoutes) &&
|
hasExitNodeRoutes(curPrefs.AdvertiseRoutes) &&
|
||||||
!hasExitNodeRoutes(curWithExplicitEdits.AdvertiseRoutes) &&
|
!hasExitNodeRoutes(curWithExplicitEdits.AdvertiseRoutes) &&
|
||||||
!flagSet["advertise-exit-node"] {
|
!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] {
|
if hasFlag && flagSet[flagName] {
|
||||||
|
flagExplicitValue[flagName] = ev.Field(i).Interface()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Get explicit value and implicit value
|
// Get explicit value and implicit value
|
||||||
@ -585,28 +593,68 @@ func checkForAccidentalSettingReverts(flagSet map[string]bool, curPrefs *ipn.Pre
|
|||||||
}
|
}
|
||||||
switch flagName {
|
switch flagName {
|
||||||
case "":
|
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":
|
case "exit-node":
|
||||||
if !didExitNodeErr {
|
if prefName == "ExitNodeIP" {
|
||||||
didExitNodeErr = true
|
missing = append(missing, fmtFlagValueArg("exit-node", fmtSettingVal(exi)))
|
||||||
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"))
|
|
||||||
}
|
}
|
||||||
default:
|
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",
|
missing = append(missing, fmtFlagValueArg(flagName, fmtSettingVal(exi)))
|
||||||
flagName, fmtSettingVal(imi), fmtSettingVal(exi)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return multierror.New(errs)
|
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)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
func fmtSettingVal(v interface{}) string {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
return strconv.FormatBool(v)
|
return strconv.FormatBool(v)
|
||||||
case string, preftype.NetfilterMode:
|
case string:
|
||||||
return fmt.Sprintf("%q", v)
|
return v
|
||||||
|
case preftype.NetfilterMode:
|
||||||
|
return v.String()
|
||||||
case []string:
|
case []string:
|
||||||
return strings.Join(v, ",")
|
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)
|
return fmt.Sprint(v)
|
||||||
}
|
}
|
||||||
|
@ -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 from github.com/alexbrainman/sspi/negotiate
|
||||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
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 from github.com/peterbourgon/ff/v2/ffcli
|
||||||
github.com/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli
|
github.com/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli
|
||||||
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
|
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
|
||||||
|
1
go.mod
1
go.mod
@ -15,6 +15,7 @@ require (
|
|||||||
github.com/google/go-cmp v0.5.4
|
github.com/google/go-cmp v0.5.4
|
||||||
github.com/goreleaser/nfpm v1.1.10
|
github.com/goreleaser/nfpm v1.1.10
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b
|
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/klauspost/compress v1.10.10
|
||||||
github.com/kr/pty v1.1.8
|
github.com/kr/pty v1.1.8
|
||||||
github.com/mdlayher/netlink v1.3.2
|
github.com/mdlayher/netlink v1.3.2
|
||||||
|
2
go.sum
2
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-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 h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
|
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/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 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
|
||||||
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
|
Loading…
x
Reference in New Issue
Block a user