From 3b611b06e46a3d5c860965639ccd87eba4b0c2c5 Mon Sep 17 00:00:00 2001 From: "Greg T. Wallace" Date: Sun, 28 Jan 2024 11:16:26 -0500 Subject: [PATCH] app: minor reorg * add version number * add more logging * add command/subcommand --- pkg/app/app.go | 86 ++++++++++++++-------------- pkg/app/app_config.go | 45 --------------- pkg/app/cmd_create.go | 84 +++++++++++++++++++++++++++ pkg/app/config.go | 67 ++++++++++++++++++++++ pkg/app/{app_logger.go => logger.go} | 0 5 files changed, 193 insertions(+), 89 deletions(-) delete mode 100644 pkg/app/app_config.go create mode 100644 pkg/app/cmd_create.go create mode 100644 pkg/app/config.go rename pkg/app/{app_logger.go => logger.go} (100%) diff --git a/pkg/app/app.go b/pkg/app/app.go index 8e571a3..264e5b9 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -1,15 +1,25 @@ package app import ( - "apc-p15-tool/pkg/pkcs15" + "context" + "errors" "os" + "github.com/peterbourgon/ff/v4" + "github.com/peterbourgon/ff/v4/ffhelp" "go.uber.org/zap" ) +const ( + appVersion = "0.1.0" + + environmentVarPrefix = "APC_P15_TOOL" +) + // struct for receivers to use common app pieces type app struct { logger *zap.SugaredLogger + cmd *ff.Command config *config } @@ -27,52 +37,40 @@ func Start() { // re-init logger with configured log level app.logger = makeZapLogger(app.config.logLevel) - // break point for building additional alternate functions + // log start + app.logger.Infof("apc-p15-tool v%s", appVersion) - // function: make p15 from pem files + // get config + app.getConfig() - // Read in PEM files - keyPem, err := os.ReadFile(*app.config.keyPemFilePath) + // run it + exitCode := 0 + err := app.cmd.ParseAndRun(context.Background(), os.Args[1:], ff.WithEnvVarPrefix(environmentVarPrefix)) if err != nil { - app.logger.Fatalf("failed to read key file (%s)", err) - // FATAL + exitCode = 1 + + if errors.Is(err, ff.ErrHelp) { + // help explicitly requested + exitCode = 0 + app.logger.Info("\n\n", ffhelp.Command(app.cmd)) + + } else if errors.Is(err, ErrExtraArgs) { + // extra args (will log elsewhere, so no need to log err again) + app.logger.Info("\n\n", ffhelp.Command(app.cmd)) + + } else if errors.Is(err, ff.ErrDuplicateFlag) || + errors.Is(err, ff.ErrUnknownFlag) || + errors.Is(err, ff.ErrNoExec) { + // other error that suggests user needs to see help + app.logger.Error(err) + app.logger.Info("\n\n", ffhelp.Command(app.cmd)) + + } else { + // any other error + app.logger.Error(err) + } } - certPem, err := os.ReadFile(*app.config.certPemFilePath) - if err != nil { - app.logger.Fatalf("failed to read cert file (%s)", err) - // FATAL - } - - p15, err := pkcs15.ParsePEMToPKCS15(keyPem, certPem) - if err != nil { - app.logger.Fatalf("failed to parse pem files (%s)", err) - // FATAL - } - - // TEMP TEMP TEMP - p15File, err := p15.ToP15File() - if err != nil { - app.logger.Fatalf("failed to make p15 file (%s)", err) - // FATAL - } - - // app.logger.Debug(hex.EncodeToString(p15File)) - // app.logger.Debug(base64.RawStdEncoding.EncodeToString(p15File)) - - apcHeader, err := makeFileHeader(p15File) - if err != nil { - app.logger.Fatalf("failed to make p15 file header (%s)", err) - // FATAL - } - - apcFile := append(apcHeader, p15File...) - - err = os.WriteFile("./apctool.p15", apcFile, 0777) - if err != nil { - app.logger.Fatalf("failed to write apc p15 file (%s)", err) - // FATAL - } - - // TEMP TEMP TEMP + app.logger.Info("apc-p15-tool done") + os.Exit(exitCode) } diff --git a/pkg/app/app_config.go b/pkg/app/app_config.go deleted file mode 100644 index 9edbc3e..0000000 --- a/pkg/app/app_config.go +++ /dev/null @@ -1,45 +0,0 @@ -package app - -import ( - "os" - - "github.com/peterbourgon/ff/v4" - "github.com/peterbourgon/ff/v4/ffhelp" -) - -const ( - environmentVarPrefix = "APC_P15_TOOL" -) - -// app's config options from user -type config struct { - logLevel *string - keyPemFilePath *string - certPemFilePath *string -} - -// getConfig returns the app's configuration from either command line args, -// or environment variables -func (app *app) getConfig() { - // make config and flag set - cfg := &config{} - fs := ff.NewFlagSet("apc-p15-tool") - - // define options - cfg.logLevel = fs.StringEnum('l', "loglevel", "log level: debug, info, warn, error, dpanic, panic, or fatal", - "info", "debug", "warn", "error", "dpanic", "panic", "fatal") - - cfg.keyPemFilePath = fs.StringLong("keyfile", "", "path and filename of the rsa-2048 key in pem format") - cfg.certPemFilePath = fs.StringLong("certfile", "", "path and filename of the rsa-2048 key in pem format") - // TODO key and pem directly in a flag/env var - - // parse using args and/or ENV vars - err := ff.Parse(fs, os.Args[1:], ff.WithEnvVarPrefix(environmentVarPrefix)) - if err != nil { - app.logger.Fatal(ffhelp.Flags(fs)) - // FATAL - } - - // set app config - app.config = cfg -} diff --git a/pkg/app/cmd_create.go b/pkg/app/cmd_create.go new file mode 100644 index 0000000..d5ff2a6 --- /dev/null +++ b/pkg/app/cmd_create.go @@ -0,0 +1,84 @@ +package app + +import ( + "apc-p15-tool/pkg/pkcs15" + "context" + "errors" + "fmt" + "os" +) + +const createDefaultOutFilePath = "apctool.p15" + +// cmdCreate is the app's command to create an apc p15 file from key and cert +// pem files +func (app *app) cmdCreate(_ context.Context, args []string) error { + // extra args == error + if len(args) != 0 { + app.logger.Errorf("create: failed, extra args (%d) present", len(args)) + return ErrExtraArgs + } + + // key must be specified + if app.config.create.keyPemFilePath == nil || *app.config.create.keyPemFilePath == "" { + return errors.New("create: failed, key not specified") + } + + // cert must be specified + if app.config.create.certPemFilePath == nil || *app.config.create.certPemFilePath == "" { + return errors.New("create: failed, cert not specified") + } + + // validation done + app.logger.Infof("create: making apc p15 file from pem files") + + // Read in PEM files + keyPem, err := os.ReadFile(*app.config.create.keyPemFilePath) + if err != nil { + return fmt.Errorf("create: failed to read key file (%s)", err) + } + + certPem, err := os.ReadFile(*app.config.create.certPemFilePath) + if err != nil { + return fmt.Errorf("create: failed to read cert file (%s)", err) + } + + // make p15 struct + p15, err := pkcs15.ParsePEMToPKCS15(keyPem, certPem) + if err != nil { + return fmt.Errorf("create: failed to parse pem files (%s)", err) + } + + app.logger.Infof("create: successfully loaded pem files") + + // make file bytes + p15File, err := p15.ToP15File() + if err != nil { + return fmt.Errorf("create: failed to make p15 file (%s)", err) + } + + // make header for file bytes + apcHeader, err := makeFileHeader(p15File) + if err != nil { + return fmt.Errorf("create: failed to make p15 file header (%s)", err) + } + + // combine header with file + apcFile := append(apcHeader, p15File...) + + // determine file name (should already be done by flag parsing, but avoid nil just in case) + fileName := createDefaultOutFilePath + if app.config.create.outFilePath != nil && *app.config.create.outFilePath != "" { + fileName = *app.config.create.outFilePath + } + + // write file + err = os.WriteFile(fileName, apcFile, 0777) + if err != nil { + return fmt.Errorf("create: failed to write apc p15 file (%s)", err) + } + + app.logger.Infof("create: apc p15 file %s written to disk", fileName) + + return nil +} diff --git a/pkg/app/config.go b/pkg/app/config.go new file mode 100644 index 0000000..42fc58b --- /dev/null +++ b/pkg/app/config.go @@ -0,0 +1,67 @@ +package app + +import ( + "errors" + + "github.com/peterbourgon/ff/v4" +) + +var ( + ErrExtraArgs = errors.New("extra args present") +) + +// app's config options from user +type config struct { + logLevel *string + create struct { + keyPemFilePath *string + certPemFilePath *string + outFilePath *string + } +} + +// getConfig returns the app's configuration from either command line args, +// or environment variables +func (app *app) getConfig() { + // make config + cfg := &config{} + + // commands: + // create + // TODO: + // upload + // unpack (both key & key+cert) + + // apc-p15-tool -- root command + rootFlags := ff.NewFlagSet("apc-p15-tool") + + cfg.logLevel = rootFlags.StringEnum('l', "loglevel", "log level: debug, info, warn, error, dpanic, panic, or fatal", + "info", "debug", "warn", "error", "dpanic", "panic", "fatal") + + rootCmd := &ff.Command{ + Name: "apc-p15-tool", + Usage: "apc-p15-tool [FLAGS] SUBCOMMAND ...", + Flags: rootFlags, + } + + // create -- subcommand + createFlags := ff.NewFlagSet("create").SetParent(rootFlags) + + cfg.create.keyPemFilePath = createFlags.StringLong("keyfile", "", "path and filename of the rsa-2048 key in pem format") + cfg.create.certPemFilePath = createFlags.StringLong("certfile", "", "path and filename of the rsa-2048 key in pem format") + cfg.create.outFilePath = createFlags.StringLong("outfile", createDefaultOutFilePath, "path and filename to write the p15 file to") + + createCmd := &ff.Command{ + Name: "create", + Usage: "apc-p15-tool create --keyfile key.pem --certfile cert.pem [--outfile apctool.p15]", + ShortHelp: "create an apc p15 file from the specified key and cert pem files", + Flags: createFlags, + Exec: app.cmdCreate, + } + + rootCmd.Subcommands = append(rootCmd.Subcommands, createCmd) + + // set app cmd & cfg + app.cmd = rootCmd + app.config = cfg +} diff --git a/pkg/app/app_logger.go b/pkg/app/logger.go similarity index 100% rename from pkg/app/app_logger.go rename to pkg/app/logger.go