diff --git a/cmd/tailscale/cli/switch.go b/cmd/tailscale/cli/switch.go index af8b51326..0677da1b3 100644 --- a/cmd/tailscale/cli/switch.go +++ b/cmd/tailscale/cli/switch.go @@ -24,7 +24,7 @@ var switchCmd = &ffcli.Command{ LongHelp: `"tailscale switch" switches between logged in accounts. You can use the ID that's returned from 'tailnet switch -list' to pick which profile you want to switch to. Alternatively, you -can use the Tailnet or the account names to switch as well. +can use the Tailnet, account names, or display names to switch as well. This command is currently in alpha and may change in the future.`, @@ -46,7 +46,7 @@ func init() { seen := make(map[string]bool, 3*len(all)) wordfns := []func(prof ipn.LoginProfile) string{ func(prof ipn.LoginProfile) string { return string(prof.ID) }, - func(prof ipn.LoginProfile) string { return prof.NetworkProfile.DomainName }, + func(prof ipn.LoginProfile) string { return prof.NetworkProfile.DisplayNameOrDefault() }, func(prof ipn.LoginProfile) string { return prof.Name }, } @@ -57,7 +57,7 @@ func init() { continue } seen[word] = true - words = append(words, fmt.Sprintf("%s\tid: %s, tailnet: %s, account: %s", word, prof.ID, prof.NetworkProfile.DomainName, prof.Name)) + words = append(words, fmt.Sprintf("%s\tid: %s, tailnet: %s, account: %s", word, prof.ID, prof.NetworkProfile.DisplayNameOrDefault(), prof.Name)) } } return words, ffcomplete.ShellCompDirectiveNoFileComp, nil @@ -86,7 +86,7 @@ func listProfiles(ctx context.Context) error { } printRow( string(prof.ID), - prof.NetworkProfile.DomainName, + prof.NetworkProfile.DisplayNameOrDefault(), name, ) } @@ -107,7 +107,7 @@ func switchProfile(ctx context.Context, args []string) error { os.Exit(1) } var profID ipn.ProfileID - // Allow matching by ID, Tailnet, or Account + // Allow matching by ID, Tailnet, Account, or Display Name // in that order. for _, p := range all { if p.ID == ipn.ProfileID(args[0]) { @@ -131,6 +131,14 @@ func switchProfile(ctx context.Context, args []string) error { } } } + if profID == "" { + for _, p := range all { + if p.NetworkProfile.DisplayName == args[0] { + profID = p.ID + break + } + } + } if profID == "" { errf("No profile named %q\n", args[0]) os.Exit(1) diff --git a/ipn/prefs.go b/ipn/prefs.go index 7c3c50f73..1efb5d0fe 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -5,6 +5,7 @@ package ipn import ( "bytes" + "cmp" "encoding/json" "errors" "fmt" @@ -1001,6 +1002,13 @@ func (n NetworkProfile) RequiresBackfill() bool { return n == NetworkProfile{} } +// DisplayNameOrDefault will always return a non-empty string. +// If there is a defined display name, it will return that. +// If they did not it will default to their domain name. +func (n NetworkProfile) DisplayNameOrDefault() string { + return cmp.Or(n.DisplayName, n.DomainName) +} + // LoginProfile represents a single login profile as managed // by the ProfileManager. type LoginProfile struct {