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.
boundary/internal/cmd/base/base.go

333 lines
8.5 KiB

package base
import (
"bytes"
"context"
"flag"
"fmt"
"io"
"io/ioutil"
"regexp"
"strings"
"sync"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
const (
// maxLineLength is the maximum width of any line.
maxLineLength int = 78
// NotSetValue is a flag value for a not-set value
NotSetValue = "(not set)"
)
// reRemoveWhitespace is a regular expression for stripping whitespace from
// a string.
var reRemoveWhitespace = regexp.MustCompile(`[\s]+`)
type Command struct {
UI cli.Ui
Address string
Context context.Context
flags *FlagSets
flagsOnce sync.Once
flagCACert string
flagCAPath string
flagClientCert string
flagClientKey string
flagTLSServerName string
flagTLSInsecure bool
flagFormat string
flagField string
flagOutputCurlString bool
}
func (c *Command) SetAddress(addr string) {
c.Address = addr
}
type FlagSetBit uint
const (
FlagSetNone FlagSetBit = 1 << iota
FlagSetHTTP
FlagSetOutputField
FlagSetOutputFormat
)
// FlagSet creates the flags for this command. The result is cached on the
// command to save performance on future calls.
func (c *Command) FlagSet(bit FlagSetBit) *FlagSets {
c.flagsOnce.Do(func() {
set := NewFlagSets(c.UI)
// These flag sets will apply to all leaf subcommands.
// TODO: Optional, but FlagSetHTTP can be safely removed from the individual
// Flags() subcommands.
bit = bit | FlagSetHTTP
if bit&FlagSetHTTP != 0 {
f := set.NewFlagSet("HTTP Options")
addrStringVar := &StringVar{
Name: FlagNameAddress,
Target: &c.Address,
EnvVar: EnvWatchtowerAddress,
Completion: complete.PredictAnything,
Usage: "Address of the Watchtower controller.",
}
if c.Address != "" {
addrStringVar.Default = c.Address
} else {
addrStringVar.Default = "https://127.0.0.1:9200"
}
f.StringVar(addrStringVar)
f.StringVar(&StringVar{
Name: FlagNameCACert,
Target: &c.flagCACert,
Default: "",
EnvVar: EnvWatchtowerCACert,
Completion: complete.PredictFiles("*"),
Usage: "Path on the local disk to a single PEM-encoded CA " +
"certificate to verify the Controller or Worker's server's SSL certificate. This " +
"takes precedence over -ca-path.",
})
f.StringVar(&StringVar{
Name: FlagNameCAPath,
Target: &c.flagCAPath,
Default: "",
EnvVar: EnvWatchtowerCAPath,
Completion: complete.PredictDirs("*"),
Usage: "Path on the local disk to a directory of PEM-encoded CA " +
"certificates to verify the SSL certificate of the Controller.",
})
f.StringVar(&StringVar{
Name: FlagNameClientCert,
Target: &c.flagClientCert,
Default: "",
EnvVar: EnvWatchtowerClientCert,
Completion: complete.PredictFiles("*"),
Usage: "Path on the local disk to a single PEM-encoded CA " +
"certificate to use for TLS authentication to the Watchtower Controller. If " +
"this flag is specified, -client-key is also required.",
})
f.StringVar(&StringVar{
Name: FlagNameClientKey,
Target: &c.flagClientKey,
Default: "",
EnvVar: EnvWatchtowerClientKey,
Completion: complete.PredictFiles("*"),
Usage: "Path on the local disk to a single PEM-encoded private key " +
"matching the client certificate from -client-cert.",
})
f.StringVar(&StringVar{
Name: FlagTLSServerName,
Target: &c.flagTLSServerName,
Default: "",
EnvVar: EnvWatchtowerTLSServerName,
Completion: complete.PredictAnything,
Usage: "Name to use as the SNI host when connecting to the Watchtower " +
"server via TLS.",
})
f.BoolVar(&BoolVar{
Name: FlagNameTLSInsecure,
Target: &c.flagTLSInsecure,
Default: false,
EnvVar: EnvWatchtowerTLSInsecure,
Usage: "Disable verification of TLS certificates. Using this option " +
"is highly discouraged as it decreases the security of data " +
"transmissions to and from the Watchtower server.",
})
f.BoolVar(&BoolVar{
Name: "output-curl-string",
Target: &c.flagOutputCurlString,
Default: false,
Usage: "Instead of executing the request, print an equivalent cURL " +
"command string and exit.",
})
}
if bit&(FlagSetOutputField|FlagSetOutputFormat) != 0 {
f := set.NewFlagSet("Output Options")
if bit&FlagSetOutputField != 0 {
f.StringVar(&StringVar{
Name: "field",
Target: &c.flagField,
Default: "",
Completion: complete.PredictAnything,
Usage: "Print only the field with the given name. Specifying " +
"this option will take precedence over other formatting " +
"directives. The result will not have a trailing newline " +
"making it ideal for piping to other processes.",
})
}
if bit&FlagSetOutputFormat != 0 {
f.StringVar(&StringVar{
Name: "format",
Target: &c.flagFormat,
Default: "table",
EnvVar: EnvWatchtowerCLIFormat,
Completion: complete.PredictSet("table", "json", "yaml"),
Usage: "Print the output in the given format. Valid formats " +
"are \"table\", \"json\", or \"yaml\".",
})
}
}
c.flags = set
})
return c.flags
}
// FlagSets is a group of flag sets.
type FlagSets struct {
flagSets []*FlagSet
mainSet *flag.FlagSet
hiddens map[string]struct{}
completions complete.Flags
}
// NewFlagSets creates a new flag sets.
func NewFlagSets(ui cli.Ui) *FlagSets {
mainSet := flag.NewFlagSet("", flag.ContinueOnError)
// Errors and usage are controlled by the CLI.
mainSet.Usage = func() {}
mainSet.SetOutput(ioutil.Discard)
return &FlagSets{
flagSets: make([]*FlagSet, 0, 6),
mainSet: mainSet,
hiddens: make(map[string]struct{}),
completions: complete.Flags{},
}
}
// NewFlagSet creates a new flag set from the given flag sets.
func (f *FlagSets) NewFlagSet(name string) *FlagSet {
flagSet := NewFlagSet(name)
flagSet.mainSet = f.mainSet
flagSet.completions = f.completions
f.flagSets = append(f.flagSets, flagSet)
return flagSet
}
// Completions returns the completions for this flag set.
func (f *FlagSets) Completions() complete.Flags {
return f.completions
}
// Parse parses the given flags, returning any errors.
func (f *FlagSets) Parse(args []string) error {
return f.mainSet.Parse(args)
}
// Parsed reports whether the command-line flags have been parsed.
func (f *FlagSets) Parsed() bool {
return f.mainSet.Parsed()
}
// Args returns the remaining args after parsing.
func (f *FlagSets) Args() []string {
return f.mainSet.Args()
}
// Visit visits the flags in lexicographical order, calling fn for each. It
// visits only those flags that have been set.
func (f *FlagSets) Visit(fn func(*flag.Flag)) {
f.mainSet.Visit(fn)
}
// Help builds custom help for this command, grouping by flag set.
func (fs *FlagSets) Help() string {
var out bytes.Buffer
for _, set := range fs.flagSets {
printFlagTitle(&out, set.name+":")
set.VisitAll(func(f *flag.Flag) {
// Skip any hidden flags
if v, ok := f.Value.(FlagVisibility); ok && v.Hidden() {
return
}
printFlagDetail(&out, f)
})
}
return strings.TrimRight(out.String(), "\n")
}
// FlagSet is a grouped wrapper around a real flag set and a grouped flag set.
type FlagSet struct {
name string
flagSet *flag.FlagSet
mainSet *flag.FlagSet
completions complete.Flags
}
// NewFlagSet creates a new flag set.
func NewFlagSet(name string) *FlagSet {
return &FlagSet{
name: name,
flagSet: flag.NewFlagSet(name, flag.ContinueOnError),
}
}
// Name returns the name of this flag set.
func (f *FlagSet) Name() string {
return f.name
}
func (f *FlagSet) Visit(fn func(*flag.Flag)) {
f.flagSet.Visit(fn)
}
func (f *FlagSet) VisitAll(fn func(*flag.Flag)) {
f.flagSet.VisitAll(fn)
}
// printFlagTitle prints a consistently-formatted title to the given writer.
func printFlagTitle(w io.Writer, s string) {
fmt.Fprintf(w, "%s\n\n", s)
}
// printFlagDetail prints a single flag to the given writer.
func printFlagDetail(w io.Writer, f *flag.Flag) {
// Check if the flag is hidden - do not print any flag detail or help output
// if it is hidden.
if h, ok := f.Value.(FlagVisibility); ok && h.Hidden() {
return
}
// Check for a detailed example
example := ""
if t, ok := f.Value.(FlagExample); ok {
example = t.Example()
}
if example != "" {
fmt.Fprintf(w, " -%s=<%s>\n", f.Name, example)
} else {
fmt.Fprintf(w, " -%s\n", f.Name)
}
usage := reRemoveWhitespace.ReplaceAllString(f.Usage, " ")
indented := WrapAtLengthWithPadding(usage, 6)
fmt.Fprintf(w, "%s\n\n", indented)
}