From 091e03f071d2023290884019619a06e9ff2cd0bb Mon Sep 17 00:00:00 2001 From: Dominik Menke Date: Sun, 8 Jan 2023 14:53:15 +0100 Subject: [PATCH] docs: generate CLI help (#1785) --- .golangci.toml | 1 - Makefile | 2 +- cmd/cmd_dnshelp.go | 9 +- cmd/cmd_renew.go | 15 +-- cmd/cmd_revoke.go | 9 +- cmd/cmd_run.go | 10 +- cmd/flags.go | 11 ++- cmd/zz_gen_cmd_dnshelp.go | 13 ++- docs/content/usage/cli/Options.md | 86 +--------------- docs/data/zz_cli_help.toml | 131 +++++++++++++++++++++++++ docs/layouts/shortcodes/clihelp.html | 24 +++++ internal/dnsdocs/cli_help/generator.go | 114 +++++++++++++++++++++ internal/dnsdocs/dns.go.tmpl | 15 ++- 13 files changed, 317 insertions(+), 123 deletions(-) create mode 100644 docs/data/zz_cli_help.toml create mode 100644 docs/layouts/shortcodes/clihelp.html create mode 100644 internal/dnsdocs/cli_help/generator.go diff --git a/.golangci.toml b/.golangci.toml index a348a651..66c3e6b6 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -204,7 +204,6 @@ [[issues.exclude-rules]] path = "providers/dns/sakuracloud/client.go" text = "mu is a global variable" - [[issues.exclude-rules]] path = "providers/dns/hosttech/internal/client_test.go" text = "Duplicate words \\(0\\) found" diff --git a/Makefile b/Makefile index e59d8e69..dbc90b56 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,7 @@ generate-dns: validate-doc: generate-dns validate-doc: DOC_DIRECTORIES := ./docs/ ./cmd/ validate-doc: - if git diff --exit-code --quiet $(DOC_DIRECTORIES) 2>/dev/null; then \ + @if git diff --exit-code --quiet $(DOC_DIRECTORIES) 2>/dev/null; then \ echo 'All documentation changes are done the right way.'; \ else \ echo 'The documentation must be regenerated, please use `make generate-dns`.'; \ diff --git a/cmd/cmd_dnshelp.go b/cmd/cmd_dnshelp.go index 4210e23a..e38e0a38 100644 --- a/cmd/cmd_dnshelp.go +++ b/cmd/cmd_dnshelp.go @@ -3,7 +3,6 @@ package cmd import ( "fmt" "io" - "os" "strings" "text/tabwriter" @@ -28,16 +27,16 @@ func createDNSHelp() *cli.Command { func dnsHelp(ctx *cli.Context) error { code := ctx.String("code") if code == "" { - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + w := tabwriter.NewWriter(ctx.App.Writer, 0, 0, 2, ' ', 0) ew := &errWriter{w: w} ew.writeln(`Credentials for DNS providers must be passed through environment variables.`) ew.writeln() - ew.writeln(`To display the documentation for a DNS providers:`) + ew.writeln(`To display the documentation for a specific DNS provider, run:`) ew.writeln() ew.writeln("\t$ lego dnshelp -c code") ew.writeln() - ew.writeln("All DNS codes:") + ew.writeln("Supported DNS providers:") ew.writef("\t%s\n", allDNSCodes()) ew.writeln() ew.writeln("More information: https://go-acme.github.io/lego/dns") @@ -49,7 +48,7 @@ func dnsHelp(ctx *cli.Context) error { return w.Flush() } - return displayDNSHelp(strings.ToLower(code)) + return displayDNSHelp(ctx.App.Writer, strings.ToLower(code)) } type errWriter struct { diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index 704dc910..e3310ccc 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -56,24 +56,27 @@ func createRenew() *cli.Command { Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.", }, &cli.BoolFlag{ - Name: "must-staple", - Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.", + Name: "must-staple", + Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." + + " Only works if the CSR is generated by lego.", }, &cli.StringFlag{ Name: "renew-hook", Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.", }, &cli.StringFlag{ - Name: "preferred-chain", - Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.", + Name: "preferred-chain", + Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name." + + " If no match, the default offered chain will be used.", }, &cli.StringFlag{ Name: "always-deactivate-authorizations", Usage: "Force the authorizations to be relinquished even if the certificate request was successful.", }, &cli.BoolFlag{ - Name: "no-random-sleep", - Usage: "Do not add a random sleep before the renewal. We do not recommend using this flag if you are doing your renewals in an automated way.", + Name: "no-random-sleep", + Usage: "Do not add a random sleep before the renewal." + + " We do not recommend using this flag if you are doing your renewals in an automated way.", }, }, } diff --git a/cmd/cmd_revoke.go b/cmd/cmd_revoke.go index f735893e..cd95978d 100644 --- a/cmd/cmd_revoke.go +++ b/cmd/cmd_revoke.go @@ -18,8 +18,13 @@ func createRevoke() *cli.Command { Usage: "Keep the certificates after the revocation instead of archiving them.", }, &cli.UintFlag{ - Name: "reason", - Usage: "Identifies the reason for the certificate revocation. See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1. 0(unspecified),1(keyCompromise),2(cACompromise),3(affiliationChanged),4(superseded),5(cessationOfOperation),6(certificateHold),8(removeFromCRL),9(privilegeWithdrawn),10(aACompromise)", + Name: "reason", + Usage: "Identifies the reason for the certificate revocation." + + " See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1." + + " Valid values are:" + + " 0 (unspecified), 1 (keyCompromise), 2 (cACompromise), 3 (affiliationChanged)," + + " 4 (superseded), 5 (cessationOfOperation), 6 (certificateHold), 8 (removeFromCRL)," + + " 9 (privilegeWithdrawn), or 10 (aACompromise).", Value: acme.CRLReasonUnspecified, }, }, diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index 00259043..88ce1675 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -36,16 +36,18 @@ func createRun() *cli.Command { Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.", }, &cli.BoolFlag{ - Name: "must-staple", - Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.", + Name: "must-staple", + Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." + + " Only works if the CSR is generated by lego.", }, &cli.StringFlag{ Name: "run-hook", Usage: "Define a hook. The hook is executed when the certificates are effectively created.", }, &cli.StringFlag{ - Name: "preferred-chain", - Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.", + Name: "preferred-chain", + Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name." + + " If no match, the default offered chain will be used.", }, &cli.StringFlag{ Name: "always-deactivate-authorizations", diff --git a/cmd/flags.go b/cmd/flags.go index 8f9ff446..2322e4eb 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -77,8 +77,9 @@ func CreateFlags(defaultPath string) []cli.Flag { Value: "Host", }, &cli.StringFlag{ - Name: "http.webroot", - Usage: "Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge. This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge", + Name: "http.webroot", + Usage: "Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge." + + " This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge", }, &cli.StringSliceFlag{ Name: "http.memcached-host", @@ -102,8 +103,10 @@ func CreateFlags(defaultPath string) []cli.Flag { Usage: "By setting this flag to true, disables the need to wait the propagation of the TXT record to all authoritative name servers.", }, &cli.StringSliceFlag{ - Name: "dns.resolvers", - Usage: "Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.", + Name: "dns.resolvers", + Usage: "Set the resolvers to use for performing recursive DNS queries." + + " Supported: host:port." + + " The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.", }, &cli.IntFlag{ Name: "http-timeout", diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index ff0d6e3c..52ab1892 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -5,7 +5,7 @@ package cmd import ( "fmt" - "os" + "io" "sort" "strings" "text/tabwriter" @@ -127,8 +127,8 @@ func allDNSCodes() string { return strings.Join(providers, ", ") } -func displayDNSHelp(name string) error { - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) +func displayDNSHelp(w io.Writer, name string) error { + w = tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) ew := &errWriter{w: w} switch name { @@ -2439,9 +2439,8 @@ func displayDNSHelp(name string) error { return fmt.Errorf("%q is not yet supported", name) } - if ew.err != nil { - return fmt.Errorf("error: %w", ew.err) + if flusher, ok := w.(interface{ Flush() error }); ok { + return flusher.Flush() } - - return w.Flush() + return nil } diff --git a/docs/content/usage/cli/Options.md b/docs/content/usage/cli/Options.md index 905c4136..bdc62ac2 100644 --- a/docs/content/usage/cli/Options.md +++ b/docs/content/usage/cli/Options.md @@ -8,91 +8,7 @@ weight: 4 ## Usage -{{< tabs >}} -{{% tab name="lego --help" %}} -```slim -NAME: - lego - Let's Encrypt client written in Go - -USAGE: - lego [global options] command [command options] [arguments...] - -COMMANDS: - run Register an account, then create and install a certificate - revoke Revoke a certificate - renew Renew a certificate - dnshelp Shows additional help for the '--dns' global option - list Display certificates and accounts information. - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS: - --domains value, -d value Add a domain to the process. Can be specified multiple times. - --server value, -s value CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. (default: "https://acme-v02.api.letsencrypt.org/directory") - --accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service. (default: false) - --email value, -m value Email used for registration and recovery contact. - --csr value, -c value Certificate signing request filename, if an external CSR is to be used. - --eab Use External Account Binding for account registration. Requires --kid and --hmac. (default: false) - --kid value Key identifier from External CA. Used for External Account Binding. - --hmac value MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding. - --key-type value, -k value Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384. (default: "ec256") - --filename value (deprecated) Filename of the generated certificate. - --path value Directory to use for storing the data. (default: "./.lego") [$LEGO_PATH] - --http Use the HTTP challenge to solve challenges. Can be mixed with other types of challenges. (default: false) - --http.port value Set the port and interface to use for HTTP based challenges to listen on.Supported: interface:port or :port. (default: ":80") - --http.proxy-header value Validate against this HTTP header when solving HTTP based challenges behind a reverse proxy. (default: "Host") - --http.webroot value Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge. This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge - --http.memcached-host value Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts. - --tls Use the TLS challenge to solve challenges. Can be mixed with other types of challenges. (default: false) - --tls.port value Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port. (default: ":443") - --dns value Solve a DNS challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage. - --dns.disable-cp By setting this flag to true, disables the need to wait the propagation of the TXT record to all authoritative name servers. (default: false) - --dns.resolvers value Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined. - --http-timeout value Set the HTTP timeout value to a specific value in seconds. (default: 0) - --dns-timeout value Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name servers queries. (default: 10) - --pem Generate a .pem file by concatenating the .key and .crt files together. (default: false) - --pfx Generate a .pfx (PKCS#12) file by with the .key and .crt and issuer .crt files together. (default: false) - --pfx.pass value The password used to encrypt the .pfx (PCKS#12) file. (default: "changeit") - --cert.timeout value Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30) - --help, -h show help (default: false) - --version, -v print the version (default: false) -``` -{{% /tab %}} -{{% tab name="lego run --help" %}} -```slim -NAME: - lego run - Register an account, then create and install a certificate - -USAGE: - lego run [command options] [arguments...] - -OPTIONS: - --no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false) - --must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false) - --run-hook value Define a hook. The hook is executed when the certificates are effectively created. - --preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. - --always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful. -``` -{{% /tab %}} -{{% tab name="lego renew --help" %}} -```slim -NAME: - lego renew - Renew a certificate - -USAGE: - lego renew [command options] [arguments...] - -OPTIONS: - --days value The number of days left on a certificate to renew it. (default: 30) - --reuse-key Used to indicate you want to reuse your current private key for the new certificate. (default: false) - --no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false) - --must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false) - --renew-hook value Define a hook. The hook is executed only when the certificates are effectively renewed. - --preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. - --always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful. - --no-random-sleep Do not add a random sleep before the renewal. We do not recommend using this flag if you are doing your renewals in an automated way. (default: false) -``` -{{% /tab %}} -{{< /tabs >}} +{{< clihelp >}} When using the standard `--path` option, all certificates and account configurations are saved to a folder `.lego` in the current working directory. diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml new file mode 100644 index 00000000..14134118 --- /dev/null +++ b/docs/data/zz_cli_help.toml @@ -0,0 +1,131 @@ +# THIS FILE IS AUTO-GENERATED. PLEASE DO NOT EDIT. + + +[[command]] +title = "lego help" +content = """ +NAME: + lego - Let's Encrypt client written in Go + +USAGE: + lego [global options] command [command options] [arguments...] + +COMMANDS: + run Register an account, then create and install a certificate + revoke Revoke a certificate + renew Renew a certificate + dnshelp Shows additional help for the '--dns' global option + list Display certificates and accounts information. + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS: + --accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service. (default: false) + --cert.timeout value Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30) + --csr value, -c value Certificate signing request filename, if an external CSR is to be used. + --dns value Solve a DNS challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage. + --dns-timeout value Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name servers queries. (default: 10) + --dns.disable-cp By setting this flag to true, disables the need to wait the propagation of the TXT record to all authoritative name servers. (default: false) + --dns.resolvers value [ --dns.resolvers value ] Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined. + --domains value, -d value [ --domains value, -d value ] Add a domain to the process. Can be specified multiple times. + --eab Use External Account Binding for account registration. Requires --kid and --hmac. (default: false) + --email value, -m value Email used for registration and recovery contact. + --filename value (deprecated) Filename of the generated certificate. + --help, -h show help (default: false) + --hmac value MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding. + --http Use the HTTP challenge to solve challenges. Can be mixed with other types of challenges. (default: false) + --http-timeout value Set the HTTP timeout value to a specific value in seconds. (default: 0) + --http.memcached-host value [ --http.memcached-host value ] Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts. + --http.port value Set the port and interface to use for HTTP based challenges to listen on.Supported: interface:port or :port. (default: ":80") + --http.proxy-header value Validate against this HTTP header when solving HTTP based challenges behind a reverse proxy. (default: "Host") + --http.webroot value Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge. This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge + --key-type value, -k value Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384. (default: "ec256") + --kid value Key identifier from External CA. Used for External Account Binding. + --path value Directory to use for storing the data. (default: "./.lego") [$LEGO_PATH] + --pem Generate a .pem file by concatenating the .key and .crt files together. (default: false) + --pfx Generate a .pfx (PKCS#12) file by with the .key and .crt and issuer .crt files together. (default: false) + --pfx.pass value The password used to encrypt the .pfx (PCKS#12) file. (default: "changeit") + --server value, -s value CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. (default: "https://acme-v02.api.letsencrypt.org/directory") + --tls Use the TLS challenge to solve challenges. Can be mixed with other types of challenges. (default: false) + --tls.port value Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port. (default: ":443") + --user-agent value Add to the user-agent sent to the CA to identify an application embedding lego-cli +""" + +[[command]] +title = "lego help run" +content = """ +NAME: + lego run - Register an account, then create and install a certificate + +USAGE: + lego run [command options] [arguments...] + +OPTIONS: + --always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful. + --must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false) + --no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false) + --preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. + --run-hook value Define a hook. The hook is executed when the certificates are effectively created. +""" + +[[command]] +title = "lego help renew" +content = """ +NAME: + lego renew - Renew a certificate + +USAGE: + lego renew [command options] [arguments...] + +OPTIONS: + --always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful. + --days value The number of days left on a certificate to renew it. (default: 30) + --must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false) + --no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false) + --no-random-sleep Do not add a random sleep before the renewal. We do not recommend using this flag if you are doing your renewals in an automated way. (default: false) + --preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. + --renew-hook value Define a hook. The hook is executed only when the certificates are effectively renewed. + --reuse-key Used to indicate you want to reuse your current private key for the new certificate. (default: false) +""" + +[[command]] +title = "lego help revoke" +content = """ +NAME: + lego revoke - Revoke a certificate + +USAGE: + lego revoke [command options] [arguments...] + +OPTIONS: + --keep, -k Keep the certificates after the revocation instead of archiving them. (default: false) + --reason value Identifies the reason for the certificate revocation. See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1. Valid values are: 0 (unspecified), 1 (keyCompromise), 2 (cACompromise), 3 (affiliationChanged), 4 (superseded), 5 (cessationOfOperation), 6 (certificateHold), 8 (removeFromCRL), 9 (privilegeWithdrawn), or 10 (aACompromise). (default: 0) +""" + +[[command]] +title = "lego help list" +content = """ +NAME: + lego list - Display certificates and accounts information. + +USAGE: + lego list [command options] [arguments...] + +OPTIONS: + --accounts, -a Display accounts. (default: false) + --names, -n Display certificate common names only. (default: false) +""" + +[[command]] +title = "lego dnshelp" +content = """ +Credentials for DNS providers must be passed through environment variables. + +To display the documentation for a specific DNS provider, run: + + $ lego dnshelp -c code + +Supported DNS providers: + acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, bindman, bluecat, checkdomain, civo, clouddns, cloudflare, cloudns, cloudxns, conoha, constellix, desec, designate, digitalocean, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, hetzner, hostingde, hosttech, httpreq, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, iwantmyname, joker, liara, lightsail, linode, liquidweb, loopia, luadns, manual, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, ns1, oraclecloud, otc, ovh, pdns, porkbun, rackspace, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, servercow, simply, sonic, stackpath, tencentcloud, transip, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, vscale, vultr, wedos, yandex, yandexcloud, zoneee, zonomi + +More information: https://go-acme.github.io/lego/dns +""" diff --git a/docs/layouts/shortcodes/clihelp.html b/docs/layouts/shortcodes/clihelp.html new file mode 100644 index 00000000..b3bd95e8 --- /dev/null +++ b/docs/layouts/shortcodes/clihelp.html @@ -0,0 +1,24 @@ +
+
+ {{ $commands := index $.Site.Data.zz_cli_help "command" }} + {{ range $idx, $tab := $commands }} + + {{ end }} +
+
+ {{ range $idx, $tab := $commands }} +
+
{{ .content }}
+
+ {{ end }} +
+
diff --git a/internal/dnsdocs/cli_help/generator.go b/internal/dnsdocs/cli_help/generator.go new file mode 100644 index 00000000..4f87ab65 --- /dev/null +++ b/internal/dnsdocs/cli_help/generator.go @@ -0,0 +1,114 @@ +package main + +//go:generate go run . + +import ( + "bytes" + "fmt" + "log" + "os" + "strings" + "text/template" + + "github.com/go-acme/lego/v4/cmd" + "github.com/urfave/cli/v2" +) + +const outputFile = "../../../docs/data/zz_cli_help.toml" + +const baseTemplate = `# THIS FILE IS AUTO-GENERATED. PLEASE DO NOT EDIT. + +{{ range .}} +[[command]] +title = "{{.Title}}" +content = """ +{{.Help}} +""" +{{end -}} +` + +type commandHelp struct { + Title string + Help string +} + +func main() { + log.SetFlags(0) + + err := generate() + if err != nil { + log.Fatal(err) + } + + log.Println("cli_help.toml updated") +} + +func generate() error { + app := createStubApp() + + outputTpl := template.Must(template.New("output").Parse(baseTemplate)) + + // collect output of various help pages + var help []commandHelp + for _, args := range [][]string{ + {"lego", "help"}, + {"lego", "help", "run"}, + {"lego", "help", "renew"}, + {"lego", "help", "revoke"}, + {"lego", "help", "list"}, + {"lego", "dnshelp"}, + } { + content, err := run(app, args) + if err != nil { + return fmt.Errorf("running %s failed: %w", args, err) + } + + help = append(help, content) + } + + f, err := os.Create(outputFile) + if err != nil { + return fmt.Errorf("cannot open cli_help.toml: %w", err) + } + + err = outputTpl.Execute(f, help) + defer func() { _ = f.Close() }() + if err != nil { + return fmt.Errorf("failed to write cli_help.toml: %w", err) + } + + return nil +} + +// createStubApp Construct cli app, very similar to cmd/lego/main.go. +// Notable differences: +// - substitute "." for CWD in default config path, as the user will very likely see a different path +// - do not include version information, because we're likely running against a snapshot +// - skip DNS help and provider list, as initialization takes time, and we don't generate `lego dns --help` here. +func createStubApp() *cli.App { + app := cli.NewApp() + app.Name = "lego" + app.HelpName = "lego" + app.Usage = "Let's Encrypt client written in Go" + app.Flags = cmd.CreateFlags("./.lego") + app.Commands = cmd.CreateCommands() + + return app +} + +func run(app *cli.App, args []string) (h commandHelp, err error) { + w := app.Writer + defer func() { app.Writer = w }() + + var buf bytes.Buffer + app.Writer = &buf + + if err := app.Run(args); err != nil { + return h, err + } + + return commandHelp{ + Title: strings.Join(args, " "), + Help: strings.TrimSpace(buf.String()), + }, nil +} diff --git a/internal/dnsdocs/dns.go.tmpl b/internal/dnsdocs/dns.go.tmpl index f3e9a796..6ccadb45 100644 --- a/internal/dnsdocs/dns.go.tmpl +++ b/internal/dnsdocs/dns.go.tmpl @@ -5,7 +5,7 @@ package cmd import ( "fmt" - "os" + "io" "sort" "strings" "text/tabwriter" @@ -22,8 +22,8 @@ func allDNSCodes() string { return strings.Join(providers, ", ") } -func displayDNSHelp(name string) error { - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) +func displayDNSHelp(w io.Writer, name string) error { + w = tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) ew := &errWriter{w: w} switch name { @@ -55,9 +55,8 @@ func displayDNSHelp(name string) error { return fmt.Errorf("%q is not yet supported", name) } - if ew.err != nil { - return fmt.Errorf("error: %w", ew.err) + if flusher, ok := w.(interface{ Flush() error }); ok { + return flusher.Flush() } - - return w.Flush() -} \ No newline at end of file + return nil +}