pkgproxy design: pkgproxyctl
pkgproxyctl as a multi-call binary: the command layout, the piped setup scripts and the help layout. Part of the pkgproxy design, whose index says which file holds each § number.
14. pkgproxyctl: a multi-call binary, not a second binary
pkgproxyctl is the operator-facing CLI: stats reads the stats API
(§13), setup prints client configuration
(§16.4), open opens the setup page in a
browser (§20.5), and cache invalidate and offline drive the admin API
(§16.9,
§21) — write-capable admin actions grow this
same binary rather than start a third one. It’s the same compiled binary
as the pkgproxy daemon, not a second cmd/ directory:
cmd/pkgproxy/main.go wraps both in one go-cmdr/router.Cmd with
UseArgv0: true, which dispatches on the base name the binary was invoked
as, and every packaging (snap/snapcraft.yaml, and image-garden’s own)
installs pkgproxyctl as a symlink to pkgproxy.
The reason is binary size: pkgproxyctl is thin (an HTTP-over-UNIX-socket
client and some table formatting), but a second Go binary would statically
link its own copy of the runtime, net/http, encoding/json and the rest
already inside pkgproxy, roughly doubling what ships for a few hundred
lines of CLI. Below the name dispatch, pkgproxyctl’s own command tree is
an ordinary go-cmdr router (ctl.go).
14.1 One cmd_$foo.go file per command
cmd/pkgproxy groups source files by command rather than by topic: one
cmdr.Cmd implementation (and its cmd_$foo_test.go) per file, named
after the command (cmd_setup_lxd.go is pkgproxyctl setup lxd), so a
command is findable by name alone. Code several commands share lives in
files that are not named cmd_* (guide.go, usage.go,
instructions.go, statssocket.go, …). pkgproxyctl stats decodes the
daemon’s own stats.Entry rather than declaring a parallel response type.
14.2 Piped setup scripts
Every pkgproxyctl setup command prints a guide on a terminal and a shell
script when its output is piped (| sh); the setup page serves the same
scripts. Each piped script is one { ... } block (scriptBegin): sh reads
a script from a pipe as it goes, so a command that reads its stdin, as lxc profile create does when stdin is not a terminal, would otherwise take the
rest of it. A script with nothing to do prints nothing, as an empty block is
not valid sh. A setup command that fails prints exit 1 to a pipe, its
error going to stderr as always, so that pkgproxyctl setup X | sh fails
rather than run an empty script and succeed. setup go -undo, setup lxd -undo, setup lxd-remote -undo and the undo of setup snapcraft, setup rockcraft and setup charmcraft are safe to repeat, and to run before
setting up: a project’s settings are cleared only when the project exists,
as a craft tool’s does not before its first build. The scripts’ own messages
are translated
(§22).
14.3 One help layout, with the positional arguments
go-cmdr’s stock usage for a flagcmd command (Usage of <name>: and the
flags) has no notion of positional arguments, so it cannot say that cache invalidate takes a URL or offline mode a mode. No go-cmdr change is
needed: flagcmd.MakeCmd installs a command’s own FlagUsageContext as
fl.Usage, and every leaf command, the daemon included, implements it
with printUsage (cmd/pkgproxy/usage.go), in the layout go-cmdr’s
router uses for its own help:
Usage: pkgproxyctl offline mode [OPTIONS] MODE
<a short description, a guide (§22): wrapped prose, verbatim lists>
Available options:
<flagcmd.PrintDefaults>
The usage line is one msgid per argument shape (Usage: %s [OPTIONS],
... URL, ... MODE), translated whole, with the placeholders
translated the way go-cmdr’s own catalog does ([OPCJE], TRYB). The
description is short: what the command does, never what the setup
guides print on a terminal. offline has status as its router’s
Implicit command, so its help says [COMMAND].
A wrong number of positional arguments prints the usage and then a
“cannot …” error, exit status 1, the way the router reports a missing
command. A command that takes none (the daemon, stats, every setup
command, offline status|on|off) rejects stray ones with rejectArgs
(cannot run <name>: unexpected arguments: "...") instead of ignoring
them: a misplaced word (pkgproxyctl setup apt -addr :3142 now) is a
mistake to report, not to guess around. Nothing that ships starts the
daemon with positional arguments (snap/snapcraft.yaml, pkgproxy-env,
the configure hook’s -check-config).