hashicorp/consul
CLI
The consul CLI is the operator interface to a running cluster. It is built on github.com/mitchellh/cli and lives entirely under command/. The binary's main.go is just a thin entrypoint:
// main.go
cmds := command.RegisteredCommands(ui)
cli := &mcli.CLI{Args: os.Args[1:], Commands: cmds, ...}
exitCode, err := cli.Run()command.RegisteredCommands (in command/registry.go) returns a map[string]mcli.CommandFactory — a flat map keyed by command path with values that lazily construct each subcommand. The Mitchell CLI library handles parsing, autocompletion, and help generation from that map.
Directory layout
command/
├── registry.go # The big map of (key → factory) entries
├── cli/ # Shared CLI plumbing (BasicUI, etc.)
├── flags/ # Common flag definitions (HTTP, DNS, ACL token, ...)
├── helpers/ # Output formatting, error rendering
├── agent/ # `consul agent`
├── acl/ # `consul acl ...` (token, policy, role, auth-method, binding-rule, templated-policy)
├── catalog/ # `consul catalog ...`
├── config/ # `consul config ...` (config entries)
├── connect/ # `consul connect ...` (CA, envoy, proxy, expose, redirect-traffic)
├── debug/ # `consul debug`
├── event/ # `consul event`
├── exec/ # `consul exec`
├── intention/ # `consul intention ...` (legacy intentions tooling)
├── kv/ # `consul kv put|get|delete|export|import`
├── lock/ # `consul lock`
├── members/ # `consul members`
├── monitor/ # `consul monitor` (live log tail)
├── operator/ # `consul operator ...` (autopilot, raft, usage, utilization, area)
├── peering/ # `consul peering ...`
├── resource/ # `consul resource ...` (v2 resource API)
├── services/ # `consul services ...`
├── snapshot/ # `consul snapshot ...` (save/restore/inspect/decode)
├── tls/ # `consul tls ca|cert ...` helper for cert generation
├── troubleshoot/ # `consul troubleshoot ...` mesh diagnostics
├── validate/ # `consul validate` config validator
├── watch/ # `consul watch`
├── join/, leave/, forceleave/, info/, login/, logout/, keygen/, keyring/, maint/, reload/, rtt/, version/
└── ...Subcommand surface (193 packages)
The full surface is most easily explored by reading command/registry.go. A flavor:
| Top-level | Selected sub-commands |
|---|---|
agent |
The long-running daemon |
acl |
bootstrap, policy, role, token, auth-method, binding-rule, templated-policy, set-agent-token |
catalog |
datacenters, nodes, services |
config |
read, write, list, delete |
connect |
ca get/set, envoy (bootstrap helper), proxy, expose, redirect-traffic |
intention |
create, delete, get, list, match, check (legacy compatibility) |
kv |
put, get, delete, export, import |
members |
List Serf members for LAN/WAN/segment |
operator |
autopilot get/set/state, raft list-peers/remove-peer/transfer-leader, usage, utilization |
peering |
generate, establish, list, read, delete, exported-services |
resource |
read, list, apply, delete (and *-grpc variants for the gRPC API) |
services |
register, deregister, export, exported-services, imported-services |
snapshot |
save, restore, inspect, decode |
tls |
ca create, cert create |
troubleshoot |
proxy, upstreams, ports |
debug, event, exec, info, join, leave, force-leave, keygen, keyring, lock, login, logout, maint, monitor, reload, rtt, validate, version, watch |
one command each |
Anatomy of a subcommand
Every subcommand package follows the same shape:
// command/<name>/<name>.go
type cmd struct {
UI cli.Ui
flags *flag.FlagSet
http *flags.HTTPFlags
help string
}
func New(ui cli.Ui) cli.Command {
c := &cmd{UI: ui}
c.init()
return c
}
func (c *cmd) init() {
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
c.http = &flags.HTTPFlags{}
flags.Merge(c.flags, c.http.ClientFlags())
c.help = flags.Usage(helpText, c.flags)
}
func (c *cmd) Run(args []string) int {
if err := c.flags.Parse(args); err != nil { return 1 }
client, err := c.http.APIClient()
// ... call api.Client methods, render result with c.UI ...
}
func (c *cmd) Synopsis() string { return synopsis }
func (c *cmd) Help() string { return c.help }Almost every subcommand goes through the public api package (api/) to talk to a running agent. That keeps the CLI thin and ensures public Go users have access to everything the CLI does.
Shared flag plumbing
command/flags/ defines:
HTTPFlags—-http-addr,-token,-token-file,-ca-file, TLS optionsServerFlags—-datacenter,-namespace,-partitionEnvoyFlags,LogFlags,UsageFlags, ...
Subcommands compose these with flags.Merge. The HTTP flags land in api.Config via HTTPFlags.APIClient(), ensuring uniform behavior across commands.
Output formatting
command/helpers/ and per-command helpers.go files contain table renderers, JSON pretty-printers, and the colored output used by consul members and consul operator raft list-peers. The base cli.BasicUI (in command/cli/) wraps Mitchell's mcli.BasicUi and adds color support keyed off of the -no-color flag.
Special cases
consul agentis the only subcommand that doesn't talk to an existing agent — it is the agent (command/agent/agent.gocallsagent.New(...)and runs the long-lived process).consul resource ... -grpcsubcommands talk to the v2 resource service over gRPC instead of HTTP.consul connect envoygenerates an Envoy bootstrap config and execsenvoydirectly. Code incommand/connect/envoy/.consul connect proxyis the legacy in-tree built-in proxy (now mostly superseded by Envoy/dataplane).consul lockruns an arbitrary user command while holding a Consul session-based lock; useful for leader election in scripts.
Entry points for modification
- To add a new top-level command: create
command/<name>/<name>.gowithNew(ui)and add anentry{"<name>", func(...) ...}line tocommand/registry.go. - To add a sub-command (e.g.,
consul foo bar): createcommand/foo/bar/bar.goand add the correspondingentry{"foo bar", ...}line. - To add a new shared flag: extend
command/flags/. The HTTP flags are the most commonly extended. - To customize output across commands: extend
command/helpers/rather than per-command files.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.