From 95d4d9c4c1aa9ffbd7bd58238933309d2a64a190 Mon Sep 17 00:00:00 2001 From: max furman Date: Thu, 22 Nov 2018 15:59:33 -0500 Subject: [PATCH] update the help and usage information --- Gopkg.lock | 29 +++++- cmd/step-ca/main.go | 220 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 195 insertions(+), 54 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 26989f78..3cb7e06b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -123,6 +123,22 @@ revision = "15d26544def341f036c5f8dca987a4cbe575032c" version = "v1.2.1" +[[projects]] + branch = "master" + digest = "1:8baa3b16f20963c54e296627ea1dabfd79d1b486f81baf8759e99d73bddf2687" + name = "github.com/samfoo/ansi" + packages = ["."] + pruneopts = "UT" + revision = "b6bd2ded7189ce35bc02233b554eb56a5146af73" + +[[projects]] + branch = "master" + digest = "1:def689e73e9252f6f7fe66834a76751a41b767e03daab299e607e7226c58a855" + name = "github.com/shurcooL/sanitized_anchor_name" + packages = ["."] + pruneopts = "UT" + revision = "86672fcb3f950f35f2e675df2240550f2a50762f" + [[projects]] digest = "1:3f53e9e4dfbb664cd62940c9c4b65a2171c66acd0b7621a1a6b8e78513525a52" name = "github.com/sirupsen/logrus" @@ -141,7 +157,7 @@ [[projects]] branch = "ca-commands" - digest = "1:41e2386e08278707d5a20237c10a2c82f4658de64593fe070652c11bb3880812" + digest = "1:1731f58ec5ba2770296fb304504798c6f06059bd4645fd85f207de7be0b3add0" name = "github.com/smallstep/cli" packages = [ "crypto/keys", @@ -151,11 +167,13 @@ "crypto/x509util", "errs", "jose", + "pkg/blackfriday", "pkg/x509", + "usage", "utils", ] pruneopts = "UT" - revision = "e2cf66cdd8f458a28ed9c5a6efc78fcc5500bdd0" + revision = "f6b9a18d11bd82c79876b18e57cd475e87be5fcc" [[projects]] branch = "master" @@ -190,9 +208,11 @@ [[projects]] branch = "master" - digest = "1:5afed8b82638da362e14321ec6175b96351226f6662707801a0ec740bfd29840" + digest = "1:acfafa29853fd970d5d5ac6ca3b7350b5aef5999196d32bb9b049e21c8caf726" name = "golang.org/x/net" packages = [ + "html", + "html/atom", "http/httpguts", "http2", "http2/hpack", @@ -295,9 +315,12 @@ "github.com/smallstep/cli/crypto/randutil", "github.com/smallstep/cli/crypto/tlsutil", "github.com/smallstep/cli/crypto/x509util", + "github.com/smallstep/cli/errs", "github.com/smallstep/cli/jose", "github.com/smallstep/cli/pkg/x509", + "github.com/smallstep/cli/usage", "github.com/tsenart/deadcode", + "github.com/urfave/cli", "golang.org/x/net/http2", "gopkg.in/square/go-jose.v2", "gopkg.in/square/go-jose.v2/jwt", diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index 3ead011e..98c47aaa 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -2,12 +2,13 @@ package main import ( "bytes" - "flag" "fmt" "io/ioutil" + "log" "net/http" "os" - "path" + "reflect" + "regexp" "runtime" "time" "unicode" @@ -15,73 +16,170 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/ca" + "github.com/smallstep/cli/errs" + "github.com/smallstep/cli/usage" + "github.com/urfave/cli" ) -// Version is set by an LDFLAG at build time representing the git tag or commit -// for the current release -var Version = "N/A" +// commit and buildTime are filled in during build by the Makefile +var ( + BuildTime = "N/A" + Version = "N/A" +) -// BuildTime is set by an LDFLAG at build time representing the timestamp at -// the time of build -var BuildTime = "N/A" - -func usage() { - fmt.Fprintf(os.Stderr, "Usage: %s [options] \n\n", path.Base(os.Args[0])) - flag.PrintDefaults() +// Version returns the current version of the binary. +func version() string { + out := Version + if out == "N/A" { + out = "0000000-dev" + } + return fmt.Sprintf("Smallstep CLI/%s (%s/%s)", + out, runtime.GOOS, runtime.GOARCH) } -func printVersion() { - version, buildTime := Version, BuildTime - if version == "N/A" { - version = "0000000-dev" +// ReleaseDate returns the time of when the binary was built. +func releaseDate() string { + out := BuildTime + if out == "N/A" { + out = time.Now().UTC().Format("2006-01-02 15:04 MST") } - if buildTime == "N/A" { - buildTime = time.Now().UTC().Format("2006-01-02 15:04 MST") - } - fmt.Printf("Smallstep CA/%s (%s/%s)\n", version, runtime.GOOS, runtime.GOARCH) - fmt.Printf("Release Date: %s\n", buildTime) + + return out +} + +// Print version and release date. +func printFullVersion() { + fmt.Printf("%s\n", version()) + fmt.Printf("Release Date: %s\n", releaseDate()) } func main() { - var version bool - var configFile, passFile string - flag.StringVar(&passFile, "password-file", "", "path to file containing a password") - flag.BoolVar(&version, "version", false, "print version and exit") - flag.Usage = usage - flag.Parse() + // Override global framework components + cli.VersionPrinter = func(c *cli.Context) { + printFullVersion() + } + cli.AppHelpTemplate = usage.AppHelpTemplate + cli.SubcommandHelpTemplate = usage.SubcommandHelpTemplate + cli.CommandHelpTemplate = usage.CommandHelpTemplate + cli.HelpPrinter = usage.HelpPrinter + cli.FlagNamePrefixer = usage.FlagNamePrefixer + cli.FlagStringer = stringifyFlag - if version { - printVersion() - os.Exit(0) + // Configure cli app + app := cli.NewApp() + app.Name = "step-ca" + app.HelpName = "step-ca" + app.Version = version() + app.Usage = "an online certificate authority for secure automated certificate management" + app.UsageText = `**step-ca** [**--password-file**=] [**--version**]` + app.Description = `**step-ca** runs the Step Online Certificate Authority +(Step CA) using the given configuration. + +See the README.md for more detailed configuration documentation. + +## POSITIONAL ARGUMENTS + + +: File that configures the operation of the Step CA; this file is generated +when you initialize the Step CA using 'step ca init' + +## EXIT CODES + +This command will run indefinitely on success and return \>0 if any error occurs. + +## EXAMPLES + +These examples assume that you have already initialized your PKI by running +'step ca init'. If you have not completed this step please see the 'Getting Started' +section of the README. + +Run the Step CA and prompt for password: +''' +$ step-ca $STEPPATH/config/ca.json +''' + +Run the Step CA and read the password from a file - this is useful for +automating deployment: +''' +$ step-ca $STEPPATH/config/ca.json --password-file ./password.txt +'''` + app.Flags = append(app.Flags, []cli.Flag{ + cli.StringFlag{ + Name: "password-file", + Usage: `path to the containing the password to decrypt the +intermediate private key.`, + }, + }...) + app.Copyright = "(c) 2018 Smallstep Labs, Inc." + + // All non-successful output should be written to stderr + app.Writer = os.Stdout + app.ErrWriter = os.Stderr + app.Commands = []cli.Command{ + cli.Command{ + Name: "version", + Usage: "Displays the current version of the cli", + // Command prints out the current version of the tool + Action: func(c *cli.Context) error { + printFullVersion() + return nil + }, + }, } - if flag.NArg() != 1 { - flag.Usage() - os.Exit(1) + // Start the golang debug logger if environment variable is set. + // See https://golang.org/pkg/net/http/pprof/ + debugProfAddr := os.Getenv("STEP_PROF_ADDR") + if debugProfAddr != "" { + go func() { + log.Println(http.ListenAndServe(debugProfAddr, nil)) + }() } - configFile = flag.Arg(0) - config, err := authority.LoadConfiguration(configFile) - if err != nil { - fatal(err) - } + app.Action = func(ctx *cli.Context) error { + passFile := ctx.String("password-file") - var password []byte - if passFile != "" { - if password, err = ioutil.ReadFile(passFile); err != nil { - fatal(errors.Wrapf(err, "error reading %s", passFile)) + // If zero cmd line args show help, if >1 cmd line args show error. + if ctx.NArg() == 0 { + return cli.ShowAppHelp(ctx) } - password = bytes.TrimRightFunc(password, unicode.IsSpace) + if err := errs.NumberOfArguments(ctx, 1); err != nil { + return err + } + + configFile := ctx.Args().Get(0) + config, err := authority.LoadConfiguration(configFile) + if err != nil { + fatal(err) + } + + var password []byte + if passFile != "" { + if password, err = ioutil.ReadFile(passFile); err != nil { + fatal(errors.Wrapf(err, "error reading %s", passFile)) + } + password = bytes.TrimRightFunc(password, unicode.IsSpace) + } + + srv, err := ca.New(config, ca.WithConfigFile(configFile), ca.WithPassword(password)) + if err != nil { + fatal(err) + } + + go ca.StopReloaderHandler(srv) + if err = srv.Run(); err != nil && err != http.ErrServerClosed { + fatal(err) + } + return nil } - srv, err := ca.New(config, ca.WithConfigFile(configFile), ca.WithPassword(password)) - if err != nil { - fatal(err) - } - - go ca.StopReloaderHandler(srv) - if err = srv.Run(); err != nil && err != http.ErrServerClosed { - fatal(err) + if err := app.Run(os.Args); err != nil { + if os.Getenv("STEPDEBUG") == "1" { + fmt.Fprintf(os.Stderr, "%+v\n", err) + } else { + fmt.Fprintln(os.Stderr, err) + } + os.Exit(1) } } @@ -96,3 +194,23 @@ func fatal(err error) { } os.Exit(2) } + +func flagValue(f cli.Flag) reflect.Value { + fv := reflect.ValueOf(f) + for fv.Kind() == reflect.Ptr { + fv = reflect.Indirect(fv) + } + return fv +} + +var placeholderString = regexp.MustCompile(`<.*?>`) + +func stringifyFlag(f cli.Flag) string { + fv := flagValue(f) + usage := fv.FieldByName("Usage").String() + placeholder := placeholderString.FindString(usage) + if placeholder == "" { + placeholder = "" + } + return cli.FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder) + "\t" + usage +}