From c7c52ee86af2c50c318fed556ebd1239f43dc247 Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Mon, 15 Sep 2025 10:45:30 -0500 Subject: [PATCH 01/26] one possible approach --- Makefile | 7 +- README.md | 22 ++++++ cmd/generate-options-docs/main.go | 36 +++++++++ .../configuration/options/docgen/docgen.go | 73 +++++++++++++++++++ 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 cmd/generate-options-docs/main.go create mode 100644 internal/configuration/options/docgen/docgen.go diff --git a/Makefile b/Makefile index d21b2c80..b6739db6 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endef # PHONY TARGETS # ==================================================================================== -.PHONY: help default install fmt vet test importfmtlint golangcilint devcheck devchecknotest +.PHONY: help default install fmt vet test importfmtlint golangcilint devcheck devchecknotest generate-options-docs .PHONY: starttestcontainer removetestcontainer spincontainer openlocalwebapi openapp protogen .PHONY: _check_env _check_ping_env _check_docker _run_pf_container _wait_for_pf _stop_pf_container @@ -83,6 +83,11 @@ golangcilint: ## Run golangci-lint for comprehensive code analysis $(GOLANGCI_LINT) run --timeout 5m ./... echo "✅ No linting issues found." +generate-options-docs: ## Generate markdown documentation for configuration options + @echo " > Docs: Generating options documentation markdown..." + $(GOCMD) run ./cmd/generate-options-docs $(OUTPUT) + echo "✅ Documentation generated. Use: make generate-options-docs OUTPUT='-o docs/options.md' to write to file." + protogen: ## Generate Go code from .proto files @echo " > Protogen: Generating gRPC code from proto files..." protoc --proto_path=./internal/proto --go_out=./internal --go-grpc_out=./internal ./internal/proto/*.proto diff --git a/README.md b/README.md index c0dad799..34d7f401 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,28 @@ and their purposes. See [Autocompletion Documentation](./docs/autocompletion/autocompletion.md) for information on loading autocompletion for select command flags. +### Generating Configuration Options Documentation + +To regenerate the markdown table of configuration options used in documentation, use the provided Makefile target instead of the previous ad-hoc test: + +```shell +make generate-options-docs +``` + +This prints the markdown to stdout. To write it directly to a file (for example updating docs/options.md): + +```shell +make generate-options-docs OUTPUT='-o docs/options.md' +``` + +The generator code lives in `cmd/generate-options-docs` and can be invoked manually as well: + +```shell +go run ./cmd/generate-options-docs -o /path/to/file.md +``` + +The logic was migrated from the skipped test `internal/configuration/options/options_test.go` (Test_outputOptionsMDInfo) to make doc generation an explicit developer action. + ## Commands Ping CLI commands have the following structure: diff --git a/cmd/generate-options-docs/main.go b/cmd/generate-options-docs/main.go new file mode 100644 index 00000000..05ac490e --- /dev/null +++ b/cmd/generate-options-docs/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/pingidentity/pingcli/internal/configuration/options/docgen" +) + +// A tiny standalone tool (invoked via `make generate-options-docs`) to output +// markdown documentation for all configuration options. +// +// Usage: +// +// go run ./cmd/generate-options-docs > options.md +// +// or via Makefile target: +// +// make generate-options-docs (writes to stdout unless -o is specified) +func main() { + outFile := flag.String("o", "", "If set, write markdown output to the provided file path instead of stdout") + flag.Parse() + + md := docgen.Markdown() + + if *outFile == "" { + fmt.Print(md) + return + } + + if err := os.WriteFile(*outFile, []byte(md), 0o644); err != nil { + fmt.Fprintf(os.Stderr, "failed to write file: %v\n", err) + os.Exit(1) + } +} diff --git a/internal/configuration/options/docgen/docgen.go b/internal/configuration/options/docgen/docgen.go new file mode 100644 index 00000000..75d9134f --- /dev/null +++ b/internal/configuration/options/docgen/docgen.go @@ -0,0 +1,73 @@ +package docgen + +// Utility to generate markdown documentation for configuration options. +// Extracted from the previous ad-hoc test (Test_outputOptionsMDInfo) so that +// documentation generation can be invoked via a Makefile target instead of a skipped test. + +import ( + "fmt" + "slices" + "strings" + + "github.com/pingidentity/pingcli/internal/configuration" + "github.com/pingidentity/pingcli/internal/configuration/options" +) + +// Markdown renders the options documentation markdown table sections. +// It ensures all options are initialized by calling configuration.InitAllOptions(). +func Markdown() string { + // Ensure options are initialized (idempotent call) + configuration.InitAllOptions() + + propertyCategoryInformation := make(map[string][]string) + + for _, option := range options.Options() { + if option.KoanfKey == "" || option.Flag == nil { + continue + } + + var flagInfo string + if option.Flag.Shorthand != "" { + flagInfo = fmt.Sprintf("--%s / -%s", option.CobraParamName, option.Flag.Shorthand) + } else { + flagInfo = fmt.Sprintf("--%s", option.CobraParamName) + } + + usageString := option.Flag.Usage + // Replace newlines with '

' so GitHub markdown renders intentional paragraph breaks. + usageString = strings.ReplaceAll(usageString, "\n", "

") + + category := "general" + if strings.Contains(option.KoanfKey, ".") { + category = strings.Split(option.KoanfKey, ".")[0] + } + + propertyCategoryInformation[category] = append( + propertyCategoryInformation[category], + fmt.Sprintf("| %s | %d | %s | %s |", option.KoanfKey, option.Type, flagInfo, usageString), + ) + } + + var outputBuilder strings.Builder + // Deterministic ordering of categories + cats := make([]string, 0, len(propertyCategoryInformation)) + for k := range propertyCategoryInformation { + cats = append(cats, k) + } + slices.Sort(cats) + + for _, category := range cats { + properties := propertyCategoryInformation[category] + slices.Sort(properties) + + outputBuilder.WriteString(fmt.Sprintf("#### %s Properties\n\n", category)) + outputBuilder.WriteString("| Config File Property | Type | Equivalent Parameter | Purpose |\n") + outputBuilder.WriteString("|---|---|---|---|\n") + for _, property := range properties { + outputBuilder.WriteString(property + "\n") + } + outputBuilder.WriteString("\n") + } + + return outputBuilder.String() +} From 70e736b9a6ffaad6bdb48de1c6c62cf972a40854 Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 17 Sep 2025 10:59:22 -0500 Subject: [PATCH 02/26] almost there --- Makefile | 31 ++- README.md | 60 ++++- cmd/generate-options-docs/main.go | 36 --- go.mod | 2 + go.sum | 2 + .../configuration/options/docgen/docgen.go | 153 +++++++++++ tools/generate-command-docs/main.go | 254 ++++++++++++++++++ tools/generate-options-docs/main.go | 42 +++ 8 files changed, 534 insertions(+), 46 deletions(-) delete mode 100644 cmd/generate-options-docs/main.go create mode 100644 tools/generate-command-docs/main.go create mode 100644 tools/generate-options-docs/main.go diff --git a/Makefile b/Makefile index b6739db6..75fd656f 100644 --- a/Makefile +++ b/Makefile @@ -40,9 +40,10 @@ endef # PHONY TARGETS # ==================================================================================== -.PHONY: help default install fmt vet test importfmtlint golangcilint devcheck devchecknotest generate-options-docs +.PHONY: help default install fmt vet test importfmtlint golangcilint devcheck devchecknotest .PHONY: starttestcontainer removetestcontainer spincontainer openlocalwebapi openapp protogen .PHONY: _check_env _check_ping_env _check_docker _run_pf_container _wait_for_pf _stop_pf_container +.PHONY: generate-options-docs generate-command-docs generate-all-docs # ==================================================================================== # USER-FACING COMMANDS @@ -83,10 +84,30 @@ golangcilint: ## Run golangci-lint for comprehensive code analysis $(GOLANGCI_LINT) run --timeout 5m ./... echo "✅ No linting issues found." -generate-options-docs: ## Generate markdown documentation for configuration options - @echo " > Docs: Generating options documentation markdown..." - $(GOCMD) run ./cmd/generate-options-docs $(OUTPUT) - echo "✅ Documentation generated. Use: make generate-options-docs OUTPUT='-o docs/options.md' to write to file." +generate-options-docs: ## Generate configuration options documentation (default: AsciiDoc into docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc) + @echo " > Docs: Generating options documentation..." + @if [ -z "$(OUTPUT)" ]; then \ + mkdir -p ./docs/dev-ux-portal-docs/general; \ + $(GOCMD) run ./tools/generate-options-docs -asciidoc -o ./docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc; \ + echo "✅ Documentation generated at docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc"; \ + else \ + $(GOCMD) run ./tools/generate-options-docs $(OUTPUT); \ + echo "✅ Documentation generated with custom OUTPUT $(OUTPUT)"; \ + fi + +generate-command-docs: ## Generate per-command AsciiDoc pages (and nav.adoc) into docs/dev-ux-portal-docs + @echo " > Docs: Generating command documentation..." + mkdir -p ./docs/dev-ux-portal-docs + $(GOCMD) run ./tools/generate-command-docs -o ./docs/dev-ux-portal-docs $(COMMAND_DOCS_ARGS) + echo "✅ Command docs generated in docs/dev-ux-portal-docs" + +generate-all-docs: ## Rebuild ALL docs from scratch (cleans doc directory, then generates options + command reference) + @echo " > Docs: Rebuilding all documentation (clean + regenerate)..." + rm -rf ./docs/dev-ux-portal-docs + mkdir -p ./docs/dev-ux-portal-docs/general + $(MAKE) generate-options-docs OUTPUT='-o docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc' + $(MAKE) generate-command-docs + @echo "✅ All documentation rebuilt." protogen: ## Generate Go code from .proto files @echo " > Protogen: Generating gRPC code from proto files..." diff --git a/README.md b/README.md index 34d7f401..9cb1f49d 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,7 @@ OR Use the following single-line PowerShell 7.4 command to install Ping CLI into '%LOCALAPPDATA%\Programs' directly. >**_NOTE:_** After installation, ensure that `%LOCALAPPDATA%\Programs` is included in your PATH environment variable. If it is not already present, add it so you can call `pingcli` directly in your terminal. + ```powershell $latestReleaseUrl = Invoke-WebRequest -Uri "https://github.com/pingidentity/pingcli/releases/latest" -MaximumRedirection 0 -ErrorAction Ignore -UseBasicParsing -SkipHttpErrorCheck; ` $RELEASE_VERSION = [System.IO.Path]::GetFileName($latestReleaseUrl.Headers.Location); ` @@ -218,25 +219,74 @@ See [Autocompletion Documentation](./docs/autocompletion/autocompletion.md) for ### Generating Configuration Options Documentation -To regenerate the markdown table of configuration options used in documentation, use the provided Makefile target instead of the previous ad-hoc test: +Use the Makefile target to generate documentation for all configuration options. By default it now writes AsciiDoc to `docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc` for portal ingestion: ```shell make generate-options-docs ``` -This prints the markdown to stdout. To write it directly to a file (for example updating docs/options.md): +Without arguments the Makefile target writes to the portal path above. You can override by providing OUTPUT arguments; without the Makefile (invoking the tool directly) omitting -o still prints to stdout. Provide an output file to write it: ```shell make generate-options-docs OUTPUT='-o docs/options.md' ``` -The generator code lives in `cmd/generate-options-docs` and can be invoked manually as well: +AsciiDoc is also supported (and is the default when using the Makefile target). The format is auto-detected from the file extension (`.adoc` / `.asciidoc`) or you can force it with `-asciidoc`: + +```shell +make generate-options-docs OUTPUT='-o docs/options.adoc' +``` + +Or run directly: + +```shell +go run ./tools/generate-options-docs -o docs/options.md +go run ./tools/generate-options-docs -o docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc +go run ./tools/generate-options-docs -asciidoc > docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc +``` + +The generated AsciiDoc mirrors the manual reference ordering (General, Service, Export, License, Request). The file `cli-configuration-settings-reference.generated.adoc` can be diffed against the manually curated `cli-configuration-settings-reference.adoc` to discover undocumented options. + +### Generating Command Reference Pages + +You can generate AsciiDoc pages for every command and subcommand. These pages include AsciiDoc attributes (:created-date:, :revdate:, :resourceid:) just below the title to match the formatting used elsewhere. + +In addition to the individual command pages, a hierarchical navigation file `nav.adoc` is always regenerated in `docs/dev-ux-portal-docs`. This file is intended for ingestion by the documentation portal (it provides the bullet + xref structure) and should not be edited manually—changes will be overwritten the next time the generator runs. + +Generate the full set into `docs/dev-ux-portal-docs`: ```shell -go run ./cmd/generate-options-docs -o /path/to/file.md +make generate-command-docs ``` -The logic was migrated from the skipped test `internal/configuration/options/options_test.go` (Test_outputOptionsMDInfo) to make doc generation an explicit developer action. +Clean up generated pages: + +```shell +make clean-command-docs +``` + +You can also invoke the generator directly (advanced use): + +```shell +go run ./tools/generate-command-docs -o ./docs/dev-ux-portal-docs +``` + +By default the date used in the headers is today; override with `-date "March 23, 2025"` if you need a stable revision date: + +### Generating All Documentation + +To force a clean rebuild of both the configuration options reference and the full command reference (including nav.adoc) in a single step (the target deletes `docs/dev-ux-portal-docs` first): + +```shell +make generate-all-docs +``` + +This target removes any existing `docs/dev-ux-portal-docs` directory, then runs `generate-options-docs` (writing the AsciiDoc configuration reference into `docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc`) followed by `generate-command-docs` (writing per-command pages plus `nav.adoc`). You can optionally inject a + + +```shell +go run ./tools/generate-command-docs -date "March 23, 2025" -o ./docs/dev-ux-portal-docs +``` ## Commands diff --git a/cmd/generate-options-docs/main.go b/cmd/generate-options-docs/main.go deleted file mode 100644 index 05ac490e..00000000 --- a/cmd/generate-options-docs/main.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "os" - - "github.com/pingidentity/pingcli/internal/configuration/options/docgen" -) - -// A tiny standalone tool (invoked via `make generate-options-docs`) to output -// markdown documentation for all configuration options. -// -// Usage: -// -// go run ./cmd/generate-options-docs > options.md -// -// or via Makefile target: -// -// make generate-options-docs (writes to stdout unless -o is specified) -func main() { - outFile := flag.String("o", "", "If set, write markdown output to the provided file path instead of stdout") - flag.Parse() - - md := docgen.Markdown() - - if *outFile == "" { - fmt.Print(md) - return - } - - if err := os.WriteFile(*outFile, []byte(md), 0o644); err != nil { - fmt.Fprintf(os.Stderr, "failed to write file: %v\n", err) - os.Exit(1) - } -} diff --git a/go.mod b/go.mod index ed2146d9..e9cebac2 100644 --- a/go.mod +++ b/go.mod @@ -76,6 +76,7 @@ require ( github.com/charmbracelet/x/term v0.2.1 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/ckaznocha/intrange v0.3.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/daixiang0/gci v0.13.6 // indirect github.com/dave/dst v0.27.3 // indirect @@ -181,6 +182,7 @@ require ( github.com/raeperd/recvcheck v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryancurrah/gomodguard v1.4.1 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect diff --git a/go.sum b/go.sum index d88dd941..d05939fa 100644 --- a/go.sum +++ b/go.sum @@ -147,6 +147,7 @@ github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nW github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= @@ -564,6 +565,7 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g= github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= diff --git a/internal/configuration/options/docgen/docgen.go b/internal/configuration/options/docgen/docgen.go index 75d9134f..469b20c4 100644 --- a/internal/configuration/options/docgen/docgen.go +++ b/internal/configuration/options/docgen/docgen.go @@ -6,8 +6,10 @@ package docgen import ( "fmt" + "path/filepath" "slices" "strings" + "time" "github.com/pingidentity/pingcli/internal/configuration" "github.com/pingidentity/pingcli/internal/configuration/options" @@ -71,3 +73,154 @@ func Markdown() string { return outputBuilder.String() } + +// ----------------------------- AsciiDoc Generation ----------------------------- + +// AsciiDoc generates a configuration reference in AsciiDoc format mirroring the +// structure of docs/cli-configuration-settings-reference.adoc. +// Sections (in order): +// +// General properties +// Ping Identity platform service properties +// Platform export properties +// License properties +// Custom request properties +// +// Additional categories can be appended over time. +func AsciiDoc() string { + configuration.InitAllOptions() + + // Collect options by root category + catMap := map[string][]options.Option{} + for _, opt := range options.Options() { + if opt.KoanfKey == "" { // skip if no key + continue + } + root := opt.KoanfKey + if strings.Contains(root, ".") { + root = strings.Split(root, ".")[0] + } + switch root { + case "service": + catMap["service"] = append(catMap["service"], opt) + case "export": + catMap["export"] = append(catMap["export"], opt) + case "license": + catMap["license"] = append(catMap["license"], opt) + case "request": + catMap["request"] = append(catMap["request"], opt) + default: + // Root (general) options are those without '.' or not matched above + if !strings.Contains(opt.KoanfKey, ".") { + catMap["general"] = append(catMap["general"], opt) + } + } + } + + // Ensure stable ordering inside each category by key + for k := range catMap { + slices.SortFunc(catMap[k], func(a, b options.Option) int { return strings.Compare(a.KoanfKey, b.KoanfKey) }) + } + + var b strings.Builder + // Header per specification + created := "March 23, 2025" // fixed created-date per request + revdate := time.Now().Format("January 2, 2006") + b.WriteString("= Configuration Settings Reference\n") + b.WriteString(fmt.Sprintf(":created-date: %s\n", created)) + b.WriteString(fmt.Sprintf(":revdate: %s\n", revdate)) + b.WriteString(":resourceid: pingcli_configuration_settings_reference\n\n") + b.WriteString("The following configuration settings can be applied when using Ping CLI.\n\n") + b.WriteString("The following configuration settings can be applied by using the xref:command_reference:pingcli_config_set.adoc[`config set` command] to persist the configuration value for a given **Configuration Key** in the Ping CLI configuration file.\n\n") + b.WriteString("The configuration file is created at `.pingcli/config.yaml` in the user's home directory.\n\n") + + // Ordered sections + ordered := []struct { + key, title string + }{ + {"general", "General Properties"}, + {"service", "Ping Identity platform service properties"}, + {"export", "Platform export properties"}, + {"license", "License properties"}, + {"request", "Custom request properties"}, + } + + for _, sec := range ordered { + opts := catMap[sec.key] + if len(opts) == 0 { + continue + } + b.WriteString("== " + sec.title + "\n\n") + b.WriteString("[cols=\"2,1,2,2\"]\n|===\n") + b.WriteString("|Configuration Key |Data Type |Equivalent Parameter |Purpose\n\n") + for _, opt := range opts { + key := normalizeAsciiDocKey(opt.KoanfKey) + dataType := asciiDocDataType(opt) + eqParam := asciiDocEquivalentParameter(opt) + purpose := sanitizeUsage(opt) + b.WriteString(fmt.Sprintf("| `%s` | %s | %s | %s\n", key, dataType, eqParam, purpose)) + } + b.WriteString("|===\n\n") + } + + return b.String() +} + +// Detect whether an output filename suggests AsciiDoc. +func ShouldOutputAsciiDoc(outPath string, explicit bool) bool { + if explicit { + return true + } + ext := strings.ToLower(filepath.Ext(outPath)) + return ext == ".adoc" || ext == ".asciidoc" +} + +func asciiDocEquivalentParameter(opt options.Option) string { + if opt.Flag == nil { + return "" + } + if opt.Flag.Shorthand != "" { + return fmt.Sprintf("`--%s` / `-%s`", opt.CobraParamName, opt.Flag.Shorthand) + } + return fmt.Sprintf("`--%s`", opt.CobraParamName) +} + +func asciiDocDataType(opt options.Option) string { + switch opt.Type { + case options.BOOL: + return "Boolean" + case options.STRING: + return "String" + case options.STRING_SLICE, options.EXPORT_SERVICES, options.HEADER: + return "String Array" + case options.UUID: + return "String (UUID Format)" + case options.EXPORT_FORMAT, options.OUTPUT_FORMAT, options.PINGFEDERATE_AUTH_TYPE, options.PINGONE_AUTH_TYPE, options.PINGONE_REGION_CODE, options.REQUEST_SERVICE, options.EXPORT_SERVICE_GROUP: + return "String (Enum)" + case options.INT: + return "Integer" + case options.LICENSE_PRODUCT, options.LICENSE_VERSION: + return "String (Enum)" + default: + return "String" + } +} + +func sanitizeUsage(opt options.Option) string { + if opt.Flag == nil { + return "" + } + usage := opt.Flag.Usage + usage = strings.ReplaceAll(usage, "

