From e01618a7c4eb5113f17f644b9b2ed8204c23a99b Mon Sep 17 00:00:00 2001 From: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com> Date: Fri, 18 Jul 2025 13:46:03 -0400 Subject: [PATCH] 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> --- cmd/tailscale/cli/serve_v2.go | 36 ++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/cmd/tailscale/cli/serve_v2.go b/cmd/tailscale/cli/serve_v2.go index 6fa1a1b08..8832a232d 100644 --- a/cmd/tailscale/cli/serve_v2.go +++ b/cmd/tailscale/cli/serve_v2.go @@ -213,6 +213,13 @@ func newServeV2Command(e *serveEnv, subcmd serveMode) *ffcli.Command { " should be a service name (e.g., svc:my-service).", Exec: e.runServeDrain, }, + { + Name: "clear", + ShortUsage: fmt.Sprintf("tailscale %s clear ", 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.