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 f8eb4006fa
commit e65bd2a268
13 changed files with 253 additions and 32 deletions

View File

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

View File

@ -5,7 +5,7 @@ import (
"strings"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/configutil"
"github.com/hashicorp/consul/command/flags"
"github.com/mitchellh/cli"
"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 "+
"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 "+
c.FlagSet.Var((*flags.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. "+

View File

@ -8,7 +8,7 @@ import (
"text/tabwriter"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/configutil"
"github.com/hashicorp/consul/command/flags"
"github.com/mitchellh/cli"
)
@ -29,7 +29,7 @@ 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 "+
c.FlagSet.Var((*flags.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 "+

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package configutil
package flags
import (
"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"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/configutil"
"github.com/hashicorp/consul/command/flags"
)
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
cleanupDeadServers flags.BoolValue
maxTrailingLogs flags.UintValue
lastContactThreshold flags.DurationValue
serverStabilizationTime flags.DurationValue
redundancyZoneTag flags.StringValue
disableUpgradeMigration flags.BoolValue
upgradeVersionTag flags.StringValue
}
func (c *OperatorAutopilotSetCommand) initFlags() {