", " ") + usage = strings.ReplaceAll(usage, "\n", " ") + usage = strings.TrimSpace(usage) + return usage +} + +// Adjust key naming to mirror existing AsciiDoc (lowercase service names, fix PEM case etc.). +func normalizeAsciiDocKey(key string) string { + key = strings.ReplaceAll(key, "pingFederate", "pingfederate") + key = strings.ReplaceAll(key, "pingOne", "pingone") + key = strings.ReplaceAll(key, "PEMFiles", "PemFiles") + return key +} diff --git a/tools/generate-command-docs/main.go b/tools/generate-command-docs/main.go new file mode 100644 index 00000000..9ba1b100 --- /dev/null +++ b/tools/generate-command-docs/main.go @@ -0,0 +1,254 @@ +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/pingidentity/pingcli/cmd" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +func main() { + outDir := flag.String("o", "./docs", "Output directory for AsciiDoc command pages") + date := flag.String("date", time.Now().Format("January 2, 2006"), "Created/revision date used in headers (e.g., March 23, 2025)") + resourcePrefix := flag.String("resource-prefix", "pingcli_command_reference_", "Prefix for :resourceid:") + version := flag.String("version", "dev", "Version string for root command init") + commit := flag.String("commit", "dev", "Commit SHA for root command init") + flag.Parse() + + root := cmd.NewRootCommand(*version, *commit) + root.DisableAutoGenTag = true + + if err := os.MkdirAll(*outDir, 0o755); err != nil { + fail("create out dir", err) + } + + // One file per command path. + walkVisible(root, func(c *cobra.Command) { + base := strings.ReplaceAll(c.CommandPath(), " ", "_") + file := filepath.Join(*outDir, base+".adoc") + depth := depthOf(c) + content := renderSingle(c, depth, *date, *resourcePrefix) + if err := os.WriteFile(file, []byte(content), 0o644); err != nil { + fail("write file "+file, err) + } + }) + + // Always (re)generate navigation file for documentation portal ingestion. + navPath := filepath.Join(*outDir, "nav.adoc") + navContent := renderNav(root) + if err := os.WriteFile(navPath, []byte(navContent), 0o644); err != nil { + fail("write nav file", err) + } +} + +func renderSingle(c *cobra.Command, depth int, date, resourcePrefix string) string { + // Manual style: always use top-level title '=' regardless of hierarchy. + base := strings.ReplaceAll(c.CommandPath(), " ", "_") + b := &strings.Builder{} + fmt.Fprintf(b, "= %s\n", c.CommandPath()) + fmt.Fprintf(b, ":created-date: %s\n:revdate: %s\n:resourceid: %s%s\n\n", date, date, resourcePrefix, base) + + // Short description (first paragraph only) + if s := strings.TrimSpace(firstLine(c.Short, c.Long)); s != "" { + b.WriteString(s) + b.WriteString("\n\n") + } + + // Synopsis section: prefer full Long (without first line duplication) else Short. + b.WriteString("== Synopsis\n\n") + if long := strings.TrimSpace(c.Long); long != "" { + // Keep full long description as-is. + b.WriteString(long) + b.WriteString("\n\n") + } else if short := strings.TrimSpace(c.Short); short != "" { + b.WriteString(short + "\n\n") + } + // Usage block + b.WriteString("----\n") + b.WriteString(strings.TrimSpace(c.UseLine()) + "\n") + b.WriteString("----\n\n") + + // Examples section (if any) - preserve original indentation & spacing. + if rawEx := c.Example; strings.TrimSpace(rawEx) != "" { + b.WriteString("== Examples\n\n") + b.WriteString("----\n") + b.WriteString(rawEx) + if !strings.HasSuffix(rawEx, "\n") { + b.WriteString("\n") + } + b.WriteString("----\n\n") + } + + // Options (non-inherited) including help flag. + local := c.NonInheritedFlags() + inherited := c.InheritedFlags() + if local != nil && local.HasAvailableFlags() { + b.WriteString("== Options\n\n") + b.WriteString(formatFlagBlock(local, true, c)) + b.WriteString("\n") + } + if inherited != nil && inherited.HasAvailableFlags() { + b.WriteString("== Options inherited from parent commands\n\n") + b.WriteString(formatFlagBlock(inherited, false, c)) + b.WriteString("\n") + } + + // More information (link back to parent) if there is a parent (omit for root). + if p := c.Parent(); p != nil { + parentFile := strings.ReplaceAll(p.CommandPath(), " ", "_") + ".adoc" + b.WriteString("== More information\n\n") + fmt.Fprintf(b, "* xref:%s[]\t - %s\n", parentFile, firstLine(p.Short, p.Long)) + b.WriteString("\n") + } + + // Subcommands listing (retain for commands that have them; use manual style heading depth) + subs := visibleSubcommands(c) + if len(subs) > 0 { + b.WriteString("== Subcommands\n\n") + sort.Slice(subs, func(i, j int) bool { return subs[i].Name() < subs[j].Name() }) + for _, sc := range subs { + name := strings.ReplaceAll(sc.CommandPath(), " ", "_") + ".adoc" + fmt.Fprintf(b, "* xref:%s[] - %s\n", name, firstLine(sc.Short, sc.Long)) + } + b.WriteString("\n") + } + + return b.String() +} + +// formatFlagBlock renders a flag set into a code-fenced block similar to manual pages. +func formatFlagBlock(fs *pflag.FlagSet, includeHelp bool, c *cobra.Command) string { + var flags []*pflag.Flag + fs.VisitAll(func(f *pflag.Flag) { flags = append(flags, f) }) + sort.Slice(flags, func(i, j int) bool { + si, sj := flags[i].Shorthand, flags[j].Shorthand + if si == sj { + return flags[i].Name < flags[j].Name + } + if si == "" { + return false + } + if sj == "" { + return true + } + return si < sj + }) + type line struct{ spec, desc string } + var lines []line + for _, f := range flags { + spec := "" + if f.Shorthand != "" { + spec = fmt.Sprintf("-%s, --%s", f.Shorthand, f.Name) + } else { + spec = fmt.Sprintf(" --%s", f.Name) + } + typeName := f.Value.Type() + if typeName != "bool" { + spec += " " + typeName + } + desc := f.Usage + if typeName == "bool" { + desc = fmt.Sprintf("%s (default %s)", desc, f.DefValue) + } else if f.DefValue != "" && f.DefValue != "" && f.DefValue != "0" && !strings.Contains(desc, "(default") { + desc = fmt.Sprintf("%s (default %s)", desc, f.DefValue) + } + desc = strings.ReplaceAll(desc, "\n", " ") + lines = append(lines, line{spec: spec, desc: desc}) + } + if includeHelp { + found := false + for _, l := range lines { + if strings.Contains(l.spec, "--help") { + found = true + break + } + } + if !found { + helpLine := line{spec: "-h, --help", desc: fmt.Sprintf("help for %s", c.Name())} + if len(lines) == 0 { + lines = append(lines, helpLine) + } else { + lines = append(lines[:1], append([]line{helpLine}, lines[1:]...)...) + } + } + } + maxSpec := 0 + for _, l := range lines { + if len(l.spec) > maxSpec { + maxSpec = len(l.spec) + } + } + var b strings.Builder + b.WriteString("----\n") + for _, l := range lines { + pad := maxSpec - len(l.spec) + if pad < 0 { + pad = 0 + } + fmt.Fprintf(&b, " %s%s %s\n", l.spec, strings.Repeat(" ", pad), l.desc) + } + b.WriteString("----\n") + return b.String() +} + +// firstLine returns the first non-empty line from short or long description. +func firstLine(short, long string) string { + if strings.TrimSpace(short) != "" { + return strings.SplitN(strings.TrimSpace(short), "\n", 2)[0] + } + if strings.TrimSpace(long) != "" { + return strings.SplitN(strings.TrimSpace(long), "\n", 2)[0] + } + return "" +} + +func visibleSubcommands(c *cobra.Command) []*cobra.Command { + var subs []*cobra.Command + for _, sc := range c.Commands() { + if sc.Hidden { + continue + } + subs = append(subs, sc) + } + return subs +} + +func walkVisible(c *cobra.Command, fn func(*cobra.Command)) { + fn(c) + children := visibleSubcommands(c) + sort.Slice(children, func(i, j int) bool { return children[i].Name() < children[j].Name() }) + for _, sc := range children { + walkVisible(sc, fn) + } +} + +func depthOf(c *cobra.Command) int { return len(strings.Split(c.CommandPath(), " ")) } + +func fail(doing string, err error) { + fmt.Fprintf(os.Stderr, "error while %s: %v\n", doing, err) + os.Exit(1) +} + +// renderNav builds nav.adoc content with hierarchical bullet list. +// Format mirrors manually created original nav.adoc but with synthetic top-level group. +func renderNav(root *cobra.Command) string { + var b strings.Builder + b.WriteString("* Command Reference\n") + walkVisible(root, func(c *cobra.Command) { + if c == root { + return + } + stars := strings.Repeat("*", depthOf(c)) + file := strings.ReplaceAll(c.CommandPath(), " ", "_") + ".adoc" + fmt.Fprintf(&b, "%s xref:command_reference:%s[]\n", stars, file) + }) + b.WriteString("\n") + return b.String() +} diff --git a/tools/generate-options-docs/main.go b/tools/generate-options-docs/main.go new file mode 100644 index 00000000..6b05588a --- /dev/null +++ b/tools/generate-options-docs/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/pingidentity/pingcli/internal/configuration/options/docgen" +) + +// A tiny standalone tool (invoked via `make generate-options-docs`) to output +// documentation for configuration options in either Markdown or AsciiDoc. +func main() { + outFile := flag.String("o", "", "Write output to file (extension .md or .adoc determines format unless flags override)") + asAsciiDoc := flag.Bool("asciidoc", false, "Force AsciiDoc output (default: Markdown unless output file has .adoc/.asciidoc)") + flag.Parse() + + // Decide format + useAscii := false + if *outFile != "" { + useAscii = docgen.ShouldOutputAsciiDoc(*outFile, *asAsciiDoc) + } else if *asAsciiDoc { + useAscii = true + } + + var content string + if useAscii { + content = docgen.AsciiDoc() + } else { + content = docgen.Markdown() + } + + if *outFile == "" { + fmt.Print(content) + return + } + + if err := os.WriteFile(*outFile, []byte(content), 0o644); err != nil { + fmt.Fprintf(os.Stderr, "failed to write file: %v\n", err) + os.Exit(1) + } +} From 14691676f6a4ff53b7aae0ec7b8637988de75940 Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 17 Sep 2025 10:59:48 -0500 Subject: [PATCH 03/26] add docs folder to git --- .../cli-configuration-settings-reference.adoc | 87 +++++++++++++++++ docs/dev-ux-portal-docs/nav.adoc | 22 +++++ docs/dev-ux-portal-docs/pingcli.adoc | 36 +++++++ .../pingcli_completion.adoc | 66 +++++++++++++ docs/dev-ux-portal-docs/pingcli_config.adoc | 56 +++++++++++ .../pingcli_config_add-profile.adoc | 54 +++++++++++ .../pingcli_config_delete-profile.adoc | 52 ++++++++++ .../pingcli_config_get.adoc | 49 ++++++++++ .../pingcli_config_list-keys.adoc | 46 +++++++++ .../pingcli_config_list-profiles.adoc | 37 ++++++++ .../pingcli_config_set-active-profile.adoc | 40 ++++++++ .../pingcli_config_set.adoc | 46 +++++++++ .../pingcli_config_unset.adoc | 43 +++++++++ .../pingcli_config_view-profile.adoc | 43 +++++++++ docs/dev-ux-portal-docs/pingcli_feedback.adoc | 35 +++++++ docs/dev-ux-portal-docs/pingcli_license.adoc | 51 ++++++++++ docs/dev-ux-portal-docs/pingcli_platform.adoc | 37 ++++++++ .../pingcli_platform_export.adoc | 95 +++++++++++++++++++ docs/dev-ux-portal-docs/pingcli_plugin.adoc | 35 +++++++ .../pingcli_plugin_add.adoc | 36 +++++++ .../pingcli_plugin_list.adoc | 36 +++++++ .../pingcli_plugin_remove.adoc | 36 +++++++ docs/dev-ux-portal-docs/pingcli_request.adoc | 66 +++++++++++++ 23 files changed, 1104 insertions(+) create mode 100644 docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc create mode 100644 docs/dev-ux-portal-docs/nav.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_completion.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_config.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_config_add-profile.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_config_delete-profile.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_config_get.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_config_list-keys.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_config_list-profiles.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_config_set-active-profile.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_config_set.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_config_unset.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_config_view-profile.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_feedback.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_license.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_platform.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_platform_export.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_plugin.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_plugin_add.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_plugin_list.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_plugin_remove.adoc create mode 100644 docs/dev-ux-portal-docs/pingcli_request.adoc diff --git a/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc b/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc new file mode 100644 index 00000000..b0547c3f --- /dev/null +++ b/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc @@ -0,0 +1,87 @@ += Configuration Settings Reference +:created-date: March 23, 2025 +:revdate: September 16, 2025 +:resourceid: pingcli_configuration_settings_reference + +The following configuration settings can be applied when using Ping CLI. + +The following configuration settings can be applied by using the xref:command_reference:pingcli_config_set.adoc[`config set` command] to persist the configuration value for a given **Configuration Key** in the Ping CLI configuration file. + +The configuration file is created at `.pingcli/config.yaml` in the user's home directory. + +== General Properties + +[cols="2,1,2,2"] +|=== +|Configuration Key |Data Type |Equivalent Parameter |Purpose + +| `activeProfile` | String | | +| `description` | String | | +| `detailedExitCode` | Boolean | `--detailed-exitcode` / `-D` | Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. +| `noColor` | Boolean | `--no-color` | Disable text output in color. (default false) +| `outputFormat` | String (Enum) | `--output-format` / `-O` | Specify the console output format. (default text) Options are: json, text. +| `plugins` | String Array | | +|=== + +== Ping Identity platform service properties + +[cols="2,1,2,2"] +|=== +|Configuration Key |Data Type |Equivalent Parameter |Purpose + +| `service.pingfederate.adminAPIPath` | String | `--pingfederate-admin-api-path` | The PingFederate API URL path used to communicate with PingFederate's admin API. (default /pf-admin-api/v1) +| `service.pingfederate.authentication.accessTokenAuth.accessToken` | String | `--pingfederate-access-token` | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. +| `service.pingfederate.authentication.basicAuth.password` | String | `--pingfederate-password` | The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. +| `service.pingfederate.authentication.basicAuth.username` | String | `--pingfederate-username` | The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication. Example: 'administrator' +| `service.pingfederate.authentication.clientCredentialsAuth.clientID` | String | `--pingfederate-client-id` | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.clientCredentialsAuth.clientSecret` | String | `--pingfederate-client-secret` | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.clientCredentialsAuth.scopes` | String Array | `--pingfederate-scopes` | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. (default []) Accepts a comma-separated string to delimit multiple scopes. Example: 'openid,profile' +| `service.pingfederate.authentication.clientCredentialsAuth.tokenURL` | String | `--pingfederate-token-url` | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.type` | String (Enum) | `--pingfederate-authentication-type` | The authentication type to use when connecting to the PingFederate admin API. Options are: accessTokenAuth, basicAuth, clientCredentialsAuth. Example: 'basicAuth' +| `service.pingfederate.caCertificatePemFiles` | String Array | `--pingfederate-ca-certificate-pem-files` | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to the PingFederate server over HTTPS. (default []) Accepts a comma-separated string to delimit multiple PEM files. +| `service.pingfederate.httpsHost` | String | `--pingfederate-https-host` | The PingFederate HTTPS host used to communicate with PingFederate's admin API. Example: 'https://pingfederate-admin.bxretail.org' +| `service.pingfederate.insecureTrustAllTLS` | Boolean | `--pingfederate-insecure-trust-all-tls` | Trust any certificate when connecting to the PingFederate server admin API. (default false) This is insecure and shouldn't be enabled outside of testing. +| `service.pingfederate.xBypassExternalValidationHeader` | Boolean | `--pingfederate-x-bypass-external-validation-header` | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). (default false) +| `service.pingone.authentication.type` | String (Enum) | `--pingone-authentication-type` | The authentication type to use to authenticate to the PingOne management API. (default worker) Options are: worker. +| `service.pingone.authentication.worker.clientID` | String (UUID Format) | `--pingone-worker-client-id` | The worker client ID used to authenticate to the PingOne management API. +| `service.pingone.authentication.worker.clientSecret` | String | `--pingone-worker-client-secret` | The worker client secret used to authenticate to the PingOne management API. +| `service.pingone.authentication.worker.environmentID` | String (UUID Format) | `--pingone-worker-environment-id` | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne management API. +| `service.pingone.regionCode` | String (Enum) | `--pingone-region-code` | The region code of the PingOne tenant. Options are: AP, AU, CA, EU, NA. Example: 'NA' +|=== + +== Platform export properties + +[cols="2,1,2,2"] +|=== +|Configuration Key |Data Type |Equivalent Parameter |Purpose + +| `export.format` | String (Enum) | `--format` / `-f` | Specifies the export format. (default HCL) Options are: HCL. +| `export.outputDirectory` | String | `--output-directory` / `-d` | Specifies the output directory for export. Can be an absolute filepath or a relative filepath of the present working directory. Example: '/Users/example/pingcli-export' Example: 'pingcli-export' +| `export.overwrite` | Boolean | `--overwrite` / `-o` | Overwrites the existing generated exports in output directory. (default false) +| `export.pingone.environmentID` | String (UUID Format) | `--pingone-export-environment-id` | The ID of the PingOne environment to export. Must be a valid PingOne UUID. +| `export.serviceGroup` | String (Enum) | `--service-group` / `-g` | Specifies the service group to export. Options are: pingone. Example: 'pingone' +| `export.services` | String Array | `--services` / `-s` | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' +|=== + +== License properties + +[cols="2,1,2,2"] +|=== +|Configuration Key |Data Type |Equivalent Parameter |Purpose + +| `license.devopsKey` | String | `--devops-key` / `-k` | The DevOps key for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. +| `license.devopsUser` | String | `--devops-user` / `-u` | The DevOps user for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. +|=== + +== Custom request properties + +[cols="2,1,2,2"] +|=== +|Configuration Key |Data Type |Equivalent Parameter |Purpose + +| `request.accessToken` | String | | +| `request.accessTokenExpiry` | Integer | | +| `request.fail` | Boolean | `--fail` / `-f` | Return non-zero exit code when HTTP custom request returns a failure status code. +| `request.service` | String (Enum) | `--service` / `-s` | The Ping service (configured in the active profile) to send the custom request to. Options are: pingone. Example: 'pingone' +|=== + diff --git a/docs/dev-ux-portal-docs/nav.adoc b/docs/dev-ux-portal-docs/nav.adoc new file mode 100644 index 00000000..cb9429e7 --- /dev/null +++ b/docs/dev-ux-portal-docs/nav.adoc @@ -0,0 +1,22 @@ +* Command Reference +** xref:command_reference:pingcli_completion.adoc[] +** xref:command_reference:pingcli_config.adoc[] +*** xref:command_reference:pingcli_config_add-profile.adoc[] +*** xref:command_reference:pingcli_config_delete-profile.adoc[] +*** xref:command_reference:pingcli_config_get.adoc[] +*** xref:command_reference:pingcli_config_list-keys.adoc[] +*** xref:command_reference:pingcli_config_list-profiles.adoc[] +*** xref:command_reference:pingcli_config_set.adoc[] +*** xref:command_reference:pingcli_config_set-active-profile.adoc[] +*** xref:command_reference:pingcli_config_unset.adoc[] +*** xref:command_reference:pingcli_config_view-profile.adoc[] +** xref:command_reference:pingcli_feedback.adoc[] +** xref:command_reference:pingcli_license.adoc[] +** xref:command_reference:pingcli_platform.adoc[] +*** xref:command_reference:pingcli_platform_export.adoc[] +** xref:command_reference:pingcli_plugin.adoc[] +*** xref:command_reference:pingcli_plugin_add.adoc[] +*** xref:command_reference:pingcli_plugin_list.adoc[] +*** xref:command_reference:pingcli_plugin_remove.adoc[] +** xref:command_reference:pingcli_request.adoc[] + diff --git a/docs/dev-ux-portal-docs/pingcli.adoc b/docs/dev-ux-portal-docs/pingcli.adoc new file mode 100644 index 00000000..78c0ca09 --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli.adoc @@ -0,0 +1,36 @@ += pingcli +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli + +A CLI tool for managing the configuration of Ping Identity products. + +== Synopsis + +A CLI tool for managing the configuration of Ping Identity products. + +---- +pingcli +---- + +== Options + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -h, --help help for pingcli + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + --no-color Disable text output in color. (default false) (default ) +---- + +== Subcommands + +* xref:pingcli_completion.adoc[] - Prints shell completion scripts +* xref:pingcli_config.adoc[] - Manage the CLI configuration. +* xref:pingcli_feedback.adoc[] - Help us improve the CLI. Report issues or send us feedback on using the CLI tool. +* xref:pingcli_license.adoc[] - Request a new evaluation license. +* xref:pingcli_platform.adoc[] - Administer and manage the Ping integrated platform. +* xref:pingcli_plugin.adoc[] - Manage Ping CLI plugins. +* xref:pingcli_request.adoc[] - Send a custom REST API request to a Ping platform service. + diff --git a/docs/dev-ux-portal-docs/pingcli_completion.adoc b/docs/dev-ux-portal-docs/pingcli_completion.adoc new file mode 100644 index 00000000..cf1f386b --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_completion.adoc @@ -0,0 +1,66 @@ += pingcli completion +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_completion + +Prints shell completion scripts + +== Synopsis + +To load completions: + +Bash: + + $ source <(pingcli completion bash) + + # To load completions for each session, execute once: + # Linux: + $ pingcli completion bash > /etc/bash_completion.d/pingcli + # macOS: + $ source <(pingcli completion zsh) + +Zsh: + + # If shell completion is not already enabled in your environment, + # you will need to enable it. You can execute the following once: + + $ echo "autoload -U compinit; compinit" >> ~/.zshrc + + # To load completions for each session, execute once: + $ pingcli completion zsh > "${fpath[1]}/_pingcli" + + # You will need to start a new shell for this setup to take effect. + +fish: + + $ pingcli completion fish | source + + # To load completions for each session, execute once: + $ pingcli completion fish > ~/.config/fish/completions/pingcli.fish + +PowerShell: + + PS> pingcli completion powershell | Out-String | Invoke-Expression + + # To load completions for every new session, run: + PS> pingcli completion powershell > pingcli.ps1 + # and source this file from your PowerShell profile. + +---- +pingcli completion [SHELL] +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli.adoc[] - A CLI tool for managing the configuration of Ping Identity products. + diff --git a/docs/dev-ux-portal-docs/pingcli_config.adoc b/docs/dev-ux-portal-docs/pingcli_config.adoc new file mode 100644 index 00000000..3754cf45 --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_config.adoc @@ -0,0 +1,56 @@ += pingcli config +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_config + +Manage the CLI configuration. + +== Synopsis + +Manage the configuration of the CLI, including Ping product connection parameters. + +The Ping CLI supports the use of configuration profiles. +Configuration profiles can be used when connecting to multiple environments using the same Ping CLI instance, such as when managing multiple development or demonstration environments. + +A pre-defined default profile will be used to store the configuration of the CLI. +Additional custom profiles can be created using the `pingcli config add-profile` command. +To use a custom profile, the `--profile` flag can be used on supported commands to specify the profile to use for that command. +To set a custom profile as the default, use the `pingcli config set-active-profile` command. + +---- +pingcli config +---- + +== Options + +---- + -U, --unmask-values Unmask secret values. (default false) (default ) + -h, --help help for config +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli.adoc[] - A CLI tool for managing the configuration of Ping Identity products. + +== Subcommands + +* xref:pingcli_config_add-profile.adoc[] - Add a new custom configuration profile. +* xref:pingcli_config_delete-profile.adoc[] - Delete a custom configuration profile. +* xref:pingcli_config_get.adoc[] - Read stored configuration settings for the CLI. +* xref:pingcli_config_list-keys.adoc[] - List all configuration keys. +* xref:pingcli_config_list-profiles.adoc[] - List all custom configuration profiles. +* xref:pingcli_config_set.adoc[] - Set stored configuration settings for the CLI. +* xref:pingcli_config_set-active-profile.adoc[] - Set a custom configuration profile as the in-use profile. +* xref:pingcli_config_unset.adoc[] - Unset stored configuration settings for the CLI. +* xref:pingcli_config_view-profile.adoc[] - View the stored configuration of a custom configuration profile. + diff --git a/docs/dev-ux-portal-docs/pingcli_config_add-profile.adoc b/docs/dev-ux-portal-docs/pingcli_config_add-profile.adoc new file mode 100644 index 00000000..c0a42863 --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_config_add-profile.adoc @@ -0,0 +1,54 @@ += pingcli config add-profile +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_config_add-profile + +Add a new custom configuration profile. + +== Synopsis + +Add a new custom configuration profile to the CLI. + +The new configuration profile will be stored in the CLI configuration file. + +---- +pingcli config add-profile [flags] +---- + +== Examples + +---- + Add a new configuration profile with a guided experience. + pingcli config add-profile + + Add a new configuration profile with a specific name and description. + pingcli config add-profile --name myprofile --description "My awesome new profile for my development environment" + + Add a new configuration profile with a guided experience and set it as the active profile. + pingcli config add-profile --set-active +---- + +== Options + +---- + -d, --description string The description of the new configuration profile. + -h, --help help for add-profile + -n, --name string The name of the new configuration profile. + -s, --set-active Set the new configuration profile as the active profile. (default false) (default ) +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + -U, --unmask-values Unmask secret values. (default false) (default ) + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli_config.adoc[] - Manage the CLI configuration. + diff --git a/docs/dev-ux-portal-docs/pingcli_config_delete-profile.adoc b/docs/dev-ux-portal-docs/pingcli_config_delete-profile.adoc new file mode 100644 index 00000000..ebcc3cc5 --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_config_delete-profile.adoc @@ -0,0 +1,52 @@ += pingcli config delete-profile +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_config_delete-profile + +Delete a custom configuration profile. + +== Synopsis + +Delete an existing custom configuration profile from the CLI. + +The profile to delete will be removed from the CLI configuration file. + +---- +pingcli config delete-profile [flags] [profile-name] +---- + +== Examples + +---- + Delete a configuration profile by selecting from the available profiles. + pingcli config delete-profile + + Delete a configuration profile by specifying the name of an existing configured profile. + pingcli config delete-profile myprofile + + Delete a configuration profile by auto-accepting the deletion. + pingcli config delete-profile --yes myprofile +---- + +== Options + +---- + -y, --yes Auto-accept the profile deletion confirmation prompt. (default false) (default ) + -h, --help help for delete-profile +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + -U, --unmask-values Unmask secret values. (default false) (default ) + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli_config.adoc[] - Manage the CLI configuration. + diff --git a/docs/dev-ux-portal-docs/pingcli_config_get.adoc b/docs/dev-ux-portal-docs/pingcli_config_get.adoc new file mode 100644 index 00000000..2a8a260f --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_config_get.adoc @@ -0,0 +1,49 @@ += pingcli config get +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_config_get + +Read stored configuration settings for the CLI. + +== Synopsis + +Read stored configuration settings for the CLI. + +The `--profile` parameter can be used to read configuration settings for a specified custom configuration profile. +Where `--profile` is not specified, configuration settings will be read for the currently active profile. + +---- +pingcli config get [flags] key +---- + +== Examples + +---- + Read all the configuration settings for the PingOne service in the active (or default) profile. + pingcli config get pingone + + Read the noColor setting for the profile named 'myprofile'. + pingcli config get --profile myprofile noColor + + Read the worker ID used to authenticate to the PingOne service management API. + pingcli config get service.pingOne.authentication.worker.environmentID + + Read the unmasked value for the request access token. + pingcli config get --unmask-values request.accessToken +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + -U, --unmask-values Unmask secret values. (default false) (default ) + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli_config.adoc[] - Manage the CLI configuration. + diff --git a/docs/dev-ux-portal-docs/pingcli_config_list-keys.adoc b/docs/dev-ux-portal-docs/pingcli_config_list-keys.adoc new file mode 100644 index 00000000..cb15fdfe --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_config_list-keys.adoc @@ -0,0 +1,46 @@ += pingcli config list-keys +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_config_list-keys + +List all configuration keys. + +== Synopsis + +View the complete list of available configuration options. These attributes can be saved via the set and unset config subcommands or stored in a profile within the config file. +For details on the configuration options visit: https://github.com/pingidentity/pingcli/blob/main/docs/tool-configuration/configuration-key.md + +---- +pingcli config list-keys [flags] +---- + +== Examples + +---- + List all configuration keys stored in the CLI configuration file. + pingcli config list-keys + pingcli config list-keys --yaml +---- + +== Options + +---- + -y, --yaml Output configuration keys in YAML format. (default false) (default ) + -h, --help help for list-keys +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + -U, --unmask-values Unmask secret values. (default false) (default ) + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli_config.adoc[] - Manage the CLI configuration. + diff --git a/docs/dev-ux-portal-docs/pingcli_config_list-profiles.adoc b/docs/dev-ux-portal-docs/pingcli_config_list-profiles.adoc new file mode 100644 index 00000000..4d3f772f --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_config_list-profiles.adoc @@ -0,0 +1,37 @@ += pingcli config list-profiles +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_config_list-profiles + +List all custom configuration profiles. + +== Synopsis + +List all custom configuration profiles stored in the CLI configuration file. + +---- +pingcli config list-profiles +---- + +== Examples + +---- + List all custom configuration profiles stored in the CLI configuration file. + pingcli config list-profiles +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + -U, --unmask-values Unmask secret values. (default false) (default ) + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli_config.adoc[] - Manage the CLI configuration. + diff --git a/docs/dev-ux-portal-docs/pingcli_config_set-active-profile.adoc b/docs/dev-ux-portal-docs/pingcli_config_set-active-profile.adoc new file mode 100644 index 00000000..909deb17 --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_config_set-active-profile.adoc @@ -0,0 +1,40 @@ += pingcli config set-active-profile +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_config_set-active-profile + +Set a custom configuration profile as the in-use profile. + +== Synopsis + +Set a custom configuration profile as the in-use profile. + +---- +pingcli config set-active-profile [flags] [profile-name] +---- + +== Examples + +---- + Set an active profile with an interactive prompt to select from an available profile. + pingcli config set-active-profile + + Set an active profile with a specific profile name. + pingcli config set-active-profile myprofile +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + -U, --unmask-values Unmask secret values. (default false) (default ) + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli_config.adoc[] - Manage the CLI configuration. + diff --git a/docs/dev-ux-portal-docs/pingcli_config_set.adoc b/docs/dev-ux-portal-docs/pingcli_config_set.adoc new file mode 100644 index 00000000..0a2f4ae7 --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_config_set.adoc @@ -0,0 +1,46 @@ += pingcli config set +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_config_set + +Set stored configuration settings for the CLI. + +== Synopsis + +Set stored configuration settings for the CLI. + +The `--profile` parameter can be used to set configuration settings for a specified custom configuration profile. +Where `--profile` is not specified, configuration settings will be set for the currently active profile. + +---- +pingcli config set [flags] key=value +---- + +== Examples + +---- + Set the color setting to true for the currently active profile. + pingcli config set color=true + + Set the PingOne tenant region code setting to 'AP' for the profile named 'myprofile'. + pingcli config set --profile myprofile service.pingOne.regionCode=AP + + Set the PingFederate basic authentication password with unmasked output + pingcli config set --profile myprofile --unmask-values service.pingFederate.authentication.basicAuth.password=1234 +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + -U, --unmask-values Unmask secret values. (default false) (default ) + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli_config.adoc[] - Manage the CLI configuration. + diff --git a/docs/dev-ux-portal-docs/pingcli_config_unset.adoc b/docs/dev-ux-portal-docs/pingcli_config_unset.adoc new file mode 100644 index 00000000..64693994 --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_config_unset.adoc @@ -0,0 +1,43 @@ += pingcli config unset +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_config_unset + +Unset stored configuration settings for the CLI. + +== Synopsis + +Unset stored configuration settings for the CLI. + +The `--profile` parameter can be used to unset configuration settings for a specified custom configuration profile. +Where `--profile` is not specified, configuration settings will be unset for the currently active profile. + +---- +pingcli config unset [flags] key +---- + +== Examples + +---- + Unset the color setting for the currently active profile. + pingcli config unset color + + Unset the PingOne tenant region code setting for the profile named 'myprofile'. + pingcli config unset --profile myprofile service.pingOne.regionCode +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + -U, --unmask-values Unmask secret values. (default false) (default ) + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli_config.adoc[] - Manage the CLI configuration. + diff --git a/docs/dev-ux-portal-docs/pingcli_config_view-profile.adoc b/docs/dev-ux-portal-docs/pingcli_config_view-profile.adoc new file mode 100644 index 00000000..c08153da --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_config_view-profile.adoc @@ -0,0 +1,43 @@ += pingcli config view-profile +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_config_view-profile + +View the stored configuration of a custom configuration profile. + +== Synopsis + +View the stored configuration of a custom configuration profile. + +---- +pingcli config view-profile [flags] [profile-name] +---- + +== Examples + +---- + View configuration for the currently active profile + pingcli config view-profile + + View configuration for a specific profile + pingcli config view-profile myprofile + + View configuration for a specific profile with unmasked values + pingcli config --unmask-values view-profile myprofile +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + -U, --unmask-values Unmask secret values. (default false) (default ) + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli_config.adoc[] - Manage the CLI configuration. + diff --git a/docs/dev-ux-portal-docs/pingcli_feedback.adoc b/docs/dev-ux-portal-docs/pingcli_feedback.adoc new file mode 100644 index 00000000..2c5559c5 --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_feedback.adoc @@ -0,0 +1,35 @@ += pingcli feedback +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_feedback + +Help us improve the CLI. Report issues or send us feedback on using the CLI tool. + +== Synopsis + +Provides links to report issues and provide feedback on using the CLI to Ping Identity. + +---- +pingcli feedback [flags] +---- + +== Examples + +---- + pingcli feedback +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli.adoc[] - A CLI tool for managing the configuration of Ping Identity products. + diff --git a/docs/dev-ux-portal-docs/pingcli_license.adoc b/docs/dev-ux-portal-docs/pingcli_license.adoc new file mode 100644 index 00000000..01ab687c --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_license.adoc @@ -0,0 +1,51 @@ += pingcli license +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_license + +Request a new evaluation license. + +== Synopsis + +Request a new evaluation license for a specific product and version. + +The new license request will be sent to the Ping Identity license server. + +---- +pingcli license [flags] +---- + +== Examples + +---- + Request a new evaluation license for PingFederate 12.0. + pingcli license request --product pingfederate --version 12.0 + + Request a new evaluation license for PingAccess 6.3. + pingcli license request --product pingaccess --version 6.3 +---- + +== Options + +---- + -k, --devops-key string The DevOps key for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. + -h, --help help for license + -p, --product string The product for which to request a license. Options are: pingaccess, pingauthorize, pingauthorize-policy-editor, pingcentral, pingdirectory, pingdirectoryproxy, pingfederate. Example: 'pingfederate' + -u, --devops-user string The DevOps user for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. + -v, --version string The version of the product for which to request a license. Must be of the form 'major.minor'. Example: '12.3' +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli.adoc[] - A CLI tool for managing the configuration of Ping Identity products. + diff --git a/docs/dev-ux-portal-docs/pingcli_platform.adoc b/docs/dev-ux-portal-docs/pingcli_platform.adoc new file mode 100644 index 00000000..96592a1d --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_platform.adoc @@ -0,0 +1,37 @@ += pingcli platform +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_platform + +Administer and manage the Ping integrated platform. + +== Synopsis + +Administer and manage the Ping integrated platform. + +When multiple products are configured in the CLI, the platform command can be used to manage one or more products collectively. + +The --profile command switch can be used to specify the profile of Ping products to be managed. + +---- +pingcli platform +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli.adoc[] - A CLI tool for managing the configuration of Ping Identity products. + +== Subcommands + +* xref:pingcli_platform_export.adoc[] - Export Configuration as Code packages for the Ping Platform. + diff --git a/docs/dev-ux-portal-docs/pingcli_platform_export.adoc b/docs/dev-ux-portal-docs/pingcli_platform_export.adoc new file mode 100644 index 00000000..fb0913d3 --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_platform_export.adoc @@ -0,0 +1,95 @@ += pingcli platform export +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_platform_export + +Export Configuration as Code packages for the Ping Platform. + +== Synopsis + +Export Configuration as Code packages for the Ping Platform. + +The CLI can export Terraform HCL to use with released Terraform providers. +The Terraform HCL option generates `import {}` block statements for resources in the target environment. +Using Terraform `import {}` blocks, the platform's configuration can be generated and imported into state management. +More information can be found at https://developer.hashicorp.com/terraform/language/import + +---- +pingcli platform export [flags] +---- + +== Examples + +---- + Export Configuration as Code for all products configured in the configuration file, applying default options. + pingcli platform export + + Export Configuration as Code packages for all configured products to a specific directory, overwriting any previous export. + pingcli platform export --output-directory /path/to/my/directory --overwrite + + Export Configuration as Code packages for all configured products, specifying the export format as Terraform HCL. + pingcli platform export --format HCL + + Export Configuration as Code packages for PingOne (core platform and SSO services). + pingcli platform export --services pingone-platform,pingone-sso + + Export all Configuration as Code packages for PingOne. The --service-group flag can be used instead of listing all pingone-* packages in --services flag. + pingcli platform export --service-group pingone + + Export Configuration as Code packages for PingOne (core platform), specifying the PingOne environment connection details. + pingcli platform export --services pingone-platform --pingone-client-environment-id 3cf2... --pingone-worker-client-id a719... --pingone-worker-client-secret ey..... --pingone-region-code EU + + Export Configuration as Code packages for PingFederate, specifying the PingFederate connection details using basic authentication. + pingcli platform export --services pingfederate --pingfederate-authentication-type basicAuth --pingfederate-username administrator --pingfederate-password 2FederateM0re --pingfederate-https-host https://pingfederate-admin.bxretail.org + + Export Configuration as Code packages for PingFederate, specifying the PingFederate connection details using OAuth 2.0 client credentials. + pingcli platform export --services pingfederate --pingfederate-authentication-type clientCredentialsAuth --pingfederate-client-id clientID --pingfederate-client-secret clientSecret --pingfederate-token-url https://pingfederate-admin.bxretail.org/as/token.oauth2 + + Export Configuration as Code packages for PingFederate, specifying optional connection properties + pingcli platform export --services pingfederate --x-bypass-external-validation=false --ca-certificate-pem-files "/path/to/cert.pem,/path/to/cert2.pem" --insecure-trust-all-tls=false +---- + +== Options + +---- + -d, --output-directory string Specifies the output directory for export. Can be an absolute filepath or a relative filepath of the present working directory. Example: '/Users/example/pingcli-export' Example: 'pingcli-export' + -h, --help help for export + -f, --format string Specifies the export format. (default HCL) Options are: HCL. + -g, --service-group string Specifies the service group to export. Options are: pingone. Example: 'pingone' + -o, --overwrite Overwrites the existing generated exports in output directory. (default false) (default ) + -s, --services []string Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' + --pingfederate-access-token string The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. + --pingfederate-admin-api-path string The PingFederate API URL path used to communicate with PingFederate's admin API. (default /pf-admin-api/v1) + --pingfederate-authentication-type string The authentication type to use when connecting to the PingFederate admin API. Options are: accessTokenAuth, basicAuth, clientCredentialsAuth. Example: 'basicAuth' + --pingfederate-ca-certificate-pem-files []string Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to the PingFederate server over HTTPS. (default []) Accepts a comma-separated string to delimit multiple PEM files. + --pingfederate-client-id string The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. + --pingfederate-client-secret string The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. + --pingfederate-https-host string The PingFederate HTTPS host used to communicate with PingFederate's admin API. Example: 'https://pingfederate-admin.bxretail.org' + --pingfederate-insecure-trust-all-tls Trust any certificate when connecting to the PingFederate server admin API. (default false) This is insecure and shouldn't be enabled outside of testing. (default ) + --pingfederate-password string The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. + --pingfederate-scopes []string The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. (default []) Accepts a comma-separated string to delimit multiple scopes. Example: 'openid,profile' + --pingfederate-token-url string The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. + --pingfederate-username string The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication. Example: 'administrator' + --pingfederate-x-bypass-external-validation-header Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). (default false) (default ) + --pingone-authentication-type string The authentication type to use to authenticate to the PingOne management API. (default worker) Options are: worker. + --pingone-export-environment-id string The ID of the PingOne environment to export. Must be a valid PingOne UUID. + --pingone-region-code string The region code of the PingOne tenant. Options are: AP, AU, CA, EU, NA. Example: 'NA' + --pingone-worker-client-id string The worker client ID used to authenticate to the PingOne management API. + --pingone-worker-client-secret string The worker client secret used to authenticate to the PingOne management API. + --pingone-worker-environment-id string The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne management API. +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli_platform.adoc[] - Administer and manage the Ping integrated platform. + diff --git a/docs/dev-ux-portal-docs/pingcli_plugin.adoc b/docs/dev-ux-portal-docs/pingcli_plugin.adoc new file mode 100644 index 00000000..51189a85 --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_plugin.adoc @@ -0,0 +1,35 @@ += pingcli plugin +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_plugin + +Manage Ping CLI plugins. + +== Synopsis + +Manage Ping CLI plugins. + +---- +pingcli plugin +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli.adoc[] - A CLI tool for managing the configuration of Ping Identity products. + +== Subcommands + +* xref:pingcli_plugin_add.adoc[] - Add a plugin to use with Ping CLI +* xref:pingcli_plugin_list.adoc[] - List all plugins currently in use with Ping CLI +* xref:pingcli_plugin_remove.adoc[] - Remove a plugin from Ping CLI + diff --git a/docs/dev-ux-portal-docs/pingcli_plugin_add.adoc b/docs/dev-ux-portal-docs/pingcli_plugin_add.adoc new file mode 100644 index 00000000..7b399f8c --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_plugin_add.adoc @@ -0,0 +1,36 @@ += pingcli plugin add +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_plugin_add + +Add a plugin to use with Ping CLI + +== Synopsis + +Add a plugin to use with Ping CLI. + +---- +pingcli plugin add plugin-executable +---- + +== Examples + +---- + Add a plugin to use with Ping CLI. + pingcli plugin add pingcli-plugin-executable +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli_plugin.adoc[] - Manage Ping CLI plugins. + diff --git a/docs/dev-ux-portal-docs/pingcli_plugin_list.adoc b/docs/dev-ux-portal-docs/pingcli_plugin_list.adoc new file mode 100644 index 00000000..f0299b16 --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_plugin_list.adoc @@ -0,0 +1,36 @@ += pingcli plugin list +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_plugin_list + +List all plugins currently in use with Ping CLI + +== Synopsis + +List all plugins currently in use with Ping CLI. + +---- +pingcli plugin list +---- + +== Examples + +---- + List all plugins currently in use with Ping CLI. + pingcli plugin list +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli_plugin.adoc[] - Manage Ping CLI plugins. + diff --git a/docs/dev-ux-portal-docs/pingcli_plugin_remove.adoc b/docs/dev-ux-portal-docs/pingcli_plugin_remove.adoc new file mode 100644 index 00000000..fad845f3 --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_plugin_remove.adoc @@ -0,0 +1,36 @@ += pingcli plugin remove +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_plugin_remove + +Remove a plugin from Ping CLI + +== Synopsis + +Remove a plugin from Ping CLI. + +---- +pingcli plugin remove plugin-executable +---- + +== Examples + +---- + Remove a plugin from Ping CLI. + pingcli plugin remove pingcli-plugin-executable +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli_plugin.adoc[] - Manage Ping CLI plugins. + diff --git a/docs/dev-ux-portal-docs/pingcli_request.adoc b/docs/dev-ux-portal-docs/pingcli_request.adoc new file mode 100644 index 00000000..2e4690f7 --- /dev/null +++ b/docs/dev-ux-portal-docs/pingcli_request.adoc @@ -0,0 +1,66 @@ += pingcli request +:created-date: September 17, 2025 +:revdate: September 17, 2025 +:resourceid: pingcli_command_reference_pingcli_request + +Send a custom REST API request to a Ping platform service. + +== Synopsis + +Send a custom REST API request to a Ping Service. + +The custom REST API request is most powerful when product connection details have been configured in the CLI configuration file. +The command offers a cURL-like experience to interact with the Ping platform services, with authentication and environment details dynamically filled by the CLI. + +---- +pingcli request [flags] API_URI +---- + +== Examples + +---- + Send a custom API request to the configured PingOne tenant, making a GET request against the /environments endpoint. + pingcli request --service pingone environments + + Send a custom API request to the configured PingOne tenant, making a GET request to retrieve JSON configuration for a specific environment. + pingcli request --service pingone --http-method GET --output-format json environments/$MY_ENVIRONMENT_ID + + Send a custom API request to the configured PingOne tenant, making a POST request to create a new environment with JSON data sourced from a file. + pingcli request --service pingone --http-method POST --data ./my-environment.json environments + + Send a custom API request to the configured PingOne tenant, making a POST request using a custom header to create users with JSON data sourced from a file. + pingcli request --service pingone --http-method POST --header "Content-Type: application/vnd.pingidentity.user.import+json" --data ./users.json environments/$MY_ENVIRONMENT_ID/users + + Send a custom API request to the configured PingOne tenant, making a POST request to create a new environment using raw JSON data. + pingcli request --service pingone --http-method POST --data-raw '{"name": "My environment"}' environments + + Send a custom API request to the configured PingOne tenant, making a DELETE request to remove an application attribute mapping. + pingcli request --service pingone --http-method DELETE environments/$MY_ENVIRONMENT_ID/applications/$MY_APPLICATION_ID/attributes/$MY_ATTRIBUTE_MAPPING_ID +---- + +== Options + +---- + -f, --fail Return non-zero exit code when HTTP custom request returns a failure status code. (default ) + -h, --help help for request + -m, --http-method string The HTTP method to use for the request. (default GET) Options are: DELETE, GET, PATCH, POST, PUT. Example: 'POST' + -r, --header []string A custom header to send in the request. Example: --header "Content-Type: application/vnd.pingidentity.user.import+json" + -s, --service string The Ping service (configured in the active profile) to send the custom request to. Options are: pingone. Example: 'pingone' + --data string The file containing data to send in the request. Example: './data.json' + --data-raw string The raw data to send in the request. Example: '{"name": "My environment"}' +---- + +== Options inherited from parent commands + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + --no-color Disable text output in color. (default false) (default ) +---- + +== More information + +* xref:pingcli.adoc[] - A CLI tool for managing the configuration of Ping Identity products. + From c2f05f6c9b8e149224202356c6da42081888649f Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 17 Sep 2025 11:05:01 -0500 Subject: [PATCH 04/26] remove extraneous default flag from Cobra behavior --- tools/generate-command-docs/main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/generate-command-docs/main.go b/tools/generate-command-docs/main.go index 9ba1b100..6caca71e 100644 --- a/tools/generate-command-docs/main.go +++ b/tools/generate-command-docs/main.go @@ -155,7 +155,10 @@ func formatFlagBlock(fs *pflag.FlagSet, includeHelp bool, c *cobra.Command) stri } desc := f.Usage if typeName == "bool" { - desc = fmt.Sprintf("%s (default %s)", desc, f.DefValue) + // Add only if usage text does not already contain a default and DefValue is meaningful. + if !strings.Contains(desc, "(default") && f.DefValue != "" { + desc = fmt.Sprintf("%s (default %s)", desc, f.DefValue) + } } else if f.DefValue != "" && f.DefValue != "" && f.DefValue != "0" && !strings.Contains(desc, "(default") { desc = fmt.Sprintf("%s (default %s)", desc, f.DefValue) } From 5c71a431758b932530301389e2e801b953deafbd Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 17 Sep 2025 11:33:49 -0500 Subject: [PATCH 05/26] working --- tools/generate-options-docs/main.go | 179 +++++++++++++++++++++++++++- 1 file changed, 175 insertions(+), 4 deletions(-) diff --git a/tools/generate-options-docs/main.go b/tools/generate-options-docs/main.go index 6b05588a..842649f5 100644 --- a/tools/generate-options-docs/main.go +++ b/tools/generate-options-docs/main.go @@ -1,11 +1,22 @@ package main +// This file contains the entire options documentation generator tool. +// It was consolidated from two separate files (main.go and docgen.go) in September 2025 +// when cleaning up references to the removed internal/options package. +// The tool generates documentation for all CLI configuration options in either +// Markdown or AsciiDoc format. + import ( "flag" "fmt" "os" + "path/filepath" + "slices" + "strings" + "time" - "github.com/pingidentity/pingcli/internal/configuration/options/docgen" + "github.com/pingidentity/pingcli/internal/configuration" + "github.com/pingidentity/pingcli/internal/configuration/options" ) // A tiny standalone tool (invoked via `make generate-options-docs`) to output @@ -18,16 +29,16 @@ func main() { // Decide format useAscii := false if *outFile != "" { - useAscii = docgen.ShouldOutputAsciiDoc(*outFile, *asAsciiDoc) + useAscii = shouldOutputAsciiDoc(*outFile, *asAsciiDoc) } else if *asAsciiDoc { useAscii = true } var content string if useAscii { - content = docgen.AsciiDoc() + content = asciiDoc() } else { - content = docgen.Markdown() + content = markdown() } if *outFile == "" { @@ -40,3 +51,163 @@ func main() { os.Exit(1) } } + +// ---- merged from former docgen.go ---- + +// markdown renders the options documentation markdown table sections. +func markdown() string { + configuration.InitAllOptions() + propertyCategoryInformation := make(map[string][]string) + for _, option := range options.Options() { + if option.KoanfKey == "" || option.Flag == nil { + continue + } + var flagInfo string + if option.Flag.Shorthand != "" { + flagInfo = fmt.Sprintf("--%s / -%s", option.CobraParamName, option.Flag.Shorthand) + } else { + flagInfo = fmt.Sprintf("--%s", option.CobraParamName) + } + usageString := strings.ReplaceAll(option.Flag.Usage, "\n", "

") + category := "general" + if strings.Contains(option.KoanfKey, ".") { + category = strings.Split(option.KoanfKey, ".")[0] + } + propertyCategoryInformation[category] = append(propertyCategoryInformation[category], fmt.Sprintf("| %s | %d | %s | %s |", option.KoanfKey, option.Type, flagInfo, usageString)) + } + var outputBuilder strings.Builder + cats := make([]string, 0, len(propertyCategoryInformation)) + for k := range propertyCategoryInformation { + cats = append(cats, k) + } + slices.Sort(cats) + for _, category := range cats { + properties := propertyCategoryInformation[category] + slices.Sort(properties) + outputBuilder.WriteString(fmt.Sprintf("#### %s Properties\n\n", category)) + outputBuilder.WriteString("| Config File Property | Type | Equivalent Parameter | Purpose |\n") + outputBuilder.WriteString("|---|---|---|---|\n") + for _, property := range properties { + outputBuilder.WriteString(property + "\n") + } + outputBuilder.WriteString("\n") + } + return outputBuilder.String() +} + +// asciiDoc generates a configuration reference in AsciiDoc format. +func asciiDoc() string { + configuration.InitAllOptions() + catMap := map[string][]options.Option{} + for _, opt := range options.Options() { + if opt.KoanfKey == "" { + continue + } + root := opt.KoanfKey + if strings.Contains(root, ".") { + root = strings.Split(root, ".")[0] + } + switch root { + case "service": + catMap["service"] = append(catMap["service"], opt) + case "export": + catMap["export"] = append(catMap["export"], opt) + case "license": + catMap["license"] = append(catMap["license"], opt) + case "request": + catMap["request"] = append(catMap["request"], opt) + default: + if !strings.Contains(opt.KoanfKey, ".") { + catMap["general"] = append(catMap["general"], opt) + } + } + } + for k := range catMap { + slices.SortFunc(catMap[k], func(a, b options.Option) int { return strings.Compare(a.KoanfKey, b.KoanfKey) }) + } + var b strings.Builder + created := "March 23, 2025" + revdate := time.Now().Format("January 2, 2006") + b.WriteString("= Configuration Settings Reference\n") + b.WriteString(fmt.Sprintf(":created-date: %s\n", created)) + b.WriteString(fmt.Sprintf(":revdate: %s\n", revdate)) + b.WriteString(":resourceid: pingcli_configuration_settings_reference\n\n") + b.WriteString("The following configuration settings can be applied when using Ping CLI.\n\n") + b.WriteString("The following configuration settings can be applied by using the xref:command_reference:pingcli_config_set.adoc[`config set` command] to persist the configuration value for a given **Configuration Key** in the Ping CLI configuration file.\n\n") + b.WriteString("The configuration file is created at `.pingcli/config.yaml` in the user's home directory.\n\n") + ordered := []struct{ key, title string }{{"general", "General Properties"}, {"service", "Ping Identity platform service properties"}, {"export", "Platform export properties"}, {"license", "License properties"}, {"request", "Custom request properties"}} + for _, sec := range ordered { + opts := catMap[sec.key] + if len(opts) == 0 { + continue + } + b.WriteString("== " + sec.title + "\n\n") + b.WriteString("[cols=\"2,1,2,2\"]\n|===\n") + b.WriteString("|Configuration Key |Data Type |Equivalent Parameter |Purpose\n\n") + for _, opt := range opts { + key := normalizeAsciiDocKeyLocal(opt.KoanfKey) + dataType := asciiDocDataTypeLocal(opt) + eqParam := asciiDocEquivalentParameterLocal(opt) + purpose := sanitizeUsageLocal(opt) + b.WriteString(fmt.Sprintf("| `%s` | %s | %s | %s\n", key, dataType, eqParam, purpose)) + } + b.WriteString("|===\n\n") + } + return b.String() +} + +func shouldOutputAsciiDoc(outPath string, explicit bool) bool { + if explicit { + return true + } + ext := strings.ToLower(filepath.Ext(outPath)) + return ext == ".adoc" || ext == ".asciidoc" +} + +func asciiDocEquivalentParameterLocal(opt options.Option) string { + if opt.Flag == nil { + return "" + } + if opt.Flag.Shorthand != "" { + return fmt.Sprintf("`--%s` / `-%s`", opt.CobraParamName, opt.Flag.Shorthand) + } + return fmt.Sprintf("`--%s`", opt.CobraParamName) +} + +func asciiDocDataTypeLocal(opt options.Option) string { + switch opt.Type { + case options.BOOL: + return "Boolean" + case options.STRING: + return "String" + case options.STRING_SLICE, options.EXPORT_SERVICES, options.HEADER: + return "String Array" + case options.UUID: + return "String (UUID Format)" + case options.EXPORT_FORMAT, options.OUTPUT_FORMAT, options.PINGFEDERATE_AUTH_TYPE, options.PINGONE_AUTH_TYPE, options.PINGONE_REGION_CODE, options.REQUEST_SERVICE, options.EXPORT_SERVICE_GROUP: + return "String (Enum)" + case options.INT: + return "Integer" + case options.LICENSE_PRODUCT, options.LICENSE_VERSION: + return "String (Enum)" + default: + return "String" + } +} + +func sanitizeUsageLocal(opt options.Option) string { + if opt.Flag == nil { + return "" + } + usage := opt.Flag.Usage + usage = strings.ReplaceAll(usage, "

", " ") + usage = strings.ReplaceAll(usage, "\n", " ") + return strings.TrimSpace(usage) +} + +func normalizeAsciiDocKeyLocal(key string) string { + key = strings.ReplaceAll(key, "pingFederate", "pingfederate") + key = strings.ReplaceAll(key, "pingOne", "pingone") + key = strings.ReplaceAll(key, "PEMFiles", "PemFiles") + return key +} From 1e707ded67b6bd52bb5845c2df86db15906e3383 Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 17 Sep 2025 11:48:35 -0500 Subject: [PATCH 06/26] pre-test --- .../cli-configuration-settings-reference.adoc | 70 +++++++++++++------ docs/dev-ux-portal-docs/nav.adoc | 1 + docs/dev-ux-portal-docs/pingcli.adoc | 4 +- .../pingcli_completion.adoc | 4 +- docs/dev-ux-portal-docs/pingcli_config.adoc | 6 +- .../pingcli_config_add-profile.adoc | 8 +-- .../pingcli_config_delete-profile.adoc | 8 +-- .../pingcli_config_get.adoc | 6 +- .../pingcli_config_list-keys.adoc | 8 +-- .../pingcli_config_list-profiles.adoc | 6 +- .../pingcli_config_set-active-profile.adoc | 6 +- .../pingcli_config_set.adoc | 6 +- .../pingcli_config_unset.adoc | 6 +- .../pingcli_config_view-profile.adoc | 6 +- docs/dev-ux-portal-docs/pingcli_feedback.adoc | 4 +- docs/dev-ux-portal-docs/pingcli_license.adoc | 4 +- docs/dev-ux-portal-docs/pingcli_platform.adoc | 4 +- .../pingcli_platform_export.adoc | 10 +-- docs/dev-ux-portal-docs/pingcli_plugin.adoc | 4 +- .../pingcli_plugin_add.adoc | 4 +- .../pingcli_plugin_list.adoc | 4 +- .../pingcli_plugin_remove.adoc | 4 +- docs/dev-ux-portal-docs/pingcli_request.adoc | 6 +- tools/generate-command-docs/main.go | 6 ++ tools/generate-options-docs/main.go | 29 +++++++- 25 files changed, 142 insertions(+), 82 deletions(-) diff --git a/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc b/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc index b0547c3f..1fa12a21 100644 --- a/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc +++ b/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc @@ -1,6 +1,6 @@ = Configuration Settings Reference :created-date: March 23, 2025 -:revdate: September 16, 2025 +:revdate: September 17, 2025 :resourceid: pingcli_configuration_settings_reference The following configuration settings can be applied when using Ping CLI. @@ -17,7 +17,8 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d | `activeProfile` | String | | | `description` | String | | -| `detailedExitCode` | Boolean | `--detailed-exitcode` / `-D` | Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. +| `detailedExitCode` | Boolean | `--detailed-exitcode` / `-D` | Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or +warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. | `noColor` | Boolean | `--no-color` | Disable text output in color. (default false) | `outputFormat` | String (Enum) | `--output-format` / `-O` | Specify the console output format. (default text) Options are: json, text. | `plugins` | String Array | | @@ -29,23 +30,40 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d |=== |Configuration Key |Data Type |Equivalent Parameter |Purpose -| `service.pingfederate.adminAPIPath` | String | `--pingfederate-admin-api-path` | The PingFederate API URL path used to communicate with PingFederate's admin API. (default /pf-admin-api/v1) -| `service.pingfederate.authentication.accessTokenAuth.accessToken` | String | `--pingfederate-access-token` | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. -| `service.pingfederate.authentication.basicAuth.password` | String | `--pingfederate-password` | The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. -| `service.pingfederate.authentication.basicAuth.username` | String | `--pingfederate-username` | The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication. Example: 'administrator' -| `service.pingfederate.authentication.clientCredentialsAuth.clientID` | String | `--pingfederate-client-id` | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. -| `service.pingfederate.authentication.clientCredentialsAuth.clientSecret` | String | `--pingfederate-client-secret` | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. -| `service.pingfederate.authentication.clientCredentialsAuth.scopes` | String Array | `--pingfederate-scopes` | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. (default []) Accepts a comma-separated string to delimit multiple scopes. Example: 'openid,profile' -| `service.pingfederate.authentication.clientCredentialsAuth.tokenURL` | String | `--pingfederate-token-url` | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. -| `service.pingfederate.authentication.type` | String (Enum) | `--pingfederate-authentication-type` | The authentication type to use when connecting to the PingFederate admin API. Options are: accessTokenAuth, basicAuth, clientCredentialsAuth. Example: 'basicAuth' -| `service.pingfederate.caCertificatePemFiles` | String Array | `--pingfederate-ca-certificate-pem-files` | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to the PingFederate server over HTTPS. (default []) Accepts a comma-separated string to delimit multiple PEM files. -| `service.pingfederate.httpsHost` | String | `--pingfederate-https-host` | The PingFederate HTTPS host used to communicate with PingFederate's admin API. Example: 'https://pingfederate-admin.bxretail.org' -| `service.pingfederate.insecureTrustAllTLS` | Boolean | `--pingfederate-insecure-trust-all-tls` | Trust any certificate when connecting to the PingFederate server admin API. (default false) This is insecure and shouldn't be enabled outside of testing. -| `service.pingfederate.xBypassExternalValidationHeader` | Boolean | `--pingfederate-x-bypass-external-validation-header` | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). (default false) -| `service.pingone.authentication.type` | String (Enum) | `--pingone-authentication-type` | The authentication type to use to authenticate to the PingOne management API. (default worker) Options are: worker. +| `service.pingfederate.adminAPIPath` | String | `--pingfederate-admin-api-path` | The PingFederate API URL path used to communicate with PingFederate's admin API. (default +/pf-admin-api/v1) +| `service.pingfederate.authentication.accessTokenAuth.accessToken` | String | `--pingfederate-access-token` | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom +OAuth 2.0 token method. +| `service.pingfederate.authentication.basicAuth.password` | String | `--pingfederate-password` | The PingFederate password used to authenticate to the PingFederate admin API when using basic +authentication. +| `service.pingfederate.authentication.basicAuth.username` | String | `--pingfederate-username` | The PingFederate username used to authenticate to the PingFederate admin API when using basic +authentication. Example: 'administrator' +| `service.pingfederate.authentication.clientCredentialsAuth.clientID` | String | `--pingfederate-client-id` | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the +OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.clientCredentialsAuth.clientSecret` | String | `--pingfederate-client-secret` | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using +the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.clientCredentialsAuth.scopes` | String Array | `--pingfederate-scopes` | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth +2.0 client credentials grant type. (default []) Accepts a comma-separated string to delimit multiple +scopes. Example: 'openid,profile' +| `service.pingfederate.authentication.clientCredentialsAuth.tokenURL` | String | `--pingfederate-token-url` | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the +OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.type` | String (Enum) | `--pingfederate-authentication-type` | The authentication type to use when connecting to the PingFederate admin API. Options are: +accessTokenAuth, basicAuth, clientCredentialsAuth. Example: 'basicAuth' +| `service.pingfederate.caCertificatePemFiles` | String Array | `--pingfederate-ca-certificate-pem-files` | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to +the PingFederate server over HTTPS. (default []) Accepts a comma-separated string to delimit multiple +PEM files. +| `service.pingfederate.httpsHost` | String | `--pingfederate-https-host` | The PingFederate HTTPS host used to communicate with PingFederate's admin API. Example: +'https://pingfederate-admin.bxretail.org' +| `service.pingfederate.insecureTrustAllTLS` | Boolean | `--pingfederate-insecure-trust-all-tls` | Trust any certificate when connecting to the PingFederate server admin API. (default false) This is +insecure and shouldn't be enabled outside of testing. +| `service.pingfederate.xBypassExternalValidationHeader` | Boolean | `--pingfederate-x-bypass-external-validation-header` | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when +using PingFederate's admin API). (default false) +| `service.pingone.authentication.type` | String (Enum) | `--pingone-authentication-type` | The authentication type to use to authenticate to the PingOne management API. (default worker) +Options are: worker. | `service.pingone.authentication.worker.clientID` | String (UUID Format) | `--pingone-worker-client-id` | The worker client ID used to authenticate to the PingOne management API. | `service.pingone.authentication.worker.clientSecret` | String | `--pingone-worker-client-secret` | The worker client secret used to authenticate to the PingOne management API. -| `service.pingone.authentication.worker.environmentID` | String (UUID Format) | `--pingone-worker-environment-id` | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne management API. +| `service.pingone.authentication.worker.environmentID` | String (UUID Format) | `--pingone-worker-environment-id` | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne +management API. | `service.pingone.regionCode` | String (Enum) | `--pingone-region-code` | The region code of the PingOne tenant. Options are: AP, AU, CA, EU, NA. Example: 'NA' |=== @@ -56,11 +74,14 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d |Configuration Key |Data Type |Equivalent Parameter |Purpose | `export.format` | String (Enum) | `--format` / `-f` | Specifies the export format. (default HCL) Options are: HCL. -| `export.outputDirectory` | String | `--output-directory` / `-d` | Specifies the output directory for export. Can be an absolute filepath or a relative filepath of the present working directory. Example: '/Users/example/pingcli-export' Example: 'pingcli-export' +| `export.outputDirectory` | String | `--output-directory` / `-d` | Specifies the output directory for export. Can be an absolute filepath or a relative filepath of the +present working directory. Example: '/Users/example/pingcli-export' Example: 'pingcli-export' | `export.overwrite` | Boolean | `--overwrite` / `-o` | Overwrites the existing generated exports in output directory. (default false) | `export.pingone.environmentID` | String (UUID Format) | `--pingone-export-environment-id` | The ID of the PingOne environment to export. Must be a valid PingOne UUID. | `export.serviceGroup` | String (Enum) | `--service-group` / `-g` | Specifies the service group to export. Options are: pingone. Example: 'pingone' -| `export.services` | String Array | `--services` / `-s` | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' +| `export.services` | String Array | `--services` / `-s` | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. +Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, +pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' |=== == License properties @@ -69,8 +90,12 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d |=== |Configuration Key |Data Type |Equivalent Parameter |Purpose -| `license.devopsKey` | String | `--devops-key` / `-k` | The DevOps key for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. -| `license.devopsUser` | String | `--devops-user` / `-u` | The DevOps user for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. +| `license.devopsKey` | String | `--devops-key` / `-k` | The DevOps key for the license request. See +https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps +user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. +| `license.devopsUser` | String | `--devops-user` / `-u` | The DevOps user for the license request. See +https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps +user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. |=== == Custom request properties @@ -82,6 +107,7 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d | `request.accessToken` | String | | | `request.accessTokenExpiry` | Integer | | | `request.fail` | Boolean | `--fail` / `-f` | Return non-zero exit code when HTTP custom request returns a failure status code. -| `request.service` | String (Enum) | `--service` / `-s` | The Ping service (configured in the active profile) to send the custom request to. Options are: pingone. Example: 'pingone' +| `request.service` | String (Enum) | `--service` / `-s` | The Ping service (configured in the active profile) to send the custom request to. Options are: +pingone. Example: 'pingone' |=== diff --git a/docs/dev-ux-portal-docs/nav.adoc b/docs/dev-ux-portal-docs/nav.adoc index cb9429e7..4d0e1cc1 100644 --- a/docs/dev-ux-portal-docs/nav.adoc +++ b/docs/dev-ux-portal-docs/nav.adoc @@ -1,4 +1,5 @@ * Command Reference +** xref:command_reference:pingcli.adoc[] ** xref:command_reference:pingcli_completion.adoc[] ** xref:command_reference:pingcli_config.adoc[] *** xref:command_reference:pingcli_config_add-profile.adoc[] diff --git a/docs/dev-ux-portal-docs/pingcli.adoc b/docs/dev-ux-portal-docs/pingcli.adoc index 78c0ca09..614851a7 100644 --- a/docs/dev-ux-portal-docs/pingcli.adoc +++ b/docs/dev-ux-portal-docs/pingcli.adoc @@ -18,10 +18,10 @@ pingcli ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) -h, --help help for pingcli - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - --no-color Disable text output in color. (default false) (default ) + --no-color Disable text output in color. (default false) ---- == Subcommands diff --git a/docs/dev-ux-portal-docs/pingcli_completion.adoc b/docs/dev-ux-portal-docs/pingcli_completion.adoc index cf1f386b..efc2a1e9 100644 --- a/docs/dev-ux-portal-docs/pingcli_completion.adoc +++ b/docs/dev-ux-portal-docs/pingcli_completion.adoc @@ -54,10 +54,10 @@ pingcli completion [SHELL] ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - --no-color Disable text output in color. (default false) (default ) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_config.adoc b/docs/dev-ux-portal-docs/pingcli_config.adoc index 3754cf45..8bbc72be 100644 --- a/docs/dev-ux-portal-docs/pingcli_config.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config.adoc @@ -24,7 +24,7 @@ pingcli config == Options ---- - -U, --unmask-values Unmask secret values. (default false) (default ) + -U, --unmask-values Unmask secret values. (default false) -h, --help help for config ---- @@ -32,10 +32,10 @@ pingcli config ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - --no-color Disable text output in color. (default false) (default ) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_config_add-profile.adoc b/docs/dev-ux-portal-docs/pingcli_config_add-profile.adoc index c0a42863..e3c7f644 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_add-profile.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_add-profile.adoc @@ -34,18 +34,18 @@ pingcli config add-profile [flags] -d, --description string The description of the new configuration profile. -h, --help help for add-profile -n, --name string The name of the new configuration profile. - -s, --set-active Set the new configuration profile as the active profile. (default false) (default ) + -s, --set-active Set the new configuration profile as the active profile. (default false) ---- == Options inherited from parent commands ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - -U, --unmask-values Unmask secret values. (default false) (default ) - --no-color Disable text output in color. (default false) (default ) + -U, --unmask-values Unmask secret values. (default false) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_config_delete-profile.adoc b/docs/dev-ux-portal-docs/pingcli_config_delete-profile.adoc index ebcc3cc5..733eee13 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_delete-profile.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_delete-profile.adoc @@ -31,7 +31,7 @@ pingcli config delete-profile [flags] [profile-name] == Options ---- - -y, --yes Auto-accept the profile deletion confirmation prompt. (default false) (default ) + -y, --yes Auto-accept the profile deletion confirmation prompt. (default false) -h, --help help for delete-profile ---- @@ -39,11 +39,11 @@ pingcli config delete-profile [flags] [profile-name] ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - -U, --unmask-values Unmask secret values. (default false) (default ) - --no-color Disable text output in color. (default false) (default ) + -U, --unmask-values Unmask secret values. (default false) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_config_get.adoc b/docs/dev-ux-portal-docs/pingcli_config_get.adoc index 2a8a260f..454071b1 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_get.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_get.adoc @@ -36,11 +36,11 @@ pingcli config get [flags] key ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - -U, --unmask-values Unmask secret values. (default false) (default ) - --no-color Disable text output in color. (default false) (default ) + -U, --unmask-values Unmask secret values. (default false) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_config_list-keys.adoc b/docs/dev-ux-portal-docs/pingcli_config_list-keys.adoc index cb15fdfe..a5f2e01f 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_list-keys.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_list-keys.adoc @@ -25,7 +25,7 @@ pingcli config list-keys [flags] == Options ---- - -y, --yaml Output configuration keys in YAML format. (default false) (default ) + -y, --yaml Output configuration keys in YAML format. (default false) -h, --help help for list-keys ---- @@ -33,11 +33,11 @@ pingcli config list-keys [flags] ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - -U, --unmask-values Unmask secret values. (default false) (default ) - --no-color Disable text output in color. (default false) (default ) + -U, --unmask-values Unmask secret values. (default false) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_config_list-profiles.adoc b/docs/dev-ux-portal-docs/pingcli_config_list-profiles.adoc index 4d3f772f..e3cbedc3 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_list-profiles.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_list-profiles.adoc @@ -24,11 +24,11 @@ pingcli config list-profiles ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - -U, --unmask-values Unmask secret values. (default false) (default ) - --no-color Disable text output in color. (default false) (default ) + -U, --unmask-values Unmask secret values. (default false) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_config_set-active-profile.adoc b/docs/dev-ux-portal-docs/pingcli_config_set-active-profile.adoc index 909deb17..2f5c9be5 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_set-active-profile.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_set-active-profile.adoc @@ -27,11 +27,11 @@ pingcli config set-active-profile [flags] [profile-name] ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - -U, --unmask-values Unmask secret values. (default false) (default ) - --no-color Disable text output in color. (default false) (default ) + -U, --unmask-values Unmask secret values. (default false) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_config_set.adoc b/docs/dev-ux-portal-docs/pingcli_config_set.adoc index 0a2f4ae7..83a0483c 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_set.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_set.adoc @@ -33,11 +33,11 @@ pingcli config set [flags] key=value ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - -U, --unmask-values Unmask secret values. (default false) (default ) - --no-color Disable text output in color. (default false) (default ) + -U, --unmask-values Unmask secret values. (default false) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_config_unset.adoc b/docs/dev-ux-portal-docs/pingcli_config_unset.adoc index 64693994..b83b0b1e 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_unset.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_unset.adoc @@ -30,11 +30,11 @@ pingcli config unset [flags] key ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - -U, --unmask-values Unmask secret values. (default false) (default ) - --no-color Disable text output in color. (default false) (default ) + -U, --unmask-values Unmask secret values. (default false) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_config_view-profile.adoc b/docs/dev-ux-portal-docs/pingcli_config_view-profile.adoc index c08153da..617c6da4 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_view-profile.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_view-profile.adoc @@ -30,11 +30,11 @@ pingcli config view-profile [flags] [profile-name] ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - -U, --unmask-values Unmask secret values. (default false) (default ) - --no-color Disable text output in color. (default false) (default ) + -U, --unmask-values Unmask secret values. (default false) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_feedback.adoc b/docs/dev-ux-portal-docs/pingcli_feedback.adoc index 2c5559c5..eee2abaa 100644 --- a/docs/dev-ux-portal-docs/pingcli_feedback.adoc +++ b/docs/dev-ux-portal-docs/pingcli_feedback.adoc @@ -23,10 +23,10 @@ pingcli feedback [flags] ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - --no-color Disable text output in color. (default false) (default ) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_license.adoc b/docs/dev-ux-portal-docs/pingcli_license.adoc index 01ab687c..804beca4 100644 --- a/docs/dev-ux-portal-docs/pingcli_license.adoc +++ b/docs/dev-ux-portal-docs/pingcli_license.adoc @@ -39,10 +39,10 @@ pingcli license [flags] ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - --no-color Disable text output in color. (default false) (default ) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_platform.adoc b/docs/dev-ux-portal-docs/pingcli_platform.adoc index 96592a1d..18e28fb5 100644 --- a/docs/dev-ux-portal-docs/pingcli_platform.adoc +++ b/docs/dev-ux-portal-docs/pingcli_platform.adoc @@ -21,10 +21,10 @@ pingcli platform ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - --no-color Disable text output in color. (default false) (default ) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_platform_export.adoc b/docs/dev-ux-portal-docs/pingcli_platform_export.adoc index fb0913d3..01e49604 100644 --- a/docs/dev-ux-portal-docs/pingcli_platform_export.adoc +++ b/docs/dev-ux-portal-docs/pingcli_platform_export.adoc @@ -56,7 +56,7 @@ pingcli platform export [flags] -h, --help help for export -f, --format string Specifies the export format. (default HCL) Options are: HCL. -g, --service-group string Specifies the service group to export. Options are: pingone. Example: 'pingone' - -o, --overwrite Overwrites the existing generated exports in output directory. (default false) (default ) + -o, --overwrite Overwrites the existing generated exports in output directory. (default false) -s, --services []string Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' --pingfederate-access-token string The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. --pingfederate-admin-api-path string The PingFederate API URL path used to communicate with PingFederate's admin API. (default /pf-admin-api/v1) @@ -65,12 +65,12 @@ pingcli platform export [flags] --pingfederate-client-id string The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. --pingfederate-client-secret string The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. --pingfederate-https-host string The PingFederate HTTPS host used to communicate with PingFederate's admin API. Example: 'https://pingfederate-admin.bxretail.org' - --pingfederate-insecure-trust-all-tls Trust any certificate when connecting to the PingFederate server admin API. (default false) This is insecure and shouldn't be enabled outside of testing. (default ) + --pingfederate-insecure-trust-all-tls Trust any certificate when connecting to the PingFederate server admin API. (default false) This is insecure and shouldn't be enabled outside of testing. --pingfederate-password string The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. --pingfederate-scopes []string The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. (default []) Accepts a comma-separated string to delimit multiple scopes. Example: 'openid,profile' --pingfederate-token-url string The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. --pingfederate-username string The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication. Example: 'administrator' - --pingfederate-x-bypass-external-validation-header Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). (default false) (default ) + --pingfederate-x-bypass-external-validation-header Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). (default false) --pingone-authentication-type string The authentication type to use to authenticate to the PingOne management API. (default worker) Options are: worker. --pingone-export-environment-id string The ID of the PingOne environment to export. Must be a valid PingOne UUID. --pingone-region-code string The region code of the PingOne tenant. Options are: AP, AU, CA, EU, NA. Example: 'NA' @@ -83,10 +83,10 @@ pingcli platform export [flags] ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - --no-color Disable text output in color. (default false) (default ) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_plugin.adoc b/docs/dev-ux-portal-docs/pingcli_plugin.adoc index 51189a85..71e91f57 100644 --- a/docs/dev-ux-portal-docs/pingcli_plugin.adoc +++ b/docs/dev-ux-portal-docs/pingcli_plugin.adoc @@ -17,10 +17,10 @@ pingcli plugin ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - --no-color Disable text output in color. (default false) (default ) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_plugin_add.adoc b/docs/dev-ux-portal-docs/pingcli_plugin_add.adoc index 7b399f8c..8087c1b8 100644 --- a/docs/dev-ux-portal-docs/pingcli_plugin_add.adoc +++ b/docs/dev-ux-portal-docs/pingcli_plugin_add.adoc @@ -24,10 +24,10 @@ pingcli plugin add plugin-executable ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - --no-color Disable text output in color. (default false) (default ) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_plugin_list.adoc b/docs/dev-ux-portal-docs/pingcli_plugin_list.adoc index f0299b16..0af89309 100644 --- a/docs/dev-ux-portal-docs/pingcli_plugin_list.adoc +++ b/docs/dev-ux-portal-docs/pingcli_plugin_list.adoc @@ -24,10 +24,10 @@ pingcli plugin list ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - --no-color Disable text output in color. (default false) (default ) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_plugin_remove.adoc b/docs/dev-ux-portal-docs/pingcli_plugin_remove.adoc index fad845f3..52031554 100644 --- a/docs/dev-ux-portal-docs/pingcli_plugin_remove.adoc +++ b/docs/dev-ux-portal-docs/pingcli_plugin_remove.adoc @@ -24,10 +24,10 @@ pingcli plugin remove plugin-executable ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - --no-color Disable text output in color. (default false) (default ) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/docs/dev-ux-portal-docs/pingcli_request.adoc b/docs/dev-ux-portal-docs/pingcli_request.adoc index 2e4690f7..92bdbab8 100644 --- a/docs/dev-ux-portal-docs/pingcli_request.adoc +++ b/docs/dev-ux-portal-docs/pingcli_request.adoc @@ -41,7 +41,7 @@ pingcli request [flags] API_URI == Options ---- - -f, --fail Return non-zero exit code when HTTP custom request returns a failure status code. (default ) + -f, --fail Return non-zero exit code when HTTP custom request returns a failure status code. -h, --help help for request -m, --http-method string The HTTP method to use for the request. (default GET) Options are: DELETE, GET, PATCH, POST, PUT. Example: 'POST' -r, --header []string A custom header to send in the request. Example: --header "Content-Type: application/vnd.pingidentity.user.import+json" @@ -54,10 +54,10 @@ pingcli request [flags] API_URI ---- -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) - -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. (default ) + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -O, --output-format string Specify the console output format. (default text) Options are: json, text. -P, --profile string The name of a configuration profile to use. - --no-color Disable text output in color. (default false) (default ) + --no-color Disable text output in color. (default false) ---- == More information diff --git a/tools/generate-command-docs/main.go b/tools/generate-command-docs/main.go index 6caca71e..51ace933 100644 --- a/tools/generate-command-docs/main.go +++ b/tools/generate-command-docs/main.go @@ -244,6 +244,12 @@ func fail(doing string, err error) { func renderNav(root *cobra.Command) string { var b strings.Builder b.WriteString("* Command Reference\n") + + // Add root command first + rootFile := strings.ReplaceAll(root.CommandPath(), " ", "_") + ".adoc" + fmt.Fprintf(&b, "** xref:command_reference:%s[]\n", rootFile) + + // Add all other commands walkVisible(root, func(c *cobra.Command) { if c == root { return diff --git a/tools/generate-options-docs/main.go b/tools/generate-options-docs/main.go index 842649f5..440939df 100644 --- a/tools/generate-options-docs/main.go +++ b/tools/generate-options-docs/main.go @@ -202,7 +202,34 @@ func sanitizeUsageLocal(opt options.Option) string { usage := opt.Flag.Usage usage = strings.ReplaceAll(usage, "

", " ") usage = strings.ReplaceAll(usage, "\n", " ") - return strings.TrimSpace(usage) + usage = strings.TrimSpace(usage) + + // Word wrap at approximately 100 characters + if len(usage) > 100 { + words := strings.Fields(usage) + var wrapped strings.Builder + lineLength := 0 + + for i, word := range words { + // If adding this word exceeds our limit and it's not the first word in the line + if lineLength+len(word) > 100 && lineLength > 0 { + wrapped.WriteString("\n") + lineLength = 0 + } + + // Add the word + if i > 0 && lineLength > 0 { + wrapped.WriteString(" ") + lineLength++ + } + wrapped.WriteString(word) + lineLength += len(word) + } + + return wrapped.String() + } + + return usage } func normalizeAsciiDocKeyLocal(key string) string { From c824ff3e33190b4ed20d32bfaab5c4fa9796d6cb Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 17 Sep 2025 14:31:40 -0500 Subject: [PATCH 07/26] working, with duplicate function utility --- go.mod | 2 - go.sum | 2 - tools/check-duplicates/main.go | 155 ++++++++++++++ tools/generate-options-docs/docgen/docgen.go | 199 +++++++++++++++++ tools/generate-options-docs/main.go | 212 +------------------ 5 files changed, 358 insertions(+), 212 deletions(-) create mode 100644 tools/check-duplicates/main.go create mode 100644 tools/generate-options-docs/docgen/docgen.go diff --git a/go.mod b/go.mod index e9cebac2..ed2146d9 100644 --- a/go.mod +++ b/go.mod @@ -76,7 +76,6 @@ require ( github.com/charmbracelet/x/term v0.2.1 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/ckaznocha/intrange v0.3.1 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/daixiang0/gci v0.13.6 // indirect github.com/dave/dst v0.27.3 // indirect @@ -182,7 +181,6 @@ require ( github.com/raeperd/recvcheck v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryancurrah/gomodguard v1.4.1 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect diff --git a/go.sum b/go.sum index d05939fa..d88dd941 100644 --- a/go.sum +++ b/go.sum @@ -147,7 +147,6 @@ github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nW github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= @@ -565,7 +564,6 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g= github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= diff --git a/tools/check-duplicates/main.go b/tools/check-duplicates/main.go new file mode 100644 index 00000000..6b84fc61 --- /dev/null +++ b/tools/check-duplicates/main.go @@ -0,0 +1,155 @@ +package main + +// duplicate function body detector +// --------------------------------- +// This small utility scans selected Go source directories and reports functions whose +// bodies are structurally identical. It's intended to help spot accidental copy/paste +// duplication (especially when refactoring small helper functions in tools or +// configuration option handling). +// +// HOW IT WORKS +// 1. Walk a curated set of directories (see includeDirs) collecting .go files (excluding tests). +// 2. Parse each file with the Go parser into an AST. +// 3. For every function with a body, iterate each statement node and build a textual +// representation using the %#v (Go-syntax) formatting of the AST nodes. +// 4. Normalize this textual representation (case fold, trim extra whitespace, remove newlines) +// to reduce noise (e.g., formatting differences) while still being stable. +// 5. Hash (SHA‑256) the normalized body representation. The hash becomes a key in a map +// to the list of (file:function) locations that share that exact body hash. +// 6. Any hash with more than one location is reported as a duplicate. +// +// WHY NORMALIZE? +// The AST %#v output can vary in insignificant whitespace. Normalization reduces false +// negatives from formatting differences but still treats any token / structural change as different. +// +// LIMITATIONS / NON-GOALS +// * Ignores function signatures (we only compare bodies). Two functions with different +// names/parameters but identical logic are flagged—which is desired for dedupe. +// * Does not attempt near-duplicate detection (e.g., only one constant differs). +// * Anonymous functions (lambdas) are ignored because we only traverse top-level *ast.FuncDecl. +// * Methods vs functions: receiver differences are ignored (body only). +// +// EXIT CODES +// 0 = No duplicates found +// 2 = One or more duplicate pairs reported +// 1 = I/O or parsing failure during traversal +// +// TYPICAL USAGE +// go run ./tools/check-duplicates +// or as a CI guard / Makefile target. +// +// To extend scanning, add paths to includeDirs. Keep the list tight to avoid noisy matches +// across unrelated packages. + +import ( + "bytes" + "crypto/sha256" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/fs" + "os" + "path/filepath" + "regexp" + "strings" +) + +// includeDirs restricts the scan to a safe subset of the repository. Adjust cautiously. +var includeDirs = []string{ + "tools", + "internal/configuration/options", +} + +// ignoreFiles filters out generated or undesirable files (currently: test sources). +var ignoreFiles = regexp.MustCompile(`_test\.go$`) + +func main() { + // Map: bodyHash -> list of locations (file:functionName) + funcMap := map[string][]string{} + err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error { + if err != nil || d.IsDir() { + return err + } + if !strings.HasSuffix(path, ".go") || ignoreFiles.MatchString(path) { + return nil + } + if !withinIncluded(path) { + return nil + } + addFile(path, funcMap) + return nil + }) + if err != nil { + fmt.Fprintln(os.Stderr, "walk error:", err) + os.Exit(1) + } + + // Build list of every pair among colliding function bodies. + var collisions [][2]string + for _, locs := range funcMap { + if len(locs) > 1 { + for i := 0; i < len(locs); i++ { + for j := i + 1; j < len(locs); j++ { + collisions = append(collisions, [2]string{locs[i], locs[j]}) + } + } + } + } + + if len(collisions) == 0 { + fmt.Println("No duplicate functions found.") + return + } + fmt.Println("Duplicate functions detected:") + for _, c := range collisions { + fmt.Printf(" - %s == %s\n", c[0], c[1]) + } + os.Exit(2) +} + +// withinIncluded returns true if the path is rooted in one of the includeDirs. +func withinIncluded(path string) bool { + for _, d := range includeDirs { + if strings.HasPrefix(path, d+"/") { + return true + } + } + return false +} + +// addFile parses a Go file, hashes each function body, and records its location under that hash key. +func addFile(path string, funcMap map[string][]string) { + src, err := os.ReadFile(path) + if err != nil { + return // Silent skip; traversal reports aggregate errors only. + } + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, path, src, parser.ParseComments) + if err != nil { + return // Skip unreadable / invalid Go sources. + } + for _, d := range f.Decls { + fd, ok := d.(*ast.FuncDecl) + if !ok || fd.Body == nil { // Skip declarations without bodies (interfaces, externs). + continue + } + var buf bytes.Buffer + for _, s := range fd.Body.List { + buf.WriteString(normalize(fmt.Sprintf("%#v", s))) + } + h := sha256.Sum256(buf.Bytes()) + key := fmt.Sprintf("%x", h) + loc := fmt.Sprintf("%s:%s", path, fd.Name.Name) + funcMap[key] = append(funcMap[key], loc) + } +} + +// normalize reduces insignificant differences in the AST statement dump so that +// logically identical bodies hash the same even if formatting varies. +func normalize(s string) string { + s = strings.ToLower(s) // case-insensitive + s = strings.Join(strings.Fields(s), " ") // collapse all whitespace runs + s = strings.ReplaceAll(s, "\n", " ") // remove line breaks entirely + return s +} diff --git a/tools/generate-options-docs/docgen/docgen.go b/tools/generate-options-docs/docgen/docgen.go new file mode 100644 index 00000000..b95e7170 --- /dev/null +++ b/tools/generate-options-docs/docgen/docgen.go @@ -0,0 +1,199 @@ +package docgen + +import ( + "fmt" + "path/filepath" + "slices" + "strings" + "time" + + "github.com/pingidentity/pingcli/internal/configuration" + "github.com/pingidentity/pingcli/internal/configuration/options" +) + +// GenerateMarkdown renders the options documentation markdown table sections. +func GenerateMarkdown() string { + configuration.InitAllOptions() + propertyCategoryInformation := make(map[string][]string) + for _, option := range options.Options() { + if option.KoanfKey == "" || option.Flag == nil { + continue + } + var flagInfo string + if option.Flag.Shorthand != "" { + flagInfo = fmt.Sprintf("--%s / -%s", option.CobraParamName, option.Flag.Shorthand) + } else { + flagInfo = fmt.Sprintf("--%s", option.CobraParamName) + } + usageString := strings.ReplaceAll(option.Flag.Usage, "\n", "

