diff --git a/cmd/tailscale/cli/serve_v2.go b/cmd/tailscale/cli/serve_v2.go index 235804eb1..5bc996819 100644 --- a/cmd/tailscale/cli/serve_v2.go +++ b/cmd/tailscale/cli/serve_v2.go @@ -268,7 +268,7 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc { return err } err = e.setServe(sc, st, dnsName, srvType, srvPort, mount, args[0], funnel) - msg = e.messageForPort(sc, st, dnsName, srvPort) + msg = e.messageForPort(sc, st, dnsName, srvType, srvPort) } if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n\n", err) @@ -377,18 +377,27 @@ func (e *serveEnv) setServe(sc *ipn.ServeConfig, st *ipnstate.Status, dnsName st return nil } +var ( + msgFunnelAvailable = "Available on the internet:" + msgServeAvailable = "Available within your tailnet:" + msgRunningInBackground = "Serve started and running in the background." + msgDisableProxy = "To disable the proxy, run: tailscale %s --%s=%d off" + msgToExit = "Press Ctrl+C to exit." +) + // messageForPort returns a message for the given port based on the // serve config and status. -func (e *serveEnv) messageForPort(sc *ipn.ServeConfig, st *ipnstate.Status, dnsName string, srvPort uint16) string { +func (e *serveEnv) messageForPort(sc *ipn.ServeConfig, st *ipnstate.Status, dnsName string, srvType serveType, srvPort uint16) string { var output strings.Builder hp := ipn.HostPort(net.JoinHostPort(dnsName, strconv.Itoa(int(srvPort)))) if sc.AllowFunnel[hp] == true { - output.WriteString("Available on the internet:\n") + output.WriteString(msgFunnelAvailable) } else { - output.WriteString("Available within your tailnet:\n") + output.WriteString(msgServeAvailable) } + output.WriteString("\n") scheme := "https" if sc.IsServingHTTP(srvPort) { @@ -404,7 +413,7 @@ func (e *serveEnv) messageForPort(sc *ipn.ServeConfig, st *ipnstate.Status, dnsN output.WriteString(fmt.Sprintf("%s://%s%s\n\n", scheme, dnsName, portPart)) if !e.bg { - output.WriteString("Press Ctrl+C to exit.") + output.WriteString(msgToExit) return output.String() } @@ -452,8 +461,13 @@ func (e *serveEnv) messageForPort(sc *ipn.ServeConfig, st *ipnstate.Status, dnsN output.WriteString(fmt.Sprintf("|--> tcp://%s\n", h.TCPForward)) } - output.WriteString("\nServe started and running in the background.\n") - output.WriteString(fmt.Sprintf("To disable the proxy, run: tailscale %s off", infoMap[e.subcmd].Name)) + subCmd := infoMap[e.subcmd].Name + subCmdSentance := strings.ToUpper(string(subCmd[0])) + subCmd[1:] + + output.WriteString("\n") + output.WriteString(fmt.Sprintf(msgRunningInBackground, subCmdSentance)) + output.WriteString("\n") + output.WriteString(fmt.Sprintf(msgDisableProxy, subCmd, srvType.String(), srvPort)) return output.String() } diff --git a/cmd/tailscale/cli/serve_v2_test.go b/cmd/tailscale/cli/serve_v2_test.go index 6366fab94..25f1e6223 100644 --- a/cmd/tailscale/cli/serve_v2_test.go +++ b/cmd/tailscale/cli/serve_v2_test.go @@ -16,6 +16,7 @@ import ( "github.com/peterbourgon/ff/v3/ffcli" "tailscale.com/ipn" + "tailscale.com/ipn/ipnstate" "tailscale.com/types/logger" ) @@ -1029,6 +1030,105 @@ func TestCleanURLPath(t *testing.T) { } } +func TestMessageForPort(t *testing.T) { + tests := []struct { + name string + subcmd serveMode + serveConfig *ipn.ServeConfig + status *ipnstate.Status + dnsName string + srvType serveType + srvPort uint16 + expected string + }{ + { + name: "funnel-https", + subcmd: funnel, + serveConfig: &ipn.ServeConfig{ + TCP: map[uint16]*ipn.TCPPortHandler{ + 443: {HTTPS: true}, + }, + Web: map[ipn.HostPort]*ipn.WebServerConfig{ + "foo.test.ts.net:443": { + Handlers: map[string]*ipn.HTTPHandler{ + "/": {Proxy: "http://127.0.0.1:3000"}, + }, + }, + }, + AllowFunnel: map[ipn.HostPort]bool{ + "foo.test.ts.net:443": true, + }, + }, + status: &ipnstate.Status{}, + dnsName: "foo.test.ts.net", + srvType: serveTypeHTTPS, + srvPort: 443, + expected: strings.Join([]string{ + msgFunnelAvailable, + "https://foo.test.ts.net", + "", + "|-- / proxy http://127.0.0.1:3000", + "", + fmt.Sprintf(msgRunningInBackground, "Funnel"), + fmt.Sprintf(msgDisableProxy, "funnel", "https", 443), + }, "\n"), + }, + { + name: "serve-http", + subcmd: serve, + serveConfig: &ipn.ServeConfig{ + TCP: map[uint16]*ipn.TCPPortHandler{ + 443: {HTTP: true}, + }, + Web: map[ipn.HostPort]*ipn.WebServerConfig{ + "foo.test.ts.net:80": { + Handlers: map[string]*ipn.HTTPHandler{ + "/": {Proxy: "http://127.0.0.1:3000"}, + }, + }, + }, + }, + status: &ipnstate.Status{}, + dnsName: "foo.test.ts.net", + srvType: serveTypeHTTP, + srvPort: 80, + expected: strings.Join([]string{ + msgServeAvailable, + "https://foo.test.ts.net:80", + "", + "|-- / proxy http://127.0.0.1:3000", + "", + fmt.Sprintf(msgRunningInBackground, "Serve"), + fmt.Sprintf(msgDisableProxy, "serve", "http", 80), + }, "\n"), + }, + } + + for _, tt := range tests { + e := &serveEnv{bg: true, subcmd: tt.subcmd} + + t.Run(tt.name, func(t *testing.T) { + actual := e.messageForPort(tt.serveConfig, tt.status, tt.dnsName, tt.srvType, tt.srvPort) + + if actual == "" { + t.Errorf("Got empty message") + } + + if actual != tt.expected { + t.Errorf("Got: %q; expected: %q", actual, tt.expected) + } + }) + } +} + +func unindent(s string) string { + lines := strings.Split(s, "\n") + for i, line := range lines { + lines[i] = strings.TrimSpace(line) + } + return strings.Join(lines, "\n") +} + func TestIsLegacyInvocation(t *testing.T) { tests := []struct { subcmd serveMode