diff --git a/lib/utils/docenvdefaults/tsh.yaml b/lib/utils/docenvdefaults/tsh.yaml new file mode 100644 index 00000000000..3f1793f68f2 --- /dev/null +++ b/lib/utils/docenvdefaults/tsh.yaml @@ -0,0 +1,74 @@ +TELEPORT_AUTH: + description: "Any defined authentication connector, including `passwordless` and `local` (i.e., no authentication connector)" + default: "none" + type: "string" + +TELEPORT_CLUSTER: + description: "Name of a Teleport root or leaf cluster" + default: "none" + type: "string" + +TELEPORT_LOGIN: + description: "Login name to be used by default on the remote host" + default: "none" + type: "string" + +TELEPORT_LOGIN_BIND_ADDR: + description: "Address in the form of host:port to bind to for login command webhook" + default: "none" + type: "string" + +TELEPORT_LOGIN_BROWSER: + description: "Set to `none` to stop the system default browser from opening for SSO logins. If the value is not `none`, `tsh` will open the system default browser." + default: "none" + type: "string" + +TELEPORT_PROXY: + description: "Address of the Teleport proxy server" + default: "none" + type: "string" + +TELEPORT_RELAY: + description: "Address of the Teleport relay server to use, \"none\" to disable the use of a relay, or \"default\" to use the default address specified by the control plane at login time. Defaults to port 443." + default: "none" + type: "string" + +TELEPORT_HEADLESS: + description: "Use headless authentication" + default: false + type: "bool" + +TELEPORT_HOME: + description: "Home location for tsh configuration and data" + default: "none" + type: "string" + +TELEPORT_USER: + description: "A Teleport user name" + default: "none" + type: "string" + +TELEPORT_ADD_KEYS_TO_AGENT: + description: "Specifies if the user certificate should be stored on the running SSH agent" + default: "auto" + type: "string" + +TELEPORT_USE_LOCAL_SSH_AGENT: + description: "Disable or enable local SSH agent integration" + default: "none" + type: "string" + +TELEPORT_GLOBAL_TSH_CONFIG: + description: "Override location of global `tsh` config file from default `/etc/tsh.yaml`" + default: "none" + type: "string" + +TELEPORT_MFA_MODE: + description: "Preferred mode for MFA and Passwordless assertions" + default: "auto" + type: "string" + +TELEPORT_IDENTITY_FILE: + description: "File path to identity file" + default: "none" + type: "string" \ No newline at end of file diff --git a/lib/utils/docs-usage.md.tmpl b/lib/utils/docs-usage.md.tmpl index ba7c8f6f68a..8bf1b6731aa 100644 --- a/lib/utils/docs-usage.md.tmpl +++ b/lib/utils/docs-usage.md.tmpl @@ -54,6 +54,10 @@ $ {{.Name}}{{template "FormatCommand" .}}{{if .Commands}} [ ...] --- title: {{.App.Name}} Reference description: Provides a comprehensive list of commands, arguments, and flags for {{.App.Name}}. +sidebar_label: {{.App.Name}} +tags: + - reference + - {{if eq .App.Name "tbot"}}mwi{{else}}platform-wide{{end}} --- This guide provides a comprehensive list of commands, arguments, and flags for @@ -62,7 +66,7 @@ This guide provides a comprehensive list of commands, arguments, and flags for {{ end -}} {{template "FormatUsage" .App -}} -{{if .Context.Flags -}} +{{if .Context.Flags|AnyVisibleFlags -}} Global flags: |Flag|Default|Description| diff --git a/lib/utils/usage_docs.go b/lib/utils/usage_docs.go index f0e34f28e8d..63580200f58 100644 --- a/lib/utils/usage_docs.go +++ b/lib/utils/usage_docs.go @@ -22,14 +22,17 @@ import ( "bytes" "cmp" _ "embed" + "errors" "fmt" "io" + "io/fs" "os" "path/filepath" "slices" "strings" "github.com/alecthomas/kingpin/v2" + "gopkg.in/yaml.v3" ) // formatThreeColMarkdownTable formats the provided row data into a three-column @@ -49,7 +52,9 @@ func flagsToRows(f []*kingpin.FlagModel) [][3]string { rows := [][3]string{} for _, flag := range f { - if flag.Hidden { + // Skip hidden flags and flags whose only purpose is to expose + // YAML-based default env variables. + if flag.Hidden || flag.Name == flag.Envar { continue } flagString := "" @@ -84,9 +89,9 @@ func anyVisibleFlags(f []*kingpin.FlagModel) bool { // provided exposes an environment variable for configuration. func anyEnvVarsForCmd(args []*kingpin.ArgModel, flags []*kingpin.FlagModel) bool { return slices.ContainsFunc(args, func(arg *kingpin.ArgModel) bool { - return arg.Envar != "" + return arg.Envar != "" && !arg.Hidden }) || slices.ContainsFunc(flags, func(flag *kingpin.FlagModel) bool { - return flag.Envar != "" + return flag.Envar != "" && !flag.Hidden }) } @@ -263,12 +268,81 @@ func updateAppUsageTemplate(r io.Reader, app *kingpin.Application) { app.UsageTemplate(buf.String()) } +// envVarDefault represents the structure of environment variable defaults in YAML files. +type envVarDefault struct { + Description string `yaml:"description"` + Default string `yaml:"default"` + Type string `yaml:"type"` +} + +// loadDefaultEnvVars loads possible default environment variables defined in a YAML file +// that matches the application name. +func loadDefaultEnvVars(appName string) (map[string]envVarDefault, error) { + pathname := filepath.Join("lib", "utils", "docenvdefaults", appName+".yaml") + data, err := os.ReadFile(pathname) + envDefaults := make(map[string]envVarDefault) + if errors.Is(err, fs.ErrNotExist) { + fmt.Printf("No doc generation config file at %v. Skipping manual environment variable additions.", pathname) + return envDefaults, nil + } + + if err != nil { + return nil, fmt.Errorf("unable to read CLI doc generation config file at %v: %w", pathname, err) + } + + if len(data) == 0 { + return nil, fmt.Errorf("read zero bytes from CLI doc generation config file at %v: %w", pathname, err) + } + + if err := yaml.Unmarshal(data, &envDefaults); err != nil { + return nil, fmt.Errorf("unable to parse YAML from %s: %w", pathname, err) + } + + for envVar, def := range envDefaults { + if def.Description == "" || def.Default == "" || def.Type == "" { + return nil, fmt.Errorf("invalid YAML structure in %s: entry %q is missing one of required fields 'description', 'default' or 'type'", pathname, envVar) + } + } + + return envDefaults, nil +} + // UpdateAppUsageTemplate updates the app usage template to print a reference -// guide for the CLI application. +// guide for the CLI application. Panics on errors since we need to keep the +// signature of UpdateAppUsageTemplate consistent with the one included without +// build tags, i.e., with no return value. func UpdateAppUsageTemplate(app *kingpin.Application, _ []string) { - // Panic when failing to open or read from the docs usage template since - // we need to keep the signature of UpdateAppUsageTemplate consistent - // with the one included without build tags, i.e., with no return value. + defaultEnvVars, err := loadDefaultEnvVars(app.Name) + if err != nil { + panic(err) + } + + existingEnvVars := make(map[string]struct{}) + for _, flag := range app.Model().Flags { + if flag.Envar != "" { + existingEnvVars[flag.Envar] = struct{}{} + } + } + + for envVarName, envVar := range defaultEnvVars { + // Check if the flag already exists in the app model to avoid + // duplicate flag errors. + if _, flagExists := existingEnvVars[envVarName]; flagExists { + continue + } + + // If the flag does not exist, create it with the default value + // and description from the YAML file. + flag := app.Flag(envVarName, envVar.Description). + Envar(envVarName). + Default(envVar.Default) + if envVar.Type == "bool" { + flag.Bool() + } else { + flag.String() + } + } + f, err := os.Open(docsUsageTemplatePath) if err != nil { panic(fmt.Sprintf("unable to open the docs usage template at %v: %v", docsUsageTemplatePath, err))