cmd/tailscale/cli: Add clear subcommand for serve services (#16509)

* cmd/tailscale/cli: add clear subcommand for serve services

This commit adds a clear subcommand for serve command, to remove all config for a passed service.
This is a short cut for user to remove services after they drain a service. As an indipendent command
it would avoid accidently remove a service on typo.

Updates tailscale/corp#22954

Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com>

* update regarding comments

Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com>

* log when clearing a non-existing service but not error

Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com>

---------

Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com>
This commit is contained in:
KevinLiang10 2025-07-18 13:46:03 -04:00 committed by GitHub
parent 6c206fab58
commit e01618a7c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -213,6 +213,13 @@ func newServeV2Command(e *serveEnv, subcmd serveMode) *ffcli.Command {
"<service> should be a service name (e.g., svc:my-service).",
Exec: e.runServeDrain,
},
{
Name: "clear",
ShortUsage: fmt.Sprintf("tailscale %s clear <service>", info.Name),
ShortHelp: "Remove all config for a service",
LongHelp: "Remove all handlers configured for the specified service.",
Exec: e.runServeClear,
},
},
}
}
@ -486,11 +493,38 @@ func (e *serveEnv) runServeDrain(ctx context.Context, args []string) error {
svc := args[0]
svcName := tailcfg.ServiceName(svc)
if err := svcName.Validate(); err != nil {
return fmt.Errorf("invalid service name: %s", err)
return fmt.Errorf("invalid service name: %w", err)
}
return e.removeServiceFromPrefs(ctx, svcName)
}
func (e *serveEnv) runServeClear(ctx context.Context, args []string) error {
if len(args) == 0 {
return errHelp
}
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)
}
sc, err := e.lc.GetServeConfig(ctx)
if err != nil {
return fmt.Errorf("error getting serve config: %w", err)
}
if _, ok := sc.Services[svc]; !ok {
log.Printf("service %s not found in serve config, nothing to clear", svc)
return nil
}
delete(sc.Services, svc)
if err := e.removeServiceFromPrefs(ctx, svc); err != nil {
return fmt.Errorf("error removing service %s from prefs: %w", svc, err)
}
return e.lc.SetServeConfig(ctx, sc)
}
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.