mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-24 16:17:11 +08:00
[buddy] CLI reference: add env var config and frontmatter (#62147)
* CLI reference generator: load default env variables from a YAML file * - Add the 'sidebar_label' and 'tags' frontmatter fields - Adjust the template and anyEnvVarsForCmd to prevent listing empty environment variable / flag lists * Clean up the CLI doc generator Remove unnecessary intermediate values: pass the unmarshaled YAML data structure directly from `loadDefaultEnvVars` to `UpdateAppUsageTemplate` without converting it from a `[][4]string`. * Improve CLI doc generator error handling Instead of silently exiting with no error if it is not possible to read the CLI doc generator config file, print an error message. If there is no CLI generator config file, skip manual environment variable additions and print a message. (Not using structured logging since this will only ever be run manually and in CI.) --------- Co-authored-by: Aatu Väisänen <aatu.vaisanen.ext@goteleport.com>
This commit is contained in:
co-authored by
Aatu Väisänen
parent
099826dd6c
commit
e0af6d71f7
@@ -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"
|
||||
@@ -54,6 +54,10 @@ $ {{.Name}}{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]
|
||||
---
|
||||
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|
|
||||
|
||||
+81
-7
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user