config: refactor commands to print help for flags (#3536)

This patch refactors the commands that use the mitchellh/cli library to
populate the command line flag set in both the Run() and the Help()
method. Earlier versions of the mitchellh/cli library relied on the
Run() method to populuate the flagset for generating the usage screen.
This has changed in later versions and was previously solved with a
small monkey patch to the library to restore the old behavior.

However, this makes upgrading the library difficult since the patch has
to be restored every time.

This patch addresses this by moving the command line flags into an
initFlags() method where appropriate and also moving all variables for
the flags from the Run() method into the command itself.

Fixes #3536
This commit is contained in:
Frank Schroeder 2017-10-11 14:51:18 +02:00 committed by Frank Schröder
parent 64e4afede3
commit 7568a19433
40 changed files with 825 additions and 800 deletions

View File

@ -49,11 +49,12 @@ type AgentCommand struct {
// readConfig is responsible for setup of our configuration using
// the command line and any file configs
func (cmd *AgentCommand) readConfig() *config.RuntimeConfig {
var flags config.Flags
fs := cmd.BaseCommand.NewFlagSet(cmd)
config.AddFlags(fs, &flags)
cmd.InitFlagSet()
if err := cmd.BaseCommand.Parse(cmd.args); err != nil {
var flags config.Flags
config.AddFlags(cmd.FlagSet, &flags)
if err := cmd.FlagSet.Parse(cmd.args); err != nil {
if !strings.Contains(err.Error(), "help requested") {
cmd.UI.Error(fmt.Sprintf("error parsing flags: %v", err))
}
@ -480,15 +481,15 @@ func (cmd *AgentCommand) Synopsis() string {
}
func (cmd *AgentCommand) Help() string {
helpText := `
cmd.InitFlagSet()
config.AddFlags(cmd.FlagSet, &config.Flags{})
return cmd.HelpCommand(`
Usage: consul agent [options]
Starts the Consul agent and runs until an interrupt is received. The
agent represents a single node in a cluster.
` + cmd.BaseCommand.Help()
return strings.TrimSpace(helpText)
`)
}
func printJSON(name string, v interface{}) {

View File

@ -35,7 +35,8 @@ type BaseCommand struct {
HideNormalFlagsHelp bool
flagSet *flag.FlagSet
FlagSet *flag.FlagSet
hidden *flag.FlagSet
// These are the options which correspond to the HTTP API options
httpAddr configutil.StringValue
@ -56,7 +57,7 @@ func (c *BaseCommand) HTTPClient() (*api.Client, error) {
if !c.hasClientHTTP() && !c.hasServerHTTP() {
panic("no http flags defined")
}
if !c.flagSet.Parsed() {
if !c.FlagSet.Parsed() {
panic("flags have not been parsed")
}
@ -141,19 +142,19 @@ func (c *BaseCommand) httpFlagsServer() *flag.FlagSet {
// NewFlagSet creates a new flag set for the given command. It automatically
// generates help output and adds the appropriate API flags.
func (c *BaseCommand) NewFlagSet(command cli.Command) *flag.FlagSet {
c.flagSet = flag.NewFlagSet("", flag.ContinueOnError)
c.flagSet.Usage = func() { c.UI.Error(command.Help()) }
func (c *BaseCommand) InitFlagSet() {
c.hidden = flag.NewFlagSet("", flag.ContinueOnError)
c.FlagSet = flag.NewFlagSet("", flag.ContinueOnError)
if c.hasClientHTTP() {
c.httpFlagsClient().VisitAll(func(f *flag.Flag) {
c.flagSet.Var(f.Value, f.Name, f.DefValue)
c.FlagSet.Var(f.Value, f.Name, f.Usage)
})
}
if c.hasServerHTTP() {
c.httpFlagsServer().VisitAll(func(f *flag.Flag) {
c.flagSet.Var(f.Value, f.Name, f.DefValue)
c.FlagSet.Var(f.Value, f.Name, f.Usage)
})
}
@ -164,24 +165,11 @@ func (c *BaseCommand) NewFlagSet(command cli.Command) *flag.FlagSet {
c.UI.Error(errScanner.Text())
}
}()
c.flagSet.SetOutput(errW)
return c.flagSet
c.FlagSet.SetOutput(errW)
}
// Parse is used to parse the underlying flag set.
func (c *BaseCommand) Parse(args []string) error {
return c.flagSet.Parse(args)
}
// Help returns the help for this flagSet.
func (c *BaseCommand) Help() string {
// Some commands with subcommands (kv/snapshot) call this without initializing
// any flags first, so exit early to avoid a panic
if c.flagSet == nil {
return ""
}
return c.helpFlagsFor(c.flagSet)
func (c *BaseCommand) HelpCommand(msg string) string {
return strings.TrimSpace(msg + c.helpFlagsFor())
}
// hasClientHTTP returns true if this meta command contains client HTTP flags.
@ -198,7 +186,11 @@ func (c *BaseCommand) hasServerHTTP() bool {
// help output. This function is sad because there's no "merging" of command
// line flags. We explicitly pull out our "common" options into another section
// by doing string comparisons :(.
func (c *BaseCommand) helpFlagsFor(f *flag.FlagSet) string {
func (c *BaseCommand) helpFlagsFor() string {
if c.FlagSet == nil {
panic("FlagSet not initialized. Did you forget to call InitFlagSet()?")
}
httpFlagsClient := c.httpFlagsClient()
httpFlagsServer := c.httpFlagsServer()
@ -220,7 +212,7 @@ func (c *BaseCommand) helpFlagsFor(f *flag.FlagSet) string {
if !c.HideNormalFlagsHelp {
firstCommand := true
f.VisitAll(func(f *flag.Flag) {
c.FlagSet.VisitAll(func(f *flag.Flag) {
// Skip HTTP flags as they will be grouped separately
if flagContains(httpFlagsClient, f) || flagContains(httpFlagsServer, f) {
return

View File

@ -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 <subcommand> [options] [args]
This command has subcommands for interacting with Consul's catalog. The
@ -41,8 +40,7 @@ Usage: consul catalog <subcommand> [options] [args]
For more examples, ask for subcommand help or view the documentation.
`
return strings.TrimSpace(helpText)
`)
}
func (c *CatalogCommand) Synopsis() string {

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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
},

View File

@ -3,7 +3,6 @@ package command
import (
"fmt"
"regexp"
"strings"
consulapi "github.com/hashicorp/consul/api"
)
@ -12,40 +11,34 @@ import (
// fire new events
type EventCommand struct {
BaseCommand
// flags
name string
node string
service string
tag string
}
func (c *EventCommand) Help() string {
helpText := `
Usage: consul event [options] [payload]
Dispatches a custom user event across a datacenter. An event must provide
a name, but a payload is optional. Events support filtering using
regular expressions on node name, service, and tag definitions.
` + c.BaseCommand.Help()
return strings.TrimSpace(helpText)
func (c *EventCommand) initFlags() {
c.InitFlagSet()
c.FlagSet.StringVar(&c.name, "name", "",
"Name of the event.")
c.FlagSet.StringVar(&c.node, "node", "",
"Regular expression to filter on node names.")
c.FlagSet.StringVar(&c.service, "service", "",
"Regular expression to filter on service instances.")
c.FlagSet.StringVar(&c.tag, "tag", "",
"Regular expression to filter on service tags. Must be used with -service.")
}
func (c *EventCommand) Run(args []string) int {
var name, node, service, tag string
f := c.BaseCommand.NewFlagSet(c)
f.StringVar(&name, "name", "",
"Name of the event.")
f.StringVar(&node, "node", "",
"Regular expression to filter on node names.")
f.StringVar(&service, "service", "",
"Regular expression to filter on service instances.")
f.StringVar(&tag, "tag", "",
"Regular expression to filter on service tags. Must be used with -service.")
if err := c.BaseCommand.Parse(args); err != nil {
c.initFlags()
if err := c.FlagSet.Parse(args); err != nil {
return 1
}
// Check for a name
if name == "" {
if c.name == "" {
c.UI.Error("Event name must be specified")
c.UI.Error("")
c.UI.Error(c.Help())
@ -53,32 +46,32 @@ func (c *EventCommand) Run(args []string) int {
}
// Validate the filters
if node != "" {
if _, err := regexp.Compile(node); err != nil {
if c.node != "" {
if _, err := regexp.Compile(c.node); err != nil {
c.UI.Error(fmt.Sprintf("Failed to compile node filter regexp: %v", err))
return 1
}
}
if service != "" {
if _, err := regexp.Compile(service); err != nil {
if c.service != "" {
if _, err := regexp.Compile(c.service); err != nil {
c.UI.Error(fmt.Sprintf("Failed to compile service filter regexp: %v", err))
return 1
}
}
if tag != "" {
if _, err := regexp.Compile(tag); err != nil {
if c.tag != "" {
if _, err := regexp.Compile(c.tag); err != nil {
c.UI.Error(fmt.Sprintf("Failed to compile tag filter regexp: %v", err))
return 1
}
}
if tag != "" && service == "" {
if c.tag != "" && c.service == "" {
c.UI.Error("Cannot provide tag filter without service filter.")
return 1
}
// Check for a payload
var payload []byte
args = f.Args()
args = c.FlagSet.Args()
switch len(args) {
case 0:
case 1:
@ -91,7 +84,7 @@ func (c *EventCommand) Run(args []string) int {
}
// Create and test the HTTP client
client, err := c.BaseCommand.HTTPClient()
client, err := c.HTTPClient()
if err != nil {
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
return 1
@ -105,11 +98,11 @@ func (c *EventCommand) Run(args []string) int {
// Prepare the request
event := client.Event()
params := &consulapi.UserEvent{
Name: name,
Name: c.name,
Payload: payload,
NodeFilter: node,
ServiceFilter: service,
TagFilter: tag,
NodeFilter: c.node,
ServiceFilter: c.service,
TagFilter: c.tag,
}
// Fire the event
@ -124,6 +117,18 @@ func (c *EventCommand) Run(args []string) int {
return 0
}
func (c *EventCommand) Help() string {
c.initFlags()
return c.HelpCommand(`
Usage: consul event [options] [payload]
Dispatches a custom user event across a datacenter. An event must provide
a name, but a payload is optional. Events support filtering using
regular expressions on node name, service, and tag definitions.
`)
}
func (c *EventCommand) Synopsis() string {
return "Fire a new event"
}

View File

@ -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

View File

@ -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"
}

View File

@ -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."
}

View File

@ -2,37 +2,30 @@ package command
import (
"fmt"
"strings"
)
// JoinCommand is a Command implementation that tells a running Consul
// agent to join another.
type JoinCommand struct {
BaseCommand
// flags
wan bool
}
func (c *JoinCommand) Help() string {
helpText := `
Usage: consul join [options] address ...
Tells a running Consul agent (with "consul agent") to join the cluster
by specifying at least one existing member.
` + c.BaseCommand.Help()
return strings.TrimSpace(helpText)
func (c *JoinCommand) initFlags() {
c.InitFlagSet()
c.FlagSet.BoolVar(&c.wan, "wan", false,
"Joins a server to another server in the WAN pool.")
}
func (c *JoinCommand) Run(args []string) int {
var wan bool
f := c.BaseCommand.NewFlagSet(c)
f.BoolVar(&wan, "wan", false, "Joins a server to another server in the WAN pool.")
if err := c.BaseCommand.Parse(args); err != nil {
c.initFlags()
if err := c.FlagSet.Parse(args); err != nil {
return 1
}
addrs := f.Args()
addrs := c.FlagSet.Args()
if len(addrs) == 0 {
c.UI.Error("At least one address to join must be specified.")
c.UI.Error("")
@ -40,7 +33,7 @@ func (c *JoinCommand) Run(args []string) int {
return 1
}
client, err := c.BaseCommand.HTTPClient()
client, err := c.HTTPClient()
if err != nil {
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
return 1
@ -48,7 +41,7 @@ func (c *JoinCommand) Run(args []string) int {
joins := 0
for _, addr := range addrs {
err := client.Agent().Join(addr, wan)
err := client.Agent().Join(addr, c.wan)
if err != nil {
c.UI.Error(fmt.Sprintf("Error joining address '%s': %s", addr, err))
} else {
@ -66,6 +59,17 @@ func (c *JoinCommand) Run(args []string) int {
return 0
}
func (c *JoinCommand) Help() string {
c.initFlags()
return c.HelpCommand(`
Usage: consul join [options] address ...
Tells a running Consul agent (with "consul agent") to join the cluster
by specifying at least one existing member.
`)
}
func (c *JoinCommand) Synopsis() string {
return "Tell Consul agent to join cluster"
}

View File

@ -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"
}

View File

@ -2,7 +2,6 @@ package command
import (
"fmt"
"strings"
"github.com/hashicorp/consul/agent"
consulapi "github.com/hashicorp/consul/api"
@ -13,33 +12,38 @@ import (
// and removing gossip encryption keys from a keyring.
type KeyringCommand struct {
BaseCommand
// flags
installKey string
useKey string
removeKey string
listKeys bool
relay int
}
func (c *KeyringCommand) Run(args []string) int {
var installKey, useKey, removeKey string
var listKeys bool
var relay int
f := c.BaseCommand.NewFlagSet(c)
f.StringVar(&installKey, "install", "",
func (c *KeyringCommand) initFlags() {
c.InitFlagSet()
c.FlagSet.StringVar(&c.installKey, "install", "",
"Install a new encryption key. This will broadcast the new key to "+
"all members in the cluster.")
f.StringVar(&useKey, "use", "",
c.FlagSet.StringVar(&c.useKey, "use", "",
"Change the primary encryption key, which is used to encrypt "+
"messages. The key must already be installed before this operation "+
"can succeed.")
f.StringVar(&removeKey, "remove", "",
c.FlagSet.StringVar(&c.removeKey, "remove", "",
"Remove the given key from the cluster. This operation may only be "+
"performed on keys which are not currently the primary key.")
f.BoolVar(&listKeys, "list", false,
c.FlagSet.BoolVar(&c.listKeys, "list", false,
"List all keys currently in use within the cluster.")
f.IntVar(&relay, "relay-factor", 0,
c.FlagSet.IntVar(&c.relay, "relay-factor", 0,
"Setting this to a non-zero value will cause nodes to relay their response "+
"to the operation through this many randomly-chosen other nodes in the "+
"cluster. The maximum allowed value is 5.")
}
if err := c.BaseCommand.Parse(args); err != nil {
func (c *KeyringCommand) Run(args []string) int {
c.initFlags()
if err := c.FlagSet.Parse(args); err != nil {
return 1
}
@ -51,8 +55,8 @@ func (c *KeyringCommand) Run(args []string) int {
}
// Only accept a single argument
found := listKeys
for _, arg := range []string{installKey, useKey, removeKey} {
found := c.listKeys
for _, arg := range []string{c.installKey, c.useKey, c.removeKey} {
if found && len(arg) > 0 {
c.UI.Error("Only a single action is allowed")
return 1
@ -67,20 +71,20 @@ func (c *KeyringCommand) Run(args []string) int {
}
// Validate the relay factor
relayFactor, err := agent.ParseRelayFactor(relay)
relayFactor, err := agent.ParseRelayFactor(c.relay)
if err != nil {
c.UI.Error(fmt.Sprintf("Error parsing relay factor: %s", err))
return 1
}
// All other operations will require a client connection
client, err := c.BaseCommand.HTTPClient()
client, err := c.HTTPClient()
if err != nil {
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
return 1
}
if listKeys {
if c.listKeys {
c.UI.Info("Gathering installed encryption keys...")
responses, err := client.Operator().KeyringList(&consulapi.QueryOptions{RelayFactor: relayFactor})
if err != nil {
@ -92,9 +96,9 @@ func (c *KeyringCommand) Run(args []string) int {
}
opts := &consulapi.WriteOptions{RelayFactor: relayFactor}
if installKey != "" {
if c.installKey != "" {
c.UI.Info("Installing new gossip encryption key...")
err := client.Operator().KeyringInstall(installKey, opts)
err := client.Operator().KeyringInstall(c.installKey, opts)
if err != nil {
c.UI.Error(fmt.Sprintf("error: %s", err))
return 1
@ -102,9 +106,9 @@ func (c *KeyringCommand) Run(args []string) int {
return 0
}
if useKey != "" {
if c.useKey != "" {
c.UI.Info("Changing primary gossip encryption key...")
err := client.Operator().KeyringUse(useKey, opts)
err := client.Operator().KeyringUse(c.useKey, opts)
if err != nil {
c.UI.Error(fmt.Sprintf("error: %s", err))
return 1
@ -112,9 +116,9 @@ func (c *KeyringCommand) Run(args []string) int {
return 0
}
if removeKey != "" {
if c.removeKey != "" {
c.UI.Info("Removing gossip encryption key...")
err := client.Operator().KeyringRemove(removeKey, opts)
err := client.Operator().KeyringRemove(c.removeKey, opts)
if err != nil {
c.UI.Error(fmt.Sprintf("error: %s", err))
return 1
@ -145,7 +149,8 @@ func (c *KeyringCommand) handleList(responses []*consulapi.KeyringResponse) {
}
func (c *KeyringCommand) Help() string {
helpText := `
c.initFlags()
return c.HelpCommand(`
Usage: consul keyring [options]
Manages encryption keys used for gossip messages. Gossip encryption is
@ -161,9 +166,7 @@ Usage: consul keyring [options]
are no errors. If any node fails to reply or reports failure, the exit code
will be 1.
` + c.BaseCommand.Help()
return strings.TrimSpace(helpText)
`)
}
func (c *KeyringCommand) Synopsis() string {

View File

@ -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 <subcommand> [options] [args]
This command has subcommands for interacting with Consul's key-value
@ -42,8 +41,7 @@ Usage: consul kv <subcommand> [options] [args]
For more examples, ask for subcommand help or view the documentation.
`
return strings.TrimSpace(helpText)
`)
}
func (c *KVCommand) Synopsis() string {

View File

@ -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)

View File

@ -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))

View File

@ -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
}

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -43,30 +43,50 @@ type LockCommand struct {
child *os.Process
childLock sync.Mutex
verbose bool
// flags
limit int
monitorRetry int
name string
passStdin bool
propagateChildCode bool
shell bool
timeout time.Duration
}
func (c *LockCommand) Help() string {
helpText := `
Usage: consul lock [options] prefix child...
func (c *LockCommand) initFlags() {
c.InitFlagSet()
c.FlagSet.BoolVar(&c.propagateChildCode, "child-exit-code", false,
"Exit 2 if the child process exited with an error if this is true, "+
"otherwise this doesn't propagate an error from the child. The "+
"default value is false.")
c.FlagSet.IntVar(&c.limit, "n", 1,
"Optional limit on the number of concurrent lock holders. The underlying "+
"implementation switches from a lock to a semaphore when the value is "+
"greater than 1. The default value is 1.")
c.FlagSet.IntVar(&c.monitorRetry, "monitor-retry", defaultMonitorRetry,
"Number of times to retry if Consul returns a 500 error while monitoring "+
"the lock. This allows riding out brief periods of unavailability "+
"without causing leader elections, but increases the amount of time "+
"required to detect a lost lock in some cases. The default value is 3, "+
"with a 1s wait between retries. Set this value to 0 to disable retires.")
c.FlagSet.StringVar(&c.name, "name", "",
"Optional name to associate with the lock session. It not provided, one "+
"is generated based on the provided child command.")
c.FlagSet.BoolVar(&c.passStdin, "pass-stdin", false,
"Pass stdin to the child process.")
c.FlagSet.BoolVar(&c.shell, "shell", true,
"Use a shell to run the command (can set a custom shell via the SHELL "+
"environment variable).")
c.FlagSet.DurationVar(&c.timeout, "timeout", 0,
"Maximum amount of time to wait to acquire the lock, specified as a "+
"duration like \"1s\" or \"3h\". The default value is 0.")
c.FlagSet.BoolVar(&c.verbose, "verbose", false,
"Enable verbose (debugging) output.")
Acquires a lock or semaphore at a given path, and invokes a child process
when successful. The child process can assume the lock is held while it
executes. If the lock is lost or communication is disrupted the child
process will be sent a SIGTERM signal and given time to gracefully exit.
After the grace period expires the process will be hard terminated.
For Consul agents on Windows, the child process is always hard terminated
with a SIGKILL, since Windows has no POSIX compatible notion for SIGTERM.
When -n=1, only a single lock holder or leader exists providing mutual
exclusion. Setting a higher value switches to a semaphore allowing multiple
holders to coordinate.
The prefix provided must have write privileges.
` + c.BaseCommand.Help()
return strings.TrimSpace(helpText)
// Deprecations
c.FlagSet.DurationVar(&c.timeout, "try", 0,
"DEPRECATED. Use -timeout instead.")
}
func (c *LockCommand) Run(args []string) int {
@ -75,59 +95,19 @@ func (c *LockCommand) Run(args []string) int {
}
func (c *LockCommand) run(args []string, lu **LockUnlock) int {
var limit int
var monitorRetry int
var name string
var passStdin bool
var propagateChildCode bool
var shell bool
var timeout time.Duration
f := c.BaseCommand.NewFlagSet(c)
f.BoolVar(&propagateChildCode, "child-exit-code", false,
"Exit 2 if the child process exited with an error if this is true, "+
"otherwise this doesn't propagate an error from the child. The "+
"default value is false.")
f.IntVar(&limit, "n", 1,
"Optional limit on the number of concurrent lock holders. The underlying "+
"implementation switches from a lock to a semaphore when the value is "+
"greater than 1. The default value is 1.")
f.IntVar(&monitorRetry, "monitor-retry", defaultMonitorRetry,
"Number of times to retry if Consul returns a 500 error while monitoring "+
"the lock. This allows riding out brief periods of unavailability "+
"without causing leader elections, but increases the amount of time "+
"required to detect a lost lock in some cases. The default value is 3, "+
"with a 1s wait between retries. Set this value to 0 to disable retires.")
f.StringVar(&name, "name", "",
"Optional name to associate with the lock session. It not provided, one "+
"is generated based on the provided child command.")
f.BoolVar(&passStdin, "pass-stdin", false,
"Pass stdin to the child process.")
f.BoolVar(&shell, "shell", true,
"Use a shell to run the command (can set a custom shell via the SHELL "+
"environment variable).")
f.DurationVar(&timeout, "timeout", 0,
"Maximum amount of time to wait to acquire the lock, specified as a "+
"duration like \"1s\" or \"3h\". The default value is 0.")
f.BoolVar(&c.verbose, "verbose", false,
"Enable verbose (debugging) output.")
// Deprecations
f.DurationVar(&timeout, "try", 0,
"DEPRECATED. Use -timeout instead.")
if err := c.BaseCommand.Parse(args); err != nil {
c.initFlags()
if err := c.FlagSet.Parse(args); err != nil {
return 1
}
// Check the limit
if limit <= 0 {
if c.limit <= 0 {
c.UI.Error(fmt.Sprintf("Lock holder limit must be positive"))
return 1
}
// Verify the prefix and child are provided
extra := f.Args()
extra := c.FlagSet.Args()
if len(extra) < 2 {
c.UI.Error("Key prefix and child command must be specified")
return 1
@ -135,27 +115,27 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int {
prefix := extra[0]
prefix = strings.TrimPrefix(prefix, "/")
if timeout < 0 {
if c.timeout < 0 {
c.UI.Error("Timeout must be positive")
return 1
}
// Calculate a session name if none provided
if name == "" {
name = fmt.Sprintf("Consul lock for '%s' at '%s'", strings.Join(extra[1:], " "), prefix)
if c.name == "" {
c.name = fmt.Sprintf("Consul lock for '%s' at '%s'", strings.Join(extra[1:], " "), prefix)
}
// Calculate oneshot
oneshot := timeout > 0
oneshot := c.timeout > 0
// Check the retry parameter
if monitorRetry < 0 {
if c.monitorRetry < 0 {
c.UI.Error("Number for 'monitor-retry' must be >= 0")
return 1
}
// Create and test the HTTP client
client, err := c.BaseCommand.HTTPClient()
client, err := c.HTTPClient()
if err != nil {
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
return 1
@ -167,10 +147,10 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int {
}
// Setup the lock or semaphore
if limit == 1 {
*lu, err = c.setupLock(client, prefix, name, oneshot, timeout, monitorRetry)
if c.limit == 1 {
*lu, err = c.setupLock(client, prefix, c.name, oneshot, c.timeout, c.monitorRetry)
} else {
*lu, err = c.setupSemaphore(client, limit, prefix, name, oneshot, timeout, monitorRetry)
*lu, err = c.setupSemaphore(client, c.limit, prefix, c.name, oneshot, c.timeout, c.monitorRetry)
}
if err != nil {
c.UI.Error(fmt.Sprintf("Lock setup failed: %s", err))
@ -204,7 +184,7 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int {
// Start the child process
childErr = make(chan error, 1)
go func() {
childErr <- c.startChild(f.Args()[1:], passStdin, shell)
childErr <- c.startChild(c.FlagSet.Args()[1:], c.passStdin, c.shell)
}()
// Monitor for shutdown, child termination, or lock loss
@ -257,7 +237,7 @@ RELEASE:
// If we detected an error from the child process then we propagate
// that.
if propagateChildCode {
if c.propagateChildCode {
return childCode
}
@ -449,6 +429,29 @@ func (c *LockCommand) killChild(childErr chan error) error {
return nil
}
func (c *LockCommand) Help() string {
c.initFlags()
return c.HelpCommand(`
Usage: consul lock [options] prefix child...
Acquires a lock or semaphore at a given path, and invokes a child process
when successful. The child process can assume the lock is held while it
executes. If the lock is lost or communication is disrupted the child
process will be sent a SIGTERM signal and given time to gracefully exit.
After the grace period expires the process will be hard terminated.
For Consul agents on Windows, the child process is always hard terminated
with a SIGKILL, since Windows has no POSIX compatible notion for SIGTERM.
When -n=1, only a single lock holder or leader exists providing mutual
exclusion. Setting a higher value switches to a semaphore allowing multiple
holders to coordinate.
The prefix provided must have write privileges.
`)
}
func (c *LockCommand) Synopsis() string {
return "Execute a command holding a lock"
}

View File

@ -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
}

View File

@ -16,50 +16,53 @@ import (
// Consul agent what members are part of the cluster currently.
type MembersCommand struct {
BaseCommand
// flags
detailed bool
wan bool
statusFilter string
segment string
}
func (c *MembersCommand) initFlags() {
c.InitFlagSet()
c.FlagSet.BoolVar(&c.detailed, "detailed", false,
"Provides detailed information about nodes.")
c.FlagSet.BoolVar(&c.wan, "wan", false,
"If the agent is in server mode, this can be used to return the other "+
"peers in the WAN pool.")
c.FlagSet.StringVar(&c.statusFilter, "status", ".*",
"If provided, output is filtered to only nodes matching the regular "+
"expression for status.")
c.FlagSet.StringVar(&c.segment, "segment", consulapi.AllSegments,
"(Enterprise-only) If provided, output is filtered to only nodes in"+
"the given segment.")
}
func (c *MembersCommand) Help() string {
helpText := `
c.initFlags()
return c.HelpCommand(`
Usage: consul members [options]
Outputs the members of a running Consul agent.
` + c.BaseCommand.Help()
return strings.TrimSpace(helpText)
`)
}
func (c *MembersCommand) Run(args []string) int {
var detailed bool
var wan bool
var statusFilter string
var segment string
f := c.BaseCommand.NewFlagSet(c)
f.BoolVar(&detailed, "detailed", false,
"Provides detailed information about nodes.")
f.BoolVar(&wan, "wan", false,
"If the agent is in server mode, this can be used to return the other "+
"peers in the WAN pool.")
f.StringVar(&statusFilter, "status", ".*",
"If provided, output is filtered to only nodes matching the regular "+
"expression for status.")
f.StringVar(&segment, "segment", consulapi.AllSegments,
"(Enterprise-only) If provided, output is filtered to only nodes in"+
"the given segment.")
if err := c.BaseCommand.Parse(args); err != nil {
c.initFlags()
if err := c.FlagSet.Parse(args); err != nil {
return 1
}
// Compile the regexp
statusRe, err := regexp.Compile(statusFilter)
statusRe, err := regexp.Compile(c.statusFilter)
if err != nil {
c.UI.Error(fmt.Sprintf("Failed to compile status regexp: %v", err))
return 1
}
client, err := c.BaseCommand.HTTPClient()
client, err := c.HTTPClient()
if err != nil {
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
return 1
@ -67,8 +70,8 @@ func (c *MembersCommand) Run(args []string) int {
// Make the request.
opts := consulapi.MembersOpts{
Segment: segment,
WAN: wan,
Segment: c.segment,
WAN: c.wan,
}
members, err := client.Agent().MembersOpts(opts)
if err != nil {
@ -83,7 +86,7 @@ func (c *MembersCommand) Run(args []string) int {
if member.Tags["segment"] == "" {
member.Tags["segment"] = "<default>"
}
if segment == consulapi.AllSegments && member.Tags["role"] == "consul" {
if c.segment == consulapi.AllSegments && member.Tags["role"] == "consul" {
member.Tags["segment"] = "<all>"
}
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)

View File

@ -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

View File

@ -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 <subcommand> [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 {

View File

@ -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 {

View File

@ -3,7 +3,6 @@ package command
import (
"flag"
"fmt"
"strings"
"time"
"github.com/hashicorp/consul/api"
@ -12,17 +11,53 @@ import (
type OperatorAutopilotSetCommand struct {
BaseCommand
// flags
cleanupDeadServers configutil.BoolValue
maxTrailingLogs configutil.UintValue
lastContactThreshold configutil.DurationValue
serverStabilizationTime configutil.DurationValue
redundancyZoneTag configutil.StringValue
disableUpgradeMigration configutil.BoolValue
upgradeVersionTag configutil.StringValue
}
func (c *OperatorAutopilotSetCommand) initFlags() {
c.InitFlagSet()
c.FlagSet.Var(&c.cleanupDeadServers, "cleanup-dead-servers",
"Controls whether Consul will automatically remove dead servers "+
"when new ones are successfully added. Must be one of `true|false`.")
c.FlagSet.Var(&c.maxTrailingLogs, "max-trailing-logs",
"Controls the maximum number of log entries that a server can trail the "+
"leader by before being considered unhealthy.")
c.FlagSet.Var(&c.lastContactThreshold, "last-contact-threshold",
"Controls the maximum amount of time a server can go without contact "+
"from the leader before being considered unhealthy. Must be a duration value "+
"such as `200ms`.")
c.FlagSet.Var(&c.serverStabilizationTime, "server-stabilization-time",
"Controls the minimum amount of time a server must be stable in the "+
"'healthy' state before being added to the cluster. Only takes effect if all "+
"servers are running Raft protocol version 3 or higher. Must be a duration "+
"value such as `10s`.")
c.FlagSet.Var(&c.redundancyZoneTag, "redundancy-zone-tag",
"(Enterprise-only) Controls the node_meta tag name used for separating servers into "+
"different redundancy zones.")
c.FlagSet.Var(&c.disableUpgradeMigration, "disable-upgrade-migration",
"(Enterprise-only) Controls whether Consul will avoid promoting new servers until "+
"it can perform a migration. Must be one of `true|false`.")
c.FlagSet.Var(&c.upgradeVersionTag, "upgrade-version-tag",
"(Enterprise-only) The node_meta tag to use for version info when performing upgrade "+
"migrations. If left blank, the Consul version will be used.")
}
func (c *OperatorAutopilotSetCommand) Help() string {
helpText := `
c.initFlags()
return c.HelpCommand(`
Usage: consul operator autopilot set-config [options]
Modifies the current Autopilot configuration.
` + c.BaseCommand.Help()
return strings.TrimSpace(helpText)
`)
}
func (c *OperatorAutopilotSetCommand) Synopsis() string {
@ -30,42 +65,8 @@ func (c *OperatorAutopilotSetCommand) Synopsis() string {
}
func (c *OperatorAutopilotSetCommand) Run(args []string) int {
var cleanupDeadServers configutil.BoolValue
var maxTrailingLogs configutil.UintValue
var lastContactThreshold configutil.DurationValue
var serverStabilizationTime configutil.DurationValue
var redundancyZoneTag configutil.StringValue
var disableUpgradeMigration configutil.BoolValue
var upgradeVersionTag configutil.StringValue
f := c.BaseCommand.NewFlagSet(c)
f.Var(&cleanupDeadServers, "cleanup-dead-servers",
"Controls whether Consul will automatically remove dead servers "+
"when new ones are successfully added. Must be one of `true|false`.")
f.Var(&maxTrailingLogs, "max-trailing-logs",
"Controls the maximum number of log entries that a server can trail the "+
"leader by before being considered unhealthy.")
f.Var(&lastContactThreshold, "last-contact-threshold",
"Controls the maximum amount of time a server can go without contact "+
"from the leader before being considered unhealthy. Must be a duration value "+
"such as `200ms`.")
f.Var(&serverStabilizationTime, "server-stabilization-time",
"Controls the minimum amount of time a server must be stable in the "+
"'healthy' state before being added to the cluster. Only takes effect if all "+
"servers are running Raft protocol version 3 or higher. Must be a duration "+
"value such as `10s`.")
f.Var(&redundancyZoneTag, "redundancy-zone-tag",
"(Enterprise-only) Controls the node_meta tag name used for separating servers into "+
"different redundancy zones.")
f.Var(&disableUpgradeMigration, "disable-upgrade-migration",
"(Enterprise-only) Controls whether Consul will avoid promoting new servers until "+
"it can perform a migration. Must be one of `true|false`.")
f.Var(&upgradeVersionTag, "upgrade-version-tag",
"(Enterprise-only) The node_meta tag to use for version info when performing upgrade "+
"migrations. If left blank, the Consul version will be used.")
if err := c.BaseCommand.Parse(args); err != nil {
c.initFlags()
if err := c.FlagSet.Parse(args); err != nil {
if err == flag.ErrHelp {
return 0
}
@ -74,7 +75,7 @@ func (c *OperatorAutopilotSetCommand) Run(args []string) int {
}
// Set up a client.
client, err := c.BaseCommand.HTTPClient()
client, err := c.HTTPClient()
if err != nil {
c.UI.Error(fmt.Sprintf("Error initializing client: %s", err))
return 1
@ -89,21 +90,21 @@ func (c *OperatorAutopilotSetCommand) Run(args []string) int {
}
// Update the config values based on the set flags.
cleanupDeadServers.Merge(&conf.CleanupDeadServers)
redundancyZoneTag.Merge(&conf.RedundancyZoneTag)
disableUpgradeMigration.Merge(&conf.DisableUpgradeMigration)
upgradeVersionTag.Merge(&conf.UpgradeVersionTag)
c.cleanupDeadServers.Merge(&conf.CleanupDeadServers)
c.redundancyZoneTag.Merge(&conf.RedundancyZoneTag)
c.disableUpgradeMigration.Merge(&conf.DisableUpgradeMigration)
c.upgradeVersionTag.Merge(&conf.UpgradeVersionTag)
trailing := uint(conf.MaxTrailingLogs)
maxTrailingLogs.Merge(&trailing)
c.maxTrailingLogs.Merge(&trailing)
conf.MaxTrailingLogs = uint64(trailing)
last := time.Duration(*conf.LastContactThreshold)
lastContactThreshold.Merge(&last)
c.lastContactThreshold.Merge(&last)
conf.LastContactThreshold = api.NewReadableDuration(last)
stablization := time.Duration(*conf.ServerStabilizationTime)
serverStabilizationTime.Merge(&stablization)
c.serverStabilizationTime.Merge(&stablization)
conf.ServerStabilizationTime = api.NewReadableDuration(stablization)
// Check-and-set the new configuration.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -28,27 +28,25 @@ func TestRTTCommand_Implements(t *testing.T) {
}
func TestRTTCommand_Run_BadArgs(t *testing.T) {
t.Parallel()
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"}},
}
for _, tt := range tests {
t.Run(strings.Join(tt.args, " "), func(t *testing.T) {
t.Parallel()
_, c := testRTTCommand(t)
if code := c.Run([]string{}); code != 1 {
if code := c.Run(tt.args); 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)
}
if code := c.Run([]string{"-wan", "node1", "node2.dc1"}); code != 1 {
t.Fatalf("expected return code 1, got %d", code)
})
}
}

View File

@ -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 <subcommand> [options] [args]
return `Usage: consul snapshot <subcommand> [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 <subcommand> [options] [args]
For more examples, ask for subcommand help or view the documentation.
`
return strings.TrimSpace(helpText)
}
func (c *SnapshotCommand) Synopsis() string {

View File

@ -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")

View File

@ -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

View File

@ -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))

View File

@ -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()

View File

@ -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

View File

@ -18,10 +18,48 @@ import (
type WatchCommand struct {
BaseCommand
ShutdownCh <-chan struct{}
// flags
watchType string
key string
prefix string
service string
tag string
passingOnly string
state string
name string
shell bool
}
func (c *WatchCommand) initFlags() {
c.InitFlagSet()
c.FlagSet.StringVar(&c.watchType, "type", "",
"Specifies the watch type. One of key, keyprefix, services, nodes, "+
"service, checks, or event.")
c.FlagSet.StringVar(&c.key, "key", "",
"Specifies the key to watch. Only for 'key' type.")
c.FlagSet.StringVar(&c.prefix, "prefix", "",
"Specifies the key prefix to watch. Only for 'keyprefix' type.")
c.FlagSet.StringVar(&c.service, "service", "",
"Specifies the service to watch. Required for 'service' type, "+
"optional for 'checks' type.")
c.FlagSet.StringVar(&c.tag, "tag", "",
"Specifies the service tag to filter on. Optional for 'service' type.")
c.FlagSet.StringVar(&c.passingOnly, "passingonly", "",
"Specifies if only hosts passing all checks are displayed. "+
"Optional for 'service' type, must be one of `[true|false]`. Defaults false.")
c.FlagSet.BoolVar(&c.shell, "shell", true,
"Use a shell to run the command (can set a custom shell via the SHELL "+
"environment variable).")
c.FlagSet.StringVar(&c.state, "state", "",
"Specifies the states to watch. Optional for 'checks' type.")
c.FlagSet.StringVar(&c.name, "name", "",
"Specifies an event name to watch. Only for 'event' type.")
}
func (c *WatchCommand) Help() string {
helpText := `
c.initFlags()
return c.HelpCommand(`
Usage: consul watch [options] [child...]
Watches for changes in a given data view from Consul. If a child process
@ -31,45 +69,17 @@ Usage: consul watch [options] [child...]
Providing the watch type is required, and other parameters may be required
or supported depending on the watch type.
` + c.BaseCommand.Help()
return strings.TrimSpace(helpText)
`)
}
func (c *WatchCommand) Run(args []string) int {
var watchType, key, prefix, service, tag, passingOnly, state, name string
var shell bool
f := c.BaseCommand.NewFlagSet(c)
f.StringVar(&watchType, "type", "",
"Specifies the watch type. One of key, keyprefix, services, nodes, "+
"service, checks, or event.")
f.StringVar(&key, "key", "",
"Specifies the key to watch. Only for 'key' type.")
f.StringVar(&prefix, "prefix", "",
"Specifies the key prefix to watch. Only for 'keyprefix' type.")
f.StringVar(&service, "service", "",
"Specifies the service to watch. Required for 'service' type, "+
"optional for 'checks' type.")
f.StringVar(&tag, "tag", "",
"Specifies the service tag to filter on. Optional for 'service' type.")
f.StringVar(&passingOnly, "passingonly", "",
"Specifies if only hosts passing all checks are displayed. "+
"Optional for 'service' type, must be one of `[true|false]`. Defaults false.")
f.BoolVar(&shell, "shell", true,
"Use a shell to run the command (can set a custom shell via the SHELL "+
"environment variable).")
f.StringVar(&state, "state", "",
"Specifies the states to watch. Optional for 'checks' type.")
f.StringVar(&name, "name", "",
"Specifies an event name to watch. Only for 'event' type.")
if err := c.BaseCommand.Parse(args); err != nil {
c.initFlags()
if err := c.FlagSet.Parse(args); err != nil {
return 1
}
// Check for a type
if watchType == "" {
if c.watchType == "" {
c.UI.Error("Watch type must be specified")
c.UI.Error("")
c.UI.Error(c.Help())
@ -78,38 +88,38 @@ func (c *WatchCommand) Run(args []string) int {
// Compile the watch parameters
params := make(map[string]interface{})
if watchType != "" {
params["type"] = watchType
if c.watchType != "" {
params["type"] = c.watchType
}
if c.BaseCommand.HTTPDatacenter() != "" {
params["datacenter"] = c.BaseCommand.HTTPDatacenter()
if c.HTTPDatacenter() != "" {
params["datacenter"] = c.HTTPDatacenter()
}
if c.BaseCommand.HTTPToken() != "" {
params["token"] = c.BaseCommand.HTTPToken()
if c.HTTPToken() != "" {
params["token"] = c.HTTPToken()
}
if key != "" {
params["key"] = key
if c.key != "" {
params["key"] = c.key
}
if prefix != "" {
params["prefix"] = prefix
if c.prefix != "" {
params["prefix"] = c.prefix
}
if service != "" {
params["service"] = service
if c.service != "" {
params["service"] = c.service
}
if tag != "" {
params["tag"] = tag
if c.tag != "" {
params["tag"] = c.tag
}
if c.BaseCommand.HTTPStale() {
params["stale"] = c.BaseCommand.HTTPStale()
if c.HTTPStale() {
params["stale"] = c.HTTPStale()
}
if state != "" {
params["state"] = state
if c.state != "" {
params["state"] = c.state
}
if name != "" {
params["name"] = name
if c.name != "" {
params["name"] = c.name
}
if passingOnly != "" {
b, err := strconv.ParseBool(passingOnly)
if c.passingOnly != "" {
b, err := strconv.ParseBool(c.passingOnly)
if err != nil {
c.UI.Error(fmt.Sprintf("Failed to parse passingonly flag: %s", err))
return 1
@ -125,7 +135,7 @@ func (c *WatchCommand) Run(args []string) int {
}
// Create and test the HTTP client
client, err := c.BaseCommand.HTTPClient()
client, err := c.HTTPClient()
if err != nil {
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
return 1
@ -142,7 +152,7 @@ func (c *WatchCommand) Run(args []string) int {
// 0: false
// 1: true
errExit := 0
if len(f.Args()) == 0 {
if len(c.FlagSet.Args()) == 0 {
wp.Handler = func(idx uint64, data interface{}) {
defer wp.Stop()
buf, err := json.MarshalIndent(data, "", " ")
@ -164,10 +174,10 @@ func (c *WatchCommand) Run(args []string) int {
var buf bytes.Buffer
var err error
var cmd *exec.Cmd
if !shell {
cmd, err = agent.ExecSubprocess(f.Args())
if !c.shell {
cmd, err = agent.ExecSubprocess(c.FlagSet.Args())
} else {
cmd, err = agent.ExecScript(strings.Join(f.Args(), " "))
cmd, err = agent.ExecScript(strings.Join(c.FlagSet.Args(), " "))
}
if err != nil {
c.UI.Error(fmt.Sprintf("Error executing handler: %s", err))
@ -215,7 +225,7 @@ func (c *WatchCommand) Run(args []string) int {
}()
// Run the watch
if err := wp.Run(c.BaseCommand.HTTPAddr()); err != nil {
if err := wp.Run(c.HTTPAddr()); err != nil {
c.UI.Error(fmt.Sprintf("Error querying Consul agent: %s", err))
return 1
}