mirror of
https://github.com/tailscale/tailscale.git
synced 2025-03-25 18:51:01 +00:00
cmd/tailscale/cli: Add output for services to serve status
This commit rewrote a part of runServeStatus and added services info to status subcomand. Fixes tailscale/corp#22954 Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com>
This commit is contained in:
parent
793ca2fb43
commit
1103609548
@ -12,6 +12,7 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
@ -625,7 +626,7 @@ func (e *serveEnv) runServeStatus(ctx context.Context, args []string) error {
|
||||
return nil
|
||||
}
|
||||
printFunnelStatus(ctx)
|
||||
if sc == nil || (len(sc.TCP) == 0 && len(sc.Web) == 0 && len(sc.AllowFunnel) == 0) {
|
||||
if sc == nil || (len(sc.TCP) == 0 && len(sc.Web) == 0 && len(sc.AllowFunnel) == 0 && len(sc.Services) == 0) {
|
||||
printf("No serve config\n")
|
||||
return nil
|
||||
}
|
||||
@ -633,26 +634,67 @@ func (e *serveEnv) runServeStatus(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sc.IsTCPForwardingAny() {
|
||||
if err := printTCPStatusTree(ctx, sc, st); err != nil {
|
||||
return err
|
||||
}
|
||||
nodeDNSName := strings.TrimSuffix(st.Self.DNSName, ".")
|
||||
if sc.IsNodeTCPForwardingAny() {
|
||||
printTCPStatusTree(sc.TCP, nodeDNSName, st.TailscaleIPs, sc.AllowFunnel)
|
||||
printf("\n")
|
||||
}
|
||||
for hp := range sc.Web {
|
||||
err := e.printWebStatusTree(sc, hp)
|
||||
if sc.IsNodeServingWeb() {
|
||||
err := printWebStatusTree(sc, nodeDNSName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printf("\n")
|
||||
}
|
||||
if sc.HostingServices() {
|
||||
if err := printServicesStatusTree(e, sc, st); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
printFunnelWarning(sc)
|
||||
return nil
|
||||
}
|
||||
|
||||
func printTCPStatusTree(ctx context.Context, sc *ipn.ServeConfig, st *ipnstate.Status) error {
|
||||
dnsName := strings.TrimSuffix(st.Self.DNSName, ".")
|
||||
for p, h := range sc.TCP {
|
||||
func printServicesStatusTree(e *serveEnv, sc *ipn.ServeConfig, st *ipnstate.Status) error {
|
||||
print("Services:\n")
|
||||
if st.Self == nil || st.Self.CapMap == nil {
|
||||
return nil
|
||||
}
|
||||
var svcIPMap tailcfg.ServiceIPMappings
|
||||
serviceIPMaps, err := tailcfg.UnmarshalNodeCapJSON[tailcfg.ServiceIPMappings](st.Self.CapMap, tailcfg.NodeAttrServiceHost)
|
||||
if len(serviceIPMaps) != 1 || err != nil {
|
||||
if len(serviceIPMaps) != 0 {
|
||||
fmt.Fprintln(e.stderr(), "error: unable to get service IP mappings")
|
||||
}
|
||||
svcIPMap = nil
|
||||
print("No service have IPs assigned yet.\n")
|
||||
print("They're either partially configured, not advertised or not approved.\n")
|
||||
} else {
|
||||
svcIPMap = serviceIPMaps[0]
|
||||
}
|
||||
print("\n")
|
||||
|
||||
for svcName, svcCfg := range sc.Services {
|
||||
print(svcName.String() + ":\n")
|
||||
host := svcName.WithoutPrefix() + "." + st.CurrentTailnet.MagicDNSSuffix
|
||||
if ips, ok := svcIPMap[svcName]; ok {
|
||||
printTCPStatusTree(svcCfg.TCP, host, ips, nil)
|
||||
} else {
|
||||
if svcIPMap != nil {
|
||||
print("Service doesn't have IPs assigned yet.\n")
|
||||
print("It's either partially configured, not advertised or not approved.\n")
|
||||
}
|
||||
printTCPStatusTree(svcCfg.TCP, host, nil, nil)
|
||||
}
|
||||
printWebStatusTree(sc, svcName.String())
|
||||
print("\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printTCPStatusTree(tcpMap map[uint16]*ipn.TCPPortHandler, dnsName string, ips []netip.Addr, allowFunnel map[ipn.HostPort]bool) {
|
||||
for p, h := range tcpMap {
|
||||
if h.TCPForward == "" {
|
||||
continue
|
||||
}
|
||||
@ -662,72 +704,81 @@ func printTCPStatusTree(ctx context.Context, sc *ipn.ServeConfig, st *ipnstate.S
|
||||
tlsStatus = "TLS terminated"
|
||||
}
|
||||
fStatus := "tailnet only"
|
||||
if sc.AllowFunnel[hp] {
|
||||
if allowFunnel[hp] {
|
||||
fStatus = "Funnel on"
|
||||
}
|
||||
printf("|-- tcp://%s (%s, %s)\n", hp, tlsStatus, fStatus)
|
||||
for _, a := range st.TailscaleIPs {
|
||||
for _, a := range ips {
|
||||
ipp := net.JoinHostPort(a.String(), strconv.Itoa(int(p)))
|
||||
printf("|-- tcp://%s\n", ipp)
|
||||
}
|
||||
printf("|--> tcp://%s\n", h.TCPForward)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *serveEnv) printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) error {
|
||||
// No-op if no serve config
|
||||
if sc == nil {
|
||||
return nil
|
||||
}
|
||||
fStatus := "tailnet only"
|
||||
if sc.AllowFunnel[hp] {
|
||||
fStatus = "Funnel on"
|
||||
}
|
||||
host, portStr, _ := net.SplitHostPort(string(hp))
|
||||
|
||||
port, err := parseServePort(portStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid port %q: %w", portStr, err)
|
||||
}
|
||||
|
||||
scheme := "https"
|
||||
if sc.IsServingHTTP(port, host) {
|
||||
scheme = "http"
|
||||
}
|
||||
|
||||
portPart := ":" + portStr
|
||||
if scheme == "http" && portStr == "80" ||
|
||||
scheme == "https" && portStr == "443" {
|
||||
portPart = ""
|
||||
}
|
||||
if scheme == "http" {
|
||||
hostname, _, _ := strings.Cut(host, ".")
|
||||
printf("%s://%s%s (%s)\n", scheme, hostname, portPart, fStatus)
|
||||
}
|
||||
printf("%s://%s%s (%s)\n", scheme, host, portPart, fStatus)
|
||||
srvTypeAndDesc := func(h *ipn.HTTPHandler) (string, string) {
|
||||
switch {
|
||||
case h.Path != "":
|
||||
return "path", h.Path
|
||||
case h.Proxy != "":
|
||||
return "proxy", h.Proxy
|
||||
case h.Text != "":
|
||||
return "text", "\"" + elipticallyTruncate(h.Text, 20) + "\""
|
||||
func printWebStatusTree(sc *ipn.ServeConfig, dnsName string) error {
|
||||
webMap := sc.Web
|
||||
forService := ipn.IsServiceName(dnsName)
|
||||
if forService {
|
||||
svcName := tailcfg.ServiceName(dnsName)
|
||||
if svc, ok := sc.Services[svcName]; ok && svc != nil {
|
||||
webMap = svc.Web
|
||||
} else {
|
||||
// If the service is not found, just silently skip it.
|
||||
return nil
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
for hp := range webMap {
|
||||
fStatus := "tailnet only"
|
||||
if !forService && sc.AllowFunnel[hp] {
|
||||
fStatus = "Funnel on"
|
||||
}
|
||||
host, portStr, _ := net.SplitHostPort(string(hp))
|
||||
|
||||
mounts := slicesx.MapKeys(sc.Web[hp].Handlers)
|
||||
sort.Slice(mounts, func(i, j int) bool {
|
||||
return len(mounts[i]) < len(mounts[j])
|
||||
})
|
||||
maxLen := len(mounts[len(mounts)-1])
|
||||
port, err := parseServePort(portStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid port %q: %w", portStr, err)
|
||||
}
|
||||
|
||||
for _, m := range mounts {
|
||||
h := sc.Web[hp].Handlers[m]
|
||||
t, d := srvTypeAndDesc(h)
|
||||
printf("%s %s%s %-5s %s\n", "|--", m, strings.Repeat(" ", maxLen-len(m)), t, d)
|
||||
scheme := "https"
|
||||
if sc.IsServingHTTP(port, dnsName) {
|
||||
scheme = "http"
|
||||
}
|
||||
|
||||
portPart := ":" + portStr
|
||||
if scheme == "http" && portStr == "80" ||
|
||||
scheme == "https" && portStr == "443" {
|
||||
portPart = ""
|
||||
}
|
||||
if scheme == "http" {
|
||||
hostname, _, _ := strings.Cut(host, ".")
|
||||
printf("%s://%s%s (%s)\n", scheme, hostname, portPart, fStatus)
|
||||
}
|
||||
printf("%s://%s%s (%s)\n", scheme, host, portPart, fStatus)
|
||||
srvTypeAndDesc := func(h *ipn.HTTPHandler) (string, string) {
|
||||
switch {
|
||||
case h.Path != "":
|
||||
return "path", h.Path
|
||||
case h.Proxy != "":
|
||||
return "proxy", h.Proxy
|
||||
case h.Text != "":
|
||||
return "text", "\"" + elipticallyTruncate(h.Text, 20) + "\""
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
mounts := slicesx.MapKeys(webMap[hp].Handlers)
|
||||
sort.Slice(mounts, func(i, j int) bool {
|
||||
return len(mounts[i]) < len(mounts[j])
|
||||
})
|
||||
maxLen := len(mounts[len(mounts)-1])
|
||||
|
||||
for _, m := range mounts {
|
||||
h := webMap[hp].Handlers[m]
|
||||
t, d := srvTypeAndDesc(h)
|
||||
printf("%s %s%s %-5s %s\n", "|--", m, strings.Repeat(" ", maxLen-len(m)), t, d)
|
||||
}
|
||||
print("\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
27
ipn/serve.go
27
ipn/serve.go
@ -230,9 +230,9 @@ func (sc *ServeConfig) HasPathHandler() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsTCPForwardingAny reports whether ServeConfig is currently forwarding in
|
||||
// IsNodeTCPForwardingAny reports whether ServeConfig is currently forwarding in
|
||||
// TCPForward mode on any port. This is exclusive of Web/HTTPS serving.
|
||||
func (sc *ServeConfig) IsTCPForwardingAny() bool {
|
||||
func (sc *ServeConfig) IsNodeTCPForwardingAny() bool {
|
||||
if sc == nil || len(sc.TCP) == 0 {
|
||||
return false
|
||||
}
|
||||
@ -244,6 +244,29 @@ func (sc *ServeConfig) IsTCPForwardingAny() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (sc *ServeConfig) IsNodeServingWeb() bool {
|
||||
if sc == nil || len(sc.Web) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, webServerConfig := range sc.Web {
|
||||
if len(webServerConfig.Handlers) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (sc *ServeConfig) HostingServices() bool {
|
||||
if sc == nil || len(sc.Services) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, svc := range sc.Services {
|
||||
if len(svc.TCP) > 0 || len(svc.Web) > 0 || svc.Tun {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsServiceName reports whether if the given string is a valid service name.
|
||||
func IsServiceName(s string) bool {
|
||||
return tailcfg.ServiceName(s).Validate() == nil
|
||||
|
Loading…
x
Reference in New Issue
Block a user