") + category := "general" + if strings.Contains(option.KoanfKey, ".") { + category = strings.Split(option.KoanfKey, ".")[0] + } + propertyCategoryInformation[category] = append(propertyCategoryInformation[category], fmt.Sprintf("| %s | %d | %s | %s |", option.KoanfKey, option.Type, flagInfo, usageString)) + } + var outputBuilder strings.Builder + cats := make([]string, 0, len(propertyCategoryInformation)) + for k := range propertyCategoryInformation { + cats = append(cats, k) + } + slices.Sort(cats) + for _, category := range cats { + properties := propertyCategoryInformation[category] + slices.Sort(properties) + outputBuilder.WriteString(fmt.Sprintf("#### %s Properties\n\n", category)) + outputBuilder.WriteString("| Config File Property | Type | Equivalent Parameter | Purpose |\n") + outputBuilder.WriteString("|---|---|---|---|\n") + for _, property := range properties { + outputBuilder.WriteString(property + "\n") + } + outputBuilder.WriteString("\n") + } + return outputBuilder.String() +} + +// GenerateAsciiDoc generates a configuration reference in AsciiDoc format. +func GenerateAsciiDoc() string { + configuration.InitAllOptions() + catMap := map[string][]options.Option{} + for _, opt := range options.Options() { + if opt.KoanfKey == "" { + continue + } + root := opt.KoanfKey + if strings.Contains(root, ".") { + root = strings.Split(root, ".")[0] + } + switch root { + case "service": + catMap["service"] = append(catMap["service"], opt) + case "export": + catMap["export"] = append(catMap["export"], opt) + case "license": + catMap["license"] = append(catMap["license"], opt) + case "request": + catMap["request"] = append(catMap["request"], opt) + default: + if !strings.Contains(opt.KoanfKey, ".") { + catMap["general"] = append(catMap["general"], opt) + } + } + } + for k := range catMap { + slices.SortFunc(catMap[k], func(a, b options.Option) int { return strings.Compare(a.KoanfKey, b.KoanfKey) }) + } + var b strings.Builder + created := "March 23, 2025" + revdate := time.Now().Format("January 2, 2006") + b.WriteString("= Configuration Settings Reference\n") + b.WriteString(fmt.Sprintf(":created-date: %s\n", created)) + b.WriteString(fmt.Sprintf(":revdate: %s\n", revdate)) + b.WriteString(":resourceid: pingcli_configuration_settings_reference\n\n") + b.WriteString("The following configuration settings can be applied when using Ping CLI.\n\n") + b.WriteString("The following configuration settings can be applied by using the xref:command_reference:pingcli_config_set.adoc[`config set` command] to persist the configuration value for a given **Configuration Key** in the Ping CLI configuration file.\n\n") + b.WriteString("The configuration file is created at `.pingcli/config.yaml` in the user's home directory.\n\n") + ordered := []struct{ key, title string }{{"general", "General Properties"}, {"service", "Ping Identity platform service properties"}, {"export", "Platform export properties"}, {"license", "License properties"}, {"request", "Custom request properties"}} + for _, sec := range ordered { + opts := catMap[sec.key] + if len(opts) == 0 { + continue + } + b.WriteString("== " + sec.title + "\n\n") + b.WriteString("[cols=\"2,1,2,2\"]\n|===\n") + b.WriteString("|Configuration Key |Data Type |Equivalent Parameter |Purpose\n\n") + for _, opt := range opts { + key := normalizeAsciiDocKey(opt.KoanfKey) + dataType := asciiDocDataType(opt) + eqParam := asciiDocEquivalentParameter(opt) + purpose := sanitizeUsage(opt) + b.WriteString(fmt.Sprintf("| `%s` | %s | %s | %s\n", key, dataType, eqParam, purpose)) + } + b.WriteString("|===\n\n") + } + return b.String() +} + +// ShouldOutputAsciiDoc determines if AsciiDoc format should be used based on file extension or explicit choice. +func ShouldOutputAsciiDoc(outPath string, explicit bool) bool { + if explicit { + return true + } + ext := strings.ToLower(filepath.Ext(outPath)) + return ext == ".adoc" || ext == ".asciidoc" +} + +// Helper functions for AsciiDoc generation +func asciiDocEquivalentParameter(opt options.Option) string { + if opt.Flag == nil { + return "" + } + if opt.Flag.Shorthand != "" { + return fmt.Sprintf("`--%s` / `-%s`", opt.CobraParamName, opt.Flag.Shorthand) + } + return fmt.Sprintf("`--%s`", opt.CobraParamName) +} + +func asciiDocDataType(opt options.Option) string { + switch opt.Type { + case options.BOOL: + return "Boolean" + case options.STRING: + return "String" + case options.STRING_SLICE, options.EXPORT_SERVICES, options.HEADER: + return "String Array" + case options.UUID: + return "String (UUID Format)" + case options.EXPORT_FORMAT, options.OUTPUT_FORMAT, options.PINGFEDERATE_AUTH_TYPE, options.PINGONE_AUTH_TYPE, options.PINGONE_REGION_CODE, options.REQUEST_SERVICE, options.EXPORT_SERVICE_GROUP: + return "String (Enum)" + case options.INT: + return "Integer" + case options.LICENSE_PRODUCT, options.LICENSE_VERSION: + return "String (Enum)" + default: + return "String" + } +} + +func sanitizeUsage(opt options.Option) string { + if opt.Flag == nil { + return "" + } + usage := opt.Flag.Usage + usage = strings.ReplaceAll(usage, "

", " ") + usage = strings.ReplaceAll(usage, "\n", " ") + usage = strings.TrimSpace(usage) + + // Word wrap at approximately 100 characters + if len(usage) > 100 { + words := strings.Fields(usage) + var wrapped strings.Builder + lineLength := 0 + + for i, word := range words { + // If adding this word exceeds our limit and it's not the first word in the line + if lineLength+len(word) > 100 && lineLength > 0 { + wrapped.WriteString("\n") + lineLength = 0 + } + + // Add the word + if i > 0 && lineLength > 0 { + wrapped.WriteString(" ") + lineLength++ + } + wrapped.WriteString(word) + lineLength += len(word) + } + + return wrapped.String() + } + + return usage +} + +func normalizeAsciiDocKey(key string) string { + key = strings.ReplaceAll(key, "pingFederate", "pingfederate") + key = strings.ReplaceAll(key, "pingOne", "pingone") + key = strings.ReplaceAll(key, "PEMFiles", "PemFiles") + return key +} diff --git a/tools/generate-options-docs/main.go b/tools/generate-options-docs/main.go index 440939df..fd96c075 100644 --- a/tools/generate-options-docs/main.go +++ b/tools/generate-options-docs/main.go @@ -1,22 +1,11 @@ package main -// This file contains the entire options documentation generator tool. -// It was consolidated from two separate files (main.go and docgen.go) in September 2025 -// when cleaning up references to the removed internal/options package. -// The tool generates documentation for all CLI configuration options in either -// Markdown or AsciiDoc format. - import ( "flag" "fmt" "os" - "path/filepath" - "slices" - "strings" - "time" - "github.com/pingidentity/pingcli/internal/configuration" - "github.com/pingidentity/pingcli/internal/configuration/options" + docgen "github.com/pingidentity/pingcli/tools/generate-options-docs/docgen" ) // A tiny standalone tool (invoked via `make generate-options-docs`) to output @@ -26,19 +15,13 @@ func main() { asAsciiDoc := flag.Bool("asciidoc", false, "Force AsciiDoc output (default: Markdown unless output file has .adoc/.asciidoc)") flag.Parse() - // Decide format - useAscii := false - if *outFile != "" { - useAscii = shouldOutputAsciiDoc(*outFile, *asAsciiDoc) - } else if *asAsciiDoc { - useAscii = true - } + useAscii := docgen.ShouldOutputAsciiDoc(*outFile, *asAsciiDoc) var content string if useAscii { - content = asciiDoc() + content = docgen.GenerateAsciiDoc() } else { - content = markdown() + content = docgen.GenerateMarkdown() } if *outFile == "" { @@ -51,190 +34,3 @@ func main() { os.Exit(1) } } - -// ---- merged from former docgen.go ---- - -// markdown renders the options documentation markdown table sections. -func markdown() string { - configuration.InitAllOptions() - propertyCategoryInformation := make(map[string][]string) - for _, option := range options.Options() { - if option.KoanfKey == "" || option.Flag == nil { - continue - } - var flagInfo string - if option.Flag.Shorthand != "" { - flagInfo = fmt.Sprintf("--%s / -%s", option.CobraParamName, option.Flag.Shorthand) - } else { - flagInfo = fmt.Sprintf("--%s", option.CobraParamName) - } - usageString := strings.ReplaceAll(option.Flag.Usage, "\n", "

