Add support for subcommands to "boulder" command (#6426)

Boulder builds a single binary which is symlinked to the different binary names, which are included in its releases.
However, requiring symlinks isn't always convenient.

This change makes the base `boulder` command usable as any of the other binary names.  If the binary is invoked as boulder, runs the second argument as the command name.  It shifts off the `boulder` from os.Args so that all the existing argument parsing can remain unchanged.

This uses the subcommand versions in integration tests, which I think is important to verify this change works, however we can debate whether or not that should be merged, since we're using the symlink method in production, that's what we want to test.

Issue #6362 suggests we want to move to a more fully-featured command-line parsing library that has proper subcommand support. This fixes one fragment of that, by providing subcommands, but is definitely nowhere near as nice as it could be with a more fully fleshed out library.  Thus this change takes a minimal-touch approach to this change, since we know a larger refactoring is coming.
This commit is contained in:
Matthew McPherrin 2022-10-06 14:21:47 -04:00 committed by GitHub
parent bdd9ad9941
commit 1d16ff9b00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 52 additions and 43 deletions

View File

@ -42,14 +42,23 @@ func main() {
func init() {
cmd.RegisterCommand("boulder", func() {
if len(os.Args) > 1 && os.Args[1] == "--list" {
for _, c := range cmd.AvailableCommands() {
if c != "boulder" {
fmt.Println(c)
}
if len(os.Args) <= 1 {
fmt.Fprintf(os.Stderr, "Call with --list to list available subcommands. Run them like boulder <subcommand>.\n")
return
}
subcommand := cmd.LookupCommand(os.Args[1])
if subcommand == nil {
fmt.Fprintf(os.Stderr, "Unknown subcommand '%s'.\n", os.Args[1])
return
}
os.Args = os.Args[1:]
subcommand()
})
cmd.RegisterCommand("--list", func() {
for _, c := range cmd.AvailableCommands() {
if c != "boulder" && c != "--list" {
fmt.Println(c)
}
} else {
fmt.Fprintf(os.Stderr, "Call with --list to list available subcommands. Symlink and run as a subcommand to run that subcommand.\n")
}
})
}

View File

@ -54,7 +54,7 @@ def test_single_ocsp():
This is a non-API test.
"""
p = subprocess.Popen(
["./bin/ocsp-responder", "--config", "test/issuer-ocsp-responder.json"])
["./bin/boulder", "ocsp-responder", "--config", "test/issuer-ocsp-responder.json"])
waitport(4003, ' '.join(p.args))
# Verify that the static OCSP responder, which answers with a
@ -218,7 +218,7 @@ def check_balance():
% address)
def run_cert_checker():
run(["./bin/cert-checker", "-config", "%s/cert-checker.json" % config_dir])
run(["./bin/boulder", "cert-checker", "-config", "%s/cert-checker.json" % config_dir])
if __name__ == "__main__":
main()

View File

@ -22,7 +22,7 @@ import (
)
func setup() (*exec.Cmd, *bytes.Buffer, akamaipb.AkamaiPurgerClient, error) {
purgerCmd := exec.Command("./bin/akamai-purger", "--config", "test/integration/testdata/akamai-purger-queue-drain-config.json")
purgerCmd := exec.Command("./bin/boulder", "akamai-purger", "--config", "test/integration/testdata/akamai-purger-queue-drain-config.json")
var outputBuffer bytes.Buffer
purgerCmd.Stdout = &outputBuffer
purgerCmd.Stderr = &outputBuffer

View File

@ -25,7 +25,7 @@ func TestCAALogChecker(t *testing.T) {
test.AssertEquals(t, len(result.Order.Authorizations), 1)
// Should be no specific output, since everything is good
cmd := exec.Command("bin/caa-log-checker", "-ra-log", "/var/log/boulder-ra.log", "-va-logs", "/var/log/boulder-va.log")
cmd := exec.Command("bin/boulder", "caa-log-checker", "-ra-log", "/var/log/boulder-ra.log", "-va-logs", "/var/log/boulder-va.log")
stdErr := new(bytes.Buffer)
cmd.Stderr = stdErr
out, err := cmd.Output()
@ -41,7 +41,7 @@ func TestCAALogChecker(t *testing.T) {
tmp, err := os.CreateTemp(os.TempDir(), "boulder-va-empty")
test.AssertNotError(t, err, "failed to create temporary file")
defer os.Remove(tmp.Name())
cmd = exec.Command("bin/caa-log-checker", "-ra-log", "/var/log/boulder-ra.log", "-va-logs", tmp.Name())
cmd = exec.Command("bin/boulder", "caa-log-checker", "-ra-log", "/var/log/boulder-ra.log", "-va-logs", tmp.Name())
stdErr = new(bytes.Buffer)
cmd.Stderr = stdErr
out, err = cmd.Output()

View File

@ -21,10 +21,10 @@ import (
func runUpdater(t *testing.T, configFile string) {
t.Helper()
binPath, err := filepath.Abs("bin/crl-updater")
test.AssertNotError(t, err, "computing crl-updater path")
binPath, err := filepath.Abs("bin/boulder")
test.AssertNotError(t, err, "computing boulder binary path")
c := exec.Command(binPath, "-config", configFile, "-debug-addr", ":8022", "-runOnce")
c := exec.Command(binPath, "crl-updater", "-config", configFile, "-debug-addr", ":8022", "-runOnce")
out, err := c.CombinedOutput()
test.AssertNotError(t, err, fmt.Sprintf("crl-updater failed: %s", out))
}

View File

@ -43,7 +43,7 @@ func TestOrphanFinder(t *testing.T) {
io.WriteString(f, fmt.Sprintf(template, precert.SerialNumber.Bytes(),
precert.Raw, cert.SerialNumber.Bytes(), cert.Raw))
f.Close()
cmd := exec.Command("./bin/orphan-finder", "parse-ca-log",
cmd := exec.Command("./bin/boulder", "orphan-finder", "parse-ca-log",
"--config", "./"+os.Getenv("BOULDER_CONFIG_DIR")+"/orphan-finder.json",
"--log-file", f.Name())
out, err := cmd.Output()

View File

@ -16,30 +16,30 @@ Service = collections.namedtuple('Service', ('name', 'debug_port', 'grpc_addr',
SERVICES = (
Service('boulder-remoteva-a',
8011, 'rva1.service.consul:9097',
('./bin/boulder-remoteva', '--config', os.path.join(config_dir, 'va-remote-a.json')),
('./bin/boulder', 'boulder-remoteva', '--config', os.path.join(config_dir, 'va-remote-a.json')),
None),
Service('boulder-remoteva-b',
8012, 'rva1.service.consul:9098',
('./bin/boulder-remoteva', '--config', os.path.join(config_dir, 'va-remote-b.json')),
('./bin/boulder', 'boulder-remoteva', '--config', os.path.join(config_dir, 'va-remote-b.json')),
None),
Service('boulder-sa-1',
8003, 'sa1.service.consul:9095',
('./bin/boulder-sa', '--config', os.path.join(config_dir, 'sa.json'), '--addr', 'sa1.service.consul:9095', '--debug-addr', ':8003'),
('./bin/boulder', 'boulder-sa', '--config', os.path.join(config_dir, 'sa.json'), '--addr', 'sa1.service.consul:9095', '--debug-addr', ':8003'),
None),
Service('boulder-sa-2',
8103, 'sa2.service.consul:9095',
('./bin/boulder-sa', '--config', os.path.join(config_dir, 'sa.json'), '--addr', 'sa2.service.consul:9095', '--debug-addr', ':8103'),
('./bin/boulder', 'boulder-sa', '--config', os.path.join(config_dir, 'sa.json'), '--addr', 'sa2.service.consul:9095', '--debug-addr', ':8103'),
None),
Service('ct-test-srv',
4500, None,
('./bin/ct-test-srv', '--config', 'test/ct-test-srv/ct-test-srv.json'), None),
Service('boulder-publisher-1',
8009, 'publisher1.service.consul:9091',
('./bin/boulder-publisher', '--config', os.path.join(config_dir, 'publisher.json'), '--addr', 'publisher1.service.consul:9091', '--debug-addr', ':8009'),
('./bin/boulder', 'boulder-publisher', '--config', os.path.join(config_dir, 'publisher.json'), '--addr', 'publisher1.service.consul:9091', '--debug-addr', ':8009'),
None),
Service('boulder-publisher-2',
8109, 'publisher2.service.consul:9091',
('./bin/boulder-publisher', '--config', os.path.join(config_dir, 'publisher.json'), '--addr', 'publisher2.service.consul:9091', '--debug-addr', ':8109'),
('./bin/boulder', 'boulder-publisher', '--config', os.path.join(config_dir, 'publisher.json'), '--addr', 'publisher2.service.consul:9091', '--debug-addr', ':8109'),
None),
Service('mail-test-srv',
9380, None,
@ -47,23 +47,23 @@ SERVICES = (
None),
Service('ocsp-responder',
8005, None,
('./bin/ocsp-responder', '--config', os.path.join(config_dir, 'ocsp-responder.json')),
('./bin/boulder', 'ocsp-responder', '--config', os.path.join(config_dir, 'ocsp-responder.json')),
('boulder-ra-1', 'boulder-ra-2')),
Service('boulder-va-1',
8004, 'va1.service.consul:9092',
('./bin/boulder-va', '--config', os.path.join(config_dir, 'va.json'), '--addr', 'va1.service.consul:9092', '--debug-addr', ':8004'),
('./bin/boulder', 'boulder-va', '--config', os.path.join(config_dir, 'va.json'), '--addr', 'va1.service.consul:9092', '--debug-addr', ':8004'),
('boulder-remoteva-a', 'boulder-remoteva-b')),
Service('boulder-va-2',
8104, 'va2.service.consul:9092',
('./bin/boulder-va', '--config', os.path.join(config_dir, 'va.json'), '--addr', 'va2.service.consul:9092', '--debug-addr', ':8104'),
('./bin/boulder', 'boulder-va', '--config', os.path.join(config_dir, 'va.json'), '--addr', 'va2.service.consul:9092', '--debug-addr', ':8104'),
('boulder-remoteva-a', 'boulder-remoteva-b')),
Service('boulder-ca-a',
8001, 'ca1.service.consul:9093',
('./bin/boulder-ca', '--config', os.path.join(config_dir, 'ca-a.json'), '--ca-addr', 'ca1.service.consul:9093', '--ocsp-addr', 'ca1.service.consul:9096', '--crl-addr', 'ca1.service.consul:9106', '--debug-addr', ':8001'),
('./bin/boulder', 'boulder-ca', '--config', os.path.join(config_dir, 'ca-a.json'), '--ca-addr', 'ca1.service.consul:9093', '--ocsp-addr', 'ca1.service.consul:9096', '--crl-addr', 'ca1.service.consul:9106', '--debug-addr', ':8001'),
('boulder-sa-1', 'boulder-sa-2')),
Service('boulder-ca-b',
8101, 'ca2.service.consul:9093',
('./bin/boulder-ca', '--config', os.path.join(config_dir, 'ca-b.json'), '--ca-addr', 'ca2.service.consul:9093', '--ocsp-addr', 'ca2.service.consul:9096', '--crl-addr', 'ca2.service.consul:9106', '--debug-addr', ':8101'),
('./bin/boulder', 'boulder-ca', '--config', os.path.join(config_dir, 'ca-b.json'), '--ca-addr', 'ca2.service.consul:9093', '--ocsp-addr', 'ca2.service.consul:9096', '--crl-addr', 'ca2.service.consul:9106', '--debug-addr', ':8101'),
('boulder-sa-1', 'boulder-sa-2')),
Service('akamai-test-srv',
6789, None,
@ -71,7 +71,7 @@ SERVICES = (
None),
Service('akamai-purger',
9666, None,
('./bin/akamai-purger', '--config', os.path.join(config_dir, 'akamai-purger.json')),
('./bin/boulder', 'akamai-purger', '--config', os.path.join(config_dir, 'akamai-purger.json')),
('akamai-test-srv',)),
Service('s3-test-srv',
7890, None,
@ -79,43 +79,43 @@ SERVICES = (
None),
Service('crl-storer',
9667, None,
('./bin/crl-storer', '--config', os.path.join(config_dir, 'crl-storer.json')),
('./bin/boulder', 'crl-storer', '--config', os.path.join(config_dir, 'crl-storer.json')),
('s3-test-srv',)),
Service('ocsp-updater',
8006, None,
('./bin/ocsp-updater', '--config', os.path.join(config_dir, 'ocsp-updater.json')),
('./bin/boulder', 'ocsp-updater', '--config', os.path.join(config_dir, 'ocsp-updater.json')),
('boulder-ca-a', 'boulder-ca-b')),
Service('crl-updater',
8021, None,
('./bin/crl-updater', '--config', os.path.join(config_dir, 'crl-updater.json')),
('./bin/boulder', 'crl-updater', '--config', os.path.join(config_dir, 'crl-updater.json')),
('boulder-ca-a', 'boulder-ca-b', 'boulder-sa-1', 'boulder-sa-2', 'crl-storer')),
Service('boulder-ra-1',
8002, 'ra1.service.consul:9094',
('./bin/boulder-ra', '--config', os.path.join(config_dir, 'ra.json'), '--addr', 'ra1.service.consul:9094', '--debug-addr', ':8002'),
('./bin/boulder', 'boulder-ra', '--config', os.path.join(config_dir, 'ra.json'), '--addr', 'ra1.service.consul:9094', '--debug-addr', ':8002'),
('boulder-sa-1', 'boulder-sa-2', 'boulder-ca-a', 'boulder-ca-b', 'boulder-va-1', 'boulder-va-2', 'akamai-purger', 'boulder-publisher-1', 'boulder-publisher-2')),
Service('boulder-ra-2',
8102, 'ra2.service.consul:9094',
('./bin/boulder-ra', '--config', os.path.join(config_dir, 'ra.json'), '--addr', 'ra2.service.consul:9094', '--debug-addr', ':8102'),
('./bin/boulder', 'boulder-ra', '--config', os.path.join(config_dir, 'ra.json'), '--addr', 'ra2.service.consul:9094', '--debug-addr', ':8102'),
('boulder-sa-1', 'boulder-sa-2', 'boulder-ca-a', 'boulder-ca-b', 'boulder-va-1', 'boulder-va-2', 'akamai-purger', 'boulder-publisher-1', 'boulder-publisher-2')),
Service('bad-key-revoker',
8020, None,
('./bin/bad-key-revoker', '--config', os.path.join(config_dir, 'bad-key-revoker.json')),
('./bin/boulder', 'bad-key-revoker', '--config', os.path.join(config_dir, 'bad-key-revoker.json')),
('boulder-ra-1', 'boulder-ra-2', 'mail-test-srv')),
Service('nonce-service-taro',
8111, 'nonce1.service.consul:9101',
('./bin/nonce-service', '--config', os.path.join(config_dir, 'nonce.json'), '--addr', 'nonce1.service.consul:9101', '--debug-addr', ':8111', '--prefix', 'taro'),
('./bin/boulder', 'nonce-service', '--config', os.path.join(config_dir, 'nonce.json'), '--addr', 'nonce1.service.consul:9101', '--debug-addr', ':8111', '--prefix', 'taro'),
None),
Service('nonce-service-zinc',
8112, 'nonce2.service.consul:9101',
('./bin/nonce-service', '--config', os.path.join(config_dir, 'nonce.json'), '--addr', 'nonce2.service.consul:9101', '--debug-addr', ':8112', '--prefix', 'zinc'),
('./bin/boulder', 'nonce-service', '--config', os.path.join(config_dir, 'nonce.json'), '--addr', 'nonce2.service.consul:9101', '--debug-addr', ':8112', '--prefix', 'zinc'),
None),
Service('boulder-wfe2',
4001, None,
('./bin/boulder-wfe2', '--config', os.path.join(config_dir, 'wfe2.json')),
('./bin/boulder', 'boulder-wfe2', '--config', os.path.join(config_dir, 'wfe2.json')),
('boulder-ra-1', 'boulder-ra-2', 'boulder-sa-1', 'boulder-sa-2', 'nonce-service-taro', 'nonce-service-zinc')),
Service('log-validator',
8016, None,
('./bin/log-validator', '--config', os.path.join(config_dir, 'log-validator.json')),
('./bin/boulder', 'log-validator', '--config', os.path.join(config_dir, 'log-validator.json')),
None),
)

View File

@ -17,10 +17,10 @@ LOGFILE=/tmp/boulder.log
docker logs boulder_tests > ${LOGFILE}
# Expect success
./bin/caa-log-checker -ra-log ${LOGFILE} -va-logs ${LOGFILE}
./bin/boulder caa-log-checker -ra-log ${LOGFILE} -va-logs ${LOGFILE}
# Expect error
./bin/caa-log-checker -ra-log ${LOGFILE} -va-logs /dev/null >/tmp/output 2>&1 &&
./bin/boulder caa-log-checker -ra-log ${LOGFILE} -va-logs /dev/null >/tmp/output 2>&1 &&
(echo "caa-log-checker succeeded when it should have failed. Output:";
cat /tmp/output;
exit 9)

View File

@ -1503,7 +1503,7 @@ def test_expiration_mailer():
requests.post("http://localhost:9381/clear", data='')
for time in (no_reminder, first_reminder, last_reminder):
print(get_future_output(
["./bin/expiration-mailer", "--config", "%s/expiration-mailer.json" % config_dir],
["./bin/boulder", "expiration-mailer", "--config", "%s/expiration-mailer.json" % config_dir],
time))
resp = requests.get("http://localhost:9381/count?to=%s" % email_addr)
mailcount = int(resp.text)
@ -1680,7 +1680,7 @@ def test_admin_revoker_cert():
# Revoke certificate by serial
reset_akamai_purges()
run(["./bin/admin-revoker", "serial-revoke",
run(["./bin/boulder", "admin-revoker", "serial-revoke",
"--config", "%s/admin-revoker.json" % config_dir,
'%x' % parsed_cert.serial_number, '1'])
@ -1701,7 +1701,7 @@ def test_admin_revoker_batched():
serialFile.write("%x\n" % parse_cert(order).serial_number)
serialFile.close()
run(["./bin/admin-revoker", "batched-serial-revoke",
run(["./bin/boulder", "admin-revoker", "batched-serial-revoke",
"--config", "%s/admin-revoker.json" % config_dir,
serialFile.name, '0', '2'])