rocsp-tool: fix printing of commandline help (#6360)

This refactors how rocsp-tool processes its subcommands so we're less
likely to make mistakes (like under- or over- including subcommands in the
help output).
This commit is contained in:
Jacob Hoffman-Andrews 2022-09-09 11:29:38 -07:00 committed by GitHub
parent bc1bf0fde4
commit d05a0f6afe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 112 additions and 73 deletions

View File

@ -8,6 +8,7 @@ import (
"fmt"
"math/rand"
"os"
"strings"
"time"
"github.com/jmhodges/clock"
@ -73,27 +74,29 @@ func main() {
}
}
var startFromID = flag.Int64("start-from-id", 0, "For load-from-db, the first ID in the certificateStatus table to scan")
func main2() error {
configFile := flag.String("config", "", "File path to the configuration file for this service")
startFromID := flag.Int64("start-from-id", 0, "For load-from-db, the first ID in the certificateStatus table to scan")
flag.Usage = helpExit
flag.Parse()
if *configFile == "" {
flag.Usage()
os.Exit(1)
if *configFile == "" || len(flag.Args()) < 1 {
helpExit()
}
rand.Seed(time.Now().UnixNano())
var c Config
err := cmd.ReadConfigFile(*configFile, &c)
var conf Config
err := cmd.ReadConfigFile(*configFile, &conf)
if err != nil {
return fmt.Errorf("reading JSON config file: %w", err)
}
_, logger := cmd.StatsAndLogging(c.Syslog, c.ROCSPTool.DebugAddr)
_, logger := cmd.StatsAndLogging(conf.Syslog, conf.ROCSPTool.DebugAddr)
defer logger.AuditPanic()
clk := cmd.Clock()
redisClient, err := rocsp_config.MakeClient(&c.ROCSPTool.Redis, clk, metrics.NoopRegisterer)
redisClient, err := rocsp_config.MakeClient(&conf.ROCSPTool.Redis, clk, metrics.NoopRegisterer)
if err != nil {
return fmt.Errorf("making client: %w", err)
}
@ -101,8 +104,8 @@ func main2() error {
var db *db.WrappedMap
var ocspGenerator capb.OCSPGeneratorClient
var scanBatchSize int
if c.ROCSPTool.LoadFromDB != nil {
lfd := c.ROCSPTool.LoadFromDB
if conf.ROCSPTool.LoadFromDB != nil {
lfd := conf.ROCSPTool.LoadFromDB
db, err = sa.InitWrappedDb(lfd.DB, nil, logger)
if err != nil {
return fmt.Errorf("connecting to DB: %w", err)
@ -119,10 +122,6 @@ func main2() error {
scanBatchSize = lfd.Speed.ScanBatchSize
}
if len(flag.Args()) < 1 {
helpExit()
}
ctx := context.Background()
cl := client{
redis: redisClient,
@ -132,8 +131,39 @@ func main2() error {
scanBatchSize: scanBatchSize,
logger: logger,
}
switch flag.Arg(0) {
case "get":
for _, sc := range subCommands {
if flag.Arg(0) == sc.name {
return sc.cmd(ctx, cl, conf, flag.Args()[1:])
}
}
fmt.Fprintf(os.Stderr, "unrecognized subcommand %q\n", flag.Arg(0))
helpExit()
return nil
}
// subCommand represents a single subcommand. `name` is the name used to invoke it, and `help` is
// its help text.
type subCommand struct {
name string
help string
cmd func(context.Context, client, Config, []string) error
}
var (
Store = subCommand{"store", "for each filename on command line, read the file as an OCSP response and store it in Redis",
func(ctx context.Context, cl client, _ Config, args []string) error {
err := cl.storeResponsesFromFiles(ctx, flag.Args()[1:])
if err != nil {
return err
}
return nil
},
}
Get = subCommand{
"get",
"for each serial on command line, fetch that serial's response and pretty-print it",
func(ctx context.Context, cl client, _ Config, args []string) error {
for _, serial := range flag.Args()[1:] {
resp, err := cl.redis.GetResponse(ctx, serial)
if err != nil {
@ -147,7 +177,11 @@ func main2() error {
fmt.Printf("%s\n", helper.PrettyResponse(parsed))
}
}
case "get-pem":
return nil
},
}
GetPEM = subCommand{"get-pem", "for each serial on command line, fetch that serial's response and print it PEM-encoded",
func(ctx context.Context, cl client, _ Config, args []string) error {
for _, serial := range flag.Args()[1:] {
resp, err := cl.redis.GetResponse(ctx, serial)
if err != nil {
@ -159,44 +193,49 @@ func main2() error {
}
pem.Encode(os.Stdout, &block)
}
case "store":
logger.Info(cmd.VersionString())
err := cl.storeResponsesFromFiles(ctx, flag.Args()[1:])
if err != nil {
return err
return nil
},
}
case "load-from-db":
logger.Info(cmd.VersionString())
LoadFromDB = subCommand{"load-from-db", "scan the database for all OCSP entries for unexpired certificates, and store in Redis",
func(ctx context.Context, cl client, c Config, args []string) error {
if c.ROCSPTool.LoadFromDB == nil {
return fmt.Errorf("config field LoadFromDB was missing")
}
err = cl.loadFromDB(ctx, c.ROCSPTool.LoadFromDB.Speed, *startFromID)
err := cl.loadFromDB(ctx, c.ROCSPTool.LoadFromDB.Speed, *startFromID)
if err != nil {
return fmt.Errorf("loading OCSP responses from DB: %w", err)
}
case "scan-responses":
logger.Info(cmd.VersionString())
return nil
},
}
ScanResponses = subCommand{"scan-responses", "scan Redis for OCSP response entries. For each entry, print the serial and base64-encoded response",
func(ctx context.Context, cl client, _ Config, args []string) error {
results := cl.redis.ScanResponses(ctx, "*")
for r := range results {
if r.Err != nil {
cmd.FailOnError(err, "while scanning")
return r.Err
}
logger.Infof("%s: %s\n", r.Serial, base64.StdEncoding.EncodeToString(r.Body))
}
default:
logger.Errf("unrecognized subcommand %q\n", flag.Arg(0))
helpExit()
fmt.Printf("%s: %s\n", r.Serial, base64.StdEncoding.EncodeToString(r.Body))
}
return nil
},
}
)
var subCommands = []subCommand{
Store, Get, GetPEM, LoadFromDB, ScanResponses,
}
func helpExit() {
fmt.Fprintf(os.Stderr, "Usage: %s [store|copy-from-db|scan-metadata|scan-responses] --config path/to/config.json\n", os.Args[0])
fmt.Fprintln(os.Stderr, " store -- for each filename on command line, read the file as an OCSP response and store it in Redis")
fmt.Fprintln(os.Stderr, " get -- for each serial on command line, fetch that serial's response and pretty-print it")
fmt.Fprintln(os.Stderr, " load-from-db -- scan the database for all OCSP entries for unexpired certificates, and store in Redis")
fmt.Fprintln(os.Stderr, " scan-metadata -- scan Redis for metadata entries. For each entry, print the serial and the age in hours")
fmt.Fprintln(os.Stderr, " scan-responses -- scan Redis for OCSP response entries. For each entry, print the serial and base64-encoded response")
var names []string
var helpStrings []string
for _, s := range subCommands {
names = append(names, s.name)
helpStrings = append(helpStrings, fmt.Sprintf(" %s -- %s", s.name, s.help))
}
fmt.Fprintf(os.Stderr, "Usage: %s [%s] --config path/to/config.json\n", os.Args[0], strings.Join(names, "|"))
os.Stderr.Write([]byte(strings.Join(helpStrings, "\n")))
fmt.Fprintln(os.Stderr)
fmt.Fprintln(os.Stderr)
flag.PrintDefaults()
os.Exit(1)