") - category := "general" - if strings.Contains(option.KoanfKey, ".") { - category = strings.Split(option.KoanfKey, ".")[0] - } - propertyCategoryInformation[category] = append(propertyCategoryInformation[category], fmt.Sprintf("| %s | %d | %s | %s |", option.KoanfKey, option.Type, flagInfo, usageString)) - } - var outputBuilder strings.Builder - cats := make([]string, 0, len(propertyCategoryInformation)) - for k := range propertyCategoryInformation { - cats = append(cats, k) - } - slices.Sort(cats) - for _, category := range cats { - properties := propertyCategoryInformation[category] - slices.Sort(properties) - outputBuilder.WriteString(fmt.Sprintf("#### %s Properties\n\n", category)) - outputBuilder.WriteString("| Config File Property | Type | Equivalent Parameter | Purpose |\n") - outputBuilder.WriteString("|---|---|---|---|\n") - for _, property := range properties { - outputBuilder.WriteString(property + "\n") - } - outputBuilder.WriteString("\n") - } - return outputBuilder.String() -} - -// asciiDoc generates a configuration reference in AsciiDoc format. -func asciiDoc() string { - configuration.InitAllOptions() - catMap := map[string][]options.Option{} - for _, opt := range options.Options() { - if opt.KoanfKey == "" { - continue - } - root := opt.KoanfKey - if strings.Contains(root, ".") { - root = strings.Split(root, ".")[0] - } - switch root { - case "service": - catMap["service"] = append(catMap["service"], opt) - case "export": - catMap["export"] = append(catMap["export"], opt) - case "license": - catMap["license"] = append(catMap["license"], opt) - case "request": - catMap["request"] = append(catMap["request"], opt) - default: - if !strings.Contains(opt.KoanfKey, ".") { - catMap["general"] = append(catMap["general"], opt) - } - } - } - for k := range catMap { - slices.SortFunc(catMap[k], func(a, b options.Option) int { return strings.Compare(a.KoanfKey, b.KoanfKey) }) - } - var b strings.Builder - created := "March 23, 2025" - revdate := time.Now().Format("January 2, 2006") - b.WriteString("= Configuration Settings Reference\n") - b.WriteString(fmt.Sprintf(":created-date: %s\n", created)) - b.WriteString(fmt.Sprintf(":revdate: %s\n", revdate)) - b.WriteString(":resourceid: pingcli_configuration_settings_reference\n\n") - b.WriteString("The following configuration settings can be applied when using Ping CLI.\n\n") - b.WriteString("The following configuration settings can be applied by using the xref:command_reference:pingcli_config_set.adoc[`config set` command] to persist the configuration value for a given **Configuration Key** in the Ping CLI configuration file.\n\n") - b.WriteString("The configuration file is created at `.pingcli/config.yaml` in the user's home directory.\n\n") - ordered := []struct{ key, title string }{{"general", "General Properties"}, {"service", "Ping Identity platform service properties"}, {"export", "Platform export properties"}, {"license", "License properties"}, {"request", "Custom request properties"}} - for _, sec := range ordered { - opts := catMap[sec.key] - if len(opts) == 0 { - continue - } - b.WriteString("== " + sec.title + "\n\n") - b.WriteString("[cols=\"2,1,2,2\"]\n|===\n") - b.WriteString("|Configuration Key |Data Type |Equivalent Parameter |Purpose\n\n") - for _, opt := range opts { - key := normalizeAsciiDocKeyLocal(opt.KoanfKey) - dataType := asciiDocDataTypeLocal(opt) - eqParam := asciiDocEquivalentParameterLocal(opt) - purpose := sanitizeUsageLocal(opt) - b.WriteString(fmt.Sprintf("| `%s` | %s | %s | %s\n", key, dataType, eqParam, purpose)) - } - b.WriteString("|===\n\n") - } - return b.String() -} - -func shouldOutputAsciiDoc(outPath string, explicit bool) bool { - if explicit { - return true - } - ext := strings.ToLower(filepath.Ext(outPath)) - return ext == ".adoc" || ext == ".asciidoc" -} - -func asciiDocEquivalentParameterLocal(opt options.Option) string { - if opt.Flag == nil { - return "" - } - if opt.Flag.Shorthand != "" { - return fmt.Sprintf("`--%s` / `-%s`", opt.CobraParamName, opt.Flag.Shorthand) - } - return fmt.Sprintf("`--%s`", opt.CobraParamName) -} - -func asciiDocDataTypeLocal(opt options.Option) string { - switch opt.Type { - case options.BOOL: - return "Boolean" - case options.STRING: - return "String" - case options.STRING_SLICE, options.EXPORT_SERVICES, options.HEADER: - return "String Array" - case options.UUID: - return "String (UUID Format)" - case options.EXPORT_FORMAT, options.OUTPUT_FORMAT, options.PINGFEDERATE_AUTH_TYPE, options.PINGONE_AUTH_TYPE, options.PINGONE_REGION_CODE, options.REQUEST_SERVICE, options.EXPORT_SERVICE_GROUP: - return "String (Enum)" - case options.INT: - return "Integer" - case options.LICENSE_PRODUCT, options.LICENSE_VERSION: - return "String (Enum)" - default: - return "String" - } -} - -func sanitizeUsageLocal(opt options.Option) string { - if opt.Flag == nil { - return "" - } - usage := opt.Flag.Usage - usage = strings.ReplaceAll(usage, "

