commands: move flag handling into flags pkg

This commit is contained in:
Frank Schroeder 2017-10-11 14:51:19 +02:00 committed by Frank Schröder
parent 366ec9a565
commit cef6a80ae6
13 changed files with 253 additions and 32 deletions

View File

@ -9,7 +9,7 @@ import (
"strings" "strings"
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/configutil" "github.com/hashicorp/consul/command/flags"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
text "github.com/tonnerre/golang-text" text "github.com/tonnerre/golang-text"
) )
@ -39,16 +39,16 @@ type BaseCommand struct {
hidden *flag.FlagSet hidden *flag.FlagSet
// These are the options which correspond to the HTTP API options // These are the options which correspond to the HTTP API options
httpAddr configutil.StringValue httpAddr flags.StringValue
token configutil.StringValue token flags.StringValue
caFile configutil.StringValue caFile flags.StringValue
caPath configutil.StringValue caPath flags.StringValue
certFile configutil.StringValue certFile flags.StringValue
keyFile configutil.StringValue keyFile flags.StringValue
tlsServerName configutil.StringValue tlsServerName flags.StringValue
datacenter configutil.StringValue datacenter flags.StringValue
stale configutil.BoolValue stale flags.BoolValue
} }
// HTTPClient returns a client with the parsed flags. It panics if the command // HTTPClient returns a client with the parsed flags. It panics if the command

View File

