From a49711b8bf256890d67bb657e99bc895fba3ce69 Mon Sep 17 00:00:00 2001 From: Frank Schroeder Date: Wed, 11 Oct 2017 14:51:18 +0200 Subject: [PATCH] config: refactor commands to print help for flags (#3536) This patch refactors the commands that use the mitchellh/cli library to populate the command line flag set in both the Run() and the Help() method. Earlier versions of the mitchellh/cli library relied on the Run() method to populuate the flagset for generating the usage screen. This has changed in later versions and was previously solved with a small monkey patch to the library to restore the old behavior. However, this makes upgrading the library difficult since the patch has to be restored every time. This patch addresses this by moving the command line flags into an initFlags() method where appropriate and also moving all variables for the flags from the Run() method into the command itself. Fixes #3536 --- command/agent.go | 17 +-- command/base.go | 42 +++----- command/catalog_command.go | 8 +- command/catalog_list_datacenters.go | 17 ++- command/catalog_list_nodes.go | 65 ++++++------ command/catalog_list_services.go | 56 +++++----- command/commands.go | 22 ++-- command/event.go | 83 ++++++++------- command/exec.go | 42 ++++---- command/force_leave.go | 24 ++--- command/info.go | 29 +++--- command/join.go | 42 ++++---- command/keygen.go | 20 ++-- command/keyring.go | 61 +++++------ command/kv_command.go | 8 +- command/kv_delete.go | 56 +++++----- command/kv_export.go | 18 ++-- command/kv_get.go | 86 ++++++++------- command/kv_import.go | 17 ++- command/kv_put.go | 98 ++++++++++-------- command/leave.go | 16 ++- command/lock.go | 155 ++++++++++++++-------------- command/maint.go | 68 ++++++------ command/members.go | 61 +++++------ command/monitor.go | 29 +++--- command/operator_autopilot.go | 9 +- command/operator_autopilot_get.go | 17 ++- command/operator_autopilot_set.go | 99 +++++++++--------- command/operator_raft_list.go | 17 ++- command/operator_raft_remove.go | 41 ++++---- command/reload.go | 15 ++- command/rtt.go | 31 +++--- command/rtt_test.go | 34 +++--- command/snapshot_command.go | 6 +- command/snapshot_inspect.go | 15 ++- command/snapshot_restore.go | 17 ++- command/snapshot_save.go | 19 ++-- command/snapshot_save_test.go | 3 +- command/validate.go | 30 +++--- command/watch.go | 132 ++++++++++++----------- 40 files changed, 825 insertions(+), 800 deletions(-) diff --git a/command/agent.go b/command/agent.go index 18c7ceb379..265cbc87f6 100644 --- a/command/agent.go +++ b/command/agent.go @@ -49,11 +49,12 @@ type AgentCommand struct { // readConfig is responsible for setup of our configuration using // the command line and any file configs func (cmd *AgentCommand) readConfig() *config.RuntimeConfig { - var flags config.Flags - fs := cmd.BaseCommand.NewFlagSet(cmd) - config.AddFlags(fs, &flags) + cmd.InitFlagSet() - if err := cmd.BaseCommand.Parse(cmd.args); err != nil { + var flags config.Flags + config.AddFlags(cmd.FlagSet, &flags) + + if err := cmd.FlagSet.Parse(cmd.args); err != nil { if !strings.Contains(err.Error(), "help requested") { cmd.UI.Error(fmt.Sprintf("error parsing flags: %v", err)) } @@ -480,15 +481,15 @@ func (cmd *AgentCommand) Synopsis() string { } func (cmd *AgentCommand) Help() string { - helpText := ` + cmd.InitFlagSet() + config.AddFlags(cmd.FlagSet, &config.Flags{}) + return cmd.HelpCommand(` Usage: consul agent [options] Starts the Consul agent and runs until an interrupt is received. The agent represents a single node in a cluster. - ` + cmd.BaseCommand.Help() - - return strings.TrimSpace(helpText) + `) } func printJSON(name string, v interface{}) { diff --git a/command/base.go b/command/base.go index ee28e505ee..441318e713 100644 --- a/command/base.go +++ b/command/base.go @@ -35,7 +35,8 @@ type BaseCommand struct { HideNormalFlagsHelp bool - flagSet *flag.FlagSet + FlagSet *flag.FlagSet + hidden *flag.FlagSet // These are the options which correspond to the HTTP API options httpAddr configutil.StringValue @@ -56,7 +57,7 @@ func (c *BaseCommand) HTTPClient() (*api.Client, error) { if !c.hasClientHTTP() && !c.hasServerHTTP() { panic("no http flags defined") } - if !c.flagSet.Parsed() { + if !c.FlagSet.Parsed() { panic("flags have not been parsed") } @@ -141,19 +142,19 @@ func (c *BaseCommand) httpFlagsServer() *flag.FlagSet { // NewFlagSet creates a new flag set for the given command. It automatically // generates help output and adds the appropriate API flags. -func (c *BaseCommand) NewFlagSet(command cli.Command) *flag.FlagSet { - c.flagSet = flag.NewFlagSet("", flag.ContinueOnError) - c.flagSet.Usage = func() { c.UI.Error(command.Help()) } +func (c *BaseCommand) InitFlagSet() { + c.hidden = flag.NewFlagSet("", flag.ContinueOnError) + c.FlagSet = flag.NewFlagSet("", flag.ContinueOnError) if c.hasClientHTTP() { c.httpFlagsClient().VisitAll(func(f *flag.Flag) { - c.flagSet.Var(f.Value, f.Name, f.DefValue) + c.FlagSet.Var(f.Value, f.Name, f.Usage) }) } if c.hasServerHTTP() { c.httpFlagsServer().VisitAll(func(f *flag.Flag) { - c.flagSet.Var(f.Value, f.Name, f.DefValue) + c.FlagSet.Var(f.Value, f.Name, f.Usage) }) } @@ -164,24 +165,11 @@ func (c *BaseCommand) NewFlagSet(command cli.Command) *flag.FlagSet { c.UI.Error(errScanner.Text()) } }() - c.flagSet.SetOutput(errW) - - return c.flagSet + c.FlagSet.SetOutput(errW) } -// Parse is used to parse the underlying flag set. -func (c *BaseCommand) Parse(args []string) error { - return c.flagSet.Parse(args) -} - -// Help returns the help for this flagSet. -func (c *BaseCommand) Help() string { - // Some commands with subcommands (kv/snapshot) call this without initializing - // any flags first, so exit early to avoid a panic - if c.flagSet == nil { - return "" - } - return c.helpFlagsFor(c.flagSet) +func (c *BaseCommand) HelpCommand(msg string) string { + return strings.TrimSpace(msg + c.helpFlagsFor()) } // hasClientHTTP returns true if this meta command contains client HTTP flags. @@ -198,7 +186,11 @@ func (c *BaseCommand) hasServerHTTP() bool { // help output. This function is sad because there's no "merging" of command // line flags. We explicitly pull out our "common" options into another section // by doing string comparisons :(. -func (c *BaseCommand) helpFlagsFor(f *flag.FlagSet) string { +func (c *BaseCommand) helpFlagsFor() string { + if c.FlagSet == nil { + panic("FlagSet not initialized. Did you forget to call InitFlagSet()?") + } + httpFlagsClient := c.httpFlagsClient() httpFlagsServer := c.httpFlagsServer() @@ -220,7 +212,7 @@ func (c *BaseCommand) helpFlagsFor(f *flag.FlagSet) string { if !c.HideNormalFlagsHelp { firstCommand := true - f.VisitAll(func(f *flag.Flag) { + c.FlagSet.VisitAll(func(f *flag.Flag) { // Skip HTTP flags as they will be grouped separately if flagContains(httpFlagsClient, f) || flagContains(httpFlagsServer, f) { return diff --git a/command/catalog_command.go b/command/catalog_command.go index 30f112460b..b7edec66c1 100644 --- a/command/catalog_command.go +++ b/command/catalog_command.go @@ -1,8 +1,6 @@ package command import ( - "strings" - "github.com/mitchellh/cli" ) @@ -17,7 +15,8 @@ func (c *CatalogCommand) Run(args []string) int { } func (c *CatalogCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul catalog [options] [args] This command has subcommands for interacting with Consul's catalog. The @@ -41,8 +40,7 @@ Usage: consul catalog [options] [args] For more examples, ask for subcommand help or view the documentation. -` - return strings.TrimSpace(helpText) +`) } func (c *CatalogCommand) Synopsis() string { diff --git a/command/catalog_list_datacenters.go b/command/catalog_list_datacenters.go index caf01a0d02..f962cccda2 100644 --- a/command/catalog_list_datacenters.go +++ b/command/catalog_list_datacenters.go @@ -2,7 +2,6 @@ package command import ( "fmt" - "strings" "github.com/mitchellh/cli" ) @@ -14,7 +13,8 @@ type CatalogListDatacentersCommand struct { } func (c *CatalogListDatacentersCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul catalog datacenters [options] Retrieves the list of all known datacenters. This datacenters are sorted in @@ -27,25 +27,22 @@ Usage: consul catalog datacenters [options] For a full list of options and examples, please see the Consul documentation. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *CatalogListDatacentersCommand) Run(args []string) int { - f := c.BaseCommand.NewFlagSet(c) - - if err := c.BaseCommand.Parse(args); err != nil { + c.InitFlagSet() + if err := c.FlagSet.Parse(args); err != nil { return 1 } - if l := len(f.Args()); l > 0 { + if l := len(c.FlagSet.Args()); l > 0 { c.UI.Error(fmt.Sprintf("Too many arguments (expected 0, got %d)", l)) return 1 } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 diff --git a/command/catalog_list_nodes.go b/command/catalog_list_nodes.go index 0cfb1a745e..c338b8ddd5 100644 --- a/command/catalog_list_nodes.go +++ b/command/catalog_list_nodes.go @@ -16,10 +16,31 @@ var _ cli.Command = (*CatalogListNodesCommand)(nil) // nodes in the catalog. type CatalogListNodesCommand struct { BaseCommand + + // flags + detailed bool + near string + nodeMeta map[string]string + service string +} + +func (c *CatalogListNodesCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.BoolVar(&c.detailed, "detailed", false, "Output detailed information about "+ + "the nodes including their addresses and metadata.") + c.FlagSet.StringVar(&c.near, "near", "", "Node name to sort the node list in ascending "+ + "order based on estimated round-trip time from that node. "+ + "Passing \"_agent\" will use this agent's node for sorting.") + c.FlagSet.Var((*configutil.FlagMapValue)(&c.nodeMeta), "node-meta", "Metadata to "+ + "filter nodes with the given `key=value` pairs. This flag may be "+ + "specified multiple times to filter on multiple sources of metadata.") + c.FlagSet.StringVar(&c.service, "service", "", "Service `id or name` to filter nodes. "+ + "Only nodes which are providing the given service will be returned.") } func (c *CatalogListNodesCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul catalog nodes [options] Retrieves the list nodes registered in a given datacenter. By default, the @@ -48,50 +69,32 @@ Usage: consul catalog nodes [options] For a full list of options and examples, please see the Consul documentation. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *CatalogListNodesCommand) Run(args []string) int { - f := c.BaseCommand.NewFlagSet(c) - - detailed := f.Bool("detailed", false, "Output detailed information about "+ - "the nodes including their addresses and metadata.") - - near := f.String("near", "", "Node name to sort the node list in ascending "+ - "order based on estimated round-trip time from that node. "+ - "Passing \"_agent\" will use this agent's node for sorting.") - - nodeMeta := make(map[string]string) - f.Var((*configutil.FlagMapValue)(&nodeMeta), "node-meta", "Metadata to "+ - "filter nodes with the given `key=value` pairs. This flag may be "+ - "specified multiple times to filter on multiple sources of metadata.") - - service := f.String("service", "", "Service `id or name` to filter nodes. "+ - "Only nodes which are providing the given service will be returned.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } - if l := len(f.Args()); l > 0 { + if l := len(c.FlagSet.Args()); l > 0 { c.UI.Error(fmt.Sprintf("Too many arguments (expected 0, got %d)", l)) return 1 } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 } var nodes []*api.Node - if *service != "" { - services, _, err := client.Catalog().Service(*service, "", &api.QueryOptions{ - Near: *near, - NodeMeta: nodeMeta, + if c.service != "" { + services, _, err := client.Catalog().Service(c.service, "", &api.QueryOptions{ + Near: c.near, + NodeMeta: c.nodeMeta, }) if err != nil { c.UI.Error(fmt.Sprintf("Error listing nodes for service: %s", err)) @@ -113,8 +116,8 @@ func (c *CatalogListNodesCommand) Run(args []string) int { } } else { nodes, _, err = client.Catalog().Nodes(&api.QueryOptions{ - Near: *near, - NodeMeta: nodeMeta, + Near: c.near, + NodeMeta: c.nodeMeta, }) if err != nil { c.UI.Error(fmt.Sprintf("Error listing nodes: %s", err)) @@ -128,7 +131,7 @@ func (c *CatalogListNodesCommand) Run(args []string) int { return 0 } - output, err := printNodes(nodes, *detailed) + output, err := printNodes(nodes, c.detailed) if err != nil { c.UI.Error(fmt.Sprintf("Error printing nodes: %s", err)) return 1 diff --git a/command/catalog_list_services.go b/command/catalog_list_services.go index 427b655668..fa2e73ef38 100644 --- a/command/catalog_list_services.go +++ b/command/catalog_list_services.go @@ -18,10 +18,29 @@ var _ cli.Command = (*CatalogListServicesCommand)(nil) // datacenters the agent knows about. type CatalogListServicesCommand struct { BaseCommand + + // flags + node string + nodeMeta map[string]string + tags bool +} + +func (c *CatalogListServicesCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.StringVar(&c.node, "node", "", + "Node `id or name` for which to list services.") + c.FlagSet.Var((*configutil.FlagMapValue)(&c.nodeMeta), "node-meta", "Metadata to "+ + "filter nodes with the given `key=value` pairs. If specified, only "+ + "services running on nodes matching the given metadata will be returned. "+ + "This flag may be specified multiple times to filter on multiple sources "+ + "of metadata.") + c.FlagSet.BoolVar(&c.tags, "tags", false, "Display each service's tags as a "+ + "comma-separated list beside each service entry.") } func (c *CatalogListServicesCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul catalog services [options] Retrieves the list services registered in a given datacenter. By default, the @@ -45,46 +64,31 @@ Usage: consul catalog services [options] For a full list of options and examples, please see the Consul documentation. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *CatalogListServicesCommand) Run(args []string) int { - f := c.BaseCommand.NewFlagSet(c) - - node := f.String("node", "", "Node `id or name` for which to list services.") - - nodeMeta := make(map[string]string) - f.Var((*configutil.FlagMapValue)(&nodeMeta), "node-meta", "Metadata to "+ - "filter nodes with the given `key=value` pairs. If specified, only "+ - "services running on nodes matching the given metadata will be returned. "+ - "This flag may be specified multiple times to filter on multiple sources "+ - "of metadata.") - - tags := f.Bool("tags", false, "Display each service's tags as a "+ - "comma-separated list beside each service entry.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } - if l := len(f.Args()); l > 0 { + if l := len(c.FlagSet.Args()); l > 0 { c.UI.Error(fmt.Sprintf("Too many arguments (expected 0, got %d)", l)) return 1 } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 } var services map[string][]string - if *node != "" { - catalogNode, _, err := client.Catalog().Node(*node, &api.QueryOptions{ - NodeMeta: nodeMeta, + if c.node != "" { + catalogNode, _, err := client.Catalog().Node(c.node, &api.QueryOptions{ + NodeMeta: c.nodeMeta, }) if err != nil { c.UI.Error(fmt.Sprintf("Error listing services for node: %s", err)) @@ -98,7 +102,7 @@ func (c *CatalogListServicesCommand) Run(args []string) int { } } else { services, _, err = client.Catalog().Services(&api.QueryOptions{ - NodeMeta: nodeMeta, + NodeMeta: c.nodeMeta, }) if err != nil { c.UI.Error(fmt.Sprintf("Error listing services: %s", err)) @@ -119,7 +123,7 @@ func (c *CatalogListServicesCommand) Run(args []string) int { } sort.Strings(order) - if *tags { + if c.tags { var b bytes.Buffer tw := tabwriter.NewWriter(&b, 0, 2, 6, ' ', 0) for _, s := range order { diff --git a/command/commands.go b/command/commands.go index 0322a6c6c9..b735839568 100644 --- a/command/commands.go +++ b/command/commands.go @@ -36,8 +36,8 @@ func init() { "catalog": func() (cli.Command, error) { return &CatalogCommand{ BaseCommand: BaseCommand{ - UI: ui, Flags: FlagSetNone, + UI: ui, }, }, nil }, @@ -100,8 +100,8 @@ func init() { "info": func() (cli.Command, error) { return &InfoCommand{ BaseCommand: BaseCommand{ - UI: ui, Flags: FlagSetClientHTTP, + UI: ui, }, }, nil }, @@ -109,8 +109,8 @@ func init() { "join": func() (cli.Command, error) { return &JoinCommand{ BaseCommand: BaseCommand{ - UI: ui, Flags: FlagSetClientHTTP, + UI: ui, }, }, nil }, @@ -118,8 +118,8 @@ func init() { "keygen": func() (cli.Command, error) { return &KeygenCommand{ BaseCommand: BaseCommand{ - UI: ui, Flags: FlagSetNone, + UI: ui, }, }, nil }, @@ -127,8 +127,8 @@ func init() { "keyring": func() (cli.Command, error) { return &KeyringCommand{ BaseCommand: BaseCommand{ - UI: ui, Flags: FlagSetClientHTTP, + UI: ui, }, }, nil }, @@ -136,8 +136,8 @@ func init() { "kv": func() (cli.Command, error) { return &KVCommand{ BaseCommand: BaseCommand{ - UI: ui, Flags: FlagSetNone, + UI: ui, }, }, nil }, @@ -145,8 +145,8 @@ func init() { "kv delete": func() (cli.Command, error) { return &KVDeleteCommand{ BaseCommand: BaseCommand{ - UI: ui, Flags: FlagSetHTTP, + UI: ui, }, }, nil }, @@ -154,8 +154,8 @@ func init() { "kv get": func() (cli.Command, error) { return &KVGetCommand{ BaseCommand: BaseCommand{ - UI: ui, Flags: FlagSetHTTP, + UI: ui, }, }, nil }, @@ -163,8 +163,8 @@ func init() { "kv put": func() (cli.Command, error) { return &KVPutCommand{ BaseCommand: BaseCommand{ - UI: ui, Flags: FlagSetHTTP, + UI: ui, }, }, nil }, @@ -172,8 +172,8 @@ func init() { "kv export": func() (cli.Command, error) { return &KVExportCommand{ BaseCommand: BaseCommand{ - UI: ui, Flags: FlagSetHTTP, + UI: ui, }, }, nil }, @@ -181,8 +181,8 @@ func init() { "kv import": func() (cli.Command, error) { return &KVImportCommand{ BaseCommand: BaseCommand{ - UI: ui, Flags: FlagSetHTTP, + UI: ui, }, }, nil }, diff --git a/command/event.go b/command/event.go index 13d5749022..1225ad023e 100644 --- a/command/event.go +++ b/command/event.go @@ -3,7 +3,6 @@ package command import ( "fmt" "regexp" - "strings" consulapi "github.com/hashicorp/consul/api" ) @@ -12,40 +11,34 @@ import ( // fire new events type EventCommand struct { BaseCommand + + // flags + name string + node string + service string + tag string } -func (c *EventCommand) Help() string { - helpText := ` -Usage: consul event [options] [payload] - - Dispatches a custom user event across a datacenter. An event must provide - a name, but a payload is optional. Events support filtering using - regular expressions on node name, service, and tag definitions. - -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +func (c *EventCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.StringVar(&c.name, "name", "", + "Name of the event.") + c.FlagSet.StringVar(&c.node, "node", "", + "Regular expression to filter on node names.") + c.FlagSet.StringVar(&c.service, "service", "", + "Regular expression to filter on service instances.") + c.FlagSet.StringVar(&c.tag, "tag", "", + "Regular expression to filter on service tags. Must be used with -service.") } func (c *EventCommand) Run(args []string) int { - var name, node, service, tag string - - f := c.BaseCommand.NewFlagSet(c) - f.StringVar(&name, "name", "", - "Name of the event.") - f.StringVar(&node, "node", "", - "Regular expression to filter on node names.") - f.StringVar(&service, "service", "", - "Regular expression to filter on service instances.") - f.StringVar(&tag, "tag", "", - "Regular expression to filter on service tags. Must be used with -service.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } // Check for a name - if name == "" { + if c.name == "" { c.UI.Error("Event name must be specified") c.UI.Error("") c.UI.Error(c.Help()) @@ -53,32 +46,32 @@ func (c *EventCommand) Run(args []string) int { } // Validate the filters - if node != "" { - if _, err := regexp.Compile(node); err != nil { + if c.node != "" { + if _, err := regexp.Compile(c.node); err != nil { c.UI.Error(fmt.Sprintf("Failed to compile node filter regexp: %v", err)) return 1 } } - if service != "" { - if _, err := regexp.Compile(service); err != nil { + if c.service != "" { + if _, err := regexp.Compile(c.service); err != nil { c.UI.Error(fmt.Sprintf("Failed to compile service filter regexp: %v", err)) return 1 } } - if tag != "" { - if _, err := regexp.Compile(tag); err != nil { + if c.tag != "" { + if _, err := regexp.Compile(c.tag); err != nil { c.UI.Error(fmt.Sprintf("Failed to compile tag filter regexp: %v", err)) return 1 } } - if tag != "" && service == "" { + if c.tag != "" && c.service == "" { c.UI.Error("Cannot provide tag filter without service filter.") return 1 } // Check for a payload var payload []byte - args = f.Args() + args = c.FlagSet.Args() switch len(args) { case 0: case 1: @@ -91,7 +84,7 @@ func (c *EventCommand) Run(args []string) int { } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -105,11 +98,11 @@ func (c *EventCommand) Run(args []string) int { // Prepare the request event := client.Event() params := &consulapi.UserEvent{ - Name: name, + Name: c.name, Payload: payload, - NodeFilter: node, - ServiceFilter: service, - TagFilter: tag, + NodeFilter: c.node, + ServiceFilter: c.service, + TagFilter: c.tag, } // Fire the event @@ -124,6 +117,18 @@ func (c *EventCommand) Run(args []string) int { return 0 } +func (c *EventCommand) Help() string { + c.initFlags() + return c.HelpCommand(` +Usage: consul event [options] [payload] + + Dispatches a custom user event across a datacenter. An event must provide + a name, but a payload is optional. Events support filtering using + regular expressions on node name, service, and tag definitions. + +`) +} + func (c *EventCommand) Synopsis() string { return "Fire a new event" } diff --git a/command/exec.go b/command/exec.go index ec04027bdc..9d95028a2f 100644 --- a/command/exec.go +++ b/command/exec.go @@ -129,32 +129,35 @@ type ExecCommand struct { stopCh chan struct{} } -func (c *ExecCommand) Run(args []string) int { - f := c.BaseCommand.NewFlagSet(c) - f.StringVar(&c.conf.node, "node", "", +func (c *ExecCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.StringVar(&c.conf.node, "node", "", "Regular expression to filter on node names.") - f.StringVar(&c.conf.service, "service", "", + c.FlagSet.StringVar(&c.conf.service, "service", "", "Regular expression to filter on service instances.") - f.StringVar(&c.conf.tag, "tag", "", + c.FlagSet.StringVar(&c.conf.tag, "tag", "", "Regular expression to filter on service tags. Must be used with -service.") - f.StringVar(&c.conf.prefix, "prefix", rExecPrefix, + c.FlagSet.StringVar(&c.conf.prefix, "prefix", rExecPrefix, "Prefix in the KV store to use for request data.") - f.BoolVar(&c.conf.shell, "shell", true, + c.FlagSet.BoolVar(&c.conf.shell, "shell", true, "Use a shell to run the command.") - f.DurationVar(&c.conf.wait, "wait", rExecQuietWait, + c.FlagSet.DurationVar(&c.conf.wait, "wait", rExecQuietWait, "Period to wait with no responses before terminating execution.") - f.DurationVar(&c.conf.replWait, "wait-repl", rExecReplicationWait, + c.FlagSet.DurationVar(&c.conf.replWait, "wait-repl", rExecReplicationWait, "Period to wait for replication before firing event. This is an "+ "optimization to allow stale reads to be performed.") - f.BoolVar(&c.conf.verbose, "verbose", false, + c.FlagSet.BoolVar(&c.conf.verbose, "verbose", false, "Enables verbose output.") +} - if err := c.BaseCommand.Parse(args); err != nil { +func (c *ExecCommand) Run(args []string) int { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } // Join the commands to execute - c.conf.cmd = strings.Join(f.Args(), " ") + c.conf.cmd = strings.Join(c.FlagSet.Args(), " ") // If there is no command, read stdin for a script input if c.conf.cmd == "-" { @@ -175,7 +178,7 @@ func (c *ExecCommand) Run(args []string) int { c.conf.script = buf.Bytes() } else if !c.conf.shell { c.conf.cmd = "" - c.conf.args = f.Args() + c.conf.args = c.FlagSet.Args() } // Ensure we have a command or script @@ -193,7 +196,7 @@ func (c *ExecCommand) Run(args []string) int { } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -206,7 +209,7 @@ func (c *ExecCommand) Run(args []string) int { c.client = client // Check if this is a foreign datacenter - if c.BaseCommand.HTTPDatacenter() != "" && c.BaseCommand.HTTPDatacenter() != info["Config"]["Datacenter"] { + if c.HTTPDatacenter() != "" && c.HTTPDatacenter() != info["Config"]["Datacenter"] { if c.conf.verbose { c.UI.Info("Remote exec in foreign datacenter, using Session TTL") } @@ -506,7 +509,7 @@ func (c *ExecCommand) createSessionForeign() (string, error) { node := services[0].Node.Node if c.conf.verbose { c.UI.Info(fmt.Sprintf("Binding session to remote node %s@%s", - node, c.BaseCommand.HTTPDatacenter())) + node, c.HTTPDatacenter())) } session := c.client.Session() @@ -628,7 +631,8 @@ func (c *ExecCommand) Synopsis() string { } func (c *ExecCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul exec [options] [-|command...] Evaluates a command on remote Consul nodes. The nodes responding can @@ -636,9 +640,7 @@ Usage: consul exec [options] [-|command...] definitions. If a command is '-', stdin will be read until EOF and used as a script input. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } // TargetedUI is a UI that wraps another UI implementation and modifies diff --git a/command/force_leave.go b/command/force_leave.go index 42fbcad8d2..1e9a45b510 100644 --- a/command/force_leave.go +++ b/command/force_leave.go @@ -2,7 +2,6 @@ package command import ( "fmt" - "strings" ) // ForceLeaveCommand is a Command implementation that tells a running Consul @@ -12,12 +11,12 @@ type ForceLeaveCommand struct { } func (c *ForceLeaveCommand) Run(args []string) int { - f := c.BaseCommand.NewFlagSet(c) - if err := c.BaseCommand.Parse(args); err != nil { + c.InitFlagSet() + if err := c.FlagSet.Parse(args); err != nil { return 1 } - nodes := f.Args() + nodes := c.FlagSet.Args() if len(nodes) != 1 { c.UI.Error("A single node name must be specified to force leave.") c.UI.Error("") @@ -25,7 +24,7 @@ func (c *ForceLeaveCommand) Run(args []string) int { return 1 } - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -40,12 +39,9 @@ func (c *ForceLeaveCommand) Run(args []string) int { return 0 } -func (c *ForceLeaveCommand) Synopsis() string { - return "Forces a member of the cluster to enter the \"left\" state" -} - func (c *ForceLeaveCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul force-leave [options] name Forces a member of a Consul cluster to enter the "left" state. Note @@ -55,7 +51,9 @@ Usage: consul force-leave [options] name Consul will attempt to reconnect to those failed nodes for some period of time before eventually reaping them. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) +} + +func (c *ForceLeaveCommand) Synopsis() string { + return "Forces a member of the cluster to enter the \"left\" state" } diff --git a/command/info.go b/command/info.go index 8ba6f44c2a..e94569a883 100644 --- a/command/info.go +++ b/command/info.go @@ -3,7 +3,6 @@ package command import ( "fmt" "sort" - "strings" ) // InfoCommand is a Command implementation that queries a running @@ -12,25 +11,13 @@ type InfoCommand struct { BaseCommand } -func (c *InfoCommand) Help() string { - helpText := ` -Usage: consul info [options] - - Provides debugging information for operators - -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) -} - func (c *InfoCommand) Run(args []string) int { - c.BaseCommand.NewFlagSet(c) - - if err := c.BaseCommand.Parse(args); err != nil { + c.InitFlagSet() + if err := c.FlagSet.Parse(args); err != nil { return 1 } - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -79,6 +66,16 @@ func (c *InfoCommand) Run(args []string) int { return 0 } +func (c *InfoCommand) Help() string { + c.InitFlagSet() + return c.HelpCommand(` +Usage: consul info [options] + + Provides debugging information for operators + +`) +} + func (c *InfoCommand) Synopsis() string { return "Provides debugging information for operators." } diff --git a/command/join.go b/command/join.go index df042791c4..ad03fca3c2 100644 --- a/command/join.go +++ b/command/join.go @@ -2,37 +2,30 @@ package command import ( "fmt" - "strings" ) // JoinCommand is a Command implementation that tells a running Consul // agent to join another. type JoinCommand struct { BaseCommand + + // flags + wan bool } -func (c *JoinCommand) Help() string { - helpText := ` -Usage: consul join [options] address ... - - Tells a running Consul agent (with "consul agent") to join the cluster - by specifying at least one existing member. - -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +func (c *JoinCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.BoolVar(&c.wan, "wan", false, + "Joins a server to another server in the WAN pool.") } func (c *JoinCommand) Run(args []string) int { - var wan bool - - f := c.BaseCommand.NewFlagSet(c) - f.BoolVar(&wan, "wan", false, "Joins a server to another server in the WAN pool.") - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } - addrs := f.Args() + addrs := c.FlagSet.Args() if len(addrs) == 0 { c.UI.Error("At least one address to join must be specified.") c.UI.Error("") @@ -40,7 +33,7 @@ func (c *JoinCommand) Run(args []string) int { return 1 } - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -48,7 +41,7 @@ func (c *JoinCommand) Run(args []string) int { joins := 0 for _, addr := range addrs { - err := client.Agent().Join(addr, wan) + err := client.Agent().Join(addr, c.wan) if err != nil { c.UI.Error(fmt.Sprintf("Error joining address '%s': %s", addr, err)) } else { @@ -66,6 +59,17 @@ func (c *JoinCommand) Run(args []string) int { return 0 } +func (c *JoinCommand) Help() string { + c.initFlags() + return c.HelpCommand(` +Usage: consul join [options] address ... + + Tells a running Consul agent (with "consul agent") to join the cluster + by specifying at least one existing member. + +`) +} + func (c *JoinCommand) Synopsis() string { return "Tell Consul agent to join cluster" } diff --git a/command/keygen.go b/command/keygen.go index 6506d5b1e4..b01e460f97 100644 --- a/command/keygen.go +++ b/command/keygen.go @@ -4,7 +4,6 @@ import ( "crypto/rand" "encoding/base64" "fmt" - "strings" ) // KeygenCommand is a Command implementation that generates an encryption @@ -14,8 +13,8 @@ type KeygenCommand struct { } func (c *KeygenCommand) Run(args []string) int { - c.BaseCommand.NewFlagSet(c) - if err := c.BaseCommand.Parse(args); err != nil { + c.InitFlagSet() + if err := c.FlagSet.Parse(args); err != nil { return 1 } @@ -34,19 +33,18 @@ func (c *KeygenCommand) Run(args []string) int { return 0 } -func (c *KeygenCommand) Synopsis() string { - return "Generates a new encryption key" -} - func (c *KeygenCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul keygen Generates a new encryption key that can be used to configure the agent to encrypt traffic. The output of this command is already in the proper format that the agent expects. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) +} + +func (c *KeygenCommand) Synopsis() string { + return "Generates a new encryption key" } diff --git a/command/keyring.go b/command/keyring.go index b824ceea7f..33f98c566a 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -2,7 +2,6 @@ package command import ( "fmt" - "strings" "github.com/hashicorp/consul/agent" consulapi "github.com/hashicorp/consul/api" @@ -13,33 +12,38 @@ import ( // and removing gossip encryption keys from a keyring. type KeyringCommand struct { BaseCommand + + // flags + installKey string + useKey string + removeKey string + listKeys bool + relay int } -func (c *KeyringCommand) Run(args []string) int { - var installKey, useKey, removeKey string - var listKeys bool - var relay int - - f := c.BaseCommand.NewFlagSet(c) - - f.StringVar(&installKey, "install", "", +func (c *KeyringCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.StringVar(&c.installKey, "install", "", "Install a new encryption key. This will broadcast the new key to "+ "all members in the cluster.") - f.StringVar(&useKey, "use", "", + c.FlagSet.StringVar(&c.useKey, "use", "", "Change the primary encryption key, which is used to encrypt "+ "messages. The key must already be installed before this operation "+ "can succeed.") - f.StringVar(&removeKey, "remove", "", + c.FlagSet.StringVar(&c.removeKey, "remove", "", "Remove the given key from the cluster. This operation may only be "+ "performed on keys which are not currently the primary key.") - f.BoolVar(&listKeys, "list", false, + c.FlagSet.BoolVar(&c.listKeys, "list", false, "List all keys currently in use within the cluster.") - f.IntVar(&relay, "relay-factor", 0, + c.FlagSet.IntVar(&c.relay, "relay-factor", 0, "Setting this to a non-zero value will cause nodes to relay their response "+ "to the operation through this many randomly-chosen other nodes in the "+ "cluster. The maximum allowed value is 5.") +} - if err := c.BaseCommand.Parse(args); err != nil { +func (c *KeyringCommand) Run(args []string) int { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } @@ -51,8 +55,8 @@ func (c *KeyringCommand) Run(args []string) int { } // Only accept a single argument - found := listKeys - for _, arg := range []string{installKey, useKey, removeKey} { + found := c.listKeys + for _, arg := range []string{c.installKey, c.useKey, c.removeKey} { if found && len(arg) > 0 { c.UI.Error("Only a single action is allowed") return 1 @@ -67,20 +71,20 @@ func (c *KeyringCommand) Run(args []string) int { } // Validate the relay factor - relayFactor, err := agent.ParseRelayFactor(relay) + relayFactor, err := agent.ParseRelayFactor(c.relay) if err != nil { c.UI.Error(fmt.Sprintf("Error parsing relay factor: %s", err)) return 1 } // All other operations will require a client connection - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 } - if listKeys { + if c.listKeys { c.UI.Info("Gathering installed encryption keys...") responses, err := client.Operator().KeyringList(&consulapi.QueryOptions{RelayFactor: relayFactor}) if err != nil { @@ -92,9 +96,9 @@ func (c *KeyringCommand) Run(args []string) int { } opts := &consulapi.WriteOptions{RelayFactor: relayFactor} - if installKey != "" { + if c.installKey != "" { c.UI.Info("Installing new gossip encryption key...") - err := client.Operator().KeyringInstall(installKey, opts) + err := client.Operator().KeyringInstall(c.installKey, opts) if err != nil { c.UI.Error(fmt.Sprintf("error: %s", err)) return 1 @@ -102,9 +106,9 @@ func (c *KeyringCommand) Run(args []string) int { return 0 } - if useKey != "" { + if c.useKey != "" { c.UI.Info("Changing primary gossip encryption key...") - err := client.Operator().KeyringUse(useKey, opts) + err := client.Operator().KeyringUse(c.useKey, opts) if err != nil { c.UI.Error(fmt.Sprintf("error: %s", err)) return 1 @@ -112,9 +116,9 @@ func (c *KeyringCommand) Run(args []string) int { return 0 } - if removeKey != "" { + if c.removeKey != "" { c.UI.Info("Removing gossip encryption key...") - err := client.Operator().KeyringRemove(removeKey, opts) + err := client.Operator().KeyringRemove(c.removeKey, opts) if err != nil { c.UI.Error(fmt.Sprintf("error: %s", err)) return 1 @@ -145,7 +149,8 @@ func (c *KeyringCommand) handleList(responses []*consulapi.KeyringResponse) { } func (c *KeyringCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul keyring [options] Manages encryption keys used for gossip messages. Gossip encryption is @@ -161,9 +166,7 @@ Usage: consul keyring [options] are no errors. If any node fails to reply or reports failure, the exit code will be 1. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *KeyringCommand) Synopsis() string { diff --git a/command/kv_command.go b/command/kv_command.go index f456dc0f57..82bc2676da 100644 --- a/command/kv_command.go +++ b/command/kv_command.go @@ -1,8 +1,6 @@ package command import ( - "strings" - "github.com/mitchellh/cli" ) @@ -17,7 +15,8 @@ func (c *KVCommand) Run(args []string) int { } func (c *KVCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul kv [options] [args] This command has subcommands for interacting with Consul's key-value @@ -42,8 +41,7 @@ Usage: consul kv [options] [args] For more examples, ask for subcommand help or view the documentation. -` - return strings.TrimSpace(helpText) +`) } func (c *KVCommand) Synopsis() string { diff --git a/command/kv_delete.go b/command/kv_delete.go index ed0b05e893..9f037fc2e2 100644 --- a/command/kv_delete.go +++ b/command/kv_delete.go @@ -2,7 +2,6 @@ package command import ( "fmt" - "strings" "github.com/hashicorp/consul/api" ) @@ -11,10 +10,28 @@ import ( // prefix of keys from the key-value store. type KVDeleteCommand struct { BaseCommand + + // flags + cas bool + modifyIndex uint64 + recurse bool +} + +func (c *KVDeleteCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.BoolVar(&c.cas, "cas", false, + "Perform a Check-And-Set operation. Specifying this value also requires "+ + "the -modify-index flag to be set. The default value is false.") + c.FlagSet.Uint64Var(&c.modifyIndex, "modify-index", 0, + "Unsigned integer representing the ModifyIndex of the key. This is "+ + "used in combination with the -cas flag.") + c.FlagSet.BoolVar(&c.recurse, "recurse", false, + "Recursively delete all keys with the path. The default value is false.") } func (c *KVDeleteCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul kv delete [options] KEY_OR_PREFIX Removes the value from Consul's key-value store at the given path. If no @@ -31,30 +48,19 @@ Usage: consul kv delete [options] KEY_OR_PREFIX This will delete the keys named "foo", "food", and "foo/bar/zip" if they existed. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *KVDeleteCommand) Run(args []string) int { - f := c.BaseCommand.NewFlagSet(c) - cas := f.Bool("cas", false, - "Perform a Check-And-Set operation. Specifying this value also requires "+ - "the -modify-index flag to be set. The default value is false.") - modifyIndex := f.Uint64("modify-index", 0, - "Unsigned integer representing the ModifyIndex of the key. This is "+ - "used in combination with the -cas flag.") - recurse := f.Bool("recurse", false, - "Recursively delete all keys with the path. The default value is false.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } key := "" // Check for arg validation - args = f.Args() + args = c.FlagSet.Args() switch len(args) { case 0: key = "" @@ -74,37 +80,37 @@ func (c *KVDeleteCommand) Run(args []string) int { // If the key is empty and we are not doing a recursive delete, this is an // error. - if key == "" && !*recurse { + if key == "" && !c.recurse { c.UI.Error("Error! Missing KEY argument") return 1 } // ModifyIndex is required for CAS - if *cas && *modifyIndex == 0 { + if c.cas && c.modifyIndex == 0 { c.UI.Error("Must specify -modify-index with -cas!") return 1 } // Specifying a ModifyIndex for a non-CAS operation is not possible. - if *modifyIndex != 0 && !*cas { + if c.modifyIndex != 0 && !c.cas { c.UI.Error("Cannot specify -modify-index without -cas!") } // It is not valid to use a CAS and recurse in the same call - if *recurse && *cas { + if c.recurse && c.cas { c.UI.Error("Cannot specify both -cas and -recurse!") return 1 } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 } switch { - case *recurse: + case c.recurse: if _, err := client.KV().DeleteTree(key, nil); err != nil { c.UI.Error(fmt.Sprintf("Error! Did not delete prefix %s: %s", key, err)) return 1 @@ -112,10 +118,10 @@ func (c *KVDeleteCommand) Run(args []string) int { c.UI.Info(fmt.Sprintf("Success! Deleted keys with prefix: %s", key)) return 0 - case *cas: + case c.cas: pair := &api.KVPair{ Key: key, - ModifyIndex: *modifyIndex, + ModifyIndex: c.modifyIndex, } success, _, err := client.KV().DeleteCAS(pair, nil) diff --git a/command/kv_export.go b/command/kv_export.go index bd7531e3bb..e3ef23b411 100644 --- a/command/kv_export.go +++ b/command/kv_export.go @@ -4,7 +4,6 @@ import ( "encoding/base64" "encoding/json" "fmt" - "strings" "github.com/hashicorp/consul/api" ) @@ -20,7 +19,8 @@ func (c *KVExportCommand) Synopsis() string { } func (c *KVExportCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul kv export [KEY_OR_PREFIX] Retrieves key-value pairs for the given prefix from Consul's key-value store, @@ -31,20 +31,18 @@ Usage: consul kv export [KEY_OR_PREFIX] For a full list of options and examples, please see the Consul documentation. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *KVExportCommand) Run(args []string) int { - f := c.BaseCommand.NewFlagSet(c) - if err := c.BaseCommand.Parse(args); err != nil { + c.InitFlagSet() + if err := c.FlagSet.Parse(args); err != nil { return 1 } key := "" // Check for arg validation - args = f.Args() + args = c.FlagSet.Args() switch len(args) { case 0: key = "" @@ -63,14 +61,14 @@ func (c *KVExportCommand) Run(args []string) int { } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 } pairs, _, err := client.KV().List(key, &api.QueryOptions{ - AllowStale: c.BaseCommand.HTTPStale(), + AllowStale: c.HTTPStale(), }) if err != nil { c.UI.Error(fmt.Sprintf("Error querying Consul agent: %s", err)) diff --git a/command/kv_get.go b/command/kv_get.go index ef83aeb69c..e98aa5af29 100644 --- a/command/kv_get.go +++ b/command/kv_get.go @@ -5,7 +5,6 @@ import ( "encoding/base64" "fmt" "io" - "strings" "text/tabwriter" "github.com/hashicorp/consul/api" @@ -15,10 +14,39 @@ import ( // a key from the key-value store. type KVGetCommand struct { BaseCommand + + // flags + base64encode bool + detailed bool + keys bool + recurse bool + separator string +} + +func (c *KVGetCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.BoolVar(&c.base64encode, "base64", false, + "Base64 encode the value. The default value is false.") + c.FlagSet.BoolVar(&c.detailed, "detailed", false, + "Provide additional metadata about the key in addition to the value such "+ + "as the ModifyIndex and any flags that may have been set on the key. "+ + "The default value is false.") + c.FlagSet.BoolVar(&c.keys, "keys", false, + "List keys which start with the given prefix, but not their values. "+ + "This is especially useful if you only need the key names themselves. "+ + "This option is commonly combined with the -separator option. The default "+ + "value is false.") + c.FlagSet.BoolVar(&c.recurse, "recurse", false, + "Recursively look at all keys prefixed with the given path. The default "+ + "value is false.") + c.FlagSet.StringVar(&c.separator, "separator", "/", + "String to use as a separator between keys. The default value is \"/\", "+ + "but this option is only taken into account when paired with the -keys flag.") } func (c *KVGetCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul kv get [options] [KEY_OR_PREFIX] Retrieves the value from Consul's key-value store at the given key name. If no @@ -49,39 +77,19 @@ Usage: consul kv get [options] [KEY_OR_PREFIX] For a full list of options and examples, please see the Consul documentation. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *KVGetCommand) Run(args []string) int { - f := c.BaseCommand.NewFlagSet(c) - base64encode := f.Bool("base64", false, - "Base64 encode the value. The default value is false.") - detailed := f.Bool("detailed", false, - "Provide additional metadata about the key in addition to the value such "+ - "as the ModifyIndex and any flags that may have been set on the key. "+ - "The default value is false.") - keys := f.Bool("keys", false, - "List keys which start with the given prefix, but not their values. "+ - "This is especially useful if you only need the key names themselves. "+ - "This option is commonly combined with the -separator option. The default "+ - "value is false.") - recurse := f.Bool("recurse", false, - "Recursively look at all keys prefixed with the given path. The default "+ - "value is false.") - separator := f.String("separator", "/", - "String to use as a separator between keys. The default value is \"/\", "+ - "but this option is only taken into account when paired with the -keys flag.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } key := "" // Check for arg validation - args = f.Args() + args = c.FlagSet.Args() switch len(args) { case 0: key = "" @@ -101,22 +109,22 @@ func (c *KVGetCommand) Run(args []string) int { // If the key is empty and we are not doing a recursive or key-based lookup, // this is an error. - if key == "" && !(*recurse || *keys) { + if key == "" && !(c.recurse || c.keys) { c.UI.Error("Error! Missing KEY argument") return 1 } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 } switch { - case *keys: - keys, _, err := client.KV().Keys(key, *separator, &api.QueryOptions{ - AllowStale: c.BaseCommand.HTTPStale(), + case c.keys: + keys, _, err := client.KV().Keys(key, c.separator, &api.QueryOptions{ + AllowStale: c.HTTPStale(), }) if err != nil { c.UI.Error(fmt.Sprintf("Error querying Consul agent: %s", err)) @@ -128,9 +136,9 @@ func (c *KVGetCommand) Run(args []string) int { } return 0 - case *recurse: + case c.recurse: pairs, _, err := client.KV().List(key, &api.QueryOptions{ - AllowStale: c.BaseCommand.HTTPStale(), + AllowStale: c.HTTPStale(), }) if err != nil { c.UI.Error(fmt.Sprintf("Error querying Consul agent: %s", err)) @@ -138,9 +146,9 @@ func (c *KVGetCommand) Run(args []string) int { } for i, pair := range pairs { - if *detailed { + if c.detailed { var b bytes.Buffer - if err := prettyKVPair(&b, pair, *base64encode); err != nil { + if err := prettyKVPair(&b, pair, c.base64encode); err != nil { c.UI.Error(fmt.Sprintf("Error rendering KV pair: %s", err)) return 1 } @@ -151,7 +159,7 @@ func (c *KVGetCommand) Run(args []string) int { c.UI.Info("") } } else { - if *base64encode { + if c.base64encode { c.UI.Info(fmt.Sprintf("%s:%s", pair.Key, base64.StdEncoding.EncodeToString(pair.Value))) } else { c.UI.Info(fmt.Sprintf("%s:%s", pair.Key, pair.Value)) @@ -162,7 +170,7 @@ func (c *KVGetCommand) Run(args []string) int { return 0 default: pair, _, err := client.KV().Get(key, &api.QueryOptions{ - AllowStale: c.BaseCommand.HTTPStale(), + AllowStale: c.HTTPStale(), }) if err != nil { c.UI.Error(fmt.Sprintf("Error querying Consul agent: %s", err)) @@ -174,9 +182,9 @@ func (c *KVGetCommand) Run(args []string) int { return 1 } - if *detailed { + if c.detailed { var b bytes.Buffer - if err := prettyKVPair(&b, pair, *base64encode); err != nil { + if err := prettyKVPair(&b, pair, c.base64encode); err != nil { c.UI.Error(fmt.Sprintf("Error rendering KV pair: %s", err)) return 1 } diff --git a/command/kv_import.go b/command/kv_import.go index 844b69efeb..27a0c59943 100644 --- a/command/kv_import.go +++ b/command/kv_import.go @@ -9,7 +9,6 @@ import ( "io" "io/ioutil" "os" - "strings" "github.com/hashicorp/consul/api" ) @@ -28,7 +27,8 @@ func (c *KVImportCommand) Synopsis() string { } func (c *KVImportCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul kv import [DATA] Imports key-value pairs to the key-value store from the JSON representation @@ -48,20 +48,17 @@ Usage: consul kv import [DATA] For a full list of options and examples, please see the Consul documentation. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *KVImportCommand) Run(args []string) int { - f := c.BaseCommand.NewFlagSet(c) - - if err := c.BaseCommand.Parse(args); err != nil { + c.InitFlagSet() + if err := c.FlagSet.Parse(args); err != nil { return 1 } // Check for arg validation - args = f.Args() + args = c.FlagSet.Args() data, err := c.dataFromArgs(args) if err != nil { c.UI.Error(fmt.Sprintf("Error! %s", err)) @@ -69,7 +66,7 @@ func (c *KVImportCommand) Run(args []string) int { } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 diff --git a/command/kv_put.go b/command/kv_put.go index 45db2e344d..a229f83ea0 100644 --- a/command/kv_put.go +++ b/command/kv_put.go @@ -7,7 +7,6 @@ import ( "io" "io/ioutil" "os" - "strings" "github.com/hashicorp/consul/api" ) @@ -19,10 +18,49 @@ type KVPutCommand struct { // testStdin is the input for testing. testStdin io.Reader + + // flags + cas bool + flags uint64 + base64encoded bool + modifyIndex uint64 + session string + acquire bool + release bool +} + +func (c *KVPutCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.BoolVar(&c.cas, "cas", false, + "Perform a Check-And-Set operation. Specifying this value also "+ + "requires the -modify-index flag to be set. The default value "+ + "is false.") + c.FlagSet.Uint64Var(&c.flags, "flags", 0, + "Unsigned integer value to assign to this key-value pair. This "+ + "value is not read by Consul, so clients can use this value however "+ + "makes sense for their use case. The default value is 0 (no flags).") + c.FlagSet.BoolVar(&c.base64encoded, "base64", false, + "Treat the data as base 64 encoded. The default value is false.") + c.FlagSet.Uint64Var(&c.modifyIndex, "modify-index", 0, + "Unsigned integer representing the ModifyIndex of the key. This is "+ + "used in combination with the -cas flag.") + c.FlagSet.StringVar(&c.session, "session", "", + "User-defined identifer for this session as a string. This is commonly "+ + "used with the -acquire and -release operations to build robust locking, "+ + "but it can be set on any key. The default value is empty (no session).") + c.FlagSet.BoolVar(&c.acquire, "acquire", false, + "Obtain a lock on the key. If the key does not exist, this operation "+ + "will create the key and obtain the lock. The session must already "+ + "exist and be specified via the -session flag. The default value is false.") + c.FlagSet.BoolVar(&c.release, "release", false, + "Forfeit the lock on the key at the given path. This requires the "+ + "-session flag to be set. The key must be held by the session in order to "+ + "be unlocked. The default value is false.") } func (c *KVPutCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul kv put [options] KEY [DATA] Writes the data to the given path in the key-value store. The data can be of @@ -55,45 +93,17 @@ Usage: consul kv put [options] KEY [DATA] Additional flags and more advanced use cases are detailed below. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *KVPutCommand) Run(args []string) int { - f := c.BaseCommand.NewFlagSet(c) - cas := f.Bool("cas", false, - "Perform a Check-And-Set operation. Specifying this value also "+ - "requires the -modify-index flag to be set. The default value "+ - "is false.") - flags := f.Uint64("flags", 0, - "Unsigned integer value to assign to this key-value pair. This "+ - "value is not read by Consul, so clients can use this value however "+ - "makes sense for their use case. The default value is 0 (no flags).") - base64encoded := f.Bool("base64", false, - "Treat the data as base 64 encoded. The default value is false.") - modifyIndex := f.Uint64("modify-index", 0, - "Unsigned integer representing the ModifyIndex of the key. This is "+ - "used in combination with the -cas flag.") - session := f.String("session", "", - "User-defined identifer for this session as a string. This is commonly "+ - "used with the -acquire and -release operations to build robust locking, "+ - "but it can be set on any key. The default value is empty (no session).") - acquire := f.Bool("acquire", false, - "Obtain a lock on the key. If the key does not exist, this operation "+ - "will create the key and obtain the lock. The session must already "+ - "exist and be specified via the -session flag. The default value is false.") - release := f.Bool("release", false, - "Forfeit the lock on the key at the given path. This requires the "+ - "-session flag to be set. The key must be held by the session in order to "+ - "be unlocked. The default value is false.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } // Check for arg validation - args = f.Args() + args = c.FlagSet.Args() key, data, err := c.dataFromArgs(args) if err != nil { c.UI.Error(fmt.Sprintf("Error! %s", err)) @@ -101,7 +111,7 @@ func (c *KVPutCommand) Run(args []string) int { } dataBytes := []byte(data) - if *base64encoded { + if c.base64encoded { dataBytes, err = base64.StdEncoding.DecodeString(data) if err != nil { c.UI.Error(fmt.Sprintf("Error! Cannot base 64 decode data: %s", err)) @@ -109,19 +119,19 @@ func (c *KVPutCommand) Run(args []string) int { } // Session is reauired for release or acquire - if (*release || *acquire) && *session == "" { + if (c.release || c.acquire) && c.session == "" { c.UI.Error("Error! Missing -session (required with -acquire and -release)") return 1 } // ModifyIndex is required for CAS - if *cas && *modifyIndex == 0 { + if c.cas && c.modifyIndex == 0 { c.UI.Error("Must specify -modify-index with -cas!") return 1 } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -129,14 +139,14 @@ func (c *KVPutCommand) Run(args []string) int { pair := &api.KVPair{ Key: key, - ModifyIndex: *modifyIndex, - Flags: *flags, + ModifyIndex: c.modifyIndex, + Flags: c.flags, Value: dataBytes, - Session: *session, + Session: c.session, } switch { - case *cas: + case c.cas: ok, _, err := client.KV().CAS(pair, nil) if err != nil { c.UI.Error(fmt.Sprintf("Error! Did not write to %s: %s", key, err)) @@ -149,7 +159,7 @@ func (c *KVPutCommand) Run(args []string) int { c.UI.Info(fmt.Sprintf("Success! Data written to: %s", key)) return 0 - case *acquire: + case c.acquire: ok, _, err := client.KV().Acquire(pair, nil) if err != nil { c.UI.Error(fmt.Sprintf("Error! Failed writing data: %s", err)) @@ -162,7 +172,7 @@ func (c *KVPutCommand) Run(args []string) int { c.UI.Info(fmt.Sprintf("Success! Lock acquired on: %s", key)) return 0 - case *release: + case c.release: ok, _, err := client.KV().Release(pair, nil) if err != nil { c.UI.Error(fmt.Sprintf("Error! Failed writing data: %s", key)) diff --git a/command/leave.go b/command/leave.go index e747d188ad..cd9a24fcef 100644 --- a/command/leave.go +++ b/command/leave.go @@ -2,7 +2,6 @@ package command import ( "fmt" - "strings" ) // LeaveCommand is a Command implementation that instructs @@ -12,29 +11,28 @@ type LeaveCommand struct { } func (c *LeaveCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul leave [options] Causes the agent to gracefully leave the Consul cluster and shutdown. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *LeaveCommand) Run(args []string) int { - f := c.BaseCommand.NewFlagSet(c) - if err := c.BaseCommand.Parse(args); err != nil { + c.InitFlagSet() + if err := c.FlagSet.Parse(args); err != nil { return 1 } - nonFlagArgs := f.Args() + nonFlagArgs := c.FlagSet.Args() if len(nonFlagArgs) > 0 { c.UI.Error(fmt.Sprintf("Error found unexpected args: %v", nonFlagArgs)) c.UI.Output(c.Help()) return 1 } - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 diff --git a/command/lock.go b/command/lock.go index 8b12f56023..9a4917699c 100644 --- a/command/lock.go +++ b/command/lock.go @@ -43,30 +43,50 @@ type LockCommand struct { child *os.Process childLock sync.Mutex verbose bool + + // flags + limit int + monitorRetry int + name string + passStdin bool + propagateChildCode bool + shell bool + timeout time.Duration } -func (c *LockCommand) Help() string { - helpText := ` -Usage: consul lock [options] prefix child... +func (c *LockCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.BoolVar(&c.propagateChildCode, "child-exit-code", false, + "Exit 2 if the child process exited with an error if this is true, "+ + "otherwise this doesn't propagate an error from the child. The "+ + "default value is false.") + c.FlagSet.IntVar(&c.limit, "n", 1, + "Optional limit on the number of concurrent lock holders. The underlying "+ + "implementation switches from a lock to a semaphore when the value is "+ + "greater than 1. The default value is 1.") + c.FlagSet.IntVar(&c.monitorRetry, "monitor-retry", defaultMonitorRetry, + "Number of times to retry if Consul returns a 500 error while monitoring "+ + "the lock. This allows riding out brief periods of unavailability "+ + "without causing leader elections, but increases the amount of time "+ + "required to detect a lost lock in some cases. The default value is 3, "+ + "with a 1s wait between retries. Set this value to 0 to disable retires.") + c.FlagSet.StringVar(&c.name, "name", "", + "Optional name to associate with the lock session. It not provided, one "+ + "is generated based on the provided child command.") + c.FlagSet.BoolVar(&c.passStdin, "pass-stdin", false, + "Pass stdin to the child process.") + c.FlagSet.BoolVar(&c.shell, "shell", true, + "Use a shell to run the command (can set a custom shell via the SHELL "+ + "environment variable).") + c.FlagSet.DurationVar(&c.timeout, "timeout", 0, + "Maximum amount of time to wait to acquire the lock, specified as a "+ + "duration like \"1s\" or \"3h\". The default value is 0.") + c.FlagSet.BoolVar(&c.verbose, "verbose", false, + "Enable verbose (debugging) output.") - Acquires a lock or semaphore at a given path, and invokes a child process - when successful. The child process can assume the lock is held while it - executes. If the lock is lost or communication is disrupted the child - process will be sent a SIGTERM signal and given time to gracefully exit. - After the grace period expires the process will be hard terminated. - - For Consul agents on Windows, the child process is always hard terminated - with a SIGKILL, since Windows has no POSIX compatible notion for SIGTERM. - - When -n=1, only a single lock holder or leader exists providing mutual - exclusion. Setting a higher value switches to a semaphore allowing multiple - holders to coordinate. - - The prefix provided must have write privileges. - -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) + // Deprecations + c.FlagSet.DurationVar(&c.timeout, "try", 0, + "DEPRECATED. Use -timeout instead.") } func (c *LockCommand) Run(args []string) int { @@ -75,59 +95,19 @@ func (c *LockCommand) Run(args []string) int { } func (c *LockCommand) run(args []string, lu **LockUnlock) int { - var limit int - var monitorRetry int - var name string - var passStdin bool - var propagateChildCode bool - var shell bool - var timeout time.Duration - - f := c.BaseCommand.NewFlagSet(c) - f.BoolVar(&propagateChildCode, "child-exit-code", false, - "Exit 2 if the child process exited with an error if this is true, "+ - "otherwise this doesn't propagate an error from the child. The "+ - "default value is false.") - f.IntVar(&limit, "n", 1, - "Optional limit on the number of concurrent lock holders. The underlying "+ - "implementation switches from a lock to a semaphore when the value is "+ - "greater than 1. The default value is 1.") - f.IntVar(&monitorRetry, "monitor-retry", defaultMonitorRetry, - "Number of times to retry if Consul returns a 500 error while monitoring "+ - "the lock. This allows riding out brief periods of unavailability "+ - "without causing leader elections, but increases the amount of time "+ - "required to detect a lost lock in some cases. The default value is 3, "+ - "with a 1s wait between retries. Set this value to 0 to disable retires.") - f.StringVar(&name, "name", "", - "Optional name to associate with the lock session. It not provided, one "+ - "is generated based on the provided child command.") - f.BoolVar(&passStdin, "pass-stdin", false, - "Pass stdin to the child process.") - f.BoolVar(&shell, "shell", true, - "Use a shell to run the command (can set a custom shell via the SHELL "+ - "environment variable).") - f.DurationVar(&timeout, "timeout", 0, - "Maximum amount of time to wait to acquire the lock, specified as a "+ - "duration like \"1s\" or \"3h\". The default value is 0.") - f.BoolVar(&c.verbose, "verbose", false, - "Enable verbose (debugging) output.") - - // Deprecations - f.DurationVar(&timeout, "try", 0, - "DEPRECATED. Use -timeout instead.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } // Check the limit - if limit <= 0 { + if c.limit <= 0 { c.UI.Error(fmt.Sprintf("Lock holder limit must be positive")) return 1 } // Verify the prefix and child are provided - extra := f.Args() + extra := c.FlagSet.Args() if len(extra) < 2 { c.UI.Error("Key prefix and child command must be specified") return 1 @@ -135,27 +115,27 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int { prefix := extra[0] prefix = strings.TrimPrefix(prefix, "/") - if timeout < 0 { + if c.timeout < 0 { c.UI.Error("Timeout must be positive") return 1 } // Calculate a session name if none provided - if name == "" { - name = fmt.Sprintf("Consul lock for '%s' at '%s'", strings.Join(extra[1:], " "), prefix) + if c.name == "" { + c.name = fmt.Sprintf("Consul lock for '%s' at '%s'", strings.Join(extra[1:], " "), prefix) } // Calculate oneshot - oneshot := timeout > 0 + oneshot := c.timeout > 0 // Check the retry parameter - if monitorRetry < 0 { + if c.monitorRetry < 0 { c.UI.Error("Number for 'monitor-retry' must be >= 0") return 1 } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -167,10 +147,10 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int { } // Setup the lock or semaphore - if limit == 1 { - *lu, err = c.setupLock(client, prefix, name, oneshot, timeout, monitorRetry) + if c.limit == 1 { + *lu, err = c.setupLock(client, prefix, c.name, oneshot, c.timeout, c.monitorRetry) } else { - *lu, err = c.setupSemaphore(client, limit, prefix, name, oneshot, timeout, monitorRetry) + *lu, err = c.setupSemaphore(client, c.limit, prefix, c.name, oneshot, c.timeout, c.monitorRetry) } if err != nil { c.UI.Error(fmt.Sprintf("Lock setup failed: %s", err)) @@ -204,7 +184,7 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int { // Start the child process childErr = make(chan error, 1) go func() { - childErr <- c.startChild(f.Args()[1:], passStdin, shell) + childErr <- c.startChild(c.FlagSet.Args()[1:], c.passStdin, c.shell) }() // Monitor for shutdown, child termination, or lock loss @@ -257,7 +237,7 @@ RELEASE: // If we detected an error from the child process then we propagate // that. - if propagateChildCode { + if c.propagateChildCode { return childCode } @@ -449,6 +429,29 @@ func (c *LockCommand) killChild(childErr chan error) error { return nil } +func (c *LockCommand) Help() string { + c.initFlags() + return c.HelpCommand(` +Usage: consul lock [options] prefix child... + + Acquires a lock or semaphore at a given path, and invokes a child process + when successful. The child process can assume the lock is held while it + executes. If the lock is lost or communication is disrupted the child + process will be sent a SIGTERM signal and given time to gracefully exit. + After the grace period expires the process will be hard terminated. + + For Consul agents on Windows, the child process is always hard terminated + with a SIGKILL, since Windows has no POSIX compatible notion for SIGTERM. + + When -n=1, only a single lock holder or leader exists providing mutual + exclusion. Setting a higher value switches to a semaphore allowing multiple + holders to coordinate. + + The prefix provided must have write privileges. + +`) +} + func (c *LockCommand) Synopsis() string { return "Execute a command holding a lock" } diff --git a/command/maint.go b/command/maint.go index 831b2d2e07..6043e9a850 100644 --- a/command/maint.go +++ b/command/maint.go @@ -9,10 +9,29 @@ import ( // node or service maintenance mode. type MaintCommand struct { BaseCommand + + // flags + enable bool + disable bool + reason string + serviceID string +} + +func (c *MaintCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.BoolVar(&c.enable, "enable", false, + "Enable maintenance mode.") + c.FlagSet.BoolVar(&c.disable, "disable", false, + "Disable maintenance mode.") + c.FlagSet.StringVar(&c.reason, "reason", "", + "Text describing the maintenance reason.") + c.FlagSet.StringVar(&c.serviceID, "service", "", + "Control maintenance mode for a specific service ID.") } func (c *MaintCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul maint [options] Places a node or service into maintenance mode. During maintenance mode, @@ -36,44 +55,31 @@ Usage: consul maint [options] If no arguments are given, the agent's maintenance status will be shown. This will return blank if nothing is currently under maintenance. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *MaintCommand) Run(args []string) int { - var enable bool - var disable bool - var reason string - var serviceID string - - f := c.BaseCommand.NewFlagSet(c) - - f.BoolVar(&enable, "enable", false, "Enable maintenance mode.") - f.BoolVar(&disable, "disable", false, "Disable maintenance mode.") - f.StringVar(&reason, "reason", "", "Text describing the maintenance reason.") - f.StringVar(&serviceID, "service", "", "Control maintenance mode for a specific service ID.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } // Ensure we don't have conflicting args - if enable && disable { + if c.enable && c.disable { c.UI.Error("Only one of -enable or -disable may be provided") return 1 } - if !enable && reason != "" { + if !c.enable && c.reason != "" { c.UI.Error("Reason may only be provided with -enable") return 1 } - if !enable && !disable && serviceID != "" { + if !c.enable && !c.disable && c.serviceID != "" { c.UI.Error("Service requires either -enable or -disable") return 1 } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -85,7 +91,7 @@ func (c *MaintCommand) Run(args []string) int { return 1 } - if !enable && !disable { + if !c.enable && !c.disable { // List mode - list nodes/services in maintenance mode checks, err := a.Checks() if err != nil { @@ -110,10 +116,10 @@ func (c *MaintCommand) Run(args []string) int { return 0 } - if enable { + if c.enable { // Enable node maintenance - if serviceID == "" { - if err := a.EnableNodeMaintenance(reason); err != nil { + if c.serviceID == "" { + if err := a.EnableNodeMaintenance(c.reason); err != nil { c.UI.Error(fmt.Sprintf("Error enabling node maintenance: %s", err)) return 1 } @@ -122,17 +128,17 @@ func (c *MaintCommand) Run(args []string) int { } // Enable service maintenance - if err := a.EnableServiceMaintenance(serviceID, reason); err != nil { + if err := a.EnableServiceMaintenance(c.serviceID, c.reason); err != nil { c.UI.Error(fmt.Sprintf("Error enabling service maintenance: %s", err)) return 1 } - c.UI.Output(fmt.Sprintf("Service maintenance is now enabled for %q", serviceID)) + c.UI.Output(fmt.Sprintf("Service maintenance is now enabled for %q", c.serviceID)) return 0 } - if disable { + if c.disable { // Disable node maintenance - if serviceID == "" { + if c.serviceID == "" { if err := a.DisableNodeMaintenance(); err != nil { c.UI.Error(fmt.Sprintf("Error disabling node maintenance: %s", err)) return 1 @@ -142,11 +148,11 @@ func (c *MaintCommand) Run(args []string) int { } // Disable service maintenance - if err := a.DisableServiceMaintenance(serviceID); err != nil { + if err := a.DisableServiceMaintenance(c.serviceID); err != nil { c.UI.Error(fmt.Sprintf("Error disabling service maintenance: %s", err)) return 1 } - c.UI.Output(fmt.Sprintf("Service maintenance is now disabled for %q", serviceID)) + c.UI.Output(fmt.Sprintf("Service maintenance is now disabled for %q", c.serviceID)) return 0 } diff --git a/command/members.go b/command/members.go index ff38696384..ec313ed1d1 100644 --- a/command/members.go +++ b/command/members.go @@ -16,50 +16,53 @@ import ( // Consul agent what members are part of the cluster currently. type MembersCommand struct { BaseCommand + + // flags + detailed bool + wan bool + statusFilter string + segment string +} + +func (c *MembersCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.BoolVar(&c.detailed, "detailed", false, + "Provides detailed information about nodes.") + c.FlagSet.BoolVar(&c.wan, "wan", false, + "If the agent is in server mode, this can be used to return the other "+ + "peers in the WAN pool.") + c.FlagSet.StringVar(&c.statusFilter, "status", ".*", + "If provided, output is filtered to only nodes matching the regular "+ + "expression for status.") + c.FlagSet.StringVar(&c.segment, "segment", consulapi.AllSegments, + "(Enterprise-only) If provided, output is filtered to only nodes in"+ + "the given segment.") } func (c *MembersCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul members [options] Outputs the members of a running Consul agent. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *MembersCommand) Run(args []string) int { - var detailed bool - var wan bool - var statusFilter string - var segment string - - f := c.BaseCommand.NewFlagSet(c) - f.BoolVar(&detailed, "detailed", false, - "Provides detailed information about nodes.") - f.BoolVar(&wan, "wan", false, - "If the agent is in server mode, this can be used to return the other "+ - "peers in the WAN pool.") - f.StringVar(&statusFilter, "status", ".*", - "If provided, output is filtered to only nodes matching the regular "+ - "expression for status.") - f.StringVar(&segment, "segment", consulapi.AllSegments, - "(Enterprise-only) If provided, output is filtered to only nodes in"+ - "the given segment.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } // Compile the regexp - statusRe, err := regexp.Compile(statusFilter) + statusRe, err := regexp.Compile(c.statusFilter) if err != nil { c.UI.Error(fmt.Sprintf("Failed to compile status regexp: %v", err)) return 1 } - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -67,8 +70,8 @@ func (c *MembersCommand) Run(args []string) int { // Make the request. opts := consulapi.MembersOpts{ - Segment: segment, - WAN: wan, + Segment: c.segment, + WAN: c.wan, } members, err := client.Agent().MembersOpts(opts) if err != nil { @@ -83,7 +86,7 @@ func (c *MembersCommand) Run(args []string) int { if member.Tags["segment"] == "" { member.Tags["segment"] = "" } - if segment == consulapi.AllSegments && member.Tags["role"] == "consul" { + if c.segment == consulapi.AllSegments && member.Tags["role"] == "consul" { member.Tags["segment"] = "" } statusString := serf.MemberStatus(member.Status).String() @@ -105,7 +108,7 @@ func (c *MembersCommand) Run(args []string) int { // Generate the output var result []string - if detailed { + if c.detailed { result = c.detailedOutput(members) } else { result = c.standardOutput(members) diff --git a/command/monitor.go b/command/monitor.go index 63c87b1961..c8d4a9f2fe 100644 --- a/command/monitor.go +++ b/command/monitor.go @@ -2,7 +2,6 @@ package command import ( "fmt" - "strings" "sync" ) @@ -15,10 +14,20 @@ type MonitorCommand struct { lock sync.Mutex quitting bool + + // flags + logLevel string +} + +func (c *MonitorCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.StringVar(&c.logLevel, "log-level", "INFO", + "Log level of the agent.") } func (c *MonitorCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul monitor [options] Shows recent log messages of a Consul agent, and attaches to the agent, @@ -27,29 +36,23 @@ Usage: consul monitor [options] example your agent may only be logging at INFO level, but with the monitor you can see the DEBUG level logs. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *MonitorCommand) Run(args []string) int { - var logLevel string - - f := c.BaseCommand.NewFlagSet(c) - f.StringVar(&logLevel, "log-level", "INFO", "Log level of the agent.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 } eventDoneCh := make(chan struct{}) - logCh, err := client.Agent().Monitor(logLevel, eventDoneCh, nil) + logCh, err := client.Agent().Monitor(c.logLevel, eventDoneCh, nil) if err != nil { c.UI.Error(fmt.Sprintf("Error starting monitor: %s", err)) return 1 diff --git a/command/operator_autopilot.go b/command/operator_autopilot.go index 5823739f53..acd1feb509 100644 --- a/command/operator_autopilot.go +++ b/command/operator_autopilot.go @@ -1,8 +1,6 @@ package command import ( - "strings" - "github.com/mitchellh/cli" ) @@ -11,15 +9,14 @@ type OperatorAutopilotCommand struct { } func (c *OperatorAutopilotCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul operator autopilot [options] The Autopilot operator command is used to interact with Consul's Autopilot subsystem. The command can be used to view or modify the current configuration. -` - - return strings.TrimSpace(helpText) +`) } func (c *OperatorAutopilotCommand) Synopsis() string { diff --git a/command/operator_autopilot_get.go b/command/operator_autopilot_get.go index e4ef81568c..7a98beac27 100644 --- a/command/operator_autopilot_get.go +++ b/command/operator_autopilot_get.go @@ -3,7 +3,6 @@ package command import ( "flag" "fmt" - "strings" "github.com/hashicorp/consul/api" ) @@ -13,14 +12,13 @@ type OperatorAutopilotGetCommand struct { } func (c *OperatorAutopilotGetCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul operator autopilot get-config [options] Displays the current Autopilot configuration. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *OperatorAutopilotGetCommand) Synopsis() string { @@ -28,9 +26,8 @@ func (c *OperatorAutopilotGetCommand) Synopsis() string { } func (c *OperatorAutopilotGetCommand) Run(args []string) int { - c.BaseCommand.NewFlagSet(c) - - if err := c.BaseCommand.Parse(args); err != nil { + c.InitFlagSet() + if err := c.FlagSet.Parse(args); err != nil { if err == flag.ErrHelp { return 0 } @@ -39,7 +36,7 @@ func (c *OperatorAutopilotGetCommand) Run(args []string) int { } // Set up a client. - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error initializing client: %s", err)) return 1 @@ -47,7 +44,7 @@ func (c *OperatorAutopilotGetCommand) Run(args []string) int { // Fetch the current configuration. opts := &api.QueryOptions{ - AllowStale: c.BaseCommand.HTTPStale(), + AllowStale: c.HTTPStale(), } config, err := client.Operator().AutopilotGetConfiguration(opts) if err != nil { diff --git a/command/operator_autopilot_set.go b/command/operator_autopilot_set.go index a864ad5855..9fcecfe766 100644 --- a/command/operator_autopilot_set.go +++ b/command/operator_autopilot_set.go @@ -3,7 +3,6 @@ package command import ( "flag" "fmt" - "strings" "time" "github.com/hashicorp/consul/api" @@ -12,17 +11,53 @@ import ( type OperatorAutopilotSetCommand struct { BaseCommand + + // flags + cleanupDeadServers configutil.BoolValue + maxTrailingLogs configutil.UintValue + lastContactThreshold configutil.DurationValue + serverStabilizationTime configutil.DurationValue + redundancyZoneTag configutil.StringValue + disableUpgradeMigration configutil.BoolValue + upgradeVersionTag configutil.StringValue +} + +func (c *OperatorAutopilotSetCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.Var(&c.cleanupDeadServers, "cleanup-dead-servers", + "Controls whether Consul will automatically remove dead servers "+ + "when new ones are successfully added. Must be one of `true|false`.") + c.FlagSet.Var(&c.maxTrailingLogs, "max-trailing-logs", + "Controls the maximum number of log entries that a server can trail the "+ + "leader by before being considered unhealthy.") + c.FlagSet.Var(&c.lastContactThreshold, "last-contact-threshold", + "Controls the maximum amount of time a server can go without contact "+ + "from the leader before being considered unhealthy. Must be a duration value "+ + "such as `200ms`.") + c.FlagSet.Var(&c.serverStabilizationTime, "server-stabilization-time", + "Controls the minimum amount of time a server must be stable in the "+ + "'healthy' state before being added to the cluster. Only takes effect if all "+ + "servers are running Raft protocol version 3 or higher. Must be a duration "+ + "value such as `10s`.") + c.FlagSet.Var(&c.redundancyZoneTag, "redundancy-zone-tag", + "(Enterprise-only) Controls the node_meta tag name used for separating servers into "+ + "different redundancy zones.") + c.FlagSet.Var(&c.disableUpgradeMigration, "disable-upgrade-migration", + "(Enterprise-only) Controls whether Consul will avoid promoting new servers until "+ + "it can perform a migration. Must be one of `true|false`.") + c.FlagSet.Var(&c.upgradeVersionTag, "upgrade-version-tag", + "(Enterprise-only) The node_meta tag to use for version info when performing upgrade "+ + "migrations. If left blank, the Consul version will be used.") } func (c *OperatorAutopilotSetCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul operator autopilot set-config [options] Modifies the current Autopilot configuration. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *OperatorAutopilotSetCommand) Synopsis() string { @@ -30,42 +65,8 @@ func (c *OperatorAutopilotSetCommand) Synopsis() string { } func (c *OperatorAutopilotSetCommand) Run(args []string) int { - var cleanupDeadServers configutil.BoolValue - var maxTrailingLogs configutil.UintValue - var lastContactThreshold configutil.DurationValue - var serverStabilizationTime configutil.DurationValue - var redundancyZoneTag configutil.StringValue - var disableUpgradeMigration configutil.BoolValue - var upgradeVersionTag configutil.StringValue - - f := c.BaseCommand.NewFlagSet(c) - - f.Var(&cleanupDeadServers, "cleanup-dead-servers", - "Controls whether Consul will automatically remove dead servers "+ - "when new ones are successfully added. Must be one of `true|false`.") - f.Var(&maxTrailingLogs, "max-trailing-logs", - "Controls the maximum number of log entries that a server can trail the "+ - "leader by before being considered unhealthy.") - f.Var(&lastContactThreshold, "last-contact-threshold", - "Controls the maximum amount of time a server can go without contact "+ - "from the leader before being considered unhealthy. Must be a duration value "+ - "such as `200ms`.") - f.Var(&serverStabilizationTime, "server-stabilization-time", - "Controls the minimum amount of time a server must be stable in the "+ - "'healthy' state before being added to the cluster. Only takes effect if all "+ - "servers are running Raft protocol version 3 or higher. Must be a duration "+ - "value such as `10s`.") - f.Var(&redundancyZoneTag, "redundancy-zone-tag", - "(Enterprise-only) Controls the node_meta tag name used for separating servers into "+ - "different redundancy zones.") - f.Var(&disableUpgradeMigration, "disable-upgrade-migration", - "(Enterprise-only) Controls whether Consul will avoid promoting new servers until "+ - "it can perform a migration. Must be one of `true|false`.") - f.Var(&upgradeVersionTag, "upgrade-version-tag", - "(Enterprise-only) The node_meta tag to use for version info when performing upgrade "+ - "migrations. If left blank, the Consul version will be used.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { if err == flag.ErrHelp { return 0 } @@ -74,7 +75,7 @@ func (c *OperatorAutopilotSetCommand) Run(args []string) int { } // Set up a client. - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error initializing client: %s", err)) return 1 @@ -89,21 +90,21 @@ func (c *OperatorAutopilotSetCommand) Run(args []string) int { } // Update the config values based on the set flags. - cleanupDeadServers.Merge(&conf.CleanupDeadServers) - redundancyZoneTag.Merge(&conf.RedundancyZoneTag) - disableUpgradeMigration.Merge(&conf.DisableUpgradeMigration) - upgradeVersionTag.Merge(&conf.UpgradeVersionTag) + c.cleanupDeadServers.Merge(&conf.CleanupDeadServers) + c.redundancyZoneTag.Merge(&conf.RedundancyZoneTag) + c.disableUpgradeMigration.Merge(&conf.DisableUpgradeMigration) + c.upgradeVersionTag.Merge(&conf.UpgradeVersionTag) trailing := uint(conf.MaxTrailingLogs) - maxTrailingLogs.Merge(&trailing) + c.maxTrailingLogs.Merge(&trailing) conf.MaxTrailingLogs = uint64(trailing) last := time.Duration(*conf.LastContactThreshold) - lastContactThreshold.Merge(&last) + c.lastContactThreshold.Merge(&last) conf.LastContactThreshold = api.NewReadableDuration(last) stablization := time.Duration(*conf.ServerStabilizationTime) - serverStabilizationTime.Merge(&stablization) + c.serverStabilizationTime.Merge(&stablization) conf.ServerStabilizationTime = api.NewReadableDuration(stablization) // Check-and-set the new configuration. diff --git a/command/operator_raft_list.go b/command/operator_raft_list.go index 3b9c80c9dd..1325b72c9f 100644 --- a/command/operator_raft_list.go +++ b/command/operator_raft_list.go @@ -3,7 +3,6 @@ package command import ( "flag" "fmt" - "strings" "github.com/hashicorp/consul/api" "github.com/ryanuber/columnize" @@ -14,14 +13,13 @@ type OperatorRaftListCommand struct { } func (c *OperatorRaftListCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul operator raft list-peers [options] Displays the current Raft peer configuration. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *OperatorRaftListCommand) Synopsis() string { @@ -29,9 +27,8 @@ func (c *OperatorRaftListCommand) Synopsis() string { } func (c *OperatorRaftListCommand) Run(args []string) int { - c.BaseCommand.NewFlagSet(c) - - if err := c.BaseCommand.Parse(args); err != nil { + c.InitFlagSet() + if err := c.FlagSet.Parse(args); err != nil { if err == flag.ErrHelp { return 0 } @@ -40,14 +37,14 @@ func (c *OperatorRaftListCommand) Run(args []string) int { } // Set up a client. - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error initializing client: %s", err)) return 1 } // Fetch the current configuration. - result, err := raftListPeers(client, c.BaseCommand.HTTPStale()) + result, err := raftListPeers(client, c.HTTPStale()) if err != nil { c.UI.Error(fmt.Sprintf("Error getting peers: %v", err)) return 1 diff --git a/command/operator_raft_remove.go b/command/operator_raft_remove.go index d1dca8e213..7ab80336f1 100644 --- a/command/operator_raft_remove.go +++ b/command/operator_raft_remove.go @@ -3,17 +3,29 @@ package command import ( "flag" "fmt" - "strings" "github.com/hashicorp/consul/api" ) type OperatorRaftRemoveCommand struct { BaseCommand + + // flags + address string + id string +} + +func (c *OperatorRaftRemoveCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.StringVar(&c.address, "address", "", + "The address to remove from the Raft configuration.") + c.FlagSet.StringVar(&c.id, "id", "", + "The ID to remove from the Raft configuration.") } func (c *OperatorRaftRemoveCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul operator raft remove-peer [options] Remove the Consul server with given -address from the Raft configuration. @@ -25,9 +37,7 @@ quorum. If the server still shows in the output of the "consul members" command, it is preferable to clean up by simply running "consul force-leave" instead of this command. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *OperatorRaftRemoveCommand) Synopsis() string { @@ -35,15 +45,8 @@ func (c *OperatorRaftRemoveCommand) Synopsis() string { } func (c *OperatorRaftRemoveCommand) Run(args []string) int { - f := c.BaseCommand.NewFlagSet(c) - - var address, id string - f.StringVar(&address, "address", "", - "The address to remove from the Raft configuration.") - f.StringVar(&id, "id", "", - "The ID to remove from the Raft configuration.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { if err == flag.ErrHelp { return 0 } @@ -52,21 +55,21 @@ func (c *OperatorRaftRemoveCommand) Run(args []string) int { } // Set up a client. - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error initializing client: %s", err)) return 1 } // Fetch the current configuration. - if err := raftRemovePeers(address, id, client.Operator()); err != nil { + if err := raftRemovePeers(c.address, c.id, client.Operator()); err != nil { c.UI.Error(fmt.Sprintf("Error removing peer: %v", err)) return 1 } - if address != "" { - c.UI.Output(fmt.Sprintf("Removed peer with address %q", address)) + if c.address != "" { + c.UI.Output(fmt.Sprintf("Removed peer with address %q", c.address)) } else { - c.UI.Output(fmt.Sprintf("Removed peer with id %q", id)) + c.UI.Output(fmt.Sprintf("Removed peer with id %q", c.id)) } return 0 diff --git a/command/reload.go b/command/reload.go index 1a37bee109..88cccbe045 100644 --- a/command/reload.go +++ b/command/reload.go @@ -2,7 +2,6 @@ package command import ( "fmt" - "strings" ) // ReloadCommand is a Command implementation that instructs @@ -12,25 +11,23 @@ type ReloadCommand struct { } func (c *ReloadCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul reload Causes the agent to reload configurations. This can be used instead of sending the SIGHUP signal to the agent. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *ReloadCommand) Run(args []string) int { - c.BaseCommand.NewFlagSet(c) - - if err := c.BaseCommand.Parse(args); err != nil { + c.InitFlagSet() + if err := c.FlagSet.Parse(args); err != nil { return 1 } - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 diff --git a/command/rtt.go b/command/rtt.go index 7fdf337c64..2974df479e 100644 --- a/command/rtt.go +++ b/command/rtt.go @@ -12,10 +12,20 @@ import ( // estimated round trip time between nodes using network coordinates. type RTTCommand struct { BaseCommand + + // flags + wan bool +} + +func (c *RTTCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.BoolVar(&c.wan, "wan", false, + "Use WAN coordinates instead of LAN coordinates.") } func (c *RTTCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul rtt [options] node1 [node2] Estimates the round trip time between two nodes using Consul's network @@ -34,24 +44,17 @@ Usage: consul rtt [options] node1 [node2] because they are maintained by independent Serf gossip areas, so they are not compatible. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *RTTCommand) Run(args []string) int { - var wan bool - - f := c.BaseCommand.NewFlagSet(c) - - f.BoolVar(&wan, "wan", false, "Use WAN coordinates instead of LAN coordinates.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } // They must provide at least one node. - nodes := f.Args() + nodes := c.FlagSet.Args() if len(nodes) < 1 || len(nodes) > 2 { c.UI.Error("One or two node names must be specified") c.UI.Error("") @@ -60,7 +63,7 @@ func (c *RTTCommand) Run(args []string) int { } // Create and test the HTTP client. - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -69,7 +72,7 @@ func (c *RTTCommand) Run(args []string) int { var source string var coord1, coord2 *coordinate.Coordinate - if wan { + if c.wan { source = "WAN" // Default the second node to the agent if none was given. diff --git a/command/rtt_test.go b/command/rtt_test.go index b2d978199c..4ab7e75be1 100644 --- a/command/rtt_test.go +++ b/command/rtt_test.go @@ -29,26 +29,24 @@ func TestRTTCommand_Implements(t *testing.T) { func TestRTTCommand_Run_BadArgs(t *testing.T) { t.Parallel() - _, c := testRTTCommand(t) - - if code := c.Run([]string{}); code != 1 { - t.Fatalf("expected return code 1, got %d", code) + tests := []struct { + args []string + }{ + {args: []string{}}, + {args: []string{"node1", "node2", "node3"}}, + {args: []string{"-wan", "node1", "node2"}}, + {args: []string{"-wan", "node1.dc1", "node2"}}, + {args: []string{"-wan", "node1", "node2.dc1"}}, } - if code := c.Run([]string{"node1", "node2", "node3"}); code != 1 { - t.Fatalf("expected return code 1, got %d", code) - } - - if code := c.Run([]string{"-wan", "node1", "node2"}); code != 1 { - t.Fatalf("expected return code 1, got %d", code) - } - - if code := c.Run([]string{"-wan", "node1.dc1", "node2"}); code != 1 { - t.Fatalf("expected return code 1, got %d", code) - } - - if code := c.Run([]string{"-wan", "node1", "node2.dc1"}); code != 1 { - t.Fatalf("expected return code 1, got %d", code) + for _, tt := range tests { + t.Run(strings.Join(tt.args, " "), func(t *testing.T) { + t.Parallel() + _, c := testRTTCommand(t) + if code := c.Run(tt.args); code != 1 { + t.Fatalf("expected return code 1, got %d", code) + } + }) } } diff --git a/command/snapshot_command.go b/command/snapshot_command.go index ac57f6787e..59520b743b 100644 --- a/command/snapshot_command.go +++ b/command/snapshot_command.go @@ -1,8 +1,6 @@ package command import ( - "strings" - "github.com/mitchellh/cli" ) @@ -17,8 +15,7 @@ func (c *SnapshotCommand) Run(args []string) int { } func (c *SnapshotCommand) Help() string { - helpText := ` -Usage: consul snapshot [options] [args] + return `Usage: consul snapshot [options] [args] This command has subcommands for saving, restoring, and inspecting the state of the Consul servers for disaster recovery. These are atomic, point-in-time @@ -47,7 +44,6 @@ Usage: consul snapshot [options] [args] For more examples, ask for subcommand help or view the documentation. ` - return strings.TrimSpace(helpText) } func (c *SnapshotCommand) Synopsis() string { diff --git a/command/snapshot_inspect.go b/command/snapshot_inspect.go index 4ffb99650a..a26041f558 100644 --- a/command/snapshot_inspect.go +++ b/command/snapshot_inspect.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "os" - "strings" "text/tabwriter" "github.com/hashicorp/consul/snapshot" @@ -17,7 +16,8 @@ type SnapshotInspectCommand struct { } func (c *SnapshotInspectCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul snapshot inspect [options] FILE Displays information about a snapshot file on disk. @@ -27,21 +27,18 @@ Usage: consul snapshot inspect [options] FILE $ consul snapshot inspect backup.snap For a full list of options and examples, please see the Consul documentation. -` - - return strings.TrimSpace(helpText) +`) } func (c *SnapshotInspectCommand) Run(args []string) int { - flagSet := c.BaseCommand.NewFlagSet(c) - - if err := c.BaseCommand.Parse(args); err != nil { + c.InitFlagSet() + if err := c.FlagSet.Parse(args); err != nil { return 1 } var file string - args = flagSet.Args() + args = c.FlagSet.Args() switch len(args) { case 0: c.UI.Error("Missing FILE argument") diff --git a/command/snapshot_restore.go b/command/snapshot_restore.go index c3ba6947a9..59c522a889 100644 --- a/command/snapshot_restore.go +++ b/command/snapshot_restore.go @@ -3,7 +3,6 @@ package command import ( "fmt" "os" - "strings" ) // SnapshotRestoreCommand is a Command implementation that is used to restore @@ -13,7 +12,8 @@ type SnapshotRestoreCommand struct { } func (c *SnapshotRestoreCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul snapshot restore [options] FILE Restores an atomic, point-in-time snapshot of the state of the Consul servers @@ -34,21 +34,18 @@ Usage: consul snapshot restore [options] FILE For a full list of options and examples, please see the Consul documentation. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *SnapshotRestoreCommand) Run(args []string) int { - flagSet := c.BaseCommand.NewFlagSet(c) - - if err := c.BaseCommand.Parse(args); err != nil { + c.InitFlagSet() + if err := c.FlagSet.Parse(args); err != nil { return 1 } var file string - args = flagSet.Args() + args = c.FlagSet.Args() switch len(args) { case 0: c.UI.Error("Missing FILE argument") @@ -61,7 +58,7 @@ func (c *SnapshotRestoreCommand) Run(args []string) int { } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 diff --git a/command/snapshot_save.go b/command/snapshot_save.go index 74c4171f46..a628e68dbe 100644 --- a/command/snapshot_save.go +++ b/command/snapshot_save.go @@ -4,7 +4,6 @@ import ( "fmt" "io" "os" - "strings" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/snapshot" @@ -17,7 +16,8 @@ type SnapshotSaveCommand struct { } func (c *SnapshotSaveCommand) Help() string { - helpText := ` + c.InitFlagSet() + return c.HelpCommand(` Usage: consul snapshot save [options] FILE Retrieves an atomic, point-in-time snapshot of the state of the Consul servers @@ -38,21 +38,18 @@ Usage: consul snapshot save [options] FILE For a full list of options and examples, please see the Consul documentation. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *SnapshotSaveCommand) Run(args []string) int { - flagSet := c.BaseCommand.NewFlagSet(c) - - if err := c.BaseCommand.Parse(args); err != nil { + c.InitFlagSet() + if err := c.FlagSet.Parse(args); err != nil { return 1 } var file string - args = flagSet.Args() + args = c.FlagSet.Args() switch len(args) { case 0: c.UI.Error("Missing FILE argument") @@ -65,7 +62,7 @@ func (c *SnapshotSaveCommand) Run(args []string) int { } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -73,7 +70,7 @@ func (c *SnapshotSaveCommand) Run(args []string) int { // Take the snapshot. snap, qm, err := client.Snapshot().Save(&api.QueryOptions{ - AllowStale: c.BaseCommand.HTTPStale(), + AllowStale: c.HTTPStale(), }) if err != nil { c.UI.Error(fmt.Sprintf("Error saving snapshot: %s", err)) diff --git a/command/snapshot_save_test.go b/command/snapshot_save_test.go index addc83c167..3c905d0f1f 100644 --- a/command/snapshot_save_test.go +++ b/command/snapshot_save_test.go @@ -33,7 +33,6 @@ func TestSnapshotSaveCommand_noTabs(t *testing.T) { func TestSnapshotSaveCommand_Validation(t *testing.T) { t.Parallel() - ui, c := testSnapshotSaveCommand(t) cases := map[string]struct { args []string @@ -50,6 +49,8 @@ func TestSnapshotSaveCommand_Validation(t *testing.T) { } for name, tc := range cases { + ui, c := testSnapshotSaveCommand(t) + // Ensure our buffer is always clear if ui.ErrorWriter != nil { ui.ErrorWriter.Reset() diff --git a/command/validate.go b/command/validate.go index 707e5d501d..971627013c 100644 --- a/command/validate.go +++ b/command/validate.go @@ -2,7 +2,6 @@ package command import ( "fmt" - "strings" "github.com/hashicorp/consul/agent/config" ) @@ -11,10 +10,20 @@ import ( // verify config files type ValidateCommand struct { BaseCommand + + // flags + quiet bool +} + +func (c *ValidateCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.BoolVar(&c.quiet, "quiet", false, + "When given, a successful run will produce no output.") } func (c *ValidateCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul validate [options] FILE_OR_DIRECTORY... Performs a basic sanity test on Consul configuration files. For each file @@ -25,24 +34,17 @@ Usage: consul validate [options] FILE_OR_DIRECTORY... Returns 0 if the configuration is valid, or 1 if there are problems. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *ValidateCommand) Run(args []string) int { - var quiet bool - - f := c.BaseCommand.NewFlagSet(c) - f.BoolVar(&quiet, "quiet", false, - "When given, a successful run will produce no output.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { c.UI.Error(err.Error()) return 1 } - configFiles := f.Args() + configFiles := c.FlagSet.Args() if len(configFiles) < 1 { c.UI.Error("Must specify at least one config file or directory") return 1 @@ -58,7 +60,7 @@ func (c *ValidateCommand) Run(args []string) int { return 1 } - if !quiet { + if !c.quiet { c.UI.Output("Configuration is valid!") } return 0 diff --git a/command/watch.go b/command/watch.go index c5f711954a..8fa8280db9 100644 --- a/command/watch.go +++ b/command/watch.go @@ -18,10 +18,48 @@ import ( type WatchCommand struct { BaseCommand ShutdownCh <-chan struct{} + + // flags + watchType string + key string + prefix string + service string + tag string + passingOnly string + state string + name string + shell bool +} + +func (c *WatchCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.StringVar(&c.watchType, "type", "", + "Specifies the watch type. One of key, keyprefix, services, nodes, "+ + "service, checks, or event.") + c.FlagSet.StringVar(&c.key, "key", "", + "Specifies the key to watch. Only for 'key' type.") + c.FlagSet.StringVar(&c.prefix, "prefix", "", + "Specifies the key prefix to watch. Only for 'keyprefix' type.") + c.FlagSet.StringVar(&c.service, "service", "", + "Specifies the service to watch. Required for 'service' type, "+ + "optional for 'checks' type.") + c.FlagSet.StringVar(&c.tag, "tag", "", + "Specifies the service tag to filter on. Optional for 'service' type.") + c.FlagSet.StringVar(&c.passingOnly, "passingonly", "", + "Specifies if only hosts passing all checks are displayed. "+ + "Optional for 'service' type, must be one of `[true|false]`. Defaults false.") + c.FlagSet.BoolVar(&c.shell, "shell", true, + "Use a shell to run the command (can set a custom shell via the SHELL "+ + "environment variable).") + c.FlagSet.StringVar(&c.state, "state", "", + "Specifies the states to watch. Optional for 'checks' type.") + c.FlagSet.StringVar(&c.name, "name", "", + "Specifies an event name to watch. Only for 'event' type.") } func (c *WatchCommand) Help() string { - helpText := ` + c.initFlags() + return c.HelpCommand(` Usage: consul watch [options] [child...] Watches for changes in a given data view from Consul. If a child process @@ -31,45 +69,17 @@ Usage: consul watch [options] [child...] Providing the watch type is required, and other parameters may be required or supported depending on the watch type. -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) +`) } func (c *WatchCommand) Run(args []string) int { - var watchType, key, prefix, service, tag, passingOnly, state, name string - var shell bool - - f := c.BaseCommand.NewFlagSet(c) - f.StringVar(&watchType, "type", "", - "Specifies the watch type. One of key, keyprefix, services, nodes, "+ - "service, checks, or event.") - f.StringVar(&key, "key", "", - "Specifies the key to watch. Only for 'key' type.") - f.StringVar(&prefix, "prefix", "", - "Specifies the key prefix to watch. Only for 'keyprefix' type.") - f.StringVar(&service, "service", "", - "Specifies the service to watch. Required for 'service' type, "+ - "optional for 'checks' type.") - f.StringVar(&tag, "tag", "", - "Specifies the service tag to filter on. Optional for 'service' type.") - f.StringVar(&passingOnly, "passingonly", "", - "Specifies if only hosts passing all checks are displayed. "+ - "Optional for 'service' type, must be one of `[true|false]`. Defaults false.") - f.BoolVar(&shell, "shell", true, - "Use a shell to run the command (can set a custom shell via the SHELL "+ - "environment variable).") - f.StringVar(&state, "state", "", - "Specifies the states to watch. Optional for 'checks' type.") - f.StringVar(&name, "name", "", - "Specifies an event name to watch. Only for 'event' type.") - - if err := c.BaseCommand.Parse(args); err != nil { + c.initFlags() + if err := c.FlagSet.Parse(args); err != nil { return 1 } // Check for a type - if watchType == "" { + if c.watchType == "" { c.UI.Error("Watch type must be specified") c.UI.Error("") c.UI.Error(c.Help()) @@ -78,38 +88,38 @@ func (c *WatchCommand) Run(args []string) int { // Compile the watch parameters params := make(map[string]interface{}) - if watchType != "" { - params["type"] = watchType + if c.watchType != "" { + params["type"] = c.watchType } - if c.BaseCommand.HTTPDatacenter() != "" { - params["datacenter"] = c.BaseCommand.HTTPDatacenter() + if c.HTTPDatacenter() != "" { + params["datacenter"] = c.HTTPDatacenter() } - if c.BaseCommand.HTTPToken() != "" { - params["token"] = c.BaseCommand.HTTPToken() + if c.HTTPToken() != "" { + params["token"] = c.HTTPToken() } - if key != "" { - params["key"] = key + if c.key != "" { + params["key"] = c.key } - if prefix != "" { - params["prefix"] = prefix + if c.prefix != "" { + params["prefix"] = c.prefix } - if service != "" { - params["service"] = service + if c.service != "" { + params["service"] = c.service } - if tag != "" { - params["tag"] = tag + if c.tag != "" { + params["tag"] = c.tag } - if c.BaseCommand.HTTPStale() { - params["stale"] = c.BaseCommand.HTTPStale() + if c.HTTPStale() { + params["stale"] = c.HTTPStale() } - if state != "" { - params["state"] = state + if c.state != "" { + params["state"] = c.state } - if name != "" { - params["name"] = name + if c.name != "" { + params["name"] = c.name } - if passingOnly != "" { - b, err := strconv.ParseBool(passingOnly) + if c.passingOnly != "" { + b, err := strconv.ParseBool(c.passingOnly) if err != nil { c.UI.Error(fmt.Sprintf("Failed to parse passingonly flag: %s", err)) return 1 @@ -125,7 +135,7 @@ func (c *WatchCommand) Run(args []string) int { } // Create and test the HTTP client - client, err := c.BaseCommand.HTTPClient() + client, err := c.HTTPClient() if err != nil { c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -142,7 +152,7 @@ func (c *WatchCommand) Run(args []string) int { // 0: false // 1: true errExit := 0 - if len(f.Args()) == 0 { + if len(c.FlagSet.Args()) == 0 { wp.Handler = func(idx uint64, data interface{}) { defer wp.Stop() buf, err := json.MarshalIndent(data, "", " ") @@ -164,10 +174,10 @@ func (c *WatchCommand) Run(args []string) int { var buf bytes.Buffer var err error var cmd *exec.Cmd - if !shell { - cmd, err = agent.ExecSubprocess(f.Args()) + if !c.shell { + cmd, err = agent.ExecSubprocess(c.FlagSet.Args()) } else { - cmd, err = agent.ExecScript(strings.Join(f.Args(), " ")) + cmd, err = agent.ExecScript(strings.Join(c.FlagSet.Args(), " ")) } if err != nil { c.UI.Error(fmt.Sprintf("Error executing handler: %s", err)) @@ -215,7 +225,7 @@ func (c *WatchCommand) Run(args []string) int { }() // Run the watch - if err := wp.Run(c.BaseCommand.HTTPAddr()); err != nil { + if err := wp.Run(c.HTTPAddr()); err != nil { c.UI.Error(fmt.Sprintf("Error querying Consul agent: %s", err)) return 1 }