", " ") - usage = strings.ReplaceAll(usage, "\n", " ") - usage = strings.TrimSpace(usage) - - // Word wrap at approximately 100 characters - if len(usage) > 100 { - words := strings.Fields(usage) - var wrapped strings.Builder - lineLength := 0 - - for i, word := range words { - // If adding this word exceeds our limit and it's not the first word in the line - if lineLength+len(word) > 100 && lineLength > 0 { - wrapped.WriteString("\n") - lineLength = 0 - } - - // Add the word - if i > 0 && lineLength > 0 { - wrapped.WriteString(" ") - lineLength++ - } - wrapped.WriteString(word) - lineLength += len(word) - } - - return wrapped.String() - } - - return usage -} - -func normalizeAsciiDocKeyLocal(key string) string { - key = strings.ReplaceAll(key, "pingFederate", "pingfederate") - key = strings.ReplaceAll(key, "pingOne", "pingone") - key = strings.ReplaceAll(key, "PEMFiles", "PemFiles") - return key -} From 95f200a71a7b45991157e6bf7b19daadbd5222e4 Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 17 Sep 2025 14:32:58 -0500 Subject: [PATCH 08/26] clean up README --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9cb1f49d..b621e65d 100644 --- a/README.md +++ b/README.md @@ -281,11 +281,10 @@ To force a clean rebuild of both the configuration options reference and the ful make generate-all-docs ``` -This target removes any existing `docs/dev-ux-portal-docs` directory, then runs `generate-options-docs` (writing the AsciiDoc configuration reference into `docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc`) followed by `generate-command-docs` (writing per-command pages plus `nav.adoc`). You can optionally inject a - +This target removes any existing `docs/dev-ux-portal-docs` directory, then runs `generate-options-docs` (writing the AsciiDoc configuration reference into `docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc`) followed by `generate-command-docs` (writing per-command pages plus `nav.adoc`). ```shell -go run ./tools/generate-command-docs -date "March 23, 2025" -o ./docs/dev-ux-portal-docs +go run ./tools/generate-command-docs -o ./docs/dev-ux-portal-docs ``` ## Commands From 77a376df22c82fa1fe7e8e9b54b8ce20bb266f45 Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 17 Sep 2025 15:03:12 -0500 Subject: [PATCH 09/26] remove line break logic that is not rendering --- .../cli-configuration-settings-reference.adoc | 68 ++++++------------- tools/generate-command-docs/main.go | 3 + tools/generate-options-docs/docgen/docgen.go | 26 +------ 3 files changed, 25 insertions(+), 72 deletions(-) diff --git a/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc b/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc index 1fa12a21..1ba79375 100644 --- a/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc +++ b/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc @@ -17,8 +17,7 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d | `activeProfile` | String | | | `description` | String | | -| `detailedExitCode` | Boolean | `--detailed-exitcode` / `-D` | Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or -warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. +| `detailedExitCode` | Boolean | `--detailed-exitcode` / `-D` | Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. | `noColor` | Boolean | `--no-color` | Disable text output in color. (default false) | `outputFormat` | String (Enum) | `--output-format` / `-O` | Specify the console output format. (default text) Options are: json, text. | `plugins` | String Array | | @@ -30,40 +29,23 @@ warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded |=== |Configuration Key |Data Type |Equivalent Parameter |Purpose -| `service.pingfederate.adminAPIPath` | String | `--pingfederate-admin-api-path` | The PingFederate API URL path used to communicate with PingFederate's admin API. (default -/pf-admin-api/v1) -| `service.pingfederate.authentication.accessTokenAuth.accessToken` | String | `--pingfederate-access-token` | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom -OAuth 2.0 token method. -| `service.pingfederate.authentication.basicAuth.password` | String | `--pingfederate-password` | The PingFederate password used to authenticate to the PingFederate admin API when using basic -authentication. -| `service.pingfederate.authentication.basicAuth.username` | String | `--pingfederate-username` | The PingFederate username used to authenticate to the PingFederate admin API when using basic -authentication. Example: 'administrator' -| `service.pingfederate.authentication.clientCredentialsAuth.clientID` | String | `--pingfederate-client-id` | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the -OAuth 2.0 client credentials grant type. -| `service.pingfederate.authentication.clientCredentialsAuth.clientSecret` | String | `--pingfederate-client-secret` | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using -the OAuth 2.0 client credentials grant type. -| `service.pingfederate.authentication.clientCredentialsAuth.scopes` | String Array | `--pingfederate-scopes` | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth -2.0 client credentials grant type. (default []) Accepts a comma-separated string to delimit multiple -scopes. Example: 'openid,profile' -| `service.pingfederate.authentication.clientCredentialsAuth.tokenURL` | String | `--pingfederate-token-url` | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the -OAuth 2.0 client credentials grant type. -| `service.pingfederate.authentication.type` | String (Enum) | `--pingfederate-authentication-type` | The authentication type to use when connecting to the PingFederate admin API. Options are: -accessTokenAuth, basicAuth, clientCredentialsAuth. Example: 'basicAuth' -| `service.pingfederate.caCertificatePemFiles` | String Array | `--pingfederate-ca-certificate-pem-files` | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to -the PingFederate server over HTTPS. (default []) Accepts a comma-separated string to delimit multiple -PEM files. -| `service.pingfederate.httpsHost` | String | `--pingfederate-https-host` | The PingFederate HTTPS host used to communicate with PingFederate's admin API. Example: -'https://pingfederate-admin.bxretail.org' -| `service.pingfederate.insecureTrustAllTLS` | Boolean | `--pingfederate-insecure-trust-all-tls` | Trust any certificate when connecting to the PingFederate server admin API. (default false) This is -insecure and shouldn't be enabled outside of testing. -| `service.pingfederate.xBypassExternalValidationHeader` | Boolean | `--pingfederate-x-bypass-external-validation-header` | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when -using PingFederate's admin API). (default false) -| `service.pingone.authentication.type` | String (Enum) | `--pingone-authentication-type` | The authentication type to use to authenticate to the PingOne management API. (default worker) -Options are: worker. +| `service.pingfederate.adminAPIPath` | String | `--pingfederate-admin-api-path` | The PingFederate API URL path used to communicate with PingFederate's admin API. (default /pf-admin-api/v1) +| `service.pingfederate.authentication.accessTokenAuth.accessToken` | String | `--pingfederate-access-token` | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. +| `service.pingfederate.authentication.basicAuth.password` | String | `--pingfederate-password` | The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. +| `service.pingfederate.authentication.basicAuth.username` | String | `--pingfederate-username` | The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication. Example: 'administrator' +| `service.pingfederate.authentication.clientCredentialsAuth.clientID` | String | `--pingfederate-client-id` | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.clientCredentialsAuth.clientSecret` | String | `--pingfederate-client-secret` | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.clientCredentialsAuth.scopes` | String Array | `--pingfederate-scopes` | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. (default []) Accepts a comma-separated string to delimit multiple scopes. Example: 'openid,profile' +| `service.pingfederate.authentication.clientCredentialsAuth.tokenURL` | String | `--pingfederate-token-url` | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.type` | String (Enum) | `--pingfederate-authentication-type` | The authentication type to use when connecting to the PingFederate admin API. Options are: accessTokenAuth, basicAuth, clientCredentialsAuth. Example: 'basicAuth' +| `service.pingfederate.caCertificatePemFiles` | String Array | `--pingfederate-ca-certificate-pem-files` | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to the PingFederate server over HTTPS. (default []) Accepts a comma-separated string to delimit multiple PEM files. +| `service.pingfederate.httpsHost` | String | `--pingfederate-https-host` | The PingFederate HTTPS host used to communicate with PingFederate's admin API. Example: 'https://pingfederate-admin.bxretail.org' +| `service.pingfederate.insecureTrustAllTLS` | Boolean | `--pingfederate-insecure-trust-all-tls` | Trust any certificate when connecting to the PingFederate server admin API. (default false) This is insecure and shouldn't be enabled outside of testing. +| `service.pingfederate.xBypassExternalValidationHeader` | Boolean | `--pingfederate-x-bypass-external-validation-header` | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). (default false) +| `service.pingone.authentication.type` | String (Enum) | `--pingone-authentication-type` | The authentication type to use to authenticate to the PingOne management API. (default worker) Options are: worker. | `service.pingone.authentication.worker.clientID` | String (UUID Format) | `--pingone-worker-client-id` | The worker client ID used to authenticate to the PingOne management API. | `service.pingone.authentication.worker.clientSecret` | String | `--pingone-worker-client-secret` | The worker client secret used to authenticate to the PingOne management API. -| `service.pingone.authentication.worker.environmentID` | String (UUID Format) | `--pingone-worker-environment-id` | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne -management API. +| `service.pingone.authentication.worker.environmentID` | String (UUID Format) | `--pingone-worker-environment-id` | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne management API. | `service.pingone.regionCode` | String (Enum) | `--pingone-region-code` | The region code of the PingOne tenant. Options are: AP, AU, CA, EU, NA. Example: 'NA' |=== @@ -74,14 +56,11 @@ management API. |Configuration Key |Data Type |Equivalent Parameter |Purpose | `export.format` | String (Enum) | `--format` / `-f` | Specifies the export format. (default HCL) Options are: HCL. -| `export.outputDirectory` | String | `--output-directory` / `-d` | Specifies the output directory for export. Can be an absolute filepath or a relative filepath of the -present working directory. Example: '/Users/example/pingcli-export' Example: 'pingcli-export' +| `export.outputDirectory` | String | `--output-directory` / `-d` | Specifies the output directory for export. Can be an absolute filepath or a relative filepath of the present working directory. Example: '/Users/example/pingcli-export' Example: 'pingcli-export' | `export.overwrite` | Boolean | `--overwrite` / `-o` | Overwrites the existing generated exports in output directory. (default false) | `export.pingone.environmentID` | String (UUID Format) | `--pingone-export-environment-id` | The ID of the PingOne environment to export. Must be a valid PingOne UUID. | `export.serviceGroup` | String (Enum) | `--service-group` / `-g` | Specifies the service group to export. Options are: pingone. Example: 'pingone' -| `export.services` | String Array | `--services` / `-s` | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. -Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, -pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' +| `export.services` | String Array | `--services` / `-s` | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' |=== == License properties @@ -90,12 +69,8 @@ pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' |=== |Configuration Key |Data Type |Equivalent Parameter |Purpose -| `license.devopsKey` | String | `--devops-key` / `-k` | The DevOps key for the license request. See -https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps -user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. -| `license.devopsUser` | String | `--devops-user` / `-u` | The DevOps user for the license request. See -https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps -user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. +| `license.devopsKey` | String | `--devops-key` / `-k` | The DevOps key for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. +| `license.devopsUser` | String | `--devops-user` / `-u` | The DevOps user for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. |=== == Custom request properties @@ -107,7 +82,6 @@ user. You can save the DevOps user and key in your profile using the 'pingcli co | `request.accessToken` | String | | | `request.accessTokenExpiry` | Integer | | | `request.fail` | Boolean | `--fail` / `-f` | Return non-zero exit code when HTTP custom request returns a failure status code. -| `request.service` | String (Enum) | `--service` / `-s` | The Ping service (configured in the active profile) to send the custom request to. Options are: -pingone. Example: 'pingone' +| `request.service` | String (Enum) | `--service` / `-s` | The Ping service (configured in the active profile) to send the custom request to. Options are: pingone. Example: 'pingone' |=== diff --git a/tools/generate-command-docs/main.go b/tools/generate-command-docs/main.go index 51ace933..616a1f82 100644 --- a/tools/generate-command-docs/main.go +++ b/tools/generate-command-docs/main.go @@ -162,7 +162,10 @@ func formatFlagBlock(fs *pflag.FlagSet, includeHelp bool, c *cobra.Command) stri } else if f.DefValue != "" && f.DefValue != "" && f.DefValue != "0" && !strings.Contains(desc, "(default") { desc = fmt.Sprintf("%s (default %s)", desc, f.DefValue) } + + // Collapse internal newlines but otherwise keep original spacing; no manual wrapping. desc = strings.ReplaceAll(desc, "\n", " ") + lines = append(lines, line{spec: spec, desc: desc}) } if includeHelp { diff --git a/tools/generate-options-docs/docgen/docgen.go b/tools/generate-options-docs/docgen/docgen.go index b95e7170..f06b17ac 100644 --- a/tools/generate-options-docs/docgen/docgen.go +++ b/tools/generate-options-docs/docgen/docgen.go @@ -163,31 +163,7 @@ func sanitizeUsage(opt options.Option) string { usage = strings.ReplaceAll(usage, "\n", " ") usage = strings.TrimSpace(usage) - // Word wrap at approximately 100 characters - if len(usage) > 100 { - words := strings.Fields(usage) - var wrapped strings.Builder - lineLength := 0 - - for i, word := range words { - // If adding this word exceeds our limit and it's not the first word in the line - if lineLength+len(word) > 100 && lineLength > 0 { - wrapped.WriteString("\n") - lineLength = 0 - } - - // Add the word - if i > 0 && lineLength > 0 { - wrapped.WriteString(" ") - lineLength++ - } - wrapped.WriteString(word) - lineLength += len(word) - } - - return wrapped.String() - } - + // No manual wrapping or hard line break insertion; return collapsed single-line usage. return usage } From 45b5478ef699fc6989972f8db66e479ef6f78b58 Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 17 Sep 2025 15:23:00 -0500 Subject: [PATCH 10/26] split readme, golden tests --- Makefile | 14 +- README.md | 102 +++---------- tools/README_DocumentGeneration.md | 144 ++++++++++++++++++ tools/generate-command-docs/main_test.go | 68 +++++++++ .../testdata/golden/README.txt | 6 + .../testdata/golden/nav.adoc | 23 +++ .../testdata/golden/pingcli.adoc | 34 +++++ .../docgen/docgen_test.go | 66 ++++++++ .../docgen/testdata/golden/README.txt | 6 + .../docgen/testdata/golden/options.adoc | 85 +++++++++++ .../docgen/testdata/golden/options.md | 56 +++++++ 11 files changed, 520 insertions(+), 84 deletions(-) create mode 100644 tools/README_DocumentGeneration.md create mode 100644 tools/generate-command-docs/main_test.go create mode 100644 tools/generate-command-docs/testdata/golden/README.txt create mode 100644 tools/generate-command-docs/testdata/golden/nav.adoc create mode 100644 tools/generate-command-docs/testdata/golden/pingcli.adoc create mode 100644 tools/generate-options-docs/docgen/docgen_test.go create mode 100644 tools/generate-options-docs/docgen/testdata/golden/README.txt create mode 100644 tools/generate-options-docs/docgen/testdata/golden/options.adoc create mode 100644 tools/generate-options-docs/docgen/testdata/golden/options.md diff --git a/Makefile b/Makefile index 75fd656f..c9e31d50 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,7 @@ golangcilint: ## Run golangci-lint for comprehensive code analysis $(GOLANGCI_LINT) run --timeout 5m ./... echo "✅ No linting issues found." -generate-options-docs: ## Generate configuration options documentation (default: AsciiDoc into docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc) +generate-options-docs: ## Generate configuration options documentation then validate via golden tests @echo " > Docs: Generating options documentation..." @if [ -z "$(OUTPUT)" ]; then \ mkdir -p ./docs/dev-ux-portal-docs/general; \ @@ -94,20 +94,24 @@ generate-options-docs: ## Generate configuration options documentation (default: $(GOCMD) run ./tools/generate-options-docs $(OUTPUT); \ echo "✅ Documentation generated with custom OUTPUT $(OUTPUT)"; \ fi + @echo " > Docs: Running golden tests for options docs..." + @$(GOCMD) test ./tools/generate-options-docs/docgen -run TestOptionsDocGeneration >/dev/null && echo "✅ Options documentation golden test passed." -generate-command-docs: ## Generate per-command AsciiDoc pages (and nav.adoc) into docs/dev-ux-portal-docs +generate-command-docs: ## Generate per-command AsciiDoc pages then validate via golden tests @echo " > Docs: Generating command documentation..." mkdir -p ./docs/dev-ux-portal-docs $(GOCMD) run ./tools/generate-command-docs -o ./docs/dev-ux-portal-docs $(COMMAND_DOCS_ARGS) - echo "✅ Command docs generated in docs/dev-ux-portal-docs" + echo "✅ Command docs generated in docs/dev-ux-portal-docs" + @echo " > Docs: Running golden tests for command docs..." + @$(GOCMD) test ./tools/generate-command-docs -run TestCommandDocGeneration >/dev/null && echo "✅ Command documentation golden test passed." -generate-all-docs: ## Rebuild ALL docs from scratch (cleans doc directory, then generates options + command reference) +generate-all-docs: ## Rebuild ALL docs then run golden tests for both sets @echo " > Docs: Rebuilding all documentation (clean + regenerate)..." rm -rf ./docs/dev-ux-portal-docs mkdir -p ./docs/dev-ux-portal-docs/general $(MAKE) generate-options-docs OUTPUT='-o docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc' $(MAKE) generate-command-docs - @echo "✅ All documentation rebuilt." + @echo "✅ All documentation rebuilt and validated via golden tests." protogen: ## Generate Go code from .proto files @echo " > Protogen: Generating gRPC code from proto files..." diff --git a/README.md b/README.md index b621e65d..fcb76264 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,17 @@ The Ping CLI is a unified command line interface for configuring and managing Pi ## Table of Contents - [Install](#install) - - [Docker](#docker) - - [macOS](#macos) - - [Linux](#linux) - - [Windows](#windows) + - [Docker](#docker) + - [macOS](#macos) + - [Linux](#linux) + - [Windows](#windows) - [Verify](#verify) - - [Checksums](#checksums) - - [GPG Signatures](#gpg-signatures) + - [Checksums](#checksums) + - [GPG Signatures](#gpg-signatures) - [Configure Ping CLI](#configure-ping-cli) - [Commands](#commands) - - [Platform Export](#platform-export) - - [Custom Request](#custom-request) + - [Platform Export](#platform-export) + - [Custom Request](#custom-request) - [Getting Help](#getting-help) ## Install @@ -30,11 +30,13 @@ The Ping CLI is a unified command line interface for configuring and managing Pi Use the [Ping CLI Docker image](https://hub.docker.com/r/pingidentity/pingcli) Pull Image: + ```shell docker pull pingidentity/pingcli:latest ``` Example Commands: + ```shell docker run --rm pingidentity/pingcli:latest @@ -43,7 +45,7 @@ docker run --rm pingidentity/pingcli:latest --version ### macOS -##### Homebrew +#### Homebrew Use PingIdentity's Homebrew tap to install Ping CLI @@ -52,7 +54,7 @@ brew tap pingidentity/tap brew install pingcli ``` -##### Manual Installation +#### Manual Installation See [the latest GitHub release](https://github.com/pingidentity/pingcli/releases/latest) for artifact downloads, artifact signatures, and the checksum file. To verify package downloads, see the [Verify Section](#verify). @@ -86,6 +88,7 @@ brew install pingcli See [the latest GitHub release](https://github.com/pingidentity/pingcli/releases/latest) for Alpine (.apk) package downloads. To verify package downloads, see the [Verify Section](#verify). > **_NOTE:_** The following commands may require `sudo` if not run as the root user. + ```shell apk add --allow-untrusted ./pingcli__linux_amd64.apk apk add --allow-untrusted ./pingcli__linux_arm64.apk @@ -96,6 +99,7 @@ apk add --allow-untrusted ./pingcli__linux_arm64.apk See [the latest GitHub release](https://github.com/pingidentity/pingcli/releases/latest) for Debian (.deb) package downloads. To verify package downloads, see the [Verify Section](#verify). > **_NOTE:_** The following commands may require `sudo` if not run as the root user. + ```shell apt install ./pingcli__linux_amd64.deb apt install ./pingcli__linux_arm64.deb @@ -106,6 +110,7 @@ apt install ./pingcli__linux_arm64.deb See [the latest GitHub release](https://github.com/pingidentity/pingcli/releases/latest) for RPM (.rpm) package downloads. To verify package downloads, see the [Verify Section](#verify). > **_NOTE:_** The following commands may require `sudo` if not run as the root user. + ```shell yum install ./pingcli__linux_amd64.rpm yum install ./pingcli__linux_arm64.rpm @@ -114,6 +119,7 @@ yum install ./pingcli__linux_arm64.rpm OR > **_NOTE:_** The following commands may require `sudo` if not run as the root user. + ```shell dnf install ./pingcli__linux_amd64.rpm dnf install ./pingcli__linux_arm64.rpm @@ -124,7 +130,7 @@ dnf install ./pingcli__linux_arm64.rpm > - Use `dnf` for Fedora 22+ and CentOS/RHEL 8+. > Both commands achieve the same result; use the one appropriate for your distribution. -##### Manual Installation +#### Manual Installation See [the latest GitHub release](https://github.com/pingidentity/pingcli/releases/latest) for artifact downloads, artifact signatures, and the checksum file. To verify package downloads, see the [Verify Section](#verify). @@ -144,7 +150,7 @@ sudo mv pingcli /usr/local/bin/pingcli; ### Windows -##### Manual Installation +#### Manual Installation See [the latest GitHub release](https://github.com/pingidentity/pingcli/releases/latest) for artifact downloads, artifact signatures, and the checksum file. To verify package downloads, see the [Verify Section](#verify). @@ -173,7 +179,7 @@ See [the latest GitHub release](https://github.com/pingidentity/pingcli/releases See [the latest GitHub release](https://github.com/pingidentity/pingcli/releases/latest) for the artifact downloads and signature files. -##### Add our public GPG Key via OpenPGP Public Key Server +#### Add our public GPG Key via OpenPGP Public Key Server ```shell gpg --keyserver keys.openpgp.org --recv-key 0x6703FFB15B36A7AC @@ -182,6 +188,7 @@ gpg --keyserver keys.openpgp.org --recv-key 0x6703FFB15B36A7AC OR ##### Add our public GPG Key via MIT PGP Public Key Server + ```shell gpg --keyserver pgp.mit.edu --recv-key 0x6703FFB15B36A7AC ``` @@ -217,75 +224,12 @@ and their purposes. See [Autocompletion Documentation](./docs/autocompletion/autocompletion.md) for information on loading autocompletion for select command flags. -### Generating Configuration Options Documentation - -Use the Makefile target to generate documentation for all configuration options. By default it now writes AsciiDoc to `docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc` for portal ingestion: - -```shell -make generate-options-docs -``` - -Without arguments the Makefile target writes to the portal path above. You can override by providing OUTPUT arguments; without the Makefile (invoking the tool directly) omitting -o still prints to stdout. Provide an output file to write it: - -```shell -make generate-options-docs OUTPUT='-o docs/options.md' -``` - -AsciiDoc is also supported (and is the default when using the Makefile target). The format is auto-detected from the file extension (`.adoc` / `.asciidoc`) or you can force it with `-asciidoc`: - -```shell -make generate-options-docs OUTPUT='-o docs/options.adoc' -``` - -Or run directly: - -```shell -go run ./tools/generate-options-docs -o docs/options.md -go run ./tools/generate-options-docs -o docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc -go run ./tools/generate-options-docs -asciidoc > docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc -``` +### Documentation Generation -The generated AsciiDoc mirrors the manual reference ordering (General, Service, Export, License, Request). The file `cli-configuration-settings-reference.generated.adoc` can be diffed against the manually curated `cli-configuration-settings-reference.adoc` to discover undocumented options. +Documentation generation instructions (configuration options reference, per-command pages, navigation, rebuild workflow, and golden test usage) have moved to a dedicated guide: -### Generating Command Reference Pages +See: `tools/README_DocumentGeneration.md` -You can generate AsciiDoc pages for every command and subcommand. These pages include AsciiDoc attributes (:created-date:, :revdate:, :resourceid:) just below the title to match the formatting used elsewhere. - -In addition to the individual command pages, a hierarchical navigation file `nav.adoc` is always regenerated in `docs/dev-ux-portal-docs`. This file is intended for ingestion by the documentation portal (it provides the bullet + xref structure) and should not be edited manually—changes will be overwritten the next time the generator runs. - -Generate the full set into `docs/dev-ux-portal-docs`: - -```shell -make generate-command-docs -``` - -Clean up generated pages: - -```shell -make clean-command-docs -``` - -You can also invoke the generator directly (advanced use): - -```shell -go run ./tools/generate-command-docs -o ./docs/dev-ux-portal-docs -``` - -By default the date used in the headers is today; override with `-date "March 23, 2025"` if you need a stable revision date: - -### Generating All Documentation - -To force a clean rebuild of both the configuration options reference and the full command reference (including nav.adoc) in a single step (the target deletes `docs/dev-ux-portal-docs` first): - -```shell -make generate-all-docs -``` - -This target removes any existing `docs/dev-ux-portal-docs` directory, then runs `generate-options-docs` (writing the AsciiDoc configuration reference into `docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc`) followed by `generate-command-docs` (writing per-command pages plus `nav.adoc`). - -```shell -go run ./tools/generate-command-docs -o ./docs/dev-ux-portal-docs -``` ## Commands diff --git a/tools/README_DocumentGeneration.md b/tools/README_DocumentGeneration.md new file mode 100644 index 00000000..692efcf2 --- /dev/null +++ b/tools/README_DocumentGeneration.md @@ -0,0 +1,144 @@ +# Documentation Generation (Configuration Options & Command Reference) + +This document explains how to generate all Ping CLI documentation artifacts, how the golden +tests validate output, and the available Makefile targets & direct `go run` equivalents. + +## Overview + +There are two primary documentation generators: + +1. Configuration Options Reference (`tools/generate-options-docs`) +2. Command Reference Pages + Navigation (`tools/generate-command-docs`) + +Both tools produce AsciiDoc that is ingested by the documentation portal. Golden tests +run automatically (via the Makefile targets) to ensure formatting changes are intentional. + +## Configuration Options Documentation + +Generate the configuration options reference (default output path is +`docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc`): + +```shell +make generate-options-docs +``` + +Override the output using the `OUTPUT` variable (the argument you pass to `OUTPUT` is +forwarded directly to the generator): + +```shell +make generate-options-docs OUTPUT='-o docs/options.md' +make generate-options-docs OUTPUT='-o docs/options.adoc' +``` + +When called through the Makefile without `OUTPUT`, AsciiDoc is written to the portal path. +When you invoke the generator directly without `-o`, output is written to stdout. + +Direct invocation examples: + +```shell +go run ./tools/generate-options-docs -o docs/options.md +go run ./tools/generate-options-docs -o docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc +go run ./tools/generate-options-docs -asciidoc > docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc +``` + +The AsciiDoc generator orders sections as: General, Service, Export, License, Request. + +## Command Reference Pages & Navigation + +Generate a page for every command and subcommand plus a hierarchical navigation file +(`nav.adoc`) suitable for portal ingestion: + +```shell +make generate-command-docs +``` + +The generator writes per-command `.adoc` files and `nav.adoc` into `docs/dev-ux-portal-docs`. +Each page includes AsciiDoc attributes: + +```adoc +:created-date: +:revdate: +:resourceid: +``` + +These values appear immediately under the document title. `nav.adoc` is always regenerated; +manual edits will be overwritten. + +Direct invocation: + +```shell +go run ./tools/generate-command-docs -o ./docs/dev-ux-portal-docs +``` + +Override the date used in the page headers (for reproducible builds) with: + +```shell +go run ./tools/generate-command-docs -date "March 23, 2025" -o ./docs/dev-ux-portal-docs +``` + +## Rebuilding All Documentation + +Force a clean rebuild (removes `docs/dev-ux-portal-docs` then regenerates both sets): + +```shell +make generate-all-docs +``` + +Sequence executed: + +1. Remove existing `docs/dev-ux-portal-docs` +2. Generate configuration options reference +3. Generate all command pages + `nav.adoc` +4. Run golden tests (each generator target runs its own test suite) + +Equivalent (manual) direct runs: + +```shell +go run ./tools/generate-options-docs -o docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc +go run ./tools/generate-command-docs -o docs/dev-ux-portal-docs +``` + +## Golden Tests Integration + +Golden tests live alongside each generator: + +- `tools/generate-options-docs/docgen/docgen_test.go` +- `tools/generate-command-docs/main_test.go` + +They compare current output against committed fixtures. Dynamic lines (`:created-date:` and +`:revdate:`) are stripped before comparison to keep goldens stable. + +To intentionally update goldens (for formatting changes): + +```shell +go test ./tools/generate-options-docs/docgen -run TestOptionsDocGeneration -update +go test ./tools/generate-command-docs -run TestCommandDocGeneration -update +``` + +Running the Makefile targets automatically executes the associated golden tests: + +```shell +make generate-options-docs +make generate-command-docs +make generate-all-docs +``` + +## Makefile Targets Summary + +| Target | Purpose | +|--------|---------| +| `generate-options-docs` | Generate configuration options reference (AsciiDoc by default) + run golden test | +| `generate-command-docs` | Generate per-command pages + navigation + run golden test | +| `generate-all-docs` | Clean and rebuild both sets (runs both golden tests) | + +## Troubleshooting + +| Issue | Resolution | +|-------|------------| +| Golden test fails after code change | Run with `-update` to refresh fixtures if changes are intentional | +| Navigation missing root command | Ensure `renderNav` includes the root (already implemented) | +| Dates cause diffs | They are normalized in tests; ensure you did not alter attribute names | + +## See Also + +Main project README: `../README.md` diff --git a/tools/generate-command-docs/main_test.go b/tools/generate-command-docs/main_test.go new file mode 100644 index 00000000..7fd410ee --- /dev/null +++ b/tools/generate-command-docs/main_test.go @@ -0,0 +1,68 @@ +package main + +import ( + "flag" + "os" + "path/filepath" + "strings" + "testing" +) + +var update = flag.Bool("update", false, "update golden files for command docs") + +// TestCommandDocGeneration generates documentation for the real root command and compares +// a subset of produced files (root command + nav) against golden fixtures. +func TestCommandDocGeneration(t *testing.T) { + flag.Parse() + + tmp := t.TempDir() + // Run the generator with deterministic date so golden files are stable. + date := "January 2, 2006" // Intentional fixed sample date + os.Args = []string{"docgen", "-o", tmp, "-date", date} + main() + + goldenDir := filepath.Join("testdata", "golden") + if err := os.MkdirAll(goldenDir, 0o755); err != nil { + t.Fatalf("mkdir golden: %v", err) + } + + files := []string{"pingcli.adoc", "nav.adoc"} + + for _, f := range files { + gotPath := filepath.Join(tmp, f) + gotBytes, err := os.ReadFile(gotPath) + if err != nil { + t.Fatalf("read generated %s: %v", f, err) + } + got := normalizeDynamic(string(gotBytes)) + + goldenPath := filepath.Join(goldenDir, f) + if *update { + if err := os.WriteFile(goldenPath, []byte(got), 0o644); err != nil { + t.Fatalf("write golden %s: %v", f, err) + } + t.Logf("updated golden: %s", f) + continue + } + wantBytes, err := os.ReadFile(goldenPath) + if err != nil { + t.Fatalf("read golden %s: %v (run with -update to create)", f, err) + } + want := normalizeDynamic(string(wantBytes)) + if got != want { + t.Errorf("mismatch for %s\n--- got ---\n%s\n--- want ---\n%s", f, got, want) + } + } +} + +// normalizeDynamic strips lines containing created / revision dates to avoid churn. +func normalizeDynamic(s string) string { + var out []string + for _, line := range strings.Split(s, "\n") { + if strings.HasPrefix(line, ":created-date:") || strings.HasPrefix(line, ":revdate:") { + continue + } + out = append(out, line) + } + return strings.Join(out, "\n") +} diff --git a/tools/generate-command-docs/testdata/golden/README.txt b/tools/generate-command-docs/testdata/golden/README.txt new file mode 100644 index 00000000..e2fa0931 --- /dev/null +++ b/tools/generate-command-docs/testdata/golden/README.txt @@ -0,0 +1,6 @@ +Golden files for command doc generation. Update with: + go test -v ./tools/generate-command-docs -update + +Files captured: + pingcli.adoc - root command page (dates stripped on comparison) + nav.adoc - navigation structure diff --git a/tools/generate-command-docs/testdata/golden/nav.adoc b/tools/generate-command-docs/testdata/golden/nav.adoc new file mode 100644 index 00000000..4d0e1cc1 --- /dev/null +++ b/tools/generate-command-docs/testdata/golden/nav.adoc @@ -0,0 +1,23 @@ +* Command Reference +** xref:command_reference:pingcli.adoc[] +** xref:command_reference:pingcli_completion.adoc[] +** xref:command_reference:pingcli_config.adoc[] +*** xref:command_reference:pingcli_config_add-profile.adoc[] +*** xref:command_reference:pingcli_config_delete-profile.adoc[] +*** xref:command_reference:pingcli_config_get.adoc[] +*** xref:command_reference:pingcli_config_list-keys.adoc[] +*** xref:command_reference:pingcli_config_list-profiles.adoc[] +*** xref:command_reference:pingcli_config_set.adoc[] +*** xref:command_reference:pingcli_config_set-active-profile.adoc[] +*** xref:command_reference:pingcli_config_unset.adoc[] +*** xref:command_reference:pingcli_config_view-profile.adoc[] +** xref:command_reference:pingcli_feedback.adoc[] +** xref:command_reference:pingcli_license.adoc[] +** xref:command_reference:pingcli_platform.adoc[] +*** xref:command_reference:pingcli_platform_export.adoc[] +** xref:command_reference:pingcli_plugin.adoc[] +*** xref:command_reference:pingcli_plugin_add.adoc[] +*** xref:command_reference:pingcli_plugin_list.adoc[] +*** xref:command_reference:pingcli_plugin_remove.adoc[] +** xref:command_reference:pingcli_request.adoc[] + diff --git a/tools/generate-command-docs/testdata/golden/pingcli.adoc b/tools/generate-command-docs/testdata/golden/pingcli.adoc new file mode 100644 index 00000000..a08126f2 --- /dev/null +++ b/tools/generate-command-docs/testdata/golden/pingcli.adoc @@ -0,0 +1,34 @@ += pingcli +:resourceid: pingcli_command_reference_pingcli + +A CLI tool for managing the configuration of Ping Identity products. + +== Synopsis + +A CLI tool for managing the configuration of Ping Identity products. + +---- +pingcli +---- + +== Options + +---- + -C, --config string The relative or full path to a custom Ping CLI configuration file. (default $HOME/.pingcli/config.yaml) + -h, --help help for pingcli + -D, --detailed-exitcode Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. + -O, --output-format string Specify the console output format. (default text) Options are: json, text. + -P, --profile string The name of a configuration profile to use. + --no-color Disable text output in color. (default false) +---- + +== Subcommands + +* xref:pingcli_completion.adoc[] - Prints shell completion scripts +* xref:pingcli_config.adoc[] - Manage the CLI configuration. +* xref:pingcli_feedback.adoc[] - Help us improve the CLI. Report issues or send us feedback on using the CLI tool. +* xref:pingcli_license.adoc[] - Request a new evaluation license. +* xref:pingcli_platform.adoc[] - Administer and manage the Ping integrated platform. +* xref:pingcli_plugin.adoc[] - Manage Ping CLI plugins. +* xref:pingcli_request.adoc[] - Send a custom REST API request to a Ping platform service. + diff --git a/tools/generate-options-docs/docgen/docgen_test.go b/tools/generate-options-docs/docgen/docgen_test.go new file mode 100644 index 00000000..6adf3987 --- /dev/null +++ b/tools/generate-options-docs/docgen/docgen_test.go @@ -0,0 +1,66 @@ +package docgen + +import ( + "flag" + "os" + "path/filepath" + "strings" + "testing" +) + +var update = flag.Bool("update", false, "update golden files for options docs") + +// TestOptionsDocGeneration validates both markdown and AsciiDoc outputs against goldens. +func TestOptionsDocGeneration(t *testing.T) { + flag.Parse() + + md := GenerateMarkdown() + adoc := GenerateAsciiDoc() + + goldenDir := filepath.Join("testdata", "golden") + if err := os.MkdirAll(goldenDir, 0o755); err != nil { + t.Fatalf("mkdir golden: %v", err) + } + + // Normalize dynamic date in AsciiDoc output before storing / comparing. + adocNorm := normalizeDynamic(adoc) + + cases := []struct { + name string + content string + }{ + {"options.md", md}, + {"options.adoc", adocNorm}, + } + + for _, tc := range cases { + goldenPath := filepath.Join(goldenDir, tc.name) + if *update { + if err := os.WriteFile(goldenPath, []byte(tc.content), 0o644); err != nil { + t.Fatalf("write golden %s: %v", tc.name, err) + } + t.Logf("updated golden: %s", tc.name) + continue + } + wantBytes, err := os.ReadFile(goldenPath) + if err != nil { + t.Fatalf("read golden %s: %v (run with -update to create)", tc.name, err) + } + want := string(wantBytes) + if tc.content != want { + t.Errorf("mismatch for %s\n--- got ---\n%s\n--- want ---\n%s", tc.name, tc.content, want) + } + } +} + +// normalizeDynamic removes lines containing :created-date: or :revdate: tokens. +func normalizeDynamic(s string) string { + var out []string + for _, line := range strings.Split(s, "\n") { + if strings.HasPrefix(line, ":created-date:") || strings.HasPrefix(line, ":revdate:") { + continue + } + out = append(out, line) + } + return strings.Join(out, "\n") +} diff --git a/tools/generate-options-docs/docgen/testdata/golden/README.txt b/tools/generate-options-docs/docgen/testdata/golden/README.txt new file mode 100644 index 00000000..265caefc --- /dev/null +++ b/tools/generate-options-docs/docgen/testdata/golden/README.txt @@ -0,0 +1,6 @@ +Golden files for options doc generation. Update with: + go test -v ./tools/generate-options-docs/docgen -update + +Files captured: + options.md - markdown table output + options.adoc - asciidoc configuration reference (dates stripped on comparison) diff --git a/tools/generate-options-docs/docgen/testdata/golden/options.adoc b/tools/generate-options-docs/docgen/testdata/golden/options.adoc new file mode 100644 index 00000000..3a23ee82 --- /dev/null +++ b/tools/generate-options-docs/docgen/testdata/golden/options.adoc @@ -0,0 +1,85 @@ += Configuration Settings Reference +:resourceid: pingcli_configuration_settings_reference + +The following configuration settings can be applied when using Ping CLI. + +The following configuration settings can be applied by using the xref:command_reference:pingcli_config_set.adoc[`config set` command] to persist the configuration value for a given **Configuration Key** in the Ping CLI configuration file. + +The configuration file is created at `.pingcli/config.yaml` in the user's home directory. + +== General Properties + +[cols="2,1,2,2"] +|=== +|Configuration Key |Data Type |Equivalent Parameter |Purpose + +| `activeProfile` | String | | +| `description` | String | | +| `detailedExitCode` | Boolean | `--detailed-exitcode` / `-D` | Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. +| `noColor` | Boolean | `--no-color` | Disable text output in color. (default false) +| `outputFormat` | String (Enum) | `--output-format` / `-O` | Specify the console output format. (default text) Options are: json, text. +| `plugins` | String Array | | +|=== + +== Ping Identity platform service properties + +[cols="2,1,2,2"] +|=== +|Configuration Key |Data Type |Equivalent Parameter |Purpose + +| `service.pingfederate.adminAPIPath` | String | `--pingfederate-admin-api-path` | The PingFederate API URL path used to communicate with PingFederate's admin API. (default /pf-admin-api/v1) +| `service.pingfederate.authentication.accessTokenAuth.accessToken` | String | `--pingfederate-access-token` | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. +| `service.pingfederate.authentication.basicAuth.password` | String | `--pingfederate-password` | The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. +| `service.pingfederate.authentication.basicAuth.username` | String | `--pingfederate-username` | The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication. Example: 'administrator' +| `service.pingfederate.authentication.clientCredentialsAuth.clientID` | String | `--pingfederate-client-id` | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.clientCredentialsAuth.clientSecret` | String | `--pingfederate-client-secret` | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.clientCredentialsAuth.scopes` | String Array | `--pingfederate-scopes` | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. (default []) Accepts a comma-separated string to delimit multiple scopes. Example: 'openid,profile' +| `service.pingfederate.authentication.clientCredentialsAuth.tokenURL` | String | `--pingfederate-token-url` | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.type` | String (Enum) | `--pingfederate-authentication-type` | The authentication type to use when connecting to the PingFederate admin API. Options are: accessTokenAuth, basicAuth, clientCredentialsAuth. Example: 'basicAuth' +| `service.pingfederate.caCertificatePemFiles` | String Array | `--pingfederate-ca-certificate-pem-files` | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to the PingFederate server over HTTPS. (default []) Accepts a comma-separated string to delimit multiple PEM files. +| `service.pingfederate.httpsHost` | String | `--pingfederate-https-host` | The PingFederate HTTPS host used to communicate with PingFederate's admin API. Example: 'https://pingfederate-admin.bxretail.org' +| `service.pingfederate.insecureTrustAllTLS` | Boolean | `--pingfederate-insecure-trust-all-tls` | Trust any certificate when connecting to the PingFederate server admin API. (default false) This is insecure and shouldn't be enabled outside of testing. +| `service.pingfederate.xBypassExternalValidationHeader` | Boolean | `--pingfederate-x-bypass-external-validation-header` | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). (default false) +| `service.pingone.authentication.type` | String (Enum) | `--pingone-authentication-type` | The authentication type to use to authenticate to the PingOne management API. (default worker) Options are: worker. +| `service.pingone.authentication.worker.clientID` | String (UUID Format) | `--pingone-worker-client-id` | The worker client ID used to authenticate to the PingOne management API. +| `service.pingone.authentication.worker.clientSecret` | String | `--pingone-worker-client-secret` | The worker client secret used to authenticate to the PingOne management API. +| `service.pingone.authentication.worker.environmentID` | String (UUID Format) | `--pingone-worker-environment-id` | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne management API. +| `service.pingone.regionCode` | String (Enum) | `--pingone-region-code` | The region code of the PingOne tenant. Options are: AP, AU, CA, EU, NA. Example: 'NA' +|=== + +== Platform export properties + +[cols="2,1,2,2"] +|=== +|Configuration Key |Data Type |Equivalent Parameter |Purpose + +| `export.format` | String (Enum) | `--format` / `-f` | Specifies the export format. (default HCL) Options are: HCL. +| `export.outputDirectory` | String | `--output-directory` / `-d` | Specifies the output directory for export. Can be an absolute filepath or a relative filepath of the present working directory. Example: '/Users/example/pingcli-export' Example: 'pingcli-export' +| `export.overwrite` | Boolean | `--overwrite` / `-o` | Overwrites the existing generated exports in output directory. (default false) +| `export.pingone.environmentID` | String (UUID Format) | `--pingone-export-environment-id` | The ID of the PingOne environment to export. Must be a valid PingOne UUID. +| `export.serviceGroup` | String (Enum) | `--service-group` / `-g` | Specifies the service group to export. Options are: pingone. Example: 'pingone' +| `export.services` | String Array | `--services` / `-s` | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' +|=== + +== License properties + +[cols="2,1,2,2"] +|=== +|Configuration Key |Data Type |Equivalent Parameter |Purpose + +| `license.devopsKey` | String | `--devops-key` / `-k` | The DevOps key for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. +| `license.devopsUser` | String | `--devops-user` / `-u` | The DevOps user for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. +|=== + +== Custom request properties + +[cols="2,1,2,2"] +|=== +|Configuration Key |Data Type |Equivalent Parameter |Purpose + +| `request.accessToken` | String | | +| `request.accessTokenExpiry` | Integer | | +| `request.fail` | Boolean | `--fail` / `-f` | Return non-zero exit code when HTTP custom request returns a failure status code. +| `request.service` | String (Enum) | `--service` / `-s` | The Ping service (configured in the active profile) to send the custom request to. Options are: pingone. Example: 'pingone' +|=== + diff --git a/tools/generate-options-docs/docgen/testdata/golden/options.md b/tools/generate-options-docs/docgen/testdata/golden/options.md new file mode 100644 index 00000000..0e5cda4a --- /dev/null +++ b/tools/generate-options-docs/docgen/testdata/golden/options.md @@ -0,0 +1,56 @@ +#### export Properties + +| Config File Property | Type | Equivalent Parameter | Purpose | +|---|---|---|---| +| export.format | 1 | --format / -f | Specifies the export format. (default HCL)

Options are: HCL. | +| export.outputDirectory | 14 | --output-directory / -d | Specifies the output directory for export. Can be an absolute filepath or a relative filepath of the present working directory.

Example: '/Users/example/pingcli-export'

Example: 'pingcli-export' | +| export.overwrite | 0 | --overwrite / -o | Overwrites the existing generated exports in output directory. (default false) | +| export.pingOne.environmentID | 16 | --pingone-export-environment-id | The ID of the PingOne environment to export. Must be a valid PingOne UUID. | +| export.serviceGroup | 2 | --service-group / -g | Specifies the service group to export.

Options are: pingone.

Example: 'pingone' | +| export.services | 3 | --services / -s | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services.

Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso.

Example: 'pingone-sso,pingone-mfa,pingfederate' | + +#### general Properties + +| Config File Property | Type | Equivalent Parameter | Purpose | +|---|---|---|---| +| detailedExitCode | 0 | --detailed-exitcode / -D | Enable detailed exit code output. (default false)

0 - pingcli command succeeded with no errors or warnings.

1 - pingcli command failed with errors.

2 - pingcli command succeeded with warnings. | +| noColor | 0 | --no-color | Disable text output in color. (default false) | +| outputFormat | 8 | --output-format / -O | Specify the console output format. (default text)

Options are: json, text. | + +#### license Properties + +| Config File Property | Type | Equivalent Parameter | Purpose | +|---|---|---|---| +| license.devopsKey | 14 | --devops-key / -k | The DevOps key for the license request.

See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user.

You can save the DevOps user and key in your profile using the 'pingcli config' commands. | +| license.devopsUser | 14 | --devops-user / -u | The DevOps user for the license request.

See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user.

You can save the DevOps user and key in your profile using the 'pingcli config' commands. | + +#### request Properties + +| Config File Property | Type | Equivalent Parameter | Purpose | +|---|---|---|---| +| request.fail | 0 | --fail / -f | Return non-zero exit code when HTTP custom request returns a failure status code. | +| request.service | 13 | --service / -s | The Ping service (configured in the active profile) to send the custom request to.

Options are: pingone.

Example: 'pingone' | + +#### service Properties + +| Config File Property | Type | Equivalent Parameter | Purpose | +|---|---|---|---| +| service.pingFederate.adminAPIPath | 14 | --pingfederate-admin-api-path | The PingFederate API URL path used to communicate with PingFederate's admin API. (default /pf-admin-api/v1) | +| service.pingFederate.authentication.accessTokenAuth.accessToken | 14 | --pingfederate-access-token | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. | +| service.pingFederate.authentication.basicAuth.password | 14 | --pingfederate-password | The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. | +| service.pingFederate.authentication.basicAuth.username | 14 | --pingfederate-username | The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication.

Example: 'administrator' | +| service.pingFederate.authentication.clientCredentialsAuth.clientID | 14 | --pingfederate-client-id | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | +| service.pingFederate.authentication.clientCredentialsAuth.clientSecret | 14 | --pingfederate-client-secret | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | +| service.pingFederate.authentication.clientCredentialsAuth.scopes | 15 | --pingfederate-scopes | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. (default [])

Accepts a comma-separated string to delimit multiple scopes.

Example: 'openid,profile' | +| service.pingFederate.authentication.clientCredentialsAuth.tokenURL | 14 | --pingfederate-token-url | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | +| service.pingFederate.authentication.type | 9 | --pingfederate-authentication-type | The authentication type to use when connecting to the PingFederate admin API.

Options are: accessTokenAuth, basicAuth, clientCredentialsAuth.

Example: 'basicAuth' | +| service.pingFederate.caCertificatePEMFiles | 15 | --pingfederate-ca-certificate-pem-files | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to the PingFederate server over HTTPS. (default [])

Accepts a comma-separated string to delimit multiple PEM files. | +| service.pingFederate.httpsHost | 14 | --pingfederate-https-host | The PingFederate HTTPS host used to communicate with PingFederate's admin API.

Example: 'https://pingfederate-admin.bxretail.org' | +| service.pingFederate.insecureTrustAllTLS | 0 | --pingfederate-insecure-trust-all-tls | Trust any certificate when connecting to the PingFederate server admin API. (default false)

This is insecure and shouldn't be enabled outside of testing. | +| service.pingFederate.xBypassExternalValidationHeader | 0 | --pingfederate-x-bypass-external-validation-header | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). (default false) | +| service.pingOne.authentication.type | 10 | --pingone-authentication-type | The authentication type to use to authenticate to the PingOne management API. (default worker)

Options are: worker. | +| service.pingOne.authentication.worker.clientID | 16 | --pingone-worker-client-id | The worker client ID used to authenticate to the PingOne management API. | +| service.pingOne.authentication.worker.clientSecret | 14 | --pingone-worker-client-secret | The worker client secret used to authenticate to the PingOne management API. | +| service.pingOne.authentication.worker.environmentID | 16 | --pingone-worker-environment-id | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne management API. | +| service.pingOne.regionCode | 11 | --pingone-region-code | The region code of the PingOne tenant.

Options are: AP, AU, CA, EU, NA.

Example: 'NA' | + From 25f57881d383340a6aae207009fade96c713b42d Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 17 Sep 2025 15:42:43 -0500 Subject: [PATCH 11/26] devcheck pass 1 --- tools/check-duplicates/main.go | 26 ++++- tools/generate-command-docs/main.go | 15 +-- tools/generate-command-docs/main_test.go | 101 +++++++++--------- .../docgen/docgen_test.go | 14 +-- tools/generate-options-docs/main.go | 2 +- 5 files changed, 91 insertions(+), 67 deletions(-) diff --git a/tools/check-duplicates/main.go b/tools/check-duplicates/main.go index 6b84fc61..020b16ce 100644 --- a/tools/check-duplicates/main.go +++ b/tools/check-duplicates/main.go @@ -48,6 +48,7 @@ import ( "go/ast" "go/parser" "go/token" + "io" "io/fs" "os" "path/filepath" @@ -89,7 +90,7 @@ func main() { var collisions [][2]string for _, locs := range funcMap { if len(locs) > 1 { - for i := 0; i < len(locs); i++ { + for i := range locs { for j := i + 1; j < len(locs); j++ { collisions = append(collisions, [2]string{locs[i], locs[j]}) } @@ -120,16 +121,31 @@ func withinIncluded(path string) bool { // addFile parses a Go file, hashes each function body, and records its location under that hash key. func addFile(path string, funcMap map[string][]string) { - src, err := os.ReadFile(path) + // Sanitize and restrict the path before opening (addresses gosec G304 false positive). + clean := filepath.Clean(path) + if filepath.IsAbs(clean) || strings.Contains(clean, "..") { + return // reject unexpected absolute or parent traversals + } + if !withinIncluded(clean) || !strings.HasSuffix(clean, ".go") || ignoreFiles.MatchString(clean) { + return + } + + f, err := os.Open(clean) // #nosec G304: path origin is controlled by WalkDir + allowlist + sanitization above if err != nil { return // Silent skip; traversal reports aggregate errors only. } + defer func() { _ = f.Close() }() + src, err := io.ReadAll(f) + if err != nil { + return + } + fset := token.NewFileSet() - f, err := parser.ParseFile(fset, path, src, parser.ParseComments) + parsed, err := parser.ParseFile(fset, clean, src, parser.ParseComments) if err != nil { return // Skip unreadable / invalid Go sources. } - for _, d := range f.Decls { + for _, d := range parsed.Decls { fd, ok := d.(*ast.FuncDecl) if !ok || fd.Body == nil { // Skip declarations without bodies (interfaces, externs). continue @@ -140,7 +156,7 @@ func addFile(path string, funcMap map[string][]string) { } h := sha256.Sum256(buf.Bytes()) key := fmt.Sprintf("%x", h) - loc := fmt.Sprintf("%s:%s", path, fd.Name.Name) + loc := fmt.Sprintf("%s:%s", clean, fd.Name.Name) funcMap[key] = append(funcMap[key], loc) } } diff --git a/tools/generate-command-docs/main.go b/tools/generate-command-docs/main.go index 616a1f82..4785a216 100644 --- a/tools/generate-command-docs/main.go +++ b/tools/generate-command-docs/main.go @@ -25,7 +25,8 @@ func main() { root := cmd.NewRootCommand(*version, *commit) root.DisableAutoGenTag = true - if err := os.MkdirAll(*outDir, 0o755); err != nil { + // Use tighter directory permissions (group readable/executable only) to satisfy gosec G301. + if err := os.MkdirAll(*outDir, 0o750); err != nil { fail("create out dir", err) } @@ -33,9 +34,9 @@ func main() { walkVisible(root, func(c *cobra.Command) { base := strings.ReplaceAll(c.CommandPath(), " ", "_") file := filepath.Join(*outDir, base+".adoc") - depth := depthOf(c) - content := renderSingle(c, depth, *date, *resourcePrefix) - if err := os.WriteFile(file, []byte(content), 0o644); err != nil { + content := renderSingle(c, *date, *resourcePrefix) + // Restrict file permissions (no world access) for consistency with directory perms. + if err := os.WriteFile(file, []byte(content), 0o600); err != nil { //nolint:gosec // docs non-sensitive; restricting world perms intentionally fail("write file "+file, err) } }) @@ -43,12 +44,12 @@ func main() { // Always (re)generate navigation file for documentation portal ingestion. navPath := filepath.Join(*outDir, "nav.adoc") navContent := renderNav(root) - if err := os.WriteFile(navPath, []byte(navContent), 0o644); err != nil { + if err := os.WriteFile(navPath, []byte(navContent), 0o600); err != nil { //nolint:gosec fail("write nav file", err) } } -func renderSingle(c *cobra.Command, depth int, date, resourcePrefix string) string { +func renderSingle(c *cobra.Command, date, resourcePrefix string) string { // Manual style: always use top-level title '=' regardless of hierarchy. base := strings.ReplaceAll(c.CommandPath(), " ", "_") b := &strings.Builder{} @@ -143,7 +144,7 @@ func formatFlagBlock(fs *pflag.FlagSet, includeHelp bool, c *cobra.Command) stri type line struct{ spec, desc string } var lines []line for _, f := range flags { - spec := "" + var spec string if f.Shorthand != "" { spec = fmt.Sprintf("-%s, --%s", f.Shorthand, f.Name) } else { diff --git a/tools/generate-command-docs/main_test.go b/tools/generate-command-docs/main_test.go index 7fd410ee..c4b262c6 100644 --- a/tools/generate-command-docs/main_test.go +++ b/tools/generate-command-docs/main_test.go @@ -1,11 +1,11 @@ package main import ( - "flag" - "os" - "path/filepath" - "strings" - "testing" + "flag" + "os" + "path/filepath" + "strings" + "testing" ) var update = flag.Bool("update", false, "update golden files for command docs") @@ -13,56 +13,61 @@ var update = flag.Bool("update", false, "update golden files for command docs") // TestCommandDocGeneration generates documentation for the real root command and compares // a subset of produced files (root command + nav) against golden fixtures. func TestCommandDocGeneration(t *testing.T) { - flag.Parse() + flag.Parse() - tmp := t.TempDir() - // Run the generator with deterministic date so golden files are stable. - date := "January 2, 2006" // Intentional fixed sample date - os.Args = []string{"docgen", "-o", tmp, "-date", date} - main() + tmp := t.TempDir() + // Run the generator with deterministic date so golden files are stable. + date := "January 2, 2006" // Intentional fixed sample date + os.Args = []string{"docgen", "-o", tmp, "-date", date} + main() - goldenDir := filepath.Join("testdata", "golden") - if err := os.MkdirAll(goldenDir, 0o755); err != nil { - t.Fatalf("mkdir golden: %v", err) - } + goldenDir := filepath.Join("testdata", "golden") + if err := os.MkdirAll(goldenDir, 0o750); err != nil { + t.Fatalf("mkdir golden: %v", err) + } - files := []string{"pingcli.adoc", "nav.adoc"} + files := []string{"pingcli.adoc", "nav.adoc"} - for _, f := range files { - gotPath := filepath.Join(tmp, f) - gotBytes, err := os.ReadFile(gotPath) - if err != nil { - t.Fatalf("read generated %s: %v", f, err) - } - got := normalizeDynamic(string(gotBytes)) + for _, f := range files { + gotPath := filepath.Join(tmp, f) + // Validate generated file path stays within temporary directory (mitigates G304) + cleanGot := filepath.Clean(gotPath) + if !strings.HasPrefix(cleanGot+string(os.PathSeparator), filepath.Clean(tmp)+string(os.PathSeparator)) { // safety check + t.Fatalf("generated file path %s is outside of temp directory", gotPath) + } + gotBytes, err := os.ReadFile(cleanGot) // #nosec G304 path validated above + if err != nil { + t.Fatalf("read generated %s: %v", f, err) + } + got := normalizeDynamic(string(gotBytes)) - goldenPath := filepath.Join(goldenDir, f) - if *update { - if err := os.WriteFile(goldenPath, []byte(got), 0o644); err != nil { - t.Fatalf("write golden %s: %v", f, err) - } - t.Logf("updated golden: %s", f) - continue - } - wantBytes, err := os.ReadFile(goldenPath) - if err != nil { - t.Fatalf("read golden %s: %v (run with -update to create)", f, err) - } - want := normalizeDynamic(string(wantBytes)) - if got != want { - t.Errorf("mismatch for %s\n--- got ---\n%s\n--- want ---\n%s", f, got, want) - } - } + goldenPath := filepath.Join(goldenDir, f) + if *update { + if err := os.WriteFile(goldenPath, []byte(got), 0o600); err != nil { // restrict world perms + t.Fatalf("write golden %s: %v", f, err) + } + t.Logf("updated golden: %s", f) + continue + } + wantBytes, err := os.ReadFile(goldenPath) // #nosec G304 reading vetted golden path + if err != nil { + t.Fatalf("read golden %s: %v (run with -update to create)", f, err) + } + want := normalizeDynamic(string(wantBytes)) + if got != want { + t.Errorf("mismatch for %s\n--- got ---\n%s\n--- want ---\n%s", f, got, want) + } + } } // normalizeDynamic strips lines containing created / revision dates to avoid churn. func normalizeDynamic(s string) string { - var out []string - for _, line := range strings.Split(s, "\n") { - if strings.HasPrefix(line, ":created-date:") || strings.HasPrefix(line, ":revdate:") { - continue - } - out = append(out, line) - } - return strings.Join(out, "\n") + out := make([]string, 0, 64) + for _, line := range strings.Split(s, "\n") { + if strings.HasPrefix(line, ":created-date:") || strings.HasPrefix(line, ":revdate:") { + continue + } + out = append(out, line) + } + return strings.Join(out, "\n") } diff --git a/tools/generate-options-docs/docgen/docgen_test.go b/tools/generate-options-docs/docgen/docgen_test.go index 6adf3987..b35bfa99 100644 --- a/tools/generate-options-docs/docgen/docgen_test.go +++ b/tools/generate-options-docs/docgen/docgen_test.go @@ -1,4 +1,4 @@ -package docgen +package docgen_test import ( "flag" @@ -6,6 +6,8 @@ import ( "path/filepath" "strings" "testing" + + docgen "github.com/pingidentity/pingcli/tools/generate-options-docs/docgen" ) var update = flag.Bool("update", false, "update golden files for options docs") @@ -14,11 +16,11 @@ var update = flag.Bool("update", false, "update golden files for options docs") func TestOptionsDocGeneration(t *testing.T) { flag.Parse() - md := GenerateMarkdown() - adoc := GenerateAsciiDoc() + md := docgen.GenerateMarkdown() + adoc := docgen.GenerateAsciiDoc() goldenDir := filepath.Join("testdata", "golden") - if err := os.MkdirAll(goldenDir, 0o755); err != nil { + if err := os.MkdirAll(goldenDir, 0o750); err != nil { // tighter perms t.Fatalf("mkdir golden: %v", err) } @@ -36,7 +38,7 @@ func TestOptionsDocGeneration(t *testing.T) { for _, tc := range cases { goldenPath := filepath.Join(goldenDir, tc.name) if *update { - if err := os.WriteFile(goldenPath, []byte(tc.content), 0o644); err != nil { + if err := os.WriteFile(goldenPath, []byte(tc.content), 0o600); err != nil { // restrict world perms t.Fatalf("write golden %s: %v", tc.name, err) } t.Logf("updated golden: %s", tc.name) @@ -55,7 +57,7 @@ func TestOptionsDocGeneration(t *testing.T) { // normalizeDynamic removes lines containing :created-date: or :revdate: tokens. func normalizeDynamic(s string) string { - var out []string + out := make([]string, 0, 128) for _, line := range strings.Split(s, "\n") { if strings.HasPrefix(line, ":created-date:") || strings.HasPrefix(line, ":revdate:") { continue diff --git a/tools/generate-options-docs/main.go b/tools/generate-options-docs/main.go index fd96c075..2276b989 100644 --- a/tools/generate-options-docs/main.go +++ b/tools/generate-options-docs/main.go @@ -29,7 +29,7 @@ func main() { return } - if err := os.WriteFile(*outFile, []byte(content), 0o644); err != nil { + if err := os.WriteFile(*outFile, []byte(content), 0o600); err != nil { // restrict world perms fmt.Fprintf(os.Stderr, "failed to write file: %v\n", err) os.Exit(1) } From 67fd9dec04de8918f1145164262964b300abf8b6 Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 17 Sep 2025 15:49:03 -0500 Subject: [PATCH 12/26] devcheck pass 2 --- tools/generate-command-docs/main.go | 12 +++++++----- tools/generate-command-docs/main_test.go | 1 + tools/generate-options-docs/docgen/docgen_test.go | 10 ++++++++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/tools/generate-command-docs/main.go b/tools/generate-command-docs/main.go index 4785a216..13582dd7 100644 --- a/tools/generate-command-docs/main.go +++ b/tools/generate-command-docs/main.go @@ -36,7 +36,7 @@ func main() { file := filepath.Join(*outDir, base+".adoc") content := renderSingle(c, *date, *resourcePrefix) // Restrict file permissions (no world access) for consistency with directory perms. - if err := os.WriteFile(file, []byte(content), 0o600); err != nil { //nolint:gosec // docs non-sensitive; restricting world perms intentionally + if err := os.WriteFile(file, []byte(content), 0o600); err != nil { fail("write file "+file, err) } }) @@ -44,7 +44,7 @@ func main() { // Always (re)generate navigation file for documentation portal ingestion. navPath := filepath.Join(*outDir, "nav.adoc") navContent := renderNav(root) - if err := os.WriteFile(navPath, []byte(navContent), 0o600); err != nil { //nolint:gosec + if err := os.WriteFile(navPath, []byte(navContent), 0o600); err != nil { fail("write nav file", err) } } @@ -142,7 +142,7 @@ func formatFlagBlock(fs *pflag.FlagSet, includeHelp bool, c *cobra.Command) stri return si < sj }) type line struct{ spec, desc string } - var lines []line + lines := make([]line, 0, len(flags)) for _, f := range flags { var spec string if f.Shorthand != "" { @@ -174,6 +174,7 @@ func formatFlagBlock(fs *pflag.FlagSet, includeHelp bool, c *cobra.Command) stri for _, l := range lines { if strings.Contains(l.spec, "--help") { found = true + break } } @@ -217,8 +218,9 @@ func firstLine(short, long string) string { } func visibleSubcommands(c *cobra.Command) []*cobra.Command { - var subs []*cobra.Command - for _, sc := range c.Commands() { + cmds := c.Commands() + subs := make([]*cobra.Command, 0, len(cmds)) + for _, sc := range cmds { if sc.Hidden { continue } diff --git a/tools/generate-command-docs/main_test.go b/tools/generate-command-docs/main_test.go index c4b262c6..1054b313 100644 --- a/tools/generate-command-docs/main_test.go +++ b/tools/generate-command-docs/main_test.go @@ -47,6 +47,7 @@ func TestCommandDocGeneration(t *testing.T) { t.Fatalf("write golden %s: %v", f, err) } t.Logf("updated golden: %s", f) + continue } wantBytes, err := os.ReadFile(goldenPath) // #nosec G304 reading vetted golden path diff --git a/tools/generate-options-docs/docgen/docgen_test.go b/tools/generate-options-docs/docgen/docgen_test.go index b35bfa99..26717e9c 100644 --- a/tools/generate-options-docs/docgen/docgen_test.go +++ b/tools/generate-options-docs/docgen/docgen_test.go @@ -37,14 +37,20 @@ func TestOptionsDocGeneration(t *testing.T) { for _, tc := range cases { goldenPath := filepath.Join(goldenDir, tc.name) + // Validate golden file path remains within goldenDir to mitigate G304 + cleanGolden := filepath.Clean(goldenPath) + if !strings.HasPrefix(cleanGolden+string(os.PathSeparator), filepath.Clean(goldenDir)+string(os.PathSeparator)) { + t.Fatalf("invalid golden file path: %s", goldenPath) + } if *update { - if err := os.WriteFile(goldenPath, []byte(tc.content), 0o600); err != nil { // restrict world perms + if err := os.WriteFile(cleanGolden, []byte(tc.content), 0o600); err != nil { // restrict world perms t.Fatalf("write golden %s: %v", tc.name, err) } t.Logf("updated golden: %s", tc.name) + continue } - wantBytes, err := os.ReadFile(goldenPath) + wantBytes, err := os.ReadFile(cleanGolden) // #nosec G304 path validated if err != nil { t.Fatalf("read golden %s: %v (run with -update to create)", tc.name, err) } From 3504dabf329171fea987250f2348c057c971a0ff Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 17 Sep 2025 16:15:19 -0500 Subject: [PATCH 13/26] finish devcheck and how-to-use guide --- tools/README_DocumentGeneration.md | 2 +- tools/README_DocumentationUpdatePortal.md | 47 +++++++++++++++++++ tools/generate-command-docs/main.go | 3 ++ tools/generate-command-docs/main_test.go | 6 +-- tools/generate-options-docs/docgen/docgen.go | 1 - .../docgen/docgen_test.go | 2 +- tools/generate-options-docs/main.go | 2 +- 7 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 tools/README_DocumentationUpdatePortal.md diff --git a/tools/README_DocumentGeneration.md b/tools/README_DocumentGeneration.md index 692efcf2..fe6da7d6 100644 --- a/tools/README_DocumentGeneration.md +++ b/tools/README_DocumentGeneration.md @@ -41,7 +41,7 @@ go run ./tools/generate-options-docs -o docs/dev-ux-portal-docs/general/cli-conf go run ./tools/generate-options-docs -asciidoc > docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc ``` -The AsciiDoc generator orders sections as: General, Service, Export, License, Request. +The AsciiDoc generator orders sections as: General, Service, Export, License, Request (as of September 2025 - new options may change this order). ## Command Reference Pages & Navigation diff --git a/tools/README_DocumentationUpdatePortal.md b/tools/README_DocumentationUpdatePortal.md new file mode 100644 index 00000000..84277342 --- /dev/null +++ b/tools/README_DocumentationUpdatePortal.md @@ -0,0 +1,47 @@ +# Documentation Portal Update (https://developer.pingidentity.com/pingcli) + +This document explains how to use the generated documentation artifacts to update +the Ping CLI documentation portal. The goal for future use is automation, but for now +this is a manual process. + +Follow these steps after generating the documentation artifacts as described in +`README_DocumentGeneration.md`. + +## Overview + +There are three primary document types produced by the generators: + +1. nav.adoc (command hierarchy navigation snippet) +2. Command reference pages (one per command/subcommand) +3. Configuration options reference (single page) + +The nav.adoc is a code snippet to be inserted into the portal's `nav.adoc` file. +The command reference pages and configuration options reference are full AsciiDoc +documents suitable for direct inclusion in the portal and can be simply copied over +to the appropriate locations. + +## Process + +### nav.adoc + +Copy the contents of `docs/dev-ux-portal-docs/nav.adoc` and paste it into the +portal's `nav.adoc` file, replacing the content that starts with `* Command Reference`. +This section will need to be replaced each time the command docs are regenerated as +new subcommands are added. + +The target file location in the portal repo is:`asciidoc/modules/ROOT/nav.adoc` + +### Command Reference Pages + +Copy the remaining `.adoc` files from `docs/dev-ux-portal-docs/` to the folder +`asciidoc/modules/command_reference\pages` of the documentation portal repository. +You can overwrite existing files and add any new ones. + +### Configuration Options Reference + +Copy the file `docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc` +to the folder `asciidoc/modules/ROOT/pages/general` of the documentation portal repository, +overwriting the existing file of the same name. + +From this point, you can follow the process for building, reviewing, and publishing +the portal documentation as outlined internally. diff --git a/tools/generate-command-docs/main.go b/tools/generate-command-docs/main.go index 13582dd7..55e80b50 100644 --- a/tools/generate-command-docs/main.go +++ b/tools/generate-command-docs/main.go @@ -26,6 +26,7 @@ func main() { root.DisableAutoGenTag = true // Use tighter directory permissions (group readable/executable only) to satisfy gosec G301. + // Not huge since these are just docs, but still better to be consistent. if err := os.MkdirAll(*outDir, 0o750); err != nil { fail("create out dir", err) } @@ -87,6 +88,8 @@ func renderSingle(c *cobra.Command, date, resourcePrefix string) string { b.WriteString("----\n\n") } + // TODO: See how to render line breaks for readability when generating AsciiDoc to portal + // Attempts to do so thus far have not worked // Options (non-inherited) including help flag. local := c.NonInheritedFlags() inherited := c.InheritedFlags() diff --git a/tools/generate-command-docs/main_test.go b/tools/generate-command-docs/main_test.go index 1054b313..254d462a 100644 --- a/tools/generate-command-docs/main_test.go +++ b/tools/generate-command-docs/main_test.go @@ -32,7 +32,7 @@ func TestCommandDocGeneration(t *testing.T) { gotPath := filepath.Join(tmp, f) // Validate generated file path stays within temporary directory (mitigates G304) cleanGot := filepath.Clean(gotPath) - if !strings.HasPrefix(cleanGot+string(os.PathSeparator), filepath.Clean(tmp)+string(os.PathSeparator)) { // safety check + if !strings.HasPrefix(cleanGot+string(os.PathSeparator), filepath.Clean(tmp)+string(os.PathSeparator)) { t.Fatalf("generated file path %s is outside of temp directory", gotPath) } gotBytes, err := os.ReadFile(cleanGot) // #nosec G304 path validated above @@ -43,14 +43,14 @@ func TestCommandDocGeneration(t *testing.T) { goldenPath := filepath.Join(goldenDir, f) if *update { - if err := os.WriteFile(goldenPath, []byte(got), 0o600); err != nil { // restrict world perms + if err := os.WriteFile(goldenPath, []byte(got), 0o600); err != nil { t.Fatalf("write golden %s: %v", f, err) } t.Logf("updated golden: %s", f) continue } - wantBytes, err := os.ReadFile(goldenPath) // #nosec G304 reading vetted golden path + wantBytes, err := os.ReadFile(goldenPath) // #nosec G304 path validated above if err != nil { t.Fatalf("read golden %s: %v (run with -update to create)", f, err) } diff --git a/tools/generate-options-docs/docgen/docgen.go b/tools/generate-options-docs/docgen/docgen.go index f06b17ac..03c7d4d8 100644 --- a/tools/generate-options-docs/docgen/docgen.go +++ b/tools/generate-options-docs/docgen/docgen.go @@ -163,7 +163,6 @@ func sanitizeUsage(opt options.Option) string { usage = strings.ReplaceAll(usage, "\n", " ") usage = strings.TrimSpace(usage) - // No manual wrapping or hard line break insertion; return collapsed single-line usage. return usage } diff --git a/tools/generate-options-docs/docgen/docgen_test.go b/tools/generate-options-docs/docgen/docgen_test.go index 26717e9c..7188cfc1 100644 --- a/tools/generate-options-docs/docgen/docgen_test.go +++ b/tools/generate-options-docs/docgen/docgen_test.go @@ -43,7 +43,7 @@ func TestOptionsDocGeneration(t *testing.T) { t.Fatalf("invalid golden file path: %s", goldenPath) } if *update { - if err := os.WriteFile(cleanGolden, []byte(tc.content), 0o600); err != nil { // restrict world perms + if err := os.WriteFile(cleanGolden, []byte(tc.content), 0o600); err != nil { t.Fatalf("write golden %s: %v", tc.name, err) } t.Logf("updated golden: %s", tc.name) diff --git a/tools/generate-options-docs/main.go b/tools/generate-options-docs/main.go index 2276b989..d7425bd1 100644 --- a/tools/generate-options-docs/main.go +++ b/tools/generate-options-docs/main.go @@ -29,7 +29,7 @@ func main() { return } - if err := os.WriteFile(*outFile, []byte(content), 0o600); err != nil { // restrict world perms + if err := os.WriteFile(*outFile, []byte(content), 0o600); err != nil { fmt.Fprintf(os.Stderr, "failed to write file: %v\n", err) os.Exit(1) } From 7bf88afb668fb53c0f2c45bc3c89a2080a592395 Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 17 Sep 2025 16:24:51 -0500 Subject: [PATCH 14/26] remove deprecated internal docgen option --- .../configuration/options/docgen/docgen.go | 226 ------------------ 1 file changed, 226 deletions(-) delete mode 100644 internal/configuration/options/docgen/docgen.go diff --git a/internal/configuration/options/docgen/docgen.go b/internal/configuration/options/docgen/docgen.go deleted file mode 100644 index 469b20c4..00000000 --- a/internal/configuration/options/docgen/docgen.go +++ /dev/null @@ -1,226 +0,0 @@ -package docgen - -// Utility to generate markdown documentation for configuration options. -// Extracted from the previous ad-hoc test (Test_outputOptionsMDInfo) so that -// documentation generation can be invoked via a Makefile target instead of a skipped test. - -import ( - "fmt" - "path/filepath" - "slices" - "strings" - "time" - - "github.com/pingidentity/pingcli/internal/configuration" - "github.com/pingidentity/pingcli/internal/configuration/options" -) - -// Markdown renders the options documentation markdown table sections. -// It ensures all options are initialized by calling configuration.InitAllOptions(). -func Markdown() string { - // Ensure options are initialized (idempotent call) - configuration.InitAllOptions() - - propertyCategoryInformation := make(map[string][]string) - - for _, option := range options.Options() { - if option.KoanfKey == "" || option.Flag == nil { - continue - } - - var flagInfo string - if option.Flag.Shorthand != "" { - flagInfo = fmt.Sprintf("--%s / -%s", option.CobraParamName, option.Flag.Shorthand) - } else { - flagInfo = fmt.Sprintf("--%s", option.CobraParamName) - } - - usageString := option.Flag.Usage - // Replace newlines with '

' so GitHub markdown renders intentional paragraph breaks. - usageString = strings.ReplaceAll(usageString, "\n", "

") - - category := "general" - if strings.Contains(option.KoanfKey, ".") { - category = strings.Split(option.KoanfKey, ".")[0] - } - - propertyCategoryInformation[category] = append( - propertyCategoryInformation[category], - fmt.Sprintf("| %s | %d | %s | %s |", option.KoanfKey, option.Type, flagInfo, usageString), - ) - } - - var outputBuilder strings.Builder - // Deterministic ordering of categories - cats := make([]string, 0, len(propertyCategoryInformation)) - for k := range propertyCategoryInformation { - cats = append(cats, k) - } - slices.Sort(cats) - - for _, category := range cats { - properties := propertyCategoryInformation[category] - slices.Sort(properties) - - outputBuilder.WriteString(fmt.Sprintf("#### %s Properties\n\n", category)) - outputBuilder.WriteString("| Config File Property | Type | Equivalent Parameter | Purpose |\n") - outputBuilder.WriteString("|---|---|---|---|\n") - for _, property := range properties { - outputBuilder.WriteString(property + "\n") - } - outputBuilder.WriteString("\n") - } - - return outputBuilder.String() -} - -// ----------------------------- AsciiDoc Generation ----------------------------- - -// AsciiDoc generates a configuration reference in AsciiDoc format mirroring the -// structure of docs/cli-configuration-settings-reference.adoc. -// Sections (in order): -// -// General properties -// Ping Identity platform service properties -// Platform export properties -// License properties -// Custom request properties -// -// Additional categories can be appended over time. -func AsciiDoc() string { - configuration.InitAllOptions() - - // Collect options by root category - catMap := map[string][]options.Option{} - for _, opt := range options.Options() { - if opt.KoanfKey == "" { // skip if no key - continue - } - root := opt.KoanfKey - if strings.Contains(root, ".") { - root = strings.Split(root, ".")[0] - } - switch root { - case "service": - catMap["service"] = append(catMap["service"], opt) - case "export": - catMap["export"] = append(catMap["export"], opt) - case "license": - catMap["license"] = append(catMap["license"], opt) - case "request": - catMap["request"] = append(catMap["request"], opt) - default: - // Root (general) options are those without '.' or not matched above - if !strings.Contains(opt.KoanfKey, ".") { - catMap["general"] = append(catMap["general"], opt) - } - } - } - - // Ensure stable ordering inside each category by key - for k := range catMap { - slices.SortFunc(catMap[k], func(a, b options.Option) int { return strings.Compare(a.KoanfKey, b.KoanfKey) }) - } - - var b strings.Builder - // Header per specification - created := "March 23, 2025" // fixed created-date per request - revdate := time.Now().Format("January 2, 2006") - b.WriteString("= Configuration Settings Reference\n") - b.WriteString(fmt.Sprintf(":created-date: %s\n", created)) - b.WriteString(fmt.Sprintf(":revdate: %s\n", revdate)) - b.WriteString(":resourceid: pingcli_configuration_settings_reference\n\n") - b.WriteString("The following configuration settings can be applied when using Ping CLI.\n\n") - b.WriteString("The following configuration settings can be applied by using the xref:command_reference:pingcli_config_set.adoc[`config set` command] to persist the configuration value for a given **Configuration Key** in the Ping CLI configuration file.\n\n") - b.WriteString("The configuration file is created at `.pingcli/config.yaml` in the user's home directory.\n\n") - - // Ordered sections - ordered := []struct { - key, title string - }{ - {"general", "General Properties"}, - {"service", "Ping Identity platform service properties"}, - {"export", "Platform export properties"}, - {"license", "License properties"}, - {"request", "Custom request properties"}, - } - - for _, sec := range ordered { - opts := catMap[sec.key] - if len(opts) == 0 { - continue - } - b.WriteString("== " + sec.title + "\n\n") - b.WriteString("[cols=\"2,1,2,2\"]\n|===\n") - b.WriteString("|Configuration Key |Data Type |Equivalent Parameter |Purpose\n\n") - for _, opt := range opts { - key := normalizeAsciiDocKey(opt.KoanfKey) - dataType := asciiDocDataType(opt) - eqParam := asciiDocEquivalentParameter(opt) - purpose := sanitizeUsage(opt) - b.WriteString(fmt.Sprintf("| `%s` | %s | %s | %s\n", key, dataType, eqParam, purpose)) - } - b.WriteString("|===\n\n") - } - - return b.String() -} - -// Detect whether an output filename suggests AsciiDoc. -func ShouldOutputAsciiDoc(outPath string, explicit bool) bool { - if explicit { - return true - } - ext := strings.ToLower(filepath.Ext(outPath)) - return ext == ".adoc" || ext == ".asciidoc" -} - -func asciiDocEquivalentParameter(opt options.Option) string { - if opt.Flag == nil { - return "" - } - if opt.Flag.Shorthand != "" { - return fmt.Sprintf("`--%s` / `-%s`", opt.CobraParamName, opt.Flag.Shorthand) - } - return fmt.Sprintf("`--%s`", opt.CobraParamName) -} - -func asciiDocDataType(opt options.Option) string { - switch opt.Type { - case options.BOOL: - return "Boolean" - case options.STRING: - return "String" - case options.STRING_SLICE, options.EXPORT_SERVICES, options.HEADER: - return "String Array" - case options.UUID: - return "String (UUID Format)" - case options.EXPORT_FORMAT, options.OUTPUT_FORMAT, options.PINGFEDERATE_AUTH_TYPE, options.PINGONE_AUTH_TYPE, options.PINGONE_REGION_CODE, options.REQUEST_SERVICE, options.EXPORT_SERVICE_GROUP: - return "String (Enum)" - case options.INT: - return "Integer" - case options.LICENSE_PRODUCT, options.LICENSE_VERSION: - return "String (Enum)" - default: - return "String" - } -} - -func sanitizeUsage(opt options.Option) string { - if opt.Flag == nil { - return "" - } - usage := opt.Flag.Usage - usage = strings.ReplaceAll(usage, "

", " ") - usage = strings.ReplaceAll(usage, "\n", " ") - usage = strings.TrimSpace(usage) - return usage -} - -// Adjust key naming to mirror existing AsciiDoc (lowercase service names, fix PEM case etc.). -func normalizeAsciiDocKey(key string) string { - key = strings.ReplaceAll(key, "pingFederate", "pingfederate") - key = strings.ReplaceAll(key, "pingOne", "pingone") - key = strings.ReplaceAll(key, "PEMFiles", "PemFiles") - return key -} From 3fa1f0b7cde770f3bf09254d4d1899fc06eadd4a Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Thu, 18 Sep 2025 10:15:50 -0500 Subject: [PATCH 15/26] final run confirmed --- Makefile | 2 +- .../general/cli-configuration-settings-reference.adoc | 2 +- docs/dev-ux-portal-docs/pingcli.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_completion.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_config.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_config_add-profile.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_config_delete-profile.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_config_get.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_config_list-keys.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_config_list-profiles.adoc | 4 ++-- .../dev-ux-portal-docs/pingcli_config_set-active-profile.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_config_set.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_config_unset.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_config_view-profile.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_feedback.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_license.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_platform.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_platform_export.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_plugin.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_plugin_add.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_plugin_list.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_plugin_remove.adoc | 4 ++-- docs/dev-ux-portal-docs/pingcli_request.adoc | 4 ++-- 23 files changed, 44 insertions(+), 44 deletions(-) diff --git a/Makefile b/Makefile index c9e31d50..1387e9a4 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endef # PHONY TARGETS # ==================================================================================== -.PHONY: help default install fmt vet test importfmtlint golangcilint devcheck devchecknotest +.PHONY: help default install fmt vet test importfmtlint golangcilint devcheck devchecknotest .PHONY: starttestcontainer removetestcontainer spincontainer openlocalwebapi openapp protogen .PHONY: _check_env _check_ping_env _check_docker _run_pf_container _wait_for_pf _stop_pf_container .PHONY: generate-options-docs generate-command-docs generate-all-docs diff --git a/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc b/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc index 1ba79375..ce9d3c72 100644 --- a/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc +++ b/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc @@ -1,6 +1,6 @@ = Configuration Settings Reference :created-date: March 23, 2025 -:revdate: September 17, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_configuration_settings_reference The following configuration settings can be applied when using Ping CLI. diff --git a/docs/dev-ux-portal-docs/pingcli.adoc b/docs/dev-ux-portal-docs/pingcli.adoc index 614851a7..637ae437 100644 --- a/docs/dev-ux-portal-docs/pingcli.adoc +++ b/docs/dev-ux-portal-docs/pingcli.adoc @@ -1,6 +1,6 @@ = pingcli -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli A CLI tool for managing the configuration of Ping Identity products. diff --git a/docs/dev-ux-portal-docs/pingcli_completion.adoc b/docs/dev-ux-portal-docs/pingcli_completion.adoc index efc2a1e9..d86bc238 100644 --- a/docs/dev-ux-portal-docs/pingcli_completion.adoc +++ b/docs/dev-ux-portal-docs/pingcli_completion.adoc @@ -1,6 +1,6 @@ = pingcli completion -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_completion Prints shell completion scripts diff --git a/docs/dev-ux-portal-docs/pingcli_config.adoc b/docs/dev-ux-portal-docs/pingcli_config.adoc index 8bbc72be..6056d051 100644 --- a/docs/dev-ux-portal-docs/pingcli_config.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config.adoc @@ -1,6 +1,6 @@ = pingcli config -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_config Manage the CLI configuration. diff --git a/docs/dev-ux-portal-docs/pingcli_config_add-profile.adoc b/docs/dev-ux-portal-docs/pingcli_config_add-profile.adoc index e3c7f644..a5c6951f 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_add-profile.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_add-profile.adoc @@ -1,6 +1,6 @@ = pingcli config add-profile -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_config_add-profile Add a new custom configuration profile. diff --git a/docs/dev-ux-portal-docs/pingcli_config_delete-profile.adoc b/docs/dev-ux-portal-docs/pingcli_config_delete-profile.adoc index 733eee13..fe0a4d8a 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_delete-profile.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_delete-profile.adoc @@ -1,6 +1,6 @@ = pingcli config delete-profile -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_config_delete-profile Delete a custom configuration profile. diff --git a/docs/dev-ux-portal-docs/pingcli_config_get.adoc b/docs/dev-ux-portal-docs/pingcli_config_get.adoc index 454071b1..a6aa8520 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_get.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_get.adoc @@ -1,6 +1,6 @@ = pingcli config get -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_config_get Read stored configuration settings for the CLI. diff --git a/docs/dev-ux-portal-docs/pingcli_config_list-keys.adoc b/docs/dev-ux-portal-docs/pingcli_config_list-keys.adoc index a5f2e01f..de516c7a 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_list-keys.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_list-keys.adoc @@ -1,6 +1,6 @@ = pingcli config list-keys -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_config_list-keys List all configuration keys. diff --git a/docs/dev-ux-portal-docs/pingcli_config_list-profiles.adoc b/docs/dev-ux-portal-docs/pingcli_config_list-profiles.adoc index e3cbedc3..42f02b9e 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_list-profiles.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_list-profiles.adoc @@ -1,6 +1,6 @@ = pingcli config list-profiles -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_config_list-profiles List all custom configuration profiles. diff --git a/docs/dev-ux-portal-docs/pingcli_config_set-active-profile.adoc b/docs/dev-ux-portal-docs/pingcli_config_set-active-profile.adoc index 2f5c9be5..cb542738 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_set-active-profile.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_set-active-profile.adoc @@ -1,6 +1,6 @@ = pingcli config set-active-profile -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_config_set-active-profile Set a custom configuration profile as the in-use profile. diff --git a/docs/dev-ux-portal-docs/pingcli_config_set.adoc b/docs/dev-ux-portal-docs/pingcli_config_set.adoc index 83a0483c..77ac8f96 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_set.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_set.adoc @@ -1,6 +1,6 @@ = pingcli config set -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_config_set Set stored configuration settings for the CLI. diff --git a/docs/dev-ux-portal-docs/pingcli_config_unset.adoc b/docs/dev-ux-portal-docs/pingcli_config_unset.adoc index b83b0b1e..a6f4538b 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_unset.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_unset.adoc @@ -1,6 +1,6 @@ = pingcli config unset -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_config_unset Unset stored configuration settings for the CLI. diff --git a/docs/dev-ux-portal-docs/pingcli_config_view-profile.adoc b/docs/dev-ux-portal-docs/pingcli_config_view-profile.adoc index 617c6da4..62482bce 100644 --- a/docs/dev-ux-portal-docs/pingcli_config_view-profile.adoc +++ b/docs/dev-ux-portal-docs/pingcli_config_view-profile.adoc @@ -1,6 +1,6 @@ = pingcli config view-profile -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_config_view-profile View the stored configuration of a custom configuration profile. diff --git a/docs/dev-ux-portal-docs/pingcli_feedback.adoc b/docs/dev-ux-portal-docs/pingcli_feedback.adoc index eee2abaa..28fd2c23 100644 --- a/docs/dev-ux-portal-docs/pingcli_feedback.adoc +++ b/docs/dev-ux-portal-docs/pingcli_feedback.adoc @@ -1,6 +1,6 @@ = pingcli feedback -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_feedback Help us improve the CLI. Report issues or send us feedback on using the CLI tool. diff --git a/docs/dev-ux-portal-docs/pingcli_license.adoc b/docs/dev-ux-portal-docs/pingcli_license.adoc index 804beca4..0010c4b5 100644 --- a/docs/dev-ux-portal-docs/pingcli_license.adoc +++ b/docs/dev-ux-portal-docs/pingcli_license.adoc @@ -1,6 +1,6 @@ = pingcli license -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_license Request a new evaluation license. diff --git a/docs/dev-ux-portal-docs/pingcli_platform.adoc b/docs/dev-ux-portal-docs/pingcli_platform.adoc index 18e28fb5..4b9dbc56 100644 --- a/docs/dev-ux-portal-docs/pingcli_platform.adoc +++ b/docs/dev-ux-portal-docs/pingcli_platform.adoc @@ -1,6 +1,6 @@ = pingcli platform -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_platform Administer and manage the Ping integrated platform. diff --git a/docs/dev-ux-portal-docs/pingcli_platform_export.adoc b/docs/dev-ux-portal-docs/pingcli_platform_export.adoc index 01e49604..f8b19ca5 100644 --- a/docs/dev-ux-portal-docs/pingcli_platform_export.adoc +++ b/docs/dev-ux-portal-docs/pingcli_platform_export.adoc @@ -1,6 +1,6 @@ = pingcli platform export -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_platform_export Export Configuration as Code packages for the Ping Platform. diff --git a/docs/dev-ux-portal-docs/pingcli_plugin.adoc b/docs/dev-ux-portal-docs/pingcli_plugin.adoc index 71e91f57..47e6f1ae 100644 --- a/docs/dev-ux-portal-docs/pingcli_plugin.adoc +++ b/docs/dev-ux-portal-docs/pingcli_plugin.adoc @@ -1,6 +1,6 @@ = pingcli plugin -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_plugin Manage Ping CLI plugins. diff --git a/docs/dev-ux-portal-docs/pingcli_plugin_add.adoc b/docs/dev-ux-portal-docs/pingcli_plugin_add.adoc index 8087c1b8..4e10c7d9 100644 --- a/docs/dev-ux-portal-docs/pingcli_plugin_add.adoc +++ b/docs/dev-ux-portal-docs/pingcli_plugin_add.adoc @@ -1,6 +1,6 @@ = pingcli plugin add -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_plugin_add Add a plugin to use with Ping CLI diff --git a/docs/dev-ux-portal-docs/pingcli_plugin_list.adoc b/docs/dev-ux-portal-docs/pingcli_plugin_list.adoc index 0af89309..481ef8af 100644 --- a/docs/dev-ux-portal-docs/pingcli_plugin_list.adoc +++ b/docs/dev-ux-portal-docs/pingcli_plugin_list.adoc @@ -1,6 +1,6 @@ = pingcli plugin list -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_plugin_list List all plugins currently in use with Ping CLI diff --git a/docs/dev-ux-portal-docs/pingcli_plugin_remove.adoc b/docs/dev-ux-portal-docs/pingcli_plugin_remove.adoc index 52031554..a47735df 100644 --- a/docs/dev-ux-portal-docs/pingcli_plugin_remove.adoc +++ b/docs/dev-ux-portal-docs/pingcli_plugin_remove.adoc @@ -1,6 +1,6 @@ = pingcli plugin remove -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_plugin_remove Remove a plugin from Ping CLI diff --git a/docs/dev-ux-portal-docs/pingcli_request.adoc b/docs/dev-ux-portal-docs/pingcli_request.adoc index 92bdbab8..3e2849d2 100644 --- a/docs/dev-ux-portal-docs/pingcli_request.adoc +++ b/docs/dev-ux-portal-docs/pingcli_request.adoc @@ -1,6 +1,6 @@ = pingcli request -:created-date: September 17, 2025 -:revdate: September 17, 2025 +:created-date: September 18, 2025 +:revdate: September 18, 2025 :resourceid: pingcli_command_reference_pingcli_request Send a custom REST API request to a Ping platform service. From 43c95018684f4f29d50f1805d28876e4317db72d Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Thu, 18 Sep 2025 14:48:49 -0500 Subject: [PATCH 16/26] more devcheck linting --- tools/check-duplicates/main.go | 4 ++++ tools/generate-command-docs/main.go | 5 +++++ tools/generate-command-docs/main_test.go | 1 + tools/generate-options-docs/docgen/docgen.go | 5 +++++ tools/generate-options-docs/docgen/docgen_test.go | 1 + tools/generate-options-docs/main.go | 1 + 6 files changed, 17 insertions(+) diff --git a/tools/check-duplicates/main.go b/tools/check-duplicates/main.go index 020b16ce..fa4b0117 100644 --- a/tools/check-duplicates/main.go +++ b/tools/check-duplicates/main.go @@ -79,6 +79,7 @@ func main() { return nil } addFile(path, funcMap) + return nil }) if err != nil { @@ -100,6 +101,7 @@ func main() { if len(collisions) == 0 { fmt.Println("No duplicate functions found.") + return } fmt.Println("Duplicate functions detected:") @@ -116,6 +118,7 @@ func withinIncluded(path string) bool { return true } } + return false } @@ -167,5 +170,6 @@ func normalize(s string) string { s = strings.ToLower(s) // case-insensitive s = strings.Join(strings.Fields(s), " ") // collapse all whitespace runs s = strings.ReplaceAll(s, "\n", " ") // remove line breaks entirely + return s } diff --git a/tools/generate-command-docs/main.go b/tools/generate-command-docs/main.go index 55e80b50..0abdc6ec 100644 --- a/tools/generate-command-docs/main.go +++ b/tools/generate-command-docs/main.go @@ -142,6 +142,7 @@ func formatFlagBlock(fs *pflag.FlagSet, includeHelp bool, c *cobra.Command) stri if sj == "" { return true } + return si < sj }) type line struct{ spec, desc string } @@ -206,6 +207,7 @@ func formatFlagBlock(fs *pflag.FlagSet, includeHelp bool, c *cobra.Command) stri fmt.Fprintf(&b, " %s%s %s\n", l.spec, strings.Repeat(" ", pad), l.desc) } b.WriteString("----\n") + return b.String() } @@ -217,6 +219,7 @@ func firstLine(short, long string) string { if strings.TrimSpace(long) != "" { return strings.SplitN(strings.TrimSpace(long), "\n", 2)[0] } + return "" } @@ -229,6 +232,7 @@ func visibleSubcommands(c *cobra.Command) []*cobra.Command { } subs = append(subs, sc) } + return subs } @@ -268,5 +272,6 @@ func renderNav(root *cobra.Command) string { fmt.Fprintf(&b, "%s xref:command_reference:%s[]\n", stars, file) }) b.WriteString("\n") + return b.String() } diff --git a/tools/generate-command-docs/main_test.go b/tools/generate-command-docs/main_test.go index 254d462a..613515d2 100644 --- a/tools/generate-command-docs/main_test.go +++ b/tools/generate-command-docs/main_test.go @@ -70,5 +70,6 @@ func normalizeDynamic(s string) string { } out = append(out, line) } + return strings.Join(out, "\n") } diff --git a/tools/generate-options-docs/docgen/docgen.go b/tools/generate-options-docs/docgen/docgen.go index 03c7d4d8..fcfe7fc3 100644 --- a/tools/generate-options-docs/docgen/docgen.go +++ b/tools/generate-options-docs/docgen/docgen.go @@ -49,6 +49,7 @@ func GenerateMarkdown() string { } outputBuilder.WriteString("\n") } + return outputBuilder.String() } @@ -110,6 +111,7 @@ func GenerateAsciiDoc() string { } b.WriteString("|===\n\n") } + return b.String() } @@ -119,6 +121,7 @@ func ShouldOutputAsciiDoc(outPath string, explicit bool) bool { return true } ext := strings.ToLower(filepath.Ext(outPath)) + return ext == ".adoc" || ext == ".asciidoc" } @@ -130,6 +133,7 @@ func asciiDocEquivalentParameter(opt options.Option) string { if opt.Flag.Shorthand != "" { return fmt.Sprintf("`--%s` / `-%s`", opt.CobraParamName, opt.Flag.Shorthand) } + return fmt.Sprintf("`--%s`", opt.CobraParamName) } @@ -170,5 +174,6 @@ func normalizeAsciiDocKey(key string) string { key = strings.ReplaceAll(key, "pingFederate", "pingfederate") key = strings.ReplaceAll(key, "pingOne", "pingone") key = strings.ReplaceAll(key, "PEMFiles", "PemFiles") + return key } diff --git a/tools/generate-options-docs/docgen/docgen_test.go b/tools/generate-options-docs/docgen/docgen_test.go index 7188cfc1..46211aa5 100644 --- a/tools/generate-options-docs/docgen/docgen_test.go +++ b/tools/generate-options-docs/docgen/docgen_test.go @@ -70,5 +70,6 @@ func normalizeDynamic(s string) string { } out = append(out, line) } + return strings.Join(out, "\n") } diff --git a/tools/generate-options-docs/main.go b/tools/generate-options-docs/main.go index d7425bd1..1b864640 100644 --- a/tools/generate-options-docs/main.go +++ b/tools/generate-options-docs/main.go @@ -26,6 +26,7 @@ func main() { if *outFile == "" { fmt.Print(content) + return } From 6dd65ed3305a85ad8232391cae26af8e13a84dd1 Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Thu, 18 Sep 2025 15:25:40 -0500 Subject: [PATCH 17/26] deterministic comparison ignoring dates --- Makefile | 1 - tools/generate-command-docs/main.go | 65 ++++++++++++++++++-- tools/generate-options-docs/docgen/docgen.go | 11 +++- tools/generate-options-docs/main.go | 48 ++++++++++++++- 4 files changed, 114 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 1387e9a4..2ceed9aa 100644 --- a/Makefile +++ b/Makefile @@ -107,7 +107,6 @@ generate-command-docs: ## Generate per-command AsciiDoc pages then validate via generate-all-docs: ## Rebuild ALL docs then run golden tests for both sets @echo " > Docs: Rebuilding all documentation (clean + regenerate)..." - rm -rf ./docs/dev-ux-portal-docs mkdir -p ./docs/dev-ux-portal-docs/general $(MAKE) generate-options-docs OUTPUT='-o docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc' $(MAKE) generate-command-docs diff --git a/tools/generate-command-docs/main.go b/tools/generate-command-docs/main.go index 0abdc6ec..6df9919d 100644 --- a/tools/generate-command-docs/main.go +++ b/tools/generate-command-docs/main.go @@ -31,31 +31,60 @@ func main() { fail("create out dir", err) } - // One file per command path. + // One file per command path with deterministic, content-based updates. walkVisible(root, func(c *cobra.Command) { base := strings.ReplaceAll(c.CommandPath(), " ", "_") file := filepath.Join(*outDir, base+".adoc") - content := renderSingle(c, *date, *resourcePrefix) + + // If file exists, extract existing created-date so it is preserved. + var existingCreated string + if oldRaw, err := os.ReadFile(file); err == nil { + existingCreated = extractDateLine(string(oldRaw), ":created-date:") + } + createdDate := *date + if existingCreated != "" { + createdDate = existingCreated + } + + content := renderSingle(c, createdDate, *date, *resourcePrefix) + + // Determine if underlying (non-date) content actually changed; if not, skip rewrite. + var prevBody string + if oldRaw, err := os.ReadFile(file); err == nil { + prevBody = normalizeForCompare(string(oldRaw)) + } + newBody := normalizeForCompare(content) + if prevBody == newBody && prevBody != "" { + // Skip updating revision date to avoid needless churn. + return + } + // Restrict file permissions (no world access) for consistency with directory perms. if err := os.WriteFile(file, []byte(content), 0o600); err != nil { fail("write file "+file, err) } }) - // Always (re)generate navigation file for documentation portal ingestion. + // Navigation file: only write if changed to keep diffs minimal. navPath := filepath.Join(*outDir, "nav.adoc") navContent := renderNav(root) + if oldNav, err := os.ReadFile(navPath); err == nil { + if string(oldNav) == navContent { + // Unchanged + return + } + } if err := os.WriteFile(navPath, []byte(navContent), 0o600); err != nil { fail("write nav file", err) } } -func renderSingle(c *cobra.Command, date, resourcePrefix string) string { +func renderSingle(c *cobra.Command, createdDate, revDate, resourcePrefix string) string { // Manual style: always use top-level title '=' regardless of hierarchy. base := strings.ReplaceAll(c.CommandPath(), " ", "_") b := &strings.Builder{} fmt.Fprintf(b, "= %s\n", c.CommandPath()) - fmt.Fprintf(b, ":created-date: %s\n:revdate: %s\n:resourceid: %s%s\n\n", date, date, resourcePrefix, base) + fmt.Fprintf(b, ":created-date: %s\n:revdate: %s\n:resourceid: %s%s\n\n", createdDate, revDate, resourcePrefix, base) // Short description (first paragraph only) if s := strings.TrimSpace(firstLine(c.Short, c.Long)); s != "" { @@ -275,3 +304,29 @@ func renderNav(root *cobra.Command) string { return b.String() } + +// normalizeForCompare removes date lines so that comparisons ignore purely date-based churn. +func normalizeForCompare(s string) string { + out := make([]string, 0, 128) + for _, line := range strings.Split(s, "\n") { + if strings.HasPrefix(line, ":created-date:") || strings.HasPrefix(line, ":revdate:") { + continue + } + out = append(out, line) + } + return strings.Join(out, "\n") +} + +// extractDateLine returns the value (sans prefix) of the first matching date line. +func extractDateLine(content, prefix string) string { + for _, line := range strings.Split(content, "\n") { + if strings.HasPrefix(line, prefix) { + // format: :created-date: VALUE + parts := strings.SplitN(line, ": ", 2) + if len(parts) == 2 { + return parts[1] + } + } + } + return "" +} diff --git a/tools/generate-options-docs/docgen/docgen.go b/tools/generate-options-docs/docgen/docgen.go index fcfe7fc3..ac606edc 100644 --- a/tools/generate-options-docs/docgen/docgen.go +++ b/tools/generate-options-docs/docgen/docgen.go @@ -54,7 +54,14 @@ func GenerateMarkdown() string { } // GenerateAsciiDoc generates a configuration reference in AsciiDoc format. -func GenerateAsciiDoc() string { +func GenerateAsciiDoc() string { // backward-compatible wrapper using legacy date behavior + created := "March 23, 2025" + revdate := time.Now().Format("January 2, 2006") + return GenerateAsciiDocWithDates(created, revdate) +} + +// GenerateAsciiDocWithDates renders AsciiDoc with explicit created and revision dates. +func GenerateAsciiDocWithDates(created, revdate string) string { configuration.InitAllOptions() catMap := map[string][]options.Option{} for _, opt := range options.Options() { @@ -84,8 +91,6 @@ func GenerateAsciiDoc() string { slices.SortFunc(catMap[k], func(a, b options.Option) int { return strings.Compare(a.KoanfKey, b.KoanfKey) }) } var b strings.Builder - created := "March 23, 2025" - revdate := time.Now().Format("January 2, 2006") b.WriteString("= Configuration Settings Reference\n") b.WriteString(fmt.Sprintf(":created-date: %s\n", created)) b.WriteString(fmt.Sprintf(":revdate: %s\n", revdate)) diff --git a/tools/generate-options-docs/main.go b/tools/generate-options-docs/main.go index 1b864640..dfe7c59b 100644 --- a/tools/generate-options-docs/main.go +++ b/tools/generate-options-docs/main.go @@ -4,6 +4,8 @@ import ( "flag" "fmt" "os" + "strings" + "time" docgen "github.com/pingidentity/pingcli/tools/generate-options-docs/docgen" ) @@ -13,25 +15,67 @@ import ( func main() { outFile := flag.String("o", "", "Write output to file (extension .md or .adoc determines format unless flags override)") asAsciiDoc := flag.Bool("asciidoc", false, "Force AsciiDoc output (default: Markdown unless output file has .adoc/.asciidoc)") + date := flag.String("date", time.Now().Format("January 2, 2006"), "Revision date to use if content changes (created-date preserved if file exists)") flag.Parse() useAscii := docgen.ShouldOutputAsciiDoc(*outFile, *asAsciiDoc) var content string if useAscii { - content = docgen.GenerateAsciiDoc() + // Pull existing created-date if file already present so we can preserve it. + created := *date + if *outFile != "" { + if raw, err := os.ReadFile(*outFile); err == nil { + if prevCreated := extractDateLine(string(raw), ":created-date:"); prevCreated != "" { + created = prevCreated + } + } + } + content = docgen.GenerateAsciiDocWithDates(created, *date) } else { content = docgen.GenerateMarkdown() } if *outFile == "" { fmt.Print(content) - return } + // Conditional write: only update if non-date content changed. + if oldRaw, err := os.ReadFile(*outFile); err == nil { + if normalizeForCompare(string(oldRaw)) == normalizeForCompare(content) { + // No meaningful change; avoid updating revision date line. + return + } + } + if err := os.WriteFile(*outFile, []byte(content), 0o600); err != nil { fmt.Fprintf(os.Stderr, "failed to write file: %v\n", err) os.Exit(1) } } + +// normalizeForCompare strips created / revision date lines for deterministic comparisons. +func normalizeForCompare(s string) string { + out := make([]string, 0, 256) + for _, line := range strings.Split(s, "\n") { + if strings.HasPrefix(line, ":created-date:") || strings.HasPrefix(line, ":revdate:") { + continue + } + out = append(out, line) + } + return strings.Join(out, "\n") +} + +// extractDateLine returns the value of a date line matching the prefix. +func extractDateLine(content, prefix string) string { + for _, line := range strings.Split(content, "\n") { + if strings.HasPrefix(line, prefix) { + parts := strings.SplitN(line, ": ", 2) + if len(parts) == 2 { + return parts[1] + } + } + } + return "" +} From f85d36ceea74c950acd715c936f674b3ec323d60 Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Thu, 18 Sep 2025 15:31:09 -0500 Subject: [PATCH 18/26] devcheck linting and G304 validation --- tools/generate-command-docs/main.go | 23 +++++++++++++++++--- tools/generate-options-docs/docgen/docgen.go | 1 + tools/generate-options-docs/main.go | 3 +++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/tools/generate-command-docs/main.go b/tools/generate-command-docs/main.go index 6df9919d..18751f1e 100644 --- a/tools/generate-command-docs/main.go +++ b/tools/generate-command-docs/main.go @@ -38,7 +38,7 @@ func main() { // If file exists, extract existing created-date so it is preserved. var existingCreated string - if oldRaw, err := os.ReadFile(file); err == nil { + if oldRaw, err := readFileIfWithin(file, *outDir); err == nil { existingCreated = extractDateLine(string(oldRaw), ":created-date:") } createdDate := *date @@ -50,7 +50,7 @@ func main() { // Determine if underlying (non-date) content actually changed; if not, skip rewrite. var prevBody string - if oldRaw, err := os.ReadFile(file); err == nil { + if oldRaw, err := readFileIfWithin(file, *outDir); err == nil { prevBody = normalizeForCompare(string(oldRaw)) } newBody := normalizeForCompare(content) @@ -68,7 +68,7 @@ func main() { // Navigation file: only write if changed to keep diffs minimal. navPath := filepath.Join(*outDir, "nav.adoc") navContent := renderNav(root) - if oldNav, err := os.ReadFile(navPath); err == nil { + if oldNav, err := readFileIfWithin(navPath, *outDir); err == nil { if string(oldNav) == navContent { // Unchanged return @@ -314,6 +314,7 @@ func normalizeForCompare(s string) string { } out = append(out, line) } + return strings.Join(out, "\n") } @@ -328,5 +329,21 @@ func extractDateLine(content, prefix string) string { } } } + return "" } + +// readFileIfWithin validates that path is within base before reading to satisfy gosec G304. +func readFileIfWithin(path, base string) ([]byte, error) { + cleanBase := filepath.Clean(base) + cleanPath := filepath.Clean(path) + if !strings.HasPrefix(cleanPath+string(os.PathSeparator), cleanBase+string(os.PathSeparator)) { + return nil, fmt.Errorf("refusing to read path outside base directory: %s", path) + } + data, err := os.ReadFile(cleanPath) // #nosec G304 path validated above + if err != nil { + return nil, err + } + + return data, nil +} diff --git a/tools/generate-options-docs/docgen/docgen.go b/tools/generate-options-docs/docgen/docgen.go index ac606edc..4620a6af 100644 --- a/tools/generate-options-docs/docgen/docgen.go +++ b/tools/generate-options-docs/docgen/docgen.go @@ -57,6 +57,7 @@ func GenerateMarkdown() string { func GenerateAsciiDoc() string { // backward-compatible wrapper using legacy date behavior created := "March 23, 2025" revdate := time.Now().Format("January 2, 2006") + return GenerateAsciiDocWithDates(created, revdate) } diff --git a/tools/generate-options-docs/main.go b/tools/generate-options-docs/main.go index dfe7c59b..105d0a9a 100644 --- a/tools/generate-options-docs/main.go +++ b/tools/generate-options-docs/main.go @@ -38,6 +38,7 @@ func main() { if *outFile == "" { fmt.Print(content) + return } @@ -64,6 +65,7 @@ func normalizeForCompare(s string) string { } out = append(out, line) } + return strings.Join(out, "\n") } @@ -77,5 +79,6 @@ func extractDateLine(content, prefix string) string { } } } + return "" } From 37131049edf7a2821df3730e0771cfe0e317c99b Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Fri, 19 Sep 2025 10:20:52 -0500 Subject: [PATCH 19/26] readme line breaks --- tools/README_DocumentationUpdatePortal.md | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/tools/README_DocumentationUpdatePortal.md b/tools/README_DocumentationUpdatePortal.md index 84277342..83abf32b 100644 --- a/tools/README_DocumentationUpdatePortal.md +++ b/tools/README_DocumentationUpdatePortal.md @@ -1,8 +1,6 @@ -# Documentation Portal Update (https://developer.pingidentity.com/pingcli) +# Documentation Portal Update Instructions -This document explains how to use the generated documentation artifacts to update -the Ping CLI documentation portal. The goal for future use is automation, but for now -this is a manual process. +This document explains how to use the generated documentation artifacts to update the Ping CLI documentation portal. The goal for future use is automation, but for now this is a manual process. Follow these steps after generating the documentation artifacts as described in `README_DocumentGeneration.md`. @@ -16,9 +14,7 @@ There are three primary document types produced by the generators: 3. Configuration options reference (single page) The nav.adoc is a code snippet to be inserted into the portal's `nav.adoc` file. -The command reference pages and configuration options reference are full AsciiDoc -documents suitable for direct inclusion in the portal and can be simply copied over -to the appropriate locations. +The command reference pages and configuration options reference are full AsciiDoc documents suitable for direct inclusion in the portal and can be simply copied over to the appropriate locations. ## Process @@ -26,8 +22,7 @@ to the appropriate locations. Copy the contents of `docs/dev-ux-portal-docs/nav.adoc` and paste it into the portal's `nav.adoc` file, replacing the content that starts with `* Command Reference`. -This section will need to be replaced each time the command docs are regenerated as -new subcommands are added. +This section will need to be replaced each time the command docs are regenerated as new subcommands are added. The target file location in the portal repo is:`asciidoc/modules/ROOT/nav.adoc` @@ -40,8 +35,6 @@ You can overwrite existing files and add any new ones. ### Configuration Options Reference Copy the file `docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc` -to the folder `asciidoc/modules/ROOT/pages/general` of the documentation portal repository, -overwriting the existing file of the same name. +to the folder `asciidoc/modules/ROOT/pages/general` of the documentation portal repository, overwriting the existing file of the same name. -From this point, you can follow the process for building, reviewing, and publishing -the portal documentation as outlined internally. +From this point, you can follow the process for building, reviewing, and publishing the portal documentation as outlined internally. From e82e702903fbb5ea8c1c62e1dd802884c527aeef Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Tue, 23 Sep 2025 09:24:26 -0500 Subject: [PATCH 20/26] add env vars, rearrange columns, update golden test --- .../cli-configuration-settings-reference.adoc | 100 +++++++++--------- tools/generate-options-docs/docgen/docgen.go | 21 ++-- .../docgen/testdata/golden/options.adoc | 98 ++++++++--------- .../docgen/testdata/golden/options.md | 82 +++++++------- 4 files changed, 155 insertions(+), 146 deletions(-) diff --git a/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc b/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc index ce9d3c72..10ffabf8 100644 --- a/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc +++ b/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc @@ -1,6 +1,6 @@ = Configuration Settings Reference :created-date: March 23, 2025 -:revdate: September 18, 2025 +:revdate: September 23, 2025 :resourceid: pingcli_configuration_settings_reference The following configuration settings can be applied when using Ping CLI. @@ -11,77 +11,77 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d == General Properties -[cols="2,1,2,2"] +[cols="2,2,2,1,3"] |=== -|Configuration Key |Data Type |Equivalent Parameter |Purpose - -| `activeProfile` | String | | -| `description` | String | | -| `detailedExitCode` | Boolean | `--detailed-exitcode` / `-D` | Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -| `noColor` | Boolean | `--no-color` | Disable text output in color. (default false) -| `outputFormat` | String (Enum) | `--output-format` / `-O` | Specify the console output format. (default text) Options are: json, text. -| `plugins` | String Array | | +|Configuration Key |Equivalent Parameter |Environment Variable |Data Type |Purpose + +| `activeProfile` | | | String | +| `description` | | | String | +| `detailedExitCode` | `--detailed-exitcode` / `-D` | PINGCLI_DETAILED_EXITCODE | Boolean | Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. +| `noColor` | `--no-color` | PINGCLI_NO_COLOR | Boolean | Disable text output in color. (default false) +| `outputFormat` | `--output-format` / `-O` | PINGCLI_OUTPUT_FORMAT | String (Enum) | Specify the console output format. (default text) Options are: json, text. +| `plugins` | | | String Array | |=== == Ping Identity platform service properties -[cols="2,1,2,2"] +[cols="2,2,2,1,3"] |=== -|Configuration Key |Data Type |Equivalent Parameter |Purpose - -| `service.pingfederate.adminAPIPath` | String | `--pingfederate-admin-api-path` | The PingFederate API URL path used to communicate with PingFederate's admin API. (default /pf-admin-api/v1) -| `service.pingfederate.authentication.accessTokenAuth.accessToken` | String | `--pingfederate-access-token` | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. -| `service.pingfederate.authentication.basicAuth.password` | String | `--pingfederate-password` | The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. -| `service.pingfederate.authentication.basicAuth.username` | String | `--pingfederate-username` | The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication. Example: 'administrator' -| `service.pingfederate.authentication.clientCredentialsAuth.clientID` | String | `--pingfederate-client-id` | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. -| `service.pingfederate.authentication.clientCredentialsAuth.clientSecret` | String | `--pingfederate-client-secret` | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. -| `service.pingfederate.authentication.clientCredentialsAuth.scopes` | String Array | `--pingfederate-scopes` | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. (default []) Accepts a comma-separated string to delimit multiple scopes. Example: 'openid,profile' -| `service.pingfederate.authentication.clientCredentialsAuth.tokenURL` | String | `--pingfederate-token-url` | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. -| `service.pingfederate.authentication.type` | String (Enum) | `--pingfederate-authentication-type` | The authentication type to use when connecting to the PingFederate admin API. Options are: accessTokenAuth, basicAuth, clientCredentialsAuth. Example: 'basicAuth' -| `service.pingfederate.caCertificatePemFiles` | String Array | `--pingfederate-ca-certificate-pem-files` | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to the PingFederate server over HTTPS. (default []) Accepts a comma-separated string to delimit multiple PEM files. -| `service.pingfederate.httpsHost` | String | `--pingfederate-https-host` | The PingFederate HTTPS host used to communicate with PingFederate's admin API. Example: 'https://pingfederate-admin.bxretail.org' -| `service.pingfederate.insecureTrustAllTLS` | Boolean | `--pingfederate-insecure-trust-all-tls` | Trust any certificate when connecting to the PingFederate server admin API. (default false) This is insecure and shouldn't be enabled outside of testing. -| `service.pingfederate.xBypassExternalValidationHeader` | Boolean | `--pingfederate-x-bypass-external-validation-header` | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). (default false) -| `service.pingone.authentication.type` | String (Enum) | `--pingone-authentication-type` | The authentication type to use to authenticate to the PingOne management API. (default worker) Options are: worker. -| `service.pingone.authentication.worker.clientID` | String (UUID Format) | `--pingone-worker-client-id` | The worker client ID used to authenticate to the PingOne management API. -| `service.pingone.authentication.worker.clientSecret` | String | `--pingone-worker-client-secret` | The worker client secret used to authenticate to the PingOne management API. -| `service.pingone.authentication.worker.environmentID` | String (UUID Format) | `--pingone-worker-environment-id` | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne management API. -| `service.pingone.regionCode` | String (Enum) | `--pingone-region-code` | The region code of the PingOne tenant. Options are: AP, AU, CA, EU, NA. Example: 'NA' +|Configuration Key |Equivalent Parameter |Environment Variable |Data Type |Purpose + +| `service.pingfederate.adminAPIPath` | `--pingfederate-admin-api-path` | PINGCLI_PINGFEDERATE_ADMIN_API_PATH | String | The PingFederate API URL path used to communicate with PingFederate's admin API. (default /pf-admin-api/v1) +| `service.pingfederate.authentication.accessTokenAuth.accessToken` | `--pingfederate-access-token` | PINGCLI_PINGFEDERATE_ACCESS_TOKEN | String | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. +| `service.pingfederate.authentication.basicAuth.password` | `--pingfederate-password` | PINGCLI_PINGFEDERATE_PASSWORD | String | The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. +| `service.pingfederate.authentication.basicAuth.username` | `--pingfederate-username` | PINGCLI_PINGFEDERATE_USERNAME | String | The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication. Example: 'administrator' +| `service.pingfederate.authentication.clientCredentialsAuth.clientID` | `--pingfederate-client-id` | PINGCLI_PINGFEDERATE_CLIENT_ID | String | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.clientCredentialsAuth.clientSecret` | `--pingfederate-client-secret` | PINGCLI_PINGFEDERATE_CLIENT_SECRET | String | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.clientCredentialsAuth.scopes` | `--pingfederate-scopes` | PINGCLI_PINGFEDERATE_SCOPES | String Array | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. (default []) Accepts a comma-separated string to delimit multiple scopes. Example: 'openid,profile' +| `service.pingfederate.authentication.clientCredentialsAuth.tokenURL` | `--pingfederate-token-url` | PINGCLI_PINGFEDERATE_TOKEN_URL | String | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.type` | `--pingfederate-authentication-type` | PINGCLI_PINGFEDERATE_AUTHENTICATION_TYPE | String (Enum) | The authentication type to use when connecting to the PingFederate admin API. Options are: accessTokenAuth, basicAuth, clientCredentialsAuth. Example: 'basicAuth' +| `service.pingfederate.caCertificatePemFiles` | `--pingfederate-ca-certificate-pem-files` | PINGCLI_PINGFEDERATE_CA_CERTIFICATE_PEM_FILES | String Array | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to the PingFederate server over HTTPS. (default []) Accepts a comma-separated string to delimit multiple PEM files. +| `service.pingfederate.httpsHost` | `--pingfederate-https-host` | PINGCLI_PINGFEDERATE_HTTPS_HOST | String | The PingFederate HTTPS host used to communicate with PingFederate's admin API. Example: 'https://pingfederate-admin.bxretail.org' +| `service.pingfederate.insecureTrustAllTLS` | `--pingfederate-insecure-trust-all-tls` | PINGCLI_PINGFEDERATE_INSECURE_TRUST_ALL_TLS | Boolean | Trust any certificate when connecting to the PingFederate server admin API. (default false) This is insecure and shouldn't be enabled outside of testing. +| `service.pingfederate.xBypassExternalValidationHeader` | `--pingfederate-x-bypass-external-validation-header` | PINGCLI_PINGFEDERATE_X_BYPASS_EXTERNAL_VALIDATION_HEADER | Boolean | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). (default false) +| `service.pingone.authentication.type` | `--pingone-authentication-type` | PINGCLI_PINGONE_AUTHENTICATION_TYPE | String (Enum) | The authentication type to use to authenticate to the PingOne management API. (default worker) Options are: worker. +| `service.pingone.authentication.worker.clientID` | `--pingone-worker-client-id` | PINGCLI_PINGONE_WORKER_CLIENT_ID | String (UUID Format) | The worker client ID used to authenticate to the PingOne management API. +| `service.pingone.authentication.worker.clientSecret` | `--pingone-worker-client-secret` | PINGCLI_PINGONE_WORKER_CLIENT_SECRET | String | The worker client secret used to authenticate to the PingOne management API. +| `service.pingone.authentication.worker.environmentID` | `--pingone-worker-environment-id` | PINGCLI_PINGONE_WORKER_ENVIRONMENT_ID | String (UUID Format) | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne management API. +| `service.pingone.regionCode` | `--pingone-region-code` | PINGCLI_PINGONE_REGION_CODE | String (Enum) | The region code of the PingOne tenant. Options are: AP, AU, CA, EU, NA. Example: 'NA' |=== == Platform export properties -[cols="2,1,2,2"] +[cols="2,2,2,1,3"] |=== -|Configuration Key |Data Type |Equivalent Parameter |Purpose - -| `export.format` | String (Enum) | `--format` / `-f` | Specifies the export format. (default HCL) Options are: HCL. -| `export.outputDirectory` | String | `--output-directory` / `-d` | Specifies the output directory for export. Can be an absolute filepath or a relative filepath of the present working directory. Example: '/Users/example/pingcli-export' Example: 'pingcli-export' -| `export.overwrite` | Boolean | `--overwrite` / `-o` | Overwrites the existing generated exports in output directory. (default false) -| `export.pingone.environmentID` | String (UUID Format) | `--pingone-export-environment-id` | The ID of the PingOne environment to export. Must be a valid PingOne UUID. -| `export.serviceGroup` | String (Enum) | `--service-group` / `-g` | Specifies the service group to export. Options are: pingone. Example: 'pingone' -| `export.services` | String Array | `--services` / `-s` | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' +|Configuration Key |Equivalent Parameter |Environment Variable |Data Type |Purpose + +| `export.format` | `--format` / `-f` | PINGCLI_EXPORT_FORMAT | String (Enum) | Specifies the export format. (default HCL) Options are: HCL. +| `export.outputDirectory` | `--output-directory` / `-d` | PINGCLI_EXPORT_OUTPUT_DIRECTORY | String | Specifies the output directory for export. Can be an absolute filepath or a relative filepath of the present working directory. Example: '/Users/example/pingcli-export' Example: 'pingcli-export' +| `export.overwrite` | `--overwrite` / `-o` | PINGCLI_EXPORT_OVERWRITE | Boolean | Overwrites the existing generated exports in output directory. (default false) +| `export.pingone.environmentID` | `--pingone-export-environment-id` | PINGCLI_PINGONE_EXPORT_ENVIRONMENT_ID | String (UUID Format) | The ID of the PingOne environment to export. Must be a valid PingOne UUID. +| `export.serviceGroup` | `--service-group` / `-g` | PINGCLI_EXPORT_SERVICE_GROUP | String (Enum) | Specifies the service group to export. Options are: pingone. Example: 'pingone' +| `export.services` | `--services` / `-s` | PINGCLI_EXPORT_SERVICES | String Array | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' |=== == License properties -[cols="2,1,2,2"] +[cols="2,2,2,1,3"] |=== -|Configuration Key |Data Type |Equivalent Parameter |Purpose +|Configuration Key |Equivalent Parameter |Environment Variable |Data Type |Purpose -| `license.devopsKey` | String | `--devops-key` / `-k` | The DevOps key for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. -| `license.devopsUser` | String | `--devops-user` / `-u` | The DevOps user for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. +| `license.devopsKey` | `--devops-key` / `-k` | PINGCLI_LICENSE_DEVOPS_KEY | String | The DevOps key for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. +| `license.devopsUser` | `--devops-user` / `-u` | PINGCLI_LICENSE_DEVOPS_USER | String | The DevOps user for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. |=== == Custom request properties -[cols="2,1,2,2"] +[cols="2,2,2,1,3"] |=== -|Configuration Key |Data Type |Equivalent Parameter |Purpose +|Configuration Key |Equivalent Parameter |Environment Variable |Data Type |Purpose -| `request.accessToken` | String | | -| `request.accessTokenExpiry` | Integer | | -| `request.fail` | Boolean | `--fail` / `-f` | Return non-zero exit code when HTTP custom request returns a failure status code. -| `request.service` | String (Enum) | `--service` / `-s` | The Ping service (configured in the active profile) to send the custom request to. Options are: pingone. Example: 'pingone' +| `request.accessToken` | | | String | +| `request.accessTokenExpiry` | | | Integer | +| `request.fail` | `--fail` / `-f` | | Boolean | Return non-zero exit code when HTTP custom request returns a failure status code. +| `request.service` | `--service` / `-s` | PINGCLI_REQUEST_SERVICE | String (Enum) | The Ping service (configured in the active profile) to send the custom request to. Options are: pingone. Example: 'pingone' |=== diff --git a/tools/generate-options-docs/docgen/docgen.go b/tools/generate-options-docs/docgen/docgen.go index 4620a6af..b01dfa8c 100644 --- a/tools/generate-options-docs/docgen/docgen.go +++ b/tools/generate-options-docs/docgen/docgen.go @@ -30,7 +30,8 @@ func GenerateMarkdown() string { if strings.Contains(option.KoanfKey, ".") { category = strings.Split(option.KoanfKey, ".")[0] } - propertyCategoryInformation[category] = append(propertyCategoryInformation[category], fmt.Sprintf("| %s | %d | %s | %s |", option.KoanfKey, option.Type, flagInfo, usageString)) + // New column order: Config Key | Equivalent Parameter | Environment Variable | Type | Purpose + propertyCategoryInformation[category] = append(propertyCategoryInformation[category], fmt.Sprintf("| %s | %s | %s | %d | %s |", option.KoanfKey, flagInfo, formatEnvVar(option.EnvVar), option.Type, usageString)) } var outputBuilder strings.Builder cats := make([]string, 0, len(propertyCategoryInformation)) @@ -42,8 +43,8 @@ func GenerateMarkdown() string { properties := propertyCategoryInformation[category] slices.Sort(properties) outputBuilder.WriteString(fmt.Sprintf("#### %s Properties\n\n", category)) - outputBuilder.WriteString("| Config File Property | Type | Equivalent Parameter | Purpose |\n") - outputBuilder.WriteString("|---|---|---|---|\n") + outputBuilder.WriteString("| Config File Property | Equivalent Parameter | Environment Variable | Type | Purpose |\n") + outputBuilder.WriteString("|---|---|---|---|---|\n") for _, property := range properties { outputBuilder.WriteString(property + "\n") } @@ -106,14 +107,16 @@ func GenerateAsciiDocWithDates(created, revdate string) string { continue } b.WriteString("== " + sec.title + "\n\n") - b.WriteString("[cols=\"2,1,2,2\"]\n|===\n") - b.WriteString("|Configuration Key |Data Type |Equivalent Parameter |Purpose\n\n") + // Column order updated: Configuration Key | Equivalent Parameter | Environment Variable | Data Type | Purpose + b.WriteString("[cols=\"2,2,2,1,3\"]\n|===\n") + b.WriteString("|Configuration Key |Equivalent Parameter |Environment Variable |Data Type |Purpose\n\n") for _, opt := range opts { key := normalizeAsciiDocKey(opt.KoanfKey) dataType := asciiDocDataType(opt) eqParam := asciiDocEquivalentParameter(opt) + envVar := opt.EnvVar purpose := sanitizeUsage(opt) - b.WriteString(fmt.Sprintf("| `%s` | %s | %s | %s\n", key, dataType, eqParam, purpose)) + b.WriteString(fmt.Sprintf("| `%s` | %s | %s | %s | %s\n", key, eqParam, formatEnvVar(envVar), dataType, purpose)) } b.WriteString("|===\n\n") } @@ -183,3 +186,9 @@ func normalizeAsciiDocKey(key string) string { return key } + +// formatEnvVar returns the environment variable name or an empty string if not set. +// This indirection keeps table generation simpler and allows future formatting changes. +func formatEnvVar(s string) string { + return strings.TrimSpace(s) +} diff --git a/tools/generate-options-docs/docgen/testdata/golden/options.adoc b/tools/generate-options-docs/docgen/testdata/golden/options.adoc index 3a23ee82..03b3390c 100644 --- a/tools/generate-options-docs/docgen/testdata/golden/options.adoc +++ b/tools/generate-options-docs/docgen/testdata/golden/options.adoc @@ -9,77 +9,77 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d == General Properties -[cols="2,1,2,2"] +[cols="2,2,2,1,3"] |=== -|Configuration Key |Data Type |Equivalent Parameter |Purpose - -| `activeProfile` | String | | -| `description` | String | | -| `detailedExitCode` | Boolean | `--detailed-exitcode` / `-D` | Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. -| `noColor` | Boolean | `--no-color` | Disable text output in color. (default false) -| `outputFormat` | String (Enum) | `--output-format` / `-O` | Specify the console output format. (default text) Options are: json, text. -| `plugins` | String Array | | +|Configuration Key |Equivalent Parameter |Environment Variable |Data Type |Purpose + +| `activeProfile` | | | String | +| `description` | | | String | +| `detailedExitCode` | `--detailed-exitcode` / `-D` | PINGCLI_DETAILED_EXITCODE | Boolean | Enable detailed exit code output. (default false) 0 - pingcli command succeeded with no errors or warnings. 1 - pingcli command failed with errors. 2 - pingcli command succeeded with warnings. +| `noColor` | `--no-color` | PINGCLI_NO_COLOR | Boolean | Disable text output in color. (default false) +| `outputFormat` | `--output-format` / `-O` | PINGCLI_OUTPUT_FORMAT | String (Enum) | Specify the console output format. (default text) Options are: json, text. +| `plugins` | | | String Array | |=== == Ping Identity platform service properties -[cols="2,1,2,2"] +[cols="2,2,2,1,3"] |=== -|Configuration Key |Data Type |Equivalent Parameter |Purpose - -| `service.pingfederate.adminAPIPath` | String | `--pingfederate-admin-api-path` | The PingFederate API URL path used to communicate with PingFederate's admin API. (default /pf-admin-api/v1) -| `service.pingfederate.authentication.accessTokenAuth.accessToken` | String | `--pingfederate-access-token` | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. -| `service.pingfederate.authentication.basicAuth.password` | String | `--pingfederate-password` | The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. -| `service.pingfederate.authentication.basicAuth.username` | String | `--pingfederate-username` | The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication. Example: 'administrator' -| `service.pingfederate.authentication.clientCredentialsAuth.clientID` | String | `--pingfederate-client-id` | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. -| `service.pingfederate.authentication.clientCredentialsAuth.clientSecret` | String | `--pingfederate-client-secret` | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. -| `service.pingfederate.authentication.clientCredentialsAuth.scopes` | String Array | `--pingfederate-scopes` | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. (default []) Accepts a comma-separated string to delimit multiple scopes. Example: 'openid,profile' -| `service.pingfederate.authentication.clientCredentialsAuth.tokenURL` | String | `--pingfederate-token-url` | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. -| `service.pingfederate.authentication.type` | String (Enum) | `--pingfederate-authentication-type` | The authentication type to use when connecting to the PingFederate admin API. Options are: accessTokenAuth, basicAuth, clientCredentialsAuth. Example: 'basicAuth' -| `service.pingfederate.caCertificatePemFiles` | String Array | `--pingfederate-ca-certificate-pem-files` | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to the PingFederate server over HTTPS. (default []) Accepts a comma-separated string to delimit multiple PEM files. -| `service.pingfederate.httpsHost` | String | `--pingfederate-https-host` | The PingFederate HTTPS host used to communicate with PingFederate's admin API. Example: 'https://pingfederate-admin.bxretail.org' -| `service.pingfederate.insecureTrustAllTLS` | Boolean | `--pingfederate-insecure-trust-all-tls` | Trust any certificate when connecting to the PingFederate server admin API. (default false) This is insecure and shouldn't be enabled outside of testing. -| `service.pingfederate.xBypassExternalValidationHeader` | Boolean | `--pingfederate-x-bypass-external-validation-header` | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). (default false) -| `service.pingone.authentication.type` | String (Enum) | `--pingone-authentication-type` | The authentication type to use to authenticate to the PingOne management API. (default worker) Options are: worker. -| `service.pingone.authentication.worker.clientID` | String (UUID Format) | `--pingone-worker-client-id` | The worker client ID used to authenticate to the PingOne management API. -| `service.pingone.authentication.worker.clientSecret` | String | `--pingone-worker-client-secret` | The worker client secret used to authenticate to the PingOne management API. -| `service.pingone.authentication.worker.environmentID` | String (UUID Format) | `--pingone-worker-environment-id` | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne management API. -| `service.pingone.regionCode` | String (Enum) | `--pingone-region-code` | The region code of the PingOne tenant. Options are: AP, AU, CA, EU, NA. Example: 'NA' +|Configuration Key |Equivalent Parameter |Environment Variable |Data Type |Purpose + +| `service.pingfederate.adminAPIPath` | `--pingfederate-admin-api-path` | PINGCLI_PINGFEDERATE_ADMIN_API_PATH | String | The PingFederate API URL path used to communicate with PingFederate's admin API. (default /pf-admin-api/v1) +| `service.pingfederate.authentication.accessTokenAuth.accessToken` | `--pingfederate-access-token` | PINGCLI_PINGFEDERATE_ACCESS_TOKEN | String | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. +| `service.pingfederate.authentication.basicAuth.password` | `--pingfederate-password` | PINGCLI_PINGFEDERATE_PASSWORD | String | The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. +| `service.pingfederate.authentication.basicAuth.username` | `--pingfederate-username` | PINGCLI_PINGFEDERATE_USERNAME | String | The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication. Example: 'administrator' +| `service.pingfederate.authentication.clientCredentialsAuth.clientID` | `--pingfederate-client-id` | PINGCLI_PINGFEDERATE_CLIENT_ID | String | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.clientCredentialsAuth.clientSecret` | `--pingfederate-client-secret` | PINGCLI_PINGFEDERATE_CLIENT_SECRET | String | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.clientCredentialsAuth.scopes` | `--pingfederate-scopes` | PINGCLI_PINGFEDERATE_SCOPES | String Array | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. (default []) Accepts a comma-separated string to delimit multiple scopes. Example: 'openid,profile' +| `service.pingfederate.authentication.clientCredentialsAuth.tokenURL` | `--pingfederate-token-url` | PINGCLI_PINGFEDERATE_TOKEN_URL | String | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. +| `service.pingfederate.authentication.type` | `--pingfederate-authentication-type` | PINGCLI_PINGFEDERATE_AUTHENTICATION_TYPE | String (Enum) | The authentication type to use when connecting to the PingFederate admin API. Options are: accessTokenAuth, basicAuth, clientCredentialsAuth. Example: 'basicAuth' +| `service.pingfederate.caCertificatePemFiles` | `--pingfederate-ca-certificate-pem-files` | PINGCLI_PINGFEDERATE_CA_CERTIFICATE_PEM_FILES | String Array | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to the PingFederate server over HTTPS. (default []) Accepts a comma-separated string to delimit multiple PEM files. +| `service.pingfederate.httpsHost` | `--pingfederate-https-host` | PINGCLI_PINGFEDERATE_HTTPS_HOST | String | The PingFederate HTTPS host used to communicate with PingFederate's admin API. Example: 'https://pingfederate-admin.bxretail.org' +| `service.pingfederate.insecureTrustAllTLS` | `--pingfederate-insecure-trust-all-tls` | PINGCLI_PINGFEDERATE_INSECURE_TRUST_ALL_TLS | Boolean | Trust any certificate when connecting to the PingFederate server admin API. (default false) This is insecure and shouldn't be enabled outside of testing. +| `service.pingfederate.xBypassExternalValidationHeader` | `--pingfederate-x-bypass-external-validation-header` | PINGCLI_PINGFEDERATE_X_BYPASS_EXTERNAL_VALIDATION_HEADER | Boolean | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). (default false) +| `service.pingone.authentication.type` | `--pingone-authentication-type` | PINGCLI_PINGONE_AUTHENTICATION_TYPE | String (Enum) | The authentication type to use to authenticate to the PingOne management API. (default worker) Options are: worker. +| `service.pingone.authentication.worker.clientID` | `--pingone-worker-client-id` | PINGCLI_PINGONE_WORKER_CLIENT_ID | String (UUID Format) | The worker client ID used to authenticate to the PingOne management API. +| `service.pingone.authentication.worker.clientSecret` | `--pingone-worker-client-secret` | PINGCLI_PINGONE_WORKER_CLIENT_SECRET | String | The worker client secret used to authenticate to the PingOne management API. +| `service.pingone.authentication.worker.environmentID` | `--pingone-worker-environment-id` | PINGCLI_PINGONE_WORKER_ENVIRONMENT_ID | String (UUID Format) | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne management API. +| `service.pingone.regionCode` | `--pingone-region-code` | PINGCLI_PINGONE_REGION_CODE | String (Enum) | The region code of the PingOne tenant. Options are: AP, AU, CA, EU, NA. Example: 'NA' |=== == Platform export properties -[cols="2,1,2,2"] +[cols="2,2,2,1,3"] |=== -|Configuration Key |Data Type |Equivalent Parameter |Purpose - -| `export.format` | String (Enum) | `--format` / `-f` | Specifies the export format. (default HCL) Options are: HCL. -| `export.outputDirectory` | String | `--output-directory` / `-d` | Specifies the output directory for export. Can be an absolute filepath or a relative filepath of the present working directory. Example: '/Users/example/pingcli-export' Example: 'pingcli-export' -| `export.overwrite` | Boolean | `--overwrite` / `-o` | Overwrites the existing generated exports in output directory. (default false) -| `export.pingone.environmentID` | String (UUID Format) | `--pingone-export-environment-id` | The ID of the PingOne environment to export. Must be a valid PingOne UUID. -| `export.serviceGroup` | String (Enum) | `--service-group` / `-g` | Specifies the service group to export. Options are: pingone. Example: 'pingone' -| `export.services` | String Array | `--services` / `-s` | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' +|Configuration Key |Equivalent Parameter |Environment Variable |Data Type |Purpose + +| `export.format` | `--format` / `-f` | PINGCLI_EXPORT_FORMAT | String (Enum) | Specifies the export format. (default HCL) Options are: HCL. +| `export.outputDirectory` | `--output-directory` / `-d` | PINGCLI_EXPORT_OUTPUT_DIRECTORY | String | Specifies the output directory for export. Can be an absolute filepath or a relative filepath of the present working directory. Example: '/Users/example/pingcli-export' Example: 'pingcli-export' +| `export.overwrite` | `--overwrite` / `-o` | PINGCLI_EXPORT_OVERWRITE | Boolean | Overwrites the existing generated exports in output directory. (default false) +| `export.pingone.environmentID` | `--pingone-export-environment-id` | PINGCLI_PINGONE_EXPORT_ENVIRONMENT_ID | String (UUID Format) | The ID of the PingOne environment to export. Must be a valid PingOne UUID. +| `export.serviceGroup` | `--service-group` / `-g` | PINGCLI_EXPORT_SERVICE_GROUP | String (Enum) | Specifies the service group to export. Options are: pingone. Example: 'pingone' +| `export.services` | `--services` / `-s` | PINGCLI_EXPORT_SERVICES | String Array | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' |=== == License properties -[cols="2,1,2,2"] +[cols="2,2,2,1,3"] |=== -|Configuration Key |Data Type |Equivalent Parameter |Purpose +|Configuration Key |Equivalent Parameter |Environment Variable |Data Type |Purpose -| `license.devopsKey` | String | `--devops-key` / `-k` | The DevOps key for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. -| `license.devopsUser` | String | `--devops-user` / `-u` | The DevOps user for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. +| `license.devopsKey` | `--devops-key` / `-k` | PINGCLI_LICENSE_DEVOPS_KEY | String | The DevOps key for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. +| `license.devopsUser` | `--devops-user` / `-u` | PINGCLI_LICENSE_DEVOPS_USER | String | The DevOps user for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. |=== == Custom request properties -[cols="2,1,2,2"] +[cols="2,2,2,1,3"] |=== -|Configuration Key |Data Type |Equivalent Parameter |Purpose +|Configuration Key |Equivalent Parameter |Environment Variable |Data Type |Purpose -| `request.accessToken` | String | | -| `request.accessTokenExpiry` | Integer | | -| `request.fail` | Boolean | `--fail` / `-f` | Return non-zero exit code when HTTP custom request returns a failure status code. -| `request.service` | String (Enum) | `--service` / `-s` | The Ping service (configured in the active profile) to send the custom request to. Options are: pingone. Example: 'pingone' +| `request.accessToken` | | | String | +| `request.accessTokenExpiry` | | | Integer | +| `request.fail` | `--fail` / `-f` | | Boolean | Return non-zero exit code when HTTP custom request returns a failure status code. +| `request.service` | `--service` / `-s` | PINGCLI_REQUEST_SERVICE | String (Enum) | The Ping service (configured in the active profile) to send the custom request to. Options are: pingone. Example: 'pingone' |=== diff --git a/tools/generate-options-docs/docgen/testdata/golden/options.md b/tools/generate-options-docs/docgen/testdata/golden/options.md index 0e5cda4a..95ce4867 100644 --- a/tools/generate-options-docs/docgen/testdata/golden/options.md +++ b/tools/generate-options-docs/docgen/testdata/golden/options.md @@ -1,56 +1,56 @@ #### export Properties -| Config File Property | Type | Equivalent Parameter | Purpose | -|---|---|---|---| -| export.format | 1 | --format / -f | Specifies the export format. (default HCL)

Options are: HCL. | -| export.outputDirectory | 14 | --output-directory / -d | Specifies the output directory for export. Can be an absolute filepath or a relative filepath of the present working directory.

Example: '/Users/example/pingcli-export'

Example: 'pingcli-export' | -| export.overwrite | 0 | --overwrite / -o | Overwrites the existing generated exports in output directory. (default false) | -| export.pingOne.environmentID | 16 | --pingone-export-environment-id | The ID of the PingOne environment to export. Must be a valid PingOne UUID. | -| export.serviceGroup | 2 | --service-group / -g | Specifies the service group to export.

Options are: pingone.

Example: 'pingone' | -| export.services | 3 | --services / -s | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services.

Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso.

Example: 'pingone-sso,pingone-mfa,pingfederate' | +| Config File Property | Equivalent Parameter | Environment Variable | Type | Purpose | +|---|---|---|---|---| +| export.format | --format / -f | PINGCLI_EXPORT_FORMAT | 1 | Specifies the export format. (default HCL)

Options are: HCL. | +| export.outputDirectory | --output-directory / -d | PINGCLI_EXPORT_OUTPUT_DIRECTORY | 14 | Specifies the output directory for export. Can be an absolute filepath or a relative filepath of the present working directory.

Example: '/Users/example/pingcli-export'

Example: 'pingcli-export' | +| export.overwrite | --overwrite / -o | PINGCLI_EXPORT_OVERWRITE | 0 | Overwrites the existing generated exports in output directory. (default false) | +| export.pingOne.environmentID | --pingone-export-environment-id | PINGCLI_PINGONE_EXPORT_ENVIRONMENT_ID | 16 | The ID of the PingOne environment to export. Must be a valid PingOne UUID. | +| export.serviceGroup | --service-group / -g | PINGCLI_EXPORT_SERVICE_GROUP | 2 | Specifies the service group to export.

Options are: pingone.

Example: 'pingone' | +| export.services | --services / -s | PINGCLI_EXPORT_SERVICES | 3 | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services.

Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso.

Example: 'pingone-sso,pingone-mfa,pingfederate' | #### general Properties -| Config File Property | Type | Equivalent Parameter | Purpose | -|---|---|---|---| -| detailedExitCode | 0 | --detailed-exitcode / -D | Enable detailed exit code output. (default false)

0 - pingcli command succeeded with no errors or warnings.

1 - pingcli command failed with errors.

2 - pingcli command succeeded with warnings. | -| noColor | 0 | --no-color | Disable text output in color. (default false) | -| outputFormat | 8 | --output-format / -O | Specify the console output format. (default text)

Options are: json, text. | +| Config File Property | Equivalent Parameter | Environment Variable | Type | Purpose | +|---|---|---|---|---| +| detailedExitCode | --detailed-exitcode / -D | PINGCLI_DETAILED_EXITCODE | 0 | Enable detailed exit code output. (default false)

0 - pingcli command succeeded with no errors or warnings.

1 - pingcli command failed with errors.

2 - pingcli command succeeded with warnings. | +| noColor | --no-color | PINGCLI_NO_COLOR | 0 | Disable text output in color. (default false) | +| outputFormat | --output-format / -O | PINGCLI_OUTPUT_FORMAT | 8 | Specify the console output format. (default text)

Options are: json, text. | #### license Properties -| Config File Property | Type | Equivalent Parameter | Purpose | -|---|---|---|---| -| license.devopsKey | 14 | --devops-key / -k | The DevOps key for the license request.

See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user.

You can save the DevOps user and key in your profile using the 'pingcli config' commands. | -| license.devopsUser | 14 | --devops-user / -u | The DevOps user for the license request.

See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user.

You can save the DevOps user and key in your profile using the 'pingcli config' commands. | +| Config File Property | Equivalent Parameter | Environment Variable | Type | Purpose | +|---|---|---|---|---| +| license.devopsKey | --devops-key / -k | PINGCLI_LICENSE_DEVOPS_KEY | 14 | The DevOps key for the license request.

See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user.

You can save the DevOps user and key in your profile using the 'pingcli config' commands. | +| license.devopsUser | --devops-user / -u | PINGCLI_LICENSE_DEVOPS_USER | 14 | The DevOps user for the license request.

See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user.

You can save the DevOps user and key in your profile using the 'pingcli config' commands. | #### request Properties -| Config File Property | Type | Equivalent Parameter | Purpose | -|---|---|---|---| -| request.fail | 0 | --fail / -f | Return non-zero exit code when HTTP custom request returns a failure status code. | -| request.service | 13 | --service / -s | The Ping service (configured in the active profile) to send the custom request to.

Options are: pingone.

Example: 'pingone' | +| Config File Property | Equivalent Parameter | Environment Variable | Type | Purpose | +|---|---|---|---|---| +| request.fail | --fail / -f | | 0 | Return non-zero exit code when HTTP custom request returns a failure status code. | +| request.service | --service / -s | PINGCLI_REQUEST_SERVICE | 13 | The Ping service (configured in the active profile) to send the custom request to.

Options are: pingone.

Example: 'pingone' | #### service Properties -| Config File Property | Type | Equivalent Parameter | Purpose | -|---|---|---|---| -| service.pingFederate.adminAPIPath | 14 | --pingfederate-admin-api-path | The PingFederate API URL path used to communicate with PingFederate's admin API. (default /pf-admin-api/v1) | -| service.pingFederate.authentication.accessTokenAuth.accessToken | 14 | --pingfederate-access-token | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. | -| service.pingFederate.authentication.basicAuth.password | 14 | --pingfederate-password | The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. | -| service.pingFederate.authentication.basicAuth.username | 14 | --pingfederate-username | The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication.

Example: 'administrator' | -| service.pingFederate.authentication.clientCredentialsAuth.clientID | 14 | --pingfederate-client-id | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | -| service.pingFederate.authentication.clientCredentialsAuth.clientSecret | 14 | --pingfederate-client-secret | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | -| service.pingFederate.authentication.clientCredentialsAuth.scopes | 15 | --pingfederate-scopes | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. (default [])

Accepts a comma-separated string to delimit multiple scopes.

Example: 'openid,profile' | -| service.pingFederate.authentication.clientCredentialsAuth.tokenURL | 14 | --pingfederate-token-url | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | -| service.pingFederate.authentication.type | 9 | --pingfederate-authentication-type | The authentication type to use when connecting to the PingFederate admin API.

Options are: accessTokenAuth, basicAuth, clientCredentialsAuth.

Example: 'basicAuth' | -| service.pingFederate.caCertificatePEMFiles | 15 | --pingfederate-ca-certificate-pem-files | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to the PingFederate server over HTTPS. (default [])

Accepts a comma-separated string to delimit multiple PEM files. | -| service.pingFederate.httpsHost | 14 | --pingfederate-https-host | The PingFederate HTTPS host used to communicate with PingFederate's admin API.

Example: 'https://pingfederate-admin.bxretail.org' | -| service.pingFederate.insecureTrustAllTLS | 0 | --pingfederate-insecure-trust-all-tls | Trust any certificate when connecting to the PingFederate server admin API. (default false)

This is insecure and shouldn't be enabled outside of testing. | -| service.pingFederate.xBypassExternalValidationHeader | 0 | --pingfederate-x-bypass-external-validation-header | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). (default false) | -| service.pingOne.authentication.type | 10 | --pingone-authentication-type | The authentication type to use to authenticate to the PingOne management API. (default worker)

Options are: worker. | -| service.pingOne.authentication.worker.clientID | 16 | --pingone-worker-client-id | The worker client ID used to authenticate to the PingOne management API. | -| service.pingOne.authentication.worker.clientSecret | 14 | --pingone-worker-client-secret | The worker client secret used to authenticate to the PingOne management API. | -| service.pingOne.authentication.worker.environmentID | 16 | --pingone-worker-environment-id | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne management API. | -| service.pingOne.regionCode | 11 | --pingone-region-code | The region code of the PingOne tenant.

Options are: AP, AU, CA, EU, NA.

Example: 'NA' | +| Config File Property | Equivalent Parameter | Environment Variable | Type | Purpose | +|---|---|---|---|---| +| service.pingFederate.adminAPIPath | --pingfederate-admin-api-path | PINGCLI_PINGFEDERATE_ADMIN_API_PATH | 14 | The PingFederate API URL path used to communicate with PingFederate's admin API. (default /pf-admin-api/v1) | +| service.pingFederate.authentication.accessTokenAuth.accessToken | --pingfederate-access-token | PINGCLI_PINGFEDERATE_ACCESS_TOKEN | 14 | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. | +| service.pingFederate.authentication.basicAuth.password | --pingfederate-password | PINGCLI_PINGFEDERATE_PASSWORD | 14 | The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. | +| service.pingFederate.authentication.basicAuth.username | --pingfederate-username | PINGCLI_PINGFEDERATE_USERNAME | 14 | The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication.

Example: 'administrator' | +| service.pingFederate.authentication.clientCredentialsAuth.clientID | --pingfederate-client-id | PINGCLI_PINGFEDERATE_CLIENT_ID | 14 | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | +| service.pingFederate.authentication.clientCredentialsAuth.clientSecret | --pingfederate-client-secret | PINGCLI_PINGFEDERATE_CLIENT_SECRET | 14 | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | +| service.pingFederate.authentication.clientCredentialsAuth.scopes | --pingfederate-scopes | PINGCLI_PINGFEDERATE_SCOPES | 15 | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. (default [])

Accepts a comma-separated string to delimit multiple scopes.

Example: 'openid,profile' | +| service.pingFederate.authentication.clientCredentialsAuth.tokenURL | --pingfederate-token-url | PINGCLI_PINGFEDERATE_TOKEN_URL | 14 | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | +| service.pingFederate.authentication.type | --pingfederate-authentication-type | PINGCLI_PINGFEDERATE_AUTHENTICATION_TYPE | 9 | The authentication type to use when connecting to the PingFederate admin API.

Options are: accessTokenAuth, basicAuth, clientCredentialsAuth.

Example: 'basicAuth' | +| service.pingFederate.caCertificatePEMFiles | --pingfederate-ca-certificate-pem-files | PINGCLI_PINGFEDERATE_CA_CERTIFICATE_PEM_FILES | 15 | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to the PingFederate server over HTTPS. (default [])

Accepts a comma-separated string to delimit multiple PEM files. | +| service.pingFederate.httpsHost | --pingfederate-https-host | PINGCLI_PINGFEDERATE_HTTPS_HOST | 14 | The PingFederate HTTPS host used to communicate with PingFederate's admin API.

Example: 'https://pingfederate-admin.bxretail.org' | +| service.pingFederate.insecureTrustAllTLS | --pingfederate-insecure-trust-all-tls | PINGCLI_PINGFEDERATE_INSECURE_TRUST_ALL_TLS | 0 | Trust any certificate when connecting to the PingFederate server admin API. (default false)

This is insecure and shouldn't be enabled outside of testing. | +| service.pingFederate.xBypassExternalValidationHeader | --pingfederate-x-bypass-external-validation-header | PINGCLI_PINGFEDERATE_X_BYPASS_EXTERNAL_VALIDATION_HEADER | 0 | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). (default false) | +| service.pingOne.authentication.type | --pingone-authentication-type | PINGCLI_PINGONE_AUTHENTICATION_TYPE | 10 | The authentication type to use to authenticate to the PingOne management API. (default worker)

Options are: worker. | +| service.pingOne.authentication.worker.clientID | --pingone-worker-client-id | PINGCLI_PINGONE_WORKER_CLIENT_ID | 16 | The worker client ID used to authenticate to the PingOne management API. | +| service.pingOne.authentication.worker.clientSecret | --pingone-worker-client-secret | PINGCLI_PINGONE_WORKER_CLIENT_SECRET | 14 | The worker client secret used to authenticate to the PingOne management API. | +| service.pingOne.authentication.worker.environmentID | --pingone-worker-environment-id | PINGCLI_PINGONE_WORKER_ENVIRONMENT_ID | 16 | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne management API. | +| service.pingOne.regionCode | --pingone-region-code | PINGCLI_PINGONE_REGION_CODE | 11 | The region code of the PingOne tenant.

Options are: AP, AU, CA, EU, NA.

Example: 'NA' | From 796be68212abf484a0f5c48c4b6e117bf5c58ac0 Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 24 Sep 2025 09:21:05 -0500 Subject: [PATCH 21/26] refactor shared function --- README.md | 2 +- tools/docutil/normalize.go | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 tools/docutil/normalize.go diff --git a/README.md b/README.md index fcb76264..3f38fb5d 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,7 @@ dnf install ./pingcli__linux_arm64.rpm ``` > **_NOTE:_** + > - Use `yum` for CentOS/RHEL 7 and earlier, and for older Fedora systems. > - Use `dnf` for Fedora 22+ and CentOS/RHEL 8+. > Both commands achieve the same result; use the one appropriate for your distribution. @@ -230,7 +231,6 @@ Documentation generation instructions (configuration options reference, per-comm See: `tools/README_DocumentGeneration.md` - ## Commands Ping CLI commands have the following structure: diff --git a/tools/docutil/normalize.go b/tools/docutil/normalize.go new file mode 100644 index 00000000..61ef829a --- /dev/null +++ b/tools/docutil/normalize.go @@ -0,0 +1,25 @@ +// Package docutil provides small helpers for documentation generators to share. +package docutil + +import ( + "bufio" + "strings" +) + +// NormalizeForCompare returns a version of the input with volatile header lines removed. +// Currently strips :created-date: and :revdate: lines so generators can perform +// stable comparisons and avoid rewriting files when only the date changes. +func NormalizeForCompare(s string) string { + var b strings.Builder + scanner := bufio.NewScanner(strings.NewReader(s)) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, ":created-date:") || strings.HasPrefix(line, ":revdate:") { + continue + } + b.WriteString(line) + b.WriteByte('\n') + } + // Trim trailing newline for consistency with previous implementation. + return strings.TrimSuffix(b.String(), "\n") +} From ba69fbdc1f317f91ed0ede2b0eb197fc31d5a706 Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 24 Sep 2025 09:31:13 -0500 Subject: [PATCH 22/26] refactor redundant tests, updated golden test files --- tools/docutil/normalize.go | 15 ++++++++ tools/generate-command-docs/main.go | 35 +++---------------- tools/generate-command-docs/main_test.go | 19 +++------- .../docgen/docgen_test.go | 16 ++------- .../docgen/testdata/golden/options.adoc | 1 - tools/generate-options-docs/main.go | 33 ++--------------- 6 files changed, 28 insertions(+), 91 deletions(-) diff --git a/tools/docutil/normalize.go b/tools/docutil/normalize.go index 61ef829a..c05d8e5d 100644 --- a/tools/docutil/normalize.go +++ b/tools/docutil/normalize.go @@ -23,3 +23,18 @@ func NormalizeForCompare(s string) string { // Trim trailing newline for consistency with previous implementation. return strings.TrimSuffix(b.String(), "\n") } + +// ExtractDateLine returns the value of a date-like header line matching the given prefix. +// Example: prefix ":created-date:" matches a line like ":created-date: March 23, 2025" and returns "March 23, 2025". +func ExtractDateLine(content, prefix string) string { + for _, line := range strings.Split(content, "\n") { + if strings.HasPrefix(line, prefix) { + parts := strings.SplitN(line, ": ", 2) + if len(parts) == 2 { + return parts[1] + } + } + } + + return "" +} diff --git a/tools/generate-command-docs/main.go b/tools/generate-command-docs/main.go index 18751f1e..b24e2da6 100644 --- a/tools/generate-command-docs/main.go +++ b/tools/generate-command-docs/main.go @@ -10,6 +10,7 @@ import ( "time" "github.com/pingidentity/pingcli/cmd" + "github.com/pingidentity/pingcli/tools/docutil" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -39,7 +40,7 @@ func main() { // If file exists, extract existing created-date so it is preserved. var existingCreated string if oldRaw, err := readFileIfWithin(file, *outDir); err == nil { - existingCreated = extractDateLine(string(oldRaw), ":created-date:") + existingCreated = docutil.ExtractDateLine(string(oldRaw), ":created-date:") } createdDate := *date if existingCreated != "" { @@ -51,9 +52,9 @@ func main() { // Determine if underlying (non-date) content actually changed; if not, skip rewrite. var prevBody string if oldRaw, err := readFileIfWithin(file, *outDir); err == nil { - prevBody = normalizeForCompare(string(oldRaw)) + prevBody = docutil.NormalizeForCompare(string(oldRaw)) } - newBody := normalizeForCompare(content) + newBody := docutil.NormalizeForCompare(content) if prevBody == newBody && prevBody != "" { // Skip updating revision date to avoid needless churn. return @@ -305,34 +306,6 @@ func renderNav(root *cobra.Command) string { return b.String() } -// normalizeForCompare removes date lines so that comparisons ignore purely date-based churn. -func normalizeForCompare(s string) string { - out := make([]string, 0, 128) - for _, line := range strings.Split(s, "\n") { - if strings.HasPrefix(line, ":created-date:") || strings.HasPrefix(line, ":revdate:") { - continue - } - out = append(out, line) - } - - return strings.Join(out, "\n") -} - -// extractDateLine returns the value (sans prefix) of the first matching date line. -func extractDateLine(content, prefix string) string { - for _, line := range strings.Split(content, "\n") { - if strings.HasPrefix(line, prefix) { - // format: :created-date: VALUE - parts := strings.SplitN(line, ": ", 2) - if len(parts) == 2 { - return parts[1] - } - } - } - - return "" -} - // readFileIfWithin validates that path is within base before reading to satisfy gosec G304. func readFileIfWithin(path, base string) ([]byte, error) { cleanBase := filepath.Clean(base) diff --git a/tools/generate-command-docs/main_test.go b/tools/generate-command-docs/main_test.go index 613515d2..cc52b852 100644 --- a/tools/generate-command-docs/main_test.go +++ b/tools/generate-command-docs/main_test.go @@ -6,6 +6,8 @@ import ( "path/filepath" "strings" "testing" + + "github.com/pingidentity/pingcli/tools/docutil" ) var update = flag.Bool("update", false, "update golden files for command docs") @@ -39,7 +41,7 @@ func TestCommandDocGeneration(t *testing.T) { if err != nil { t.Fatalf("read generated %s: %v", f, err) } - got := normalizeDynamic(string(gotBytes)) + got := docutil.NormalizeForCompare(string(gotBytes)) goldenPath := filepath.Join(goldenDir, f) if *update { @@ -54,22 +56,9 @@ func TestCommandDocGeneration(t *testing.T) { if err != nil { t.Fatalf("read golden %s: %v (run with -update to create)", f, err) } - want := normalizeDynamic(string(wantBytes)) + want := docutil.NormalizeForCompare(string(wantBytes)) if got != want { t.Errorf("mismatch for %s\n--- got ---\n%s\n--- want ---\n%s", f, got, want) } } } - -// normalizeDynamic strips lines containing created / revision dates to avoid churn. -func normalizeDynamic(s string) string { - out := make([]string, 0, 64) - for _, line := range strings.Split(s, "\n") { - if strings.HasPrefix(line, ":created-date:") || strings.HasPrefix(line, ":revdate:") { - continue - } - out = append(out, line) - } - - return strings.Join(out, "\n") -} diff --git a/tools/generate-options-docs/docgen/docgen_test.go b/tools/generate-options-docs/docgen/docgen_test.go index 46211aa5..39a3180b 100644 --- a/tools/generate-options-docs/docgen/docgen_test.go +++ b/tools/generate-options-docs/docgen/docgen_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/pingidentity/pingcli/tools/docutil" docgen "github.com/pingidentity/pingcli/tools/generate-options-docs/docgen" ) @@ -25,7 +26,7 @@ func TestOptionsDocGeneration(t *testing.T) { } // Normalize dynamic date in AsciiDoc output before storing / comparing. - adocNorm := normalizeDynamic(adoc) + adocNorm := docutil.NormalizeForCompare(adoc) cases := []struct { name string @@ -60,16 +61,3 @@ func TestOptionsDocGeneration(t *testing.T) { } } } - -// normalizeDynamic removes lines containing :created-date: or :revdate: tokens. -func normalizeDynamic(s string) string { - out := make([]string, 0, 128) - for _, line := range strings.Split(s, "\n") { - if strings.HasPrefix(line, ":created-date:") || strings.HasPrefix(line, ":revdate:") { - continue - } - out = append(out, line) - } - - return strings.Join(out, "\n") -} diff --git a/tools/generate-options-docs/docgen/testdata/golden/options.adoc b/tools/generate-options-docs/docgen/testdata/golden/options.adoc index 03b3390c..af2db68d 100644 --- a/tools/generate-options-docs/docgen/testdata/golden/options.adoc +++ b/tools/generate-options-docs/docgen/testdata/golden/options.adoc @@ -82,4 +82,3 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d | `request.fail` | `--fail` / `-f` | | Boolean | Return non-zero exit code when HTTP custom request returns a failure status code. | `request.service` | `--service` / `-s` | PINGCLI_REQUEST_SERVICE | String (Enum) | The Ping service (configured in the active profile) to send the custom request to. Options are: pingone. Example: 'pingone' |=== - diff --git a/tools/generate-options-docs/main.go b/tools/generate-options-docs/main.go index 105d0a9a..41af2f0b 100644 --- a/tools/generate-options-docs/main.go +++ b/tools/generate-options-docs/main.go @@ -4,9 +4,9 @@ import ( "flag" "fmt" "os" - "strings" "time" + "github.com/pingidentity/pingcli/tools/docutil" docgen "github.com/pingidentity/pingcli/tools/generate-options-docs/docgen" ) @@ -26,7 +26,7 @@ func main() { created := *date if *outFile != "" { if raw, err := os.ReadFile(*outFile); err == nil { - if prevCreated := extractDateLine(string(raw), ":created-date:"); prevCreated != "" { + if prevCreated := docutil.ExtractDateLine(string(raw), ":created-date:"); prevCreated != "" { created = prevCreated } } @@ -44,7 +44,7 @@ func main() { // Conditional write: only update if non-date content changed. if oldRaw, err := os.ReadFile(*outFile); err == nil { - if normalizeForCompare(string(oldRaw)) == normalizeForCompare(content) { + if docutil.NormalizeForCompare(string(oldRaw)) == docutil.NormalizeForCompare(content) { // No meaningful change; avoid updating revision date line. return } @@ -55,30 +55,3 @@ func main() { os.Exit(1) } } - -// normalizeForCompare strips created / revision date lines for deterministic comparisons. -func normalizeForCompare(s string) string { - out := make([]string, 0, 256) - for _, line := range strings.Split(s, "\n") { - if strings.HasPrefix(line, ":created-date:") || strings.HasPrefix(line, ":revdate:") { - continue - } - out = append(out, line) - } - - return strings.Join(out, "\n") -} - -// extractDateLine returns the value of a date line matching the prefix. -func extractDateLine(content, prefix string) string { - for _, line := range strings.Split(content, "\n") { - if strings.HasPrefix(line, prefix) { - parts := strings.SplitN(line, ": ", 2) - if len(parts) == 2 { - return parts[1] - } - } - } - - return "" -} From b3b36cf2fb0b2c30b9bc4bfed1adbd758437b3eb Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 24 Sep 2025 10:20:31 -0500 Subject: [PATCH 23/26] error handling in duplicate finder --- tools/check-duplicates/main.go | 48 +++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/tools/check-duplicates/main.go b/tools/check-duplicates/main.go index fa4b0117..6319d3b2 100644 --- a/tools/check-duplicates/main.go +++ b/tools/check-duplicates/main.go @@ -68,6 +68,11 @@ var ignoreFiles = regexp.MustCompile(`_test\.go$`) func main() { // Map: bodyHash -> list of locations (file:functionName) funcMap := map[string][]string{} + // Collect non-fatal per-file errors (I/O, parse) to report after traversal. + var errs []string + // Counters for summary + var filesScanned int + var funcsHashed int err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() { return err @@ -78,7 +83,12 @@ func main() { if !withinIncluded(path) { return nil } - addFile(path, funcMap) + filesScanned++ + if n, e := addFile(path, funcMap); e != nil { + errs = append(errs, fmt.Sprintf("%s: %v", path, e)) + } else { + funcsHashed += n + } return nil }) @@ -99,12 +109,27 @@ func main() { } } + // Print summary and detailed output + fmt.Println("Summary:") + fmt.Printf(" Files scanned: %d\n", filesScanned) + fmt.Printf(" Functions hashed: %d\n", funcsHashed) + fmt.Printf(" Duplicate pairs: %d\n", len(collisions)) + fmt.Printf(" Errors: %d\n", len(errs)) + + if len(errs) > 0 { + fmt.Println("\nErrors during analysis:") + for _, e := range errs { + fmt.Println(" -", e) + } + os.Exit(1) + } + if len(collisions) == 0 { - fmt.Println("No duplicate functions found.") + fmt.Println("\nNo duplicate functions found.") return } - fmt.Println("Duplicate functions detected:") + fmt.Println("\nDuplicate functions detected:") for _, c := range collisions { fmt.Printf(" - %s == %s\n", c[0], c[1]) } @@ -123,31 +148,33 @@ func withinIncluded(path string) bool { } // addFile parses a Go file, hashes each function body, and records its location under that hash key. -func addFile(path string, funcMap map[string][]string) { +// addFile returns the number of function bodies hashed from the file and any error encountered. +func addFile(path string, funcMap map[string][]string) (int, error) { // Sanitize and restrict the path before opening (addresses gosec G304 false positive). clean := filepath.Clean(path) if filepath.IsAbs(clean) || strings.Contains(clean, "..") { - return // reject unexpected absolute or parent traversals + return 0, nil // reject unexpected absolute or parent traversals } if !withinIncluded(clean) || !strings.HasSuffix(clean, ".go") || ignoreFiles.MatchString(clean) { - return + return 0, nil } f, err := os.Open(clean) // #nosec G304: path origin is controlled by WalkDir + allowlist + sanitization above if err != nil { - return // Silent skip; traversal reports aggregate errors only. + return 0, fmt.Errorf("open: %w", err) } defer func() { _ = f.Close() }() src, err := io.ReadAll(f) if err != nil { - return + return 0, fmt.Errorf("read: %w", err) } fset := token.NewFileSet() parsed, err := parser.ParseFile(fset, clean, src, parser.ParseComments) if err != nil { - return // Skip unreadable / invalid Go sources. + return 0, fmt.Errorf("parse: %w", err) } + var count int for _, d := range parsed.Decls { fd, ok := d.(*ast.FuncDecl) if !ok || fd.Body == nil { // Skip declarations without bodies (interfaces, externs). @@ -161,7 +188,10 @@ func addFile(path string, funcMap map[string][]string) { key := fmt.Sprintf("%x", h) loc := fmt.Sprintf("%s:%s", clean, fd.Name.Name) funcMap[key] = append(funcMap[key], loc) + count++ } + + return count, nil } // normalize reduces insignificant differences in the AST statement dump so that From 79792f51735f670aa9e96700bc719fdfbc93f64b Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 24 Sep 2025 10:29:42 -0500 Subject: [PATCH 24/26] dynamic category detection for future use cases, case on headers --- .../cli-configuration-settings-reference.adoc | 10 +-- tools/generate-options-docs/docgen/docgen.go | 83 ++++++++++++++----- .../docgen/testdata/golden/options.adoc | 8 +- 3 files changed, 71 insertions(+), 30 deletions(-) diff --git a/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc b/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc index 10ffabf8..0dc41501 100644 --- a/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc +++ b/docs/dev-ux-portal-docs/general/cli-configuration-settings-reference.adoc @@ -1,6 +1,6 @@ = Configuration Settings Reference :created-date: March 23, 2025 -:revdate: September 23, 2025 +:revdate: September 24, 2025 :resourceid: pingcli_configuration_settings_reference The following configuration settings can be applied when using Ping CLI. @@ -23,7 +23,7 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d | `plugins` | | | String Array | |=== -== Ping Identity platform service properties +== Ping Identity Platform Service Properties [cols="2,2,2,1,3"] |=== @@ -49,7 +49,7 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d | `service.pingone.regionCode` | `--pingone-region-code` | PINGCLI_PINGONE_REGION_CODE | String (Enum) | The region code of the PingOne tenant. Options are: AP, AU, CA, EU, NA. Example: 'NA' |=== -== Platform export properties +== Platform Export Properties [cols="2,2,2,1,3"] |=== @@ -63,7 +63,7 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d | `export.services` | `--services` / `-s` | PINGCLI_EXPORT_SERVICES | String Array | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' |=== -== License properties +== License Properties [cols="2,2,2,1,3"] |=== @@ -73,7 +73,7 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d | `license.devopsUser` | `--devops-user` / `-u` | PINGCLI_LICENSE_DEVOPS_USER | String | The DevOps user for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. |=== -== Custom request properties +== Custom Request Properties [cols="2,2,2,1,3"] |=== diff --git a/tools/generate-options-docs/docgen/docgen.go b/tools/generate-options-docs/docgen/docgen.go index b01dfa8c..7e3537d2 100644 --- a/tools/generate-options-docs/docgen/docgen.go +++ b/tools/generate-options-docs/docgen/docgen.go @@ -65,29 +65,23 @@ func GenerateAsciiDoc() string { // backward-compatible wrapper using legacy dat // GenerateAsciiDocWithDates renders AsciiDoc with explicit created and revision dates. func GenerateAsciiDocWithDates(created, revdate string) string { configuration.InitAllOptions() + // Dynamically detect categories from the first segment of KoanfKey (before '.') + // Use "general" for keys without a dot. catMap := map[string][]options.Option{} for _, opt := range options.Options() { if opt.KoanfKey == "" { continue } - root := opt.KoanfKey - if strings.Contains(root, ".") { - root = strings.Split(root, ".")[0] - } - switch root { - case "service": - catMap["service"] = append(catMap["service"], opt) - case "export": - catMap["export"] = append(catMap["export"], opt) - case "license": - catMap["license"] = append(catMap["license"], opt) - case "request": - catMap["request"] = append(catMap["request"], opt) - default: - if !strings.Contains(opt.KoanfKey, ".") { - catMap["general"] = append(catMap["general"], opt) - } + category := "general" + if i := strings.Index(opt.KoanfKey, "."); i > 0 { + category = opt.KoanfKey[:i] + } else if strings.Contains(opt.KoanfKey, ".") { + // Defensive: if dot at position 0 for some reason, fallback to general + category = "general" + } else if strings.Contains(opt.KoanfKey, ".") { + category = strings.Split(opt.KoanfKey, ".")[0] } + catMap[category] = append(catMap[category], opt) } for k := range catMap { slices.SortFunc(catMap[k], func(a, b options.Option) int { return strings.Compare(a.KoanfKey, b.KoanfKey) }) @@ -100,13 +94,34 @@ func GenerateAsciiDocWithDates(created, revdate string) string { b.WriteString("The following configuration settings can be applied when using Ping CLI.\n\n") b.WriteString("The following configuration settings can be applied by using the xref:command_reference:pingcli_config_set.adoc[`config set` command] to persist the configuration value for a given **Configuration Key** in the Ping CLI configuration file.\n\n") b.WriteString("The configuration file is created at `.pingcli/config.yaml` in the user's home directory.\n\n") - ordered := []struct{ key, title string }{{"general", "General Properties"}, {"service", "Ping Identity platform service properties"}, {"export", "Platform export properties"}, {"license", "License properties"}, {"request", "Custom request properties"}} - for _, sec := range ordered { - opts := catMap[sec.key] + // Determine output order: prefer known categories in a fixed order when present, + // then append any other categories sorted alphabetically. This keeps current + // docs stable while allowing new categories to appear without code changes. + preferred := []string{"general", "service", "export", "license", "request"} + seen := map[string]bool{} + var keys []string + for _, k := range preferred { + if _, ok := catMap[k]; ok { + keys = append(keys, k) + seen[k] = true + } + } + // Add any additional categories not in preferred, sorted. + var extras []string + for k := range catMap { + if !seen[k] { + extras = append(extras, k) + } + } + slices.Sort(extras) + keys = append(keys, extras...) + + for _, k := range keys { + opts := catMap[k] if len(opts) == 0 { continue } - b.WriteString("== " + sec.title + "\n\n") + b.WriteString("== " + sectionTitle(k) + "\n\n") // Column order updated: Configuration Key | Equivalent Parameter | Environment Variable | Data Type | Purpose b.WriteString("[cols=\"2,2,2,1,3\"]\n|===\n") b.WriteString("|Configuration Key |Equivalent Parameter |Environment Variable |Data Type |Purpose\n\n") @@ -124,6 +139,32 @@ func GenerateAsciiDocWithDates(created, revdate string) string { return b.String() } +// sectionTitle returns a friendly section title for a category key, with special +// wording for well-known categories and a sensible default otherwise. +func sectionTitle(key string) string { + switch key { + case "general": + return "General Properties" + case "service": + return "Ping Identity Platform Service Properties" + case "export": + return "Platform Export Properties" + case "license": + return "License Properties" + case "request": + return "Custom Request Properties" + default: + if key == "" { + return "Properties" + } + // Capitalize first rune; avoid pulling in extra deps. + r := []rune(key) + r[0] = []rune(strings.ToUpper(string(r[0])))[0] + + return string(r) + " properties" + } +} + // ShouldOutputAsciiDoc determines if AsciiDoc format should be used based on file extension or explicit choice. func ShouldOutputAsciiDoc(outPath string, explicit bool) bool { if explicit { diff --git a/tools/generate-options-docs/docgen/testdata/golden/options.adoc b/tools/generate-options-docs/docgen/testdata/golden/options.adoc index af2db68d..fbceb3dd 100644 --- a/tools/generate-options-docs/docgen/testdata/golden/options.adoc +++ b/tools/generate-options-docs/docgen/testdata/golden/options.adoc @@ -21,7 +21,7 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d | `plugins` | | | String Array | |=== -== Ping Identity platform service properties +== Ping Identity Platform Service Properties [cols="2,2,2,1,3"] |=== @@ -47,7 +47,7 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d | `service.pingone.regionCode` | `--pingone-region-code` | PINGCLI_PINGONE_REGION_CODE | String (Enum) | The region code of the PingOne tenant. Options are: AP, AU, CA, EU, NA. Example: 'NA' |=== -== Platform export properties +== Platform Export Properties [cols="2,2,2,1,3"] |=== @@ -61,7 +61,7 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d | `export.services` | `--services` / `-s` | PINGCLI_EXPORT_SERVICES | String Array | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. Options are: pingfederate, pingone-authorize, pingone-mfa, pingone-platform, pingone-protect, pingone-sso. Example: 'pingone-sso,pingone-mfa,pingfederate' |=== -== License properties +== License Properties [cols="2,2,2,1,3"] |=== @@ -71,7 +71,7 @@ The configuration file is created at `.pingcli/config.yaml` in the user's home d | `license.devopsUser` | `--devops-user` / `-u` | PINGCLI_LICENSE_DEVOPS_USER | String | The DevOps user for the license request. See https://developer.pingidentity.com/devops/how-to/devopsRegistration.html on how to register a DevOps user. You can save the DevOps user and key in your profile using the 'pingcli config' commands. |=== -== Custom request properties +== Custom Request Properties [cols="2,2,2,1,3"] |=== From 10a84114679d553c7bacf9b7a441ff2dce36a77d Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 24 Sep 2025 10:43:53 -0500 Subject: [PATCH 25/26] fix license typing error, handle unmapped types with N/A rather thand defaulting to string --- tools/README_DocumentGeneration.md | 3 +++ tools/generate-options-docs/docgen/docgen.go | 9 ++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tools/README_DocumentGeneration.md b/tools/README_DocumentGeneration.md index fe6da7d6..0c0919b3 100644 --- a/tools/README_DocumentGeneration.md +++ b/tools/README_DocumentGeneration.md @@ -43,6 +43,8 @@ go run ./tools/generate-options-docs -asciidoc > docs/dev-ux-portal-docs/general The AsciiDoc generator orders sections as: General, Service, Export, License, Request (as of September 2025 - new options may change this order). +Data types: If a Data Type cell shows "N/A", it means the option's data type hasn't been mapped in the generator yet. Review and add a mapping in `tools/generate-options-docs/docgen/docgen.go` (see `asciiDocDataType`). This is intentional to surface new or unknown types for review rather than silently defaulting. + ## Command Reference Pages & Navigation Generate a page for every command and subcommand plus a hierarchical navigation file @@ -138,6 +140,7 @@ make generate-all-docs | Golden test fails after code change | Run with `-update` to refresh fixtures if changes are intentional | | Navigation missing root command | Ensure `renderNav` includes the root (already implemented) | | Dates cause diffs | They are normalized in tests; ensure you did not alter attribute names | +| "N/A" appears in Data Type column | The option type is not mapped in the generator. Update `asciiDocDataType` in `tools/generate-options-docs/docgen/docgen.go` (or introduce a new `options.Type` as appropriate). | ## See Also diff --git a/tools/generate-options-docs/docgen/docgen.go b/tools/generate-options-docs/docgen/docgen.go index 7e3537d2..29d4bf86 100644 --- a/tools/generate-options-docs/docgen/docgen.go +++ b/tools/generate-options-docs/docgen/docgen.go @@ -191,20 +191,19 @@ func asciiDocDataType(opt options.Option) string { switch opt.Type { case options.BOOL: return "Boolean" - case options.STRING: + case options.STRING, options.LICENSE_VERSION: return "String" case options.STRING_SLICE, options.EXPORT_SERVICES, options.HEADER: return "String Array" case options.UUID: return "String (UUID Format)" - case options.EXPORT_FORMAT, options.OUTPUT_FORMAT, options.PINGFEDERATE_AUTH_TYPE, options.PINGONE_AUTH_TYPE, options.PINGONE_REGION_CODE, options.REQUEST_SERVICE, options.EXPORT_SERVICE_GROUP: + case options.EXPORT_FORMAT, options.OUTPUT_FORMAT, options.PINGFEDERATE_AUTH_TYPE, options.PINGONE_AUTH_TYPE, options.PINGONE_REGION_CODE, options.REQUEST_SERVICE, options.EXPORT_SERVICE_GROUP, options.LICENSE_PRODUCT: return "String (Enum)" case options.INT: return "Integer" - case options.LICENSE_PRODUCT, options.LICENSE_VERSION: - return "String (Enum)" default: - return "String" + // Use an explicit fallback so unmapped types surface during review. + return "N/A" } } From 02214402692d60d40e25657c7115526c39762d13 Mon Sep 17 00:00:00 2001 From: PingDavidR Date: Wed, 24 Sep 2025 11:32:36 -0500 Subject: [PATCH 26/26] templating instead of fprint, other PR feedback --- tools/generate-command-docs/main.go | 183 ++++++++++++++++++---------- 1 file changed, 122 insertions(+), 61 deletions(-) diff --git a/tools/generate-command-docs/main.go b/tools/generate-command-docs/main.go index b24e2da6..e3bbedfa 100644 --- a/tools/generate-command-docs/main.go +++ b/tools/generate-command-docs/main.go @@ -1,12 +1,14 @@ package main import ( + "bytes" "flag" "fmt" "os" "path/filepath" "sort" "strings" + "text/template" "time" "github.com/pingidentity/pingcli/cmd" @@ -81,80 +83,105 @@ func main() { } func renderSingle(c *cobra.Command, createdDate, revDate, resourcePrefix string) string { - // Manual style: always use top-level title '=' regardless of hierarchy. - base := strings.ReplaceAll(c.CommandPath(), " ", "_") - b := &strings.Builder{} - fmt.Fprintf(b, "= %s\n", c.CommandPath()) - fmt.Fprintf(b, ":created-date: %s\n:revdate: %s\n:resourceid: %s%s\n\n", createdDate, revDate, resourcePrefix, base) - - // Short description (first paragraph only) - if s := strings.TrimSpace(firstLine(c.Short, c.Long)); s != "" { - b.WriteString(s) - b.WriteString("\n\n") + type singlePageData struct { + CommandPath string + CreatedDate string + RevDate string + ResourceID string + Short string + Synopsis string + Use string + ExampleBlock string + HasLocal bool + LocalOptions string + HasInherited bool + InheritedOptions string + ParentBlock string + SubcommandsBlock string } - // Synopsis section: prefer full Long (without first line duplication) else Short. - b.WriteString("== Synopsis\n\n") + // Precompute fields exactly matching previous output. + base := strings.ReplaceAll(c.CommandPath(), " ", "_") + short := strings.TrimSpace(firstLine(c.Short, c.Long)) + var synopsis string if long := strings.TrimSpace(c.Long); long != "" { - // Keep full long description as-is. - b.WriteString(long) - b.WriteString("\n\n") - } else if short := strings.TrimSpace(c.Short); short != "" { - b.WriteString(short + "\n\n") + synopsis = long + "\n\n" + } else if s := strings.TrimSpace(c.Short); s != "" { + synopsis = s + "\n\n" } - // Usage block - b.WriteString("----\n") - b.WriteString(strings.TrimSpace(c.UseLine()) + "\n") - b.WriteString("----\n\n") - - // Examples section (if any) - preserve original indentation & spacing. + use := strings.TrimSpace(c.UseLine()) + var exampleBlock string if rawEx := c.Example; strings.TrimSpace(rawEx) != "" { - b.WriteString("== Examples\n\n") - b.WriteString("----\n") - b.WriteString(rawEx) + var eb strings.Builder + eb.WriteString("== Examples\n\n") + eb.WriteString("----\n") + eb.WriteString(rawEx) if !strings.HasSuffix(rawEx, "\n") { - b.WriteString("\n") + eb.WriteString("\n") } - b.WriteString("----\n\n") + eb.WriteString("----\n\n") + exampleBlock = eb.String() } - // TODO: See how to render line breaks for readability when generating AsciiDoc to portal - // Attempts to do so thus far have not worked - // Options (non-inherited) including help flag. local := c.NonInheritedFlags() inherited := c.InheritedFlags() - if local != nil && local.HasAvailableFlags() { - b.WriteString("== Options\n\n") - b.WriteString(formatFlagBlock(local, true, c)) - b.WriteString("\n") + hasLocal := local != nil && local.HasAvailableFlags() + hasInherited := inherited != nil && inherited.HasAvailableFlags() + var localBlock, inheritedBlock string + if hasLocal { + localBlock = formatFlagBlock(local, true, c) } - if inherited != nil && inherited.HasAvailableFlags() { - b.WriteString("== Options inherited from parent commands\n\n") - b.WriteString(formatFlagBlock(inherited, false, c)) - b.WriteString("\n") + if hasInherited { + inheritedBlock = formatFlagBlock(inherited, false, c) } - // More information (link back to parent) if there is a parent (omit for root). + var parentBlock string if p := c.Parent(); p != nil { parentFile := strings.ReplaceAll(p.CommandPath(), " ", "_") + ".adoc" - b.WriteString("== More information\n\n") - fmt.Fprintf(b, "* xref:%s[]\t - %s\n", parentFile, firstLine(p.Short, p.Long)) - b.WriteString("\n") + var pb strings.Builder + pb.WriteString("== More information\n\n") + fmt.Fprintf(&pb, "* xref:%s[]\t - %s\n\n", parentFile, firstLine(p.Short, p.Long)) + parentBlock = pb.String() } - // Subcommands listing (retain for commands that have them; use manual style heading depth) + var subcommandsBlock string subs := visibleSubcommands(c) if len(subs) > 0 { - b.WriteString("== Subcommands\n\n") sort.Slice(subs, func(i, j int) bool { return subs[i].Name() < subs[j].Name() }) + var sb strings.Builder + sb.WriteString("== Subcommands\n\n") for _, sc := range subs { name := strings.ReplaceAll(sc.CommandPath(), " ", "_") + ".adoc" - fmt.Fprintf(b, "* xref:%s[] - %s\n", name, firstLine(sc.Short, sc.Long)) + fmt.Fprintf(&sb, "* xref:%s[] - %s\n", name, firstLine(sc.Short, sc.Long)) } - b.WriteString("\n") + sb.WriteString("\n") + subcommandsBlock = sb.String() } - return b.String() + data := singlePageData{ + CommandPath: c.CommandPath(), + CreatedDate: createdDate, + RevDate: revDate, + ResourceID: resourcePrefix + base, + Short: short, + Synopsis: synopsis, + Use: use, + ExampleBlock: exampleBlock, + HasLocal: hasLocal, + LocalOptions: localBlock, + HasInherited: hasInherited, + InheritedOptions: inheritedBlock, + ParentBlock: parentBlock, + SubcommandsBlock: subcommandsBlock, + } + + var buf bytes.Buffer + if err := singlePageTpl.Execute(&buf, data); err != nil { + // Fallback should never happen; keep previous behavior if it does. + return "" + } + + return buf.String() } // formatFlagBlock renders a flag set into a code-fenced block similar to manual pages. @@ -175,7 +202,11 @@ func formatFlagBlock(fs *pflag.FlagSet, includeHelp bool, c *cobra.Command) stri return si < sj }) - type line struct{ spec, desc string } + type line struct { + Spec string + Pad int + Desc string + } lines := make([]line, 0, len(flags)) for _, f := range flags { var spec string @@ -201,19 +232,19 @@ func formatFlagBlock(fs *pflag.FlagSet, includeHelp bool, c *cobra.Command) stri // Collapse internal newlines but otherwise keep original spacing; no manual wrapping. desc = strings.ReplaceAll(desc, "\n", " ") - lines = append(lines, line{spec: spec, desc: desc}) + lines = append(lines, line{Spec: spec, Desc: desc}) } if includeHelp { found := false for _, l := range lines { - if strings.Contains(l.spec, "--help") { + if strings.Contains(l.Spec, "--help") { found = true break } } if !found { - helpLine := line{spec: "-h, --help", desc: fmt.Sprintf("help for %s", c.Name())} + helpLine := line{Spec: "-h, --help", Desc: fmt.Sprintf("help for %s", c.Name())} if len(lines) == 0 { lines = append(lines, helpLine) } else { @@ -223,22 +254,23 @@ func formatFlagBlock(fs *pflag.FlagSet, includeHelp bool, c *cobra.Command) stri } maxSpec := 0 for _, l := range lines { - if len(l.spec) > maxSpec { - maxSpec = len(l.spec) + if len(l.Spec) > maxSpec { + maxSpec = len(l.Spec) } } - var b strings.Builder - b.WriteString("----\n") - for _, l := range lines { - pad := maxSpec - len(l.spec) + for i := range lines { + pad := maxSpec - len(lines[i].Spec) if pad < 0 { pad = 0 } - fmt.Fprintf(&b, " %s%s %s\n", l.spec, strings.Repeat(" ", pad), l.desc) + lines[i].Pad = pad + } + var buf bytes.Buffer + if err := flagBlockTpl.Execute(&buf, struct{ Lines []line }{Lines: lines}); err != nil { + return "" } - b.WriteString("----\n") - return b.String() + return buf.String() } // firstLine returns the first non-empty line from short or long description. @@ -320,3 +352,32 @@ func readFileIfWithin(path, base string) ([]byte, error) { return data, nil } + +// Templates and helpers +var singlePageTpl = template.Must(template.New("single").Parse(`= {{.CommandPath}} +:created-date: {{.CreatedDate}} +:revdate: {{.RevDate}} +:resourceid: {{.ResourceID}} + +{{if .Short}}{{.Short}} + +{{end}}== Synopsis + +{{.Synopsis}}---- +{{.Use}} +---- + +{{if .ExampleBlock}}{{.ExampleBlock}}{{end}}{{if .HasLocal}}== Options + +{{.LocalOptions}} +{{end}}{{if .HasInherited}}== Options inherited from parent commands + +{{.InheritedOptions}} +{{end}}{{if .ParentBlock}}{{.ParentBlock}}{{end}}{{if .SubcommandsBlock}}{{.SubcommandsBlock}}{{end}}`)) + +func repeat(n int) string { return strings.Repeat(" ", n) } + +var flagBlockTpl = template.Must(template.New("flag").Funcs(template.FuncMap{"repeat": repeat}).Parse(`---- +{{range .Lines}} {{.Spec}}{{repeat .Pad}} {{.Desc}} +{{end}}---- +`))