@ -5,7 +5,7 @@ import (
"strings" "strings"
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/configutil" "github.com/hashicorp/consul/command/flags"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/ryanuber/columnize" "github.com/ryanuber/columnize"
) )
@ -31,7 +31,7 @@ func (c *CatalogListNodesCommand) initFlags() {
c.FlagSet.StringVar(&c.near, "near", "", "Node name to sort the node list in ascending "+ 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. "+ "order based on estimated round-trip time from that node. "+
"Passing \"_agent\" will use this agent's node for sorting.") "Passing \"_agent\" will use this agent's node for sorting.")
c.FlagSet.Var((*configutil.FlagMapValue)(&c.nodeMeta), "node-meta", "Metadata to "+ c.FlagSet.Var((*flags.FlagMapValue)(&c.nodeMeta), "node-meta", "Metadata to "+
"filter nodes with the given `key=value` pairs. This flag may be "+ "filter nodes with the given `key=value` pairs. This flag may be "+
"specified multiple times to filter on multiple sources of metadata.") "specified multiple times to filter on multiple sources of metadata.")
c.FlagSet.StringVar(&c.service, "service", "", "Service `id or name` to filter nodes. "+ c.FlagSet.StringVar(&c.service, "service", "", "Service `id or name` to filter nodes. "+

View File

@ -8,7 +8,7 @@ import (
"text/tabwriter" "text/tabwriter"
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/configutil" "github.com/hashicorp/consul/command/flags"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -29,7 +29,7 @@ func (c *CatalogListServicesCommand) initFlags() {
c.InitFlagSet() c.InitFlagSet()
c.FlagSet.StringVar(&c.node, "node", "", c.FlagSet.StringVar(&c.node, "node", "",
"Node `id or name` for which to list services.") "Node `id or name` for which to list services.")
c.FlagSet.Var((*configutil.FlagMapValue)(&c.nodeMeta), "node-meta", "Metadata to "+ c.FlagSet.Var((*flags.FlagMapValue)(&c.nodeMeta), "node-meta", "Metadata to "+
"filter nodes with the given `key=value` pairs. If specified, only "+ "filter nodes with the given `key=value` pairs. If specified, only "+
"services running on nodes matching the given metadata will be returned. "+ "services running on nodes matching the given metadata will be returned. "+
"This flag may be specified multiple times to filter on multiple sources "+ "This flag may be specified multiple times to filter on multiple sources "+

View File

@ -1,4 +1,4 @@
package configutil package flags
import ( import (
"fmt" "fmt"

View File

@ -1,14 +1,13 @@
package configutil package flags
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"testing"
"path" "path"
"reflect" "reflect"
"strings"
"testing"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
) )
@ -108,7 +107,7 @@ func TestConfigUtil_Visit(t *testing.T) {
return nil return nil
} }
basePath := "../test/command/merge" basePath := "../../test/command/merge"
if err := Visit(basePath, visitor); err != nil { if err := Visit(basePath, visitor); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }

View File

@ -1,4 +1,4 @@
package configutil package flags
import ( import (
"flag" "flag"

View File

@ -1,4 +1,4 @@
package configutil package flags
import ( import (
"fmt" "fmt"

View File

@ -1,4 +1,4 @@
package configutil package flags
import "strings" import "strings"

View File

@ -1,4 +1,4 @@
package configutil package flags
import ( import (
"flag" "flag"

92
command/flags/http.go Normal file
View File

@ -0,0 +1,92 @@
package flags
import (
"flag"
"github.com/hashicorp/consul/api"
)
type HTTPFlags struct {
// client api flags
address StringValue
token StringValue
caFile StringValue
caPath StringValue
certFile StringValue
keyFile StringValue
tlsServerName StringValue
// server flags
datacenter StringValue
stale BoolValue
}
func (f *HTTPFlags) ClientFlags() *flag.FlagSet {
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.Var(&f.address, "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.")
fs.Var(&f.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.")
fs.Var(&f.caFile, "ca-file",
"Path to a CA file to use for TLS when communicating with Consul. This "+
"can also be specified via the CONSUL_CACERT environment variable.")
fs.Var(&f.caPath, "ca-path",
"Path to a directory of CA certificates to use for TLS when communicating "+
"with Consul. This can also be specified via the CONSUL_CAPATH environment variable.")
fs.Var(&f.certFile, "client-cert",
"Path to a client cert file to use for TLS when 'verify_incoming' is enabled. This "+
"can also be specified via the CONSUL_CLIENT_CERT environment variable.")
fs.Var(&f.keyFile, "client-key",
"Path to a client key file to use for TLS when 'verify_incoming' is enabled. This "+
"can also be specified via the CONSUL_CLIENT_KEY environment variable.")
fs.Var(&f.tlsServerName, "tls-server-name",
"The server name to use as the SNI host when connecting via TLS. This "+
"can also be specified via the CONSUL_TLS_SERVER_NAME environment variable.")
return fs
}
func (f *HTTPFlags) ServerFlags() *flag.FlagSet {
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.Var(&f.datacenter, "datacenter",
"Name of the datacenter to query. If unspecified, this will default to "+
"the datacenter of the queried agent.")
fs.Var(&f.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 fs
}
func (f *HTTPFlags) Datacenter() string {
return f.datacenter.String()
}
func (f *HTTPFlags) Stale() bool {
if f.stale.v == nil {
return false
}
return *f.stale.v
}
func (f *HTTPFlags) APIClient() (*api.Client, error) {
c := api.DefaultConfig()
f.address.Merge(&c.Address)
f.token.Merge(&c.Token)
f.caFile.Merge(&c.TLSConfig.CAFile)
f.caPath.Merge(&c.TLSConfig.CAPath)
f.certFile.Merge(&c.TLSConfig.CertFile)
f.keyFile.Merge(&c.TLSConfig.KeyFile)
f.tlsServerName.Merge(&c.TLSConfig.Address)
f.datacenter.Merge(&c.Datacenter)
return api.NewClient(c)
}

15
command/flags/merge.go Normal file
View File

@ -0,0 +1,15 @@
package flags
import "flag"
func Merge(dst, src *flag.FlagSet) {
if dst == nil {
panic("dst cannot be nil")
}
if src == nil {
return
}
src.VisitAll(func(f *flag.Flag) {
dst.Var(f.Value, f.Name, f.DefValue)
})
}

115
command/flags/usage.go Normal file
View File

@ -0,0 +1,115 @@
package flags
import (
"bytes"
"flag"
"fmt"
"io"
"strings"
text "github.com/tonnerre/golang-text"
)
func Usage(txt string, cmdFlags, clientFlags, serverFlags *flag.FlagSet) string {
u := &Usager{
Usage: txt,
CmdFlags: cmdFlags,
HTTPClientFlags: clientFlags,
HTTPServerFlags: serverFlags,
}
return u.String()
}
type Usager struct {
Usage string
CmdFlags *flag.FlagSet
HTTPClientFlags *flag.FlagSet
HTTPServerFlags *flag.FlagSet
}
func (u *Usager) String() string {
out := new(bytes.Buffer)
out.WriteString(strings.TrimSpace(u.Usage))
out.WriteString("\n")
out.WriteString("\n")
httpFlags := u.HTTPClientFlags
if httpFlags == nil {
httpFlags = u.HTTPServerFlags
} else {
Merge(httpFlags, u.HTTPServerFlags)
}
if httpFlags != nil {
printTitle(out, "HTTP API Options")
httpFlags.VisitAll(func(f *flag.Flag) {
printFlag(out, f)
})
}
if u.CmdFlags != nil {
printTitle(out, "Command Options")
u.CmdFlags.VisitAll(func(f *flag.Flag) {
if flagContains(httpFlags, f) {
return
}
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 {
if fs == nil {
return false
}
var skip bool
fs.VisitAll(func(hf *flag.Flag) {
if skip {
return
}
if f.Name == hf.Name {
skip = true
return
}
})
return skip
}
// maxLineLength is the maximum width of any line.
const maxLineLength int = 72
// 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")
}

View File

@ -6,20 +6,20 @@ import (
"time" "time"
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/configutil" "github.com/hashicorp/consul/command/flags"
) )
type OperatorAutopilotSetCommand struct { type OperatorAutopilotSetCommand struct {
BaseCommand BaseCommand
// flags // flags
cleanupDeadServers configutil.BoolValue cleanupDeadServers flags.BoolValue
maxTrailingLogs configutil.UintValue maxTrailingLogs flags.UintValue
lastContactThreshold configutil.DurationValue lastContactThreshold flags.DurationValue
serverStabilizationTime configutil.DurationValue serverStabilizationTime flags.DurationValue
redundancyZoneTag configutil.StringValue redundancyZoneTag flags.StringValue
disableUpgradeMigration configutil.BoolValue disableUpgradeMigration flags.BoolValue
upgradeVersionTag configutil.StringValue upgradeVersionTag flags.StringValue
} }
func (c *OperatorAutopilotSetCommand) initFlags() { func (c *OperatorAutopilotSetCommand) initFlags() {