mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
7.0 KiB
271 lines
7.0 KiB
package base |
|
|
|
import ( |
|
"bufio" |
|
"bytes" |
|
"flag" |
|
"fmt" |
|
"io" |
|
"strings" |
|
|
|
"github.com/hashicorp/consul/api" |
|
"github.com/mitchellh/cli" |
|
text "github.com/tonnerre/golang-text" |
|
) |
|
|
|
// maxLineLength is the maximum width of any line. |
|
const maxLineLength int = 72 |
|
|
|
// FlagSetFlags is an enum to define what flags are present in the |
|
// default FlagSet returned. |
|
type FlagSetFlags uint |
|
|
|
const ( |
|
FlagSetNone FlagSetFlags = 1 << iota |
|
FlagSetClientHTTP FlagSetFlags = 1 << iota |
|
FlagSetServerHTTP FlagSetFlags = 1 << iota |
|
|
|
FlagSetHTTP = FlagSetClientHTTP | FlagSetServerHTTP |
|
) |
|
|
|
type Command struct { |
|
Ui cli.Ui |
|
Flags FlagSetFlags |
|
|
|
flagSet *flag.FlagSet |
|
hidden *flag.FlagSet |
|
|
|
// These are the options which correspond to the HTTP API options |
|
httpAddr stringValue |
|
token stringValue |
|
datacenter stringValue |
|
stale boolValue |
|
} |
|
|
|
// HTTPClient returns a client with the parsed flags. It panics if the command |
|
// does not accept HTTP flags or if the flags have not been parsed. |
|
func (c *Command) HTTPClient() (*api.Client, error) { |
|
if !c.hasClientHTTP() && !c.hasServerHTTP() { |
|
panic("no http flags defined") |
|
} |
|
if !c.flagSet.Parsed() { |
|
panic("flags have not been parsed") |
|
} |
|
|
|
config := api.DefaultConfig() |
|
c.httpAddr.Merge(&config.Address) |
|
c.token.Merge(&config.Token) |
|
c.datacenter.Merge(&config.Datacenter) |
|
return api.NewClient(config) |
|
} |
|
|
|
func (c *Command) HTTPAddr() string { |
|
return c.httpAddr.String() |
|
} |
|
|
|
func (c *Command) HTTPToken() string { |
|
return c.token.String() |
|
} |
|
|
|
func (c *Command) HTTPDatacenter() string { |
|
return c.datacenter.String() |
|
} |
|
|
|
func (c *Command) HTTPStale() bool { |
|
var stale bool |
|
c.stale.Merge(&stale) |
|
return stale |
|
} |
|
|
|
// httpFlagsClient is the list of flags that apply to HTTP connections. |
|
func (c *Command) httpFlagsClient(f *flag.FlagSet) *flag.FlagSet { |
|
if f == nil { |
|
f = flag.NewFlagSet("", flag.ContinueOnError) |
|
} |
|
|
|
f.Var(&c.httpAddr, "http-addr", |
|
"The `address` and port of the Consul HTTP agent. The value can be an IP "+ |
|
"address or DNS address, but it must also include the port. This can "+ |
|
"also be specified via the CONSUL_HTTP_ADDR environment variable. The "+ |
|
"default value is http://127.0.0.1:8500. The scheme can also be set to "+ |
|
"HTTPS by setting the environment variable CONSUL_HTTP_SSL=true.") |
|
f.Var(&c.token, "token", |
|
"ACL token to use in the request. This can also be specified via the "+ |
|
"CONSUL_HTTP_TOKEN environment variable. If unspecified, the query will "+ |
|
"default to the token of the Consul agent at the HTTP address.") |
|
|
|
return f |
|
} |
|
|
|
// httpFlagsServer is the list of flags that apply to HTTP connections. |
|
func (c *Command) httpFlagsServer(f *flag.FlagSet) *flag.FlagSet { |
|
if f == nil { |
|
f = flag.NewFlagSet("", flag.ContinueOnError) |
|
} |
|
|
|
f.Var(&c.datacenter, "datacenter", |
|
"Name of the datacenter to query. If unspecified, this will default to "+ |
|
"the datacenter of the queried agent.") |
|
f.Var(&c.stale, "stale", |
|
"Permit any Consul server (non-leader) to respond to this request. This "+ |
|
"allows for lower latency and higher throughput, but can result in "+ |
|
"stale data. This option has no effect on non-read operations. The "+ |
|
"default value is false.") |
|
|
|
return f |
|
} |
|
|
|
// NewFlagSet creates a new flag set for the given command. It automatically |
|
// generates help output and adds the appropriate API flags. |
|
func (c *Command) NewFlagSet(command cli.Command) *flag.FlagSet { |
|
f := flag.NewFlagSet("", flag.ContinueOnError) |
|
f.Usage = func() { c.Ui.Error(command.Help()) } |
|
|
|
if c.hasClientHTTP() { |
|
c.httpFlagsClient(f) |
|
} |
|
|
|
if c.hasServerHTTP() { |
|
c.httpFlagsServer(f) |
|
} |
|
|
|
errR, errW := io.Pipe() |
|
errScanner := bufio.NewScanner(errR) |
|
go func() { |
|
for errScanner.Scan() { |
|
c.Ui.Error(errScanner.Text()) |
|
} |
|
}() |
|
f.SetOutput(errW) |
|
|
|
c.flagSet = f |
|
c.hidden = flag.NewFlagSet("", flag.ContinueOnError) |
|
|
|
return f |
|
} |
|
|
|
// HideFlags is used to set hidden flags that will not be shown in help text |
|
func (c *Command) HideFlags(flags ...string) { |
|
for _, f := range flags { |
|
c.hidden.String(f, "", "") |
|
} |
|
} |
|
|
|
// Parse is used to parse the underlying flag set. |
|
func (c *Command) Parse(args []string) error { |
|
return c.flagSet.Parse(args) |
|
} |
|
|
|
// Help returns the help for this flagSet. |
|
func (c *Command) 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) |
|
} |
|
|
|
// hasClientHTTP returns true if this meta command contains client HTTP flags. |
|
func (c *Command) hasClientHTTP() bool { |
|
return c.Flags&FlagSetClientHTTP != 0 |
|
} |
|
|
|
// hasServerHTTP returns true if this meta command contains server HTTP flags. |
|
func (c *Command) hasServerHTTP() bool { |
|
return c.Flags&FlagSetServerHTTP != 0 |
|
} |
|
|
|
// helpFlagsFor visits all flags in the given flag set and prints formatted |
|
// 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 *Command) helpFlagsFor(f *flag.FlagSet) string { |
|
httpFlagsClient := c.httpFlagsClient(nil) |
|
httpFlagsServer := c.httpFlagsServer(nil) |
|
|
|
var out bytes.Buffer |
|
|
|
firstHTTP := true |
|
if c.hasClientHTTP() { |
|
if firstHTTP { |
|
printTitle(&out, "HTTP API Options") |
|
firstHTTP = false |
|
} |
|
httpFlagsClient.VisitAll(func(f *flag.Flag) { |
|
printFlag(&out, f) |
|
}) |
|
} |
|
if c.hasServerHTTP() { |
|
if firstHTTP { |
|
printTitle(&out, "HTTP API Options") |
|
firstHTTP = false |
|
} |
|
httpFlagsServer.VisitAll(func(f *flag.Flag) { |
|
printFlag(&out, f) |
|
}) |
|
} |
|
|
|
firstCommand := true |
|
f.VisitAll(func(f *flag.Flag) { |
|
// Skip HTTP flags as they will be grouped separately |
|
if flagContains(httpFlagsClient, f) || flagContains(httpFlagsServer, f) || flagContains(c.hidden, f) { |
|
return |
|
} |
|
if firstCommand { |
|
printTitle(&out, "Command Options") |
|
firstCommand = false |
|
} |
|
printFlag(&out, f) |
|
}) |
|
|
|
return strings.TrimRight(out.String(), "\n") |
|
} |
|
|
|
// printTitle prints a consistently-formatted title to the given writer. |
|
func printTitle(w io.Writer, s string) { |
|
fmt.Fprintf(w, "%s\n\n", s) |
|
} |
|
|
|
// printFlag prints a single flag to the given writer. |
|
func printFlag(w io.Writer, f *flag.Flag) { |
|
example, _ := flag.UnquoteUsage(f) |
|
if example != "" { |
|
fmt.Fprintf(w, " -%s=<%s>\n", f.Name, example) |
|
} else { |
|
fmt.Fprintf(w, " -%s\n", f.Name) |
|
} |
|
|
|
indented := wrapAtLength(f.Usage, 5) |
|
fmt.Fprintf(w, "%s\n\n", indented) |
|
} |
|
|
|
// flagContains returns true if the given flag is contained in the given flag |
|
// set or false otherwise. |
|
func flagContains(fs *flag.FlagSet, f *flag.Flag) bool { |
|
var skip bool |
|
|
|
fs.VisitAll(func(hf *flag.Flag) { |
|
if skip { |
|
return |
|
} |
|
|
|
if f.Name == hf.Name { |
|
skip = true |
|
return |
|
} |
|
}) |
|
|
|
return skip |
|
} |
|
|
|
// wrapAtLength wraps the given text at the maxLineLength, taking into account |
|
// any provided left padding. |
|
func wrapAtLength(s string, pad int) string { |
|
wrapped := text.Wrap(s, maxLineLength-pad) |
|
lines := strings.Split(wrapped, "\n") |
|
for i, line := range lines { |
|
lines[i] = strings.Repeat(" ", pad) + line |
|
} |
|
return strings.Join(lines, "\n") |
|
}
|
|
|