mirror of https://github.com/hashicorp/boundary
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.
333 lines
8.5 KiB
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)
|
|
}
|