diff --git a/tool/teleport/common/debug.go b/tool/teleport/common/debug.go index 2d1d8d59a11e9..801e920e1982e 100644 --- a/tool/teleport/common/debug.go +++ b/tool/teleport/common/debug.go @@ -44,6 +44,8 @@ type DebugClient interface { GetLogLevel(context.Context) (string, error) // CollectProfile collects a pprof profile. CollectProfile(context.Context, string, int) ([]byte, error) + // GetReadiness checks if the instance is ready to serve requests. + GetReadiness(context.Context) (debugclient.Readiness, error) SocketPath() string } @@ -166,6 +168,34 @@ func collectProfiles(ctx context.Context, clt DebugClient, buf io.Writer, rawPro return trace.NewAggregate(tw.Close(), gw.Close()) } +// onReadyz checks if the instance is ready to serve requests. +func onReadyz(ctx context.Context, configPath string) error { + clt, dataDir, err := newDebugClient(configPath) + if err != nil { + return trace.Wrap(err) + } + + if err := readyz(ctx, clt); err != nil { + return convertToReadableErr(err, dataDir, clt.SocketPath()) + } + + return nil +} + +func readyz(ctx context.Context, clt DebugClient) error { + readiness, err := clt.GetReadiness(ctx) + if err != nil { + return trace.Wrap(err) + } + + if !readiness.Ready { + return trace.Errorf("not ready (PID:%d): %s", readiness.PID, readiness.Status) + } + + fmt.Printf("ready (PID:%d)\n", readiness.PID) + return nil +} + // newDebugClient initializes the debug client based on the Teleport // configuration. It also returns the data dir and socket path used. func newDebugClient(configPath string) (DebugClient, string, error) { diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index 9d30268084e16..11c42dfaca1b6 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -617,6 +617,7 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con collectProfilesCmd.Alias(collectProfileUsageExamples) // We're using "alias" section to display usage examples. collectProfilesCmd.Arg("PROFILES", fmt.Sprintf("Comma-separated profile names to be exported. Supported profiles: %s. Default: %s", strings.Join(slices.Collect(maps.Keys(debugclient.SupportedProfiles)), ","), strings.Join(defaultCollectProfiles, ","))).StringVar(&ccf.Profiles) collectProfilesCmd.Flag("seconds", "For CPU and trace profiles, profile for the given duration (if set to 0, it returns a profile snapshot). For other profiles, return a delta profile. Default: 0").Short('s').Default("0").IntVar(&ccf.ProfileSeconds) + readyzCmd := debugCmd.Command("readyz", "Checks if the instance is ready to serve requests.") selinuxCmd := app.Command("selinux-ssh", "Commands related to SSH SELinux module.").Hidden() selinuxCmd.Flag("config", fmt.Sprintf("Path to a configuration file [%v].", defaults.ConfigFilePath)).Short('c').ExistingFileVar(&ccf.ConfigFile) @@ -810,6 +811,8 @@ Examples: err = onGetLogLevel(ccf.ConfigFile) case collectProfilesCmd.FullCommand(): err = onCollectProfiles(ccf.ConfigFile, ccf.Profiles, ccf.ProfileSeconds) + case readyzCmd.FullCommand(): + err = onReadyz(ctx, ccf.ConfigFile) case moduleSourceCmd.FullCommand(): if runtime.GOOS != "linux" { break