cmd/tailscale/cli: add advertise command to advertise a node as service proxy to tailnet (#16620)

This commit adds a advertise subcommand for tailscale serve, that would declare the node
as a service proxy for a service. This command only adds the service to node's list of
advertised service, but doesn't modify the list of services currently advertised.

Fixes tailscale/corp#28016

Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com>
This commit is contained in:
KevinLiang10 2025-07-22 21:13:25 -04:00 committed by GitHub
parent 729d6532ff
commit 1ae6a97a73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 36 additions and 11 deletions

View File

@ -220,6 +220,16 @@ func newServeV2Command(e *serveEnv, subcmd serveMode) *ffcli.Command {
LongHelp: "Remove all handlers configured for the specified service.", LongHelp: "Remove all handlers configured for the specified service.",
Exec: e.runServeClear, Exec: e.runServeClear,
}, },
{
Name: "advertise",
ShortUsage: fmt.Sprintf("tailscale %s advertise <service>", info.Name),
ShortHelp: "Advertise this node as a service proxy to the tailnet",
LongHelp: "Advertise this node as a service proxy to the tailnet. This command is used\n" +
"to make the current node be considered as a service host for a service. This is\n" +
"useful to bring a service back after it has been drained. (i.e. after running \n" +
"`tailscale serve drain <service>`). This is not needed if you are using `tailscale serve` to initialize a service.",
Exec: e.runServeAdvertise,
},
}, },
} }
} }
@ -401,7 +411,7 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
return err return err
} }
if forService { if forService {
e.addServiceToPrefs(ctx, svcName.String()) e.addServiceToPrefs(ctx, svcName)
} }
target := "" target := ""
if len(args) > 0 { if len(args) > 0 {
@ -442,16 +452,16 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
} }
} }
func (e *serveEnv) addServiceToPrefs(ctx context.Context, serviceName string) error { func (e *serveEnv) addServiceToPrefs(ctx context.Context, serviceName tailcfg.ServiceName) error {
prefs, err := e.lc.GetPrefs(ctx) prefs, err := e.lc.GetPrefs(ctx)
if err != nil { if err != nil {
return fmt.Errorf("error getting prefs: %w", err) return fmt.Errorf("error getting prefs: %w", err)
} }
advertisedServices := prefs.AdvertiseServices advertisedServices := prefs.AdvertiseServices
if slices.Contains(advertisedServices, serviceName) { if slices.Contains(advertisedServices, serviceName.String()) {
return nil // already advertised return nil // already advertised
} }
advertisedServices = append(advertisedServices, serviceName) advertisedServices = append(advertisedServices, serviceName.String())
_, err = e.lc.EditPrefs(ctx, &ipn.MaskedPrefs{ _, err = e.lc.EditPrefs(ctx, &ipn.MaskedPrefs{
AdvertiseServicesSet: true, AdvertiseServicesSet: true,
Prefs: ipn.Prefs{ Prefs: ipn.Prefs{
@ -526,6 +536,21 @@ func (e *serveEnv) runServeClear(ctx context.Context, args []string) error {
return e.lc.SetServeConfig(ctx, sc) return e.lc.SetServeConfig(ctx, sc)
} }
func (e *serveEnv) runServeAdvertise(ctx context.Context, args []string) error {
if len(args) == 0 {
return fmt.Errorf("error: missing service name argument")
}
if len(args) != 1 {
fmt.Fprintf(Stderr, "error: invalid number of arguments\n\n")
return errHelp
}
svc := tailcfg.ServiceName(args[0])
if err := svc.Validate(); err != nil {
return fmt.Errorf("invalid service name: %w", err)
}
return e.addServiceToPrefs(ctx, svc)
}
const backgroundExistsMsg = "background configuration already exists, use `tailscale %s --%s=%d off` to remove the existing configuration" const backgroundExistsMsg = "background configuration already exists, use `tailscale %s --%s=%d off` to remove the existing configuration"
// validateConfig checks if the serve config is valid to serve the type wanted on the port. // validateConfig checks if the serve config is valid to serve the type wanted on the port.

View File

@ -1167,24 +1167,24 @@ func TestCleanURLPath(t *testing.T) {
func TestAddServiceToPrefs(t *testing.T) { func TestAddServiceToPrefs(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
dnsName string svcName tailcfg.ServiceName
startServices []string startServices []string
expected []string expected []string
}{ }{
{ {
name: "add service to empty prefs", name: "add service to empty prefs",
dnsName: "svc:foo", svcName: "svc:foo",
expected: []string{"svc:foo"}, expected: []string{"svc:foo"},
}, },
{ {
name: "add service to existing prefs", name: "add service to existing prefs",
dnsName: "svc:bar", svcName: "svc:bar",
startServices: []string{"svc:foo"}, startServices: []string{"svc:foo"},
expected: []string{"svc:foo", "svc:bar"}, expected: []string{"svc:foo", "svc:bar"},
}, },
{ {
name: "add existing service to prefs", name: "add existing service to prefs",
dnsName: "svc:foo", svcName: "svc:foo",
startServices: []string{"svc:foo"}, startServices: []string{"svc:foo"},
expected: []string{"svc:foo"}, expected: []string{"svc:foo"},
}, },
@ -1200,12 +1200,12 @@ func TestAddServiceToPrefs(t *testing.T) {
}, },
}) })
e := &serveEnv{lc: lc, bg: bgBoolFlag{true, false}} e := &serveEnv{lc: lc, bg: bgBoolFlag{true, false}}
err := e.addServiceToPrefs(ctx, tt.dnsName) err := e.addServiceToPrefs(ctx, tt.svcName)
if err != nil { if err != nil {
t.Fatalf("addServiceToPrefs(%q) returned unexpected error: %v", tt.dnsName, err) t.Fatalf("addServiceToPrefs(%q) returned unexpected error: %v", tt.svcName, err)
} }
if !slices.Equal(lc.prefs.AdvertiseServices, tt.expected) { if !slices.Equal(lc.prefs.AdvertiseServices, tt.expected) {
t.Errorf("addServiceToPrefs(%q) = %v, want %v", tt.dnsName, lc.prefs.AdvertiseServices, tt.expected) t.Errorf("addServiceToPrefs(%q) = %v, want %v", tt.svcName, lc.prefs.AdvertiseServices, tt.expected)
} }
}) })
} }