Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions lib/utils/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,18 @@ func InitCLIParser(appName, appHelp string) (app *kingpin.Application) {
return app.UsageTemplate(createUsageTemplate())
}

// InitHiddenCLIParser initializes a `kingpin.Application` that does not terminate the application
// or write any usage information to os.Stdout. Can be used in scenarios where multiple `kingpin.Application`
// instances are needed without interfering with subsequent parsing. Usage output is completely suppressed,
// and the default global `--help` flag is ignored to prevent the application from exiting.
func InitHiddenCLIParser() (app *kingpin.Application) {
app = kingpin.New("", "")
app.UsageWriter(io.Discard)
app.Terminate(func(i int) {})

return app
}

// createUsageTemplate creates an usage template for kingpin applications.
func createUsageTemplate(opts ...func(*usageTemplateOptions)) string {
opt := &usageTemplateOptions{
Expand Down
9 changes: 4 additions & 5 deletions tool/tctl/common/tctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,11 @@ func TryRun(ctx context.Context, commands []CLICommand, args []string) error {
utils.InitLogger(utils.LoggingForCLI, slog.LevelWarn)

var ccf tctlcfg.GlobalCLIFlags
muApp := utils.InitCLIParser("tctl", GlobalHelpString)
muApp := utils.InitHiddenCLIParser()
muApp.Flag("auth-server",
fmt.Sprintf("Attempts to connect to specific auth/proxy address(es) instead of local auth [%v]", defaults.AuthConnectAddr().Addr)).
Envar(authAddrEnvVar).
StringsVar(&ccf.AuthServerAddr)

// We need to parse the arguments before executing managed updates to identify
// the profile name and the required version for the current cluster.
// All other commands and flags may change between versions, so full parsing
Expand All @@ -104,9 +103,6 @@ func TryRun(ctx context.Context, commands []CLICommand, args []string) error {
slog.WarnContext(ctx, "can't identify current profile", "error", err)
}

// app is the command line parser
app := utils.InitCLIParser("tctl", GlobalHelpString)

// cfg (teleport auth server configuration) is going to be shared by all
// commands
cfg := servicecfg.MakeDefaultConfig()
Expand All @@ -124,6 +120,9 @@ func TryRun(ctx context.Context, commands []CLICommand, args []string) error {
return trace.Wrap(err)
}

// app is the command line parser
app := utils.InitCLIParser("tctl", GlobalHelpString)

// Each command will add itself to the CLI parser.
for i := range commands {
commands[i].Initialize(app, &ccf, cfg)
Expand Down
3 changes: 1 addition & 2 deletions tool/tsh/common/tsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -821,10 +821,9 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error {
// All other commands and flags may change between versions, so full parsing
// should be performed only after managed updates are applied.
var proxyArg string
muApp := utils.InitCLIParser("tsh", "")
muApp := utils.InitHiddenCLIParser()
muApp.Flag("proxy", "Teleport proxy address").Envar(proxyEnvVar).Hidden().StringVar(&proxyArg)
muApp.Flag("check-update", "Check for availability of managed update.").Envar(toolsCheckUpdateEnvVar).Hidden().BoolVar(&cf.checkManagedUpdates)

if _, err := muApp.Parse(utils.FilterArguments(args, muApp.Model())); err != nil {
slog.WarnContext(ctx, "can't identify current profile", "error", err)
}
Expand Down
23 changes: 23 additions & 0 deletions tool/tsh/common/tsh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,29 @@ func TestNoEnvVars(t *testing.T) {
require.NoError(t, trace.NewAggregate(err, ctx.Err()))
}

// TestDefaultPrintUsage verifies that the main `kingpin.Application` parser has not been
// previously terminated, and that it correctly prints the usage message when using the
// global `--help` flag or the `help` command, and both are identical.
func TestDefaultPrintUsage(t *testing.T) {
t.Parallel()
testExecutable, err := os.Executable()
require.NoError(t, err)

ctx := context.Background()

cmd := exec.CommandContext(ctx, testExecutable, "version", "--help")
cmd.Env = []string{fmt.Sprintf("%s=1", tshBinMainTestEnv), "TELEPORT_TOOLS_VERSION=off"}
flagOutput, err := cmd.CombinedOutput()
require.NoError(t, err)
require.Contains(t, string(flagOutput), "Print the tsh client and Proxy server versions for the current context")

cmd = exec.CommandContext(ctx, testExecutable, "help", "version")
cmd.Env = []string{fmt.Sprintf("%s=1", tshBinMainTestEnv), "TELEPORT_TOOLS_VERSION=off"}
commandOutput, err := cmd.CombinedOutput()
require.NoError(t, err)
require.Equal(t, string(flagOutput), string(commandOutput))
}

func TestFailedLogin(t *testing.T) {
t.Parallel()
tmpHomePath := t.TempDir()
Expand Down
Loading