diff --git a/command/agent.go b/command/agent.go index 18c7ceb379d8..265cbc87f649 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 { + cmd.InitFlagSet() + var flags config.Flags - fs := cmd.BaseCommand.NewFlagSet(cmd) - config.AddFlags(fs, &flags) + config.AddFlags(cmd.FlagSet, &flags) - if err := cmd.BaseCommand.Parse(cmd.args); err != nil { + 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 ee28e505ee5a..441318e713c3 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 -} - -// Parse is used to parse the underlying flag set. -func (c *BaseCommand) Parse(args []string) error { - return c.flagSet.Parse(args) + c.FlagSet.SetOutput(errW) } -// 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 30f112460b22..b7edec66c11e 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 caf01a0d02a4..f962cccda2ca 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 0cfb1a745e2c..c338b8ddd583 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 427b6556688a..fa2e73ef3881 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 0322a6c6c9d6..b735839568a5 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 13d5749022a5..1225ad023edb 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 -} - -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) + // flags + name string + node string + service string + tag string } -func (c *EventCommand) Run(args []string) int { - var name, node, service, tag string - - f := c.BaseCommand.NewFlagSet(c) - f.StringVar(&name, "name", "", +func (c *EventCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.StringVar(&c.name, "name", "", "Name of the event.") - f.StringVar(&node, "node", "", + c.FlagSet.StringVar(&c.node, "node", "", "Regular expression to filter on node names.") - f.StringVar(&service, "service", "", + c.FlagSet.StringVar(&c.service, "service", "", "Regular expression to filter on service instances.") - f.StringVar(&tag, "tag", "", + c.FlagSet.StringVar(&c.tag, "tag", "", "Regular expression to filter on service tags. Must be used with -service.") +} - if err := c.BaseCommand.Parse(args); err != nil { +func (c *EventCommand) Run(args []string) int { + 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 ec04027bdcc2..9d95028a2f6a 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 42fbcad8d271..1e9a45b510af 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 8ba6f44c2a6d..e94569a88393 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 df042791c436..ad03fca3c28a 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 -} -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() + // flags + wan bool +} - 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 6506d5b1e497..b01e460f976e 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 b824ceea7f5b..33f98c566a75 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 -} - -func (c *KeyringCommand) Run(args []string) int { - var installKey, useKey, removeKey string - var listKeys bool - var relay int - f := c.BaseCommand.NewFlagSet(c) + // flags + installKey string + useKey string + removeKey string + listKeys bool + relay int +} - 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 f456dc0f579f..82bc2676daff 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 ed0b05e8936e..9f037fc2e263 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 bd7531e3bb2b..e3ef23b41193 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 ef83aeb69cf0..e98aa5af297b 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 844b69efebdd..27a0c59943c3 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 45db2e344d7f..a229f83ea0e4 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 e747d188ad88..cd9a24fcef0b 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 8b12f5602300..9a4917699c76 100644 --- a/command/lock.go +++ b/command/lock.go @@ -43,91 +43,71 @@ type LockCommand struct { child *os.Process childLock sync.Mutex verbose bool -} - -func (c *LockCommand) Help() string { - helpText := ` -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. - -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) + // flags + limit int + monitorRetry int + name string + passStdin bool + propagateChildCode bool + shell bool + timeout time.Duration } -func (c *LockCommand) Run(args []string) int { - var lu *LockUnlock - return c.run(args, &lu) -} - -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, +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.") - f.IntVar(&limit, "n", 1, + 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.") - f.IntVar(&monitorRetry, "monitor-retry", defaultMonitorRetry, + 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.") - f.StringVar(&name, "name", "", + 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.") - f.BoolVar(&passStdin, "pass-stdin", false, + c.FlagSet.BoolVar(&c.passStdin, "pass-stdin", false, "Pass stdin to the child process.") - f.BoolVar(&shell, "shell", true, + c.FlagSet.BoolVar(&c.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, + 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.") - f.BoolVar(&c.verbose, "verbose", false, + c.FlagSet.BoolVar(&c.verbose, "verbose", false, "Enable verbose (debugging) output.") // Deprecations - f.DurationVar(&timeout, "try", 0, + c.FlagSet.DurationVar(&c.timeout, "try", 0, "DEPRECATED. Use -timeout instead.") +} - if err := c.BaseCommand.Parse(args); err != nil { +func (c *LockCommand) Run(args []string) int { + var lu *LockUnlock + return c.run(args, &lu) +} + +func (c *LockCommand) run(args []string, lu **LockUnlock) int { + 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 831b2d2e07da..6043e9a85078 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 ff38696384cf..ec313ed1d189 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 -} -func (c *MembersCommand) Help() string { - helpText := ` -Usage: consul members [options] - - Outputs the members of a running Consul agent. - -` + c.BaseCommand.Help() - - return strings.TrimSpace(helpText) + // flags + detailed bool + wan bool + statusFilter string + segment string } -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, +func (c *MembersCommand) initFlags() { + c.InitFlagSet() + c.FlagSet.BoolVar(&c.detailed, "detailed", false, "Provides detailed information about nodes.") - f.BoolVar(&wan, "wan", false, + 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.") - f.StringVar(&statusFilter, "status", ".*", + c.FlagSet.StringVar(&c.statusFilter, "status", ".*", "If provided, output is filtered to only nodes matching the regular "+ "expression for status.") - f.StringVar(&segment, "segment", consulapi.AllSegments, + c.FlagSet.StringVar(&c.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 { +func (c *MembersCommand) Help() string { + c.initFlags() + return c.HelpCommand(` +Usage: consul members [options] + + Outputs the members of a running Consul agent. + +`) +} + +func (c *MembersCommand) Run(args []string) int { + 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 63c87b1961b7..c8d4a9f2fe0f 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 5823739f53e2..acd1feb50987 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 e4ef81568c12..7a98beac27dc 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 a864ad585591..9fcecfe766a7 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,60 +11,62 @@ import ( type OperatorAutopilotSetCommand struct { BaseCommand -} - -func (c *OperatorAutopilotSetCommand) Help() string { - helpText := ` -Usage: consul operator autopilot set-config [options] - -Modifies the current Autopilot configuration. - -` + c.BaseCommand.Help() - return strings.TrimSpace(helpText) + // 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) Synopsis() string { - return "Modify the current Autopilot configuration" -} - -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", +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`.") - f.Var(&maxTrailingLogs, "max-trailing-logs", + 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.") - f.Var(&lastContactThreshold, "last-contact-threshold", + 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`.") - f.Var(&serverStabilizationTime, "server-stabilization-time", + 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`.") - f.Var(&redundancyZoneTag, "redundancy-zone-tag", + c.FlagSet.Var(&c.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", + 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`.") - f.Var(&upgradeVersionTag, "upgrade-version-tag", + 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 { + c.initFlags() + return c.HelpCommand(` +Usage: consul operator autopilot set-config [options] - if err := c.BaseCommand.Parse(args); err != nil { +Modifies the current Autopilot configuration. + +`) +} + +func (c *OperatorAutopilotSetCommand) Synopsis() string { + return "Modify the current Autopilot configuration" +} + +func (c *OperatorAutopilotSetCommand) Run(args []string) int { + 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 3b9c80c9dd0e..1325b72c9f37 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 d1dca8e2130e..7ab80336f1f5 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 1a37bee10988..88cccbe045ff 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 7fdf337c644f..2974df479e0d 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 b2d978199cc5..4ab7e75be1f2 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) - } - - 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) + 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{"-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 ac57f6787e0c..59520b743bee 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 4ffb99650acd..a26041f558dd 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 c3ba6947a933..59c522a88941 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 74c4171f4628..a628e68dbe34 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 addc83c1674c..3c905d0f1f5e 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 707e5d501d0a..971627013c2b 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 c5f711954aec..8fa8280db903 100644 --- a/command/watch.go +++ b/command/watch.go @@ -18,58 +18,68 @@ import ( type WatchCommand struct { BaseCommand ShutdownCh <-chan struct{} -} - -func (c *WatchCommand) Help() string { - helpText := ` -Usage: consul watch [options] [child...] - - Watches for changes in a given data view from Consul. If a child process - is specified, it will be invoked with the latest results on changes. Otherwise, - the latest values are dumped to stdout and the watch terminates. - - 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) + // flags + watchType string + key string + prefix string + service string + tag string + passingOnly string + state string + name string + shell bool } -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", "", +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.") - f.StringVar(&key, "key", "", + c.FlagSet.StringVar(&c.key, "key", "", "Specifies the key to watch. Only for 'key' type.") - f.StringVar(&prefix, "prefix", "", + c.FlagSet.StringVar(&c.prefix, "prefix", "", "Specifies the key prefix to watch. Only for 'keyprefix' type.") - f.StringVar(&service, "service", "", + c.FlagSet.StringVar(&c.service, "service", "", "Specifies the service to watch. Required for 'service' type, "+ "optional for 'checks' type.") - f.StringVar(&tag, "tag", "", + c.FlagSet.StringVar(&c.tag, "tag", "", "Specifies the service tag to filter on. Optional for 'service' type.") - f.StringVar(&passingOnly, "passingonly", "", + 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.") - f.BoolVar(&shell, "shell", true, + c.FlagSet.BoolVar(&c.shell, "shell", true, "Use a shell to run the command (can set a custom shell via the SHELL "+ "environment variable).") - f.StringVar(&state, "state", "", + c.FlagSet.StringVar(&c.state, "state", "", "Specifies the states to watch. Optional for 'checks' type.") - f.StringVar(&name, "name", "", + c.FlagSet.StringVar(&c.name, "name", "", "Specifies an event name to watch. Only for 'event' type.") +} + +func (c *WatchCommand) Help() string { + c.initFlags() + return c.HelpCommand(` +Usage: consul watch [options] [child...] + + Watches for changes in a given data view from Consul. If a child process + is specified, it will be invoked with the latest results on changes. Otherwise, + the latest values are dumped to stdout and the watch terminates. + + Providing the watch type is required, and other parameters may be required + or supported depending on the watch type. - if err := c.BaseCommand.Parse(args); err != nil { +`) +} + +func (c *WatchCommand) Run(args []string) int { + 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 }