Files
threshold/internal/cmd/base/base.go
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

693 lines
19 KiB
Go
Raw Normal View History

2020-10-21 09:00:17 -04:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-02-12 13:53:27 -05:00
package base
import (
"bytes"
"context"
"errors"
2020-02-12 13:53:27 -05:00
"flag"
"fmt"
"io"
2020-05-04 23:04:55 -04:00
"os"
"os/signal"
2020-02-12 13:53:27 -05:00
"regexp"
"strings"
"sync"
"syscall"
2020-02-12 13:53:27 -05:00
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/globals"
kms_plugin_assets "github.com/hashicorp/boundary/plugins/kms"
2021-09-08 12:15:42 -04:00
"github.com/hashicorp/boundary/sdk/wrapper"
"github.com/hashicorp/go-hclog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
configutil "github.com/hashicorp/go-secure-stdlib/configutil/v2"
"github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/hashicorp/go-secure-stdlib/pluginutil/v2"
2020-02-12 13:53:27 -05:00
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
type EnabledPlugin uint
const (
EnabledPluginUnknown EnabledPlugin = iota
EnabledPluginLoopback
EnabledPluginAws
EnabledPluginHostAzure
2024-04-12 11:28:40 -04:00
EnabledPluginMinio
2024-11-08 11:09:29 -05:00
EnabledPluginGCP
)
2024-04-12 11:28:40 -04:00
// MinioEnabled controls if the Minio storage plugin should be initiated or not
var MinioEnabled bool
func (e EnabledPlugin) String() string {
switch e {
case EnabledPluginLoopback:
return "Loopback"
case EnabledPluginAws:
return "AWS"
case EnabledPluginHostAzure:
return "Azure"
2024-04-12 11:28:40 -04:00
case EnabledPluginMinio:
return "MinIO"
2024-11-08 11:09:29 -05:00
case EnabledPluginGCP:
return "GCP"
default:
return ""
}
}
const (
CommandSuccess int = iota
CommandApiError
CommandCliError
CommandUserError
)
2020-02-12 13:53:27 -05:00
const (
// maxLineLength is the maximum width of any line.
maxLineLength int = 78
envToken = "BOUNDARY_TOKEN"
EnvTokenName = "BOUNDARY_TOKEN_NAME"
EnvKeyringType = "BOUNDARY_KEYRING_TYPE"
envRecoveryConfig = "BOUNDARY_RECOVERY_CONFIG"
envSkipCacheDaemon = "BOUNDARY_SKIP_CACHE_DAEMON"
envSkipClientAgent = "BOUNDARY_SKIP_CLIENT_AGENT"
EnvClientAgentPort = "BOUNDARY_CLIENT_AGENT_LISTENING_PORT"
EnvBoundaryClientAgentCliErrorOutput = "BOUNDARY_CLIENT_AGENT_CLI_ERROR_OUTPUT"
2021-04-08 20:54:32 -04:00
StoredTokenName = "HashiCorp Boundary Auth Token"
2020-02-12 13:53:27 -05:00
)
// reRemoveWhitespace is a regular expression for stripping whitespace from
// a string.
var reRemoveWhitespace = regexp.MustCompile(`[\s]+`)
var DevOnlyControllerFlags = func(*Command, *FlagSet) {}
2020-03-10 15:48:04 -04:00
type Command struct {
2022-09-19 09:59:18 -04:00
Context context.Context
ContextCancel context.CancelFunc
UI cli.Ui
ShutdownCh chan struct{}
2020-02-12 13:53:27 -05:00
Opts []Option
2020-02-12 13:53:27 -05:00
flags *FlagSets
flagsOnce sync.Once
FlagAddr string
2020-08-17 15:38:41 -04:00
flagVerbose bool
2020-05-04 23:04:55 -04:00
flagTLSCACert string
flagTLSCAPath string
flagTLSClientCert string
flagTLSClientKey string
2020-02-12 13:53:27 -05:00
flagTLSServerName string
flagTLSInsecure bool
flagFormat string
FlagToken string
FlagTokenName string
FlagKeyringType string
FlagRecoveryConfig string
FlagOutputCurlString bool
FlagSkipCacheDaemon bool
FlagSkipClientAgent bool
FlagOutputClientAgentCliError bool
FlagClientAgentPort uint16
2020-05-04 23:04:55 -04:00
FlagScopeId string
FlagScopeName string
FlagPluginId string
FlagPluginName string
FlagId string
FlagName string
FlagDescription string
FlagAuthMethodId string
FlagHostCatalogId string
FlagCredentialStoreId string
FlagVersion int64
FlagRecursive bool
FlagFilter string
FlagTags map[string][]string
FlagOutputFile string // the output file for the command
FlagNoClobber bool // Don't clobber the output file
// Attribute values
FlagAttributes string
FlagAttrs []CombinedSliceFlagValue
// Secret values
FlagSecrets string
FlagScrts []CombinedSliceFlagValue
2022-09-16 15:00:04 -07:00
// Object values
FlagObject string
FlagKv []CombinedSliceFlagValue
2020-05-04 23:04:55 -04:00
client *api.Client
// This will be intialized, if needed, in Config() when instantiating a
// recovery wrapper, if requested. It's then called as a deferred function
// on the Run method of the various generated commands.
WrapperCleanupFunc func() error
2020-02-12 13:53:27 -05:00
}
// New returns a new instance of a base.Command type
func NewCommand(ui cli.Ui, opt ...Option) *Command {
2023-12-07 16:03:28 -08:00
opts := GetOpts(opt...)
ctx, cancel := context.WithCancel(context.Background())
ret := &Command{
UI: ui,
ShutdownCh: MakeShutdownCh(),
Context: ctx,
FlagId: opts.withImplicitId,
}
go func() {
<-ret.ShutdownCh
cancel()
}()
return ret
}
2022-09-19 09:59:18 -04:00
// New returns a new instance of a base.Command type that does not intercept the shutdown channel
func NewServerCommand(ui cli.Ui) *Command {
ctx, cancel := context.WithCancel(context.Background())
ret := &Command{
UI: ui,
ShutdownCh: MakeShutdownCh(),
Context: ctx,
ContextCancel: cancel,
}
return ret
}
// MakeShutdownCh returns a channel that can be used for shutdown
// notifications for commands. This channel will send a message for every
// SIGINT or SIGTERM received.
func MakeShutdownCh() chan struct{} {
resultCh := make(chan struct{})
shutdownCh := make(chan os.Signal, 4)
signal.Notify(shutdownCh, os.Interrupt, syscall.SIGTERM)
go func() {
2022-09-19 09:59:18 -04:00
for {
<-shutdownCh
resultCh <- struct{}{}
}
}()
return resultCh
}
func (c *Command) BaseCommand() *Command {
return c
}
2020-05-04 23:04:55 -04:00
// Client returns the HTTP API client. The client is cached on the command to
// save performance on future calls.
2020-08-18 17:46:34 -04:00
func (c *Command) Client(opt ...Option) (*api.Client, error) {
// Read the cached client if present
2020-05-04 23:04:55 -04:00
if c.client != nil {
return c.client, nil
}
opts := GetOpts(opt...)
2020-08-18 17:46:34 -04:00
2020-05-04 23:04:55 -04:00
config, err := api.DefaultConfig()
if err != nil {
return nil, err
}
2023-06-11 20:40:47 -04:00
if c.FlagOutputCurlString {
config.OutputCurlString = c.FlagOutputCurlString
2020-05-04 23:04:55 -04:00
}
c.client, err = api.NewClient(config)
if err != nil {
return nil, err
}
if c.FlagAddr != "" {
if err := c.client.SetAddr(c.FlagAddr); err != nil {
2020-08-07 13:50:39 -04:00
return nil, fmt.Errorf("error setting address on client: %w", err)
}
2020-05-04 23:04:55 -04:00
}
// If we need custom TLS configuration, then set it
var modifiedTLS bool
tlsConfig := config.TLSConfig
2020-09-24 12:19:00 -04:00
if c.flagTLSCACert != "" {
2020-05-04 23:04:55 -04:00
tlsConfig.CACert = c.flagTLSCACert
modifiedTLS = true
}
2020-09-24 12:19:00 -04:00
if c.flagTLSCAPath != "" {
2020-05-04 23:04:55 -04:00
tlsConfig.CAPath = c.flagTLSCAPath
modifiedTLS = true
}
2020-09-24 12:19:00 -04:00
if c.flagTLSClientCert != "" {
2020-05-04 23:04:55 -04:00
tlsConfig.ClientCert = c.flagTLSClientCert
modifiedTLS = true
}
2020-09-24 12:19:00 -04:00
if c.flagTLSClientKey != "" {
2020-05-04 23:04:55 -04:00
tlsConfig.ClientKey = c.flagTLSClientKey
modifiedTLS = true
}
2020-09-24 12:19:00 -04:00
if c.flagTLSServerName != "" {
2020-05-04 23:04:55 -04:00
tlsConfig.ServerName = c.flagTLSServerName
modifiedTLS = true
}
if c.flagTLSInsecure {
tlsConfig.Insecure = c.flagTLSInsecure
modifiedTLS = true
}
if modifiedTLS {
// Setup TLS config
if err := c.client.SetTLSConfig(tlsConfig); err != nil {
return nil, fmt.Errorf("failed to setup TLS config: %w", err)
2020-05-04 23:04:55 -04:00
}
}
// Turn off retries on the CLI
2020-08-10 17:15:02 -04:00
if os.Getenv(api.EnvBoundaryMaxRetries) == "" {
2020-05-04 23:04:55 -04:00
c.client.SetMaxRetries(0)
}
switch {
case opts.withNoTokenValue:
c.client.SetToken("")
case c.FlagRecoveryConfig != "":
wrapper, cleanupFunc, err := wrapper.GetWrapperFromPath(
c.Context,
c.FlagRecoveryConfig,
globals.KmsPurposeRecovery,
configutil.WithPluginOptions(
pluginutil.WithPluginsMap(kms_plugin_assets.BuiltinKmsPlugins()),
pluginutil.WithPluginsFilesystem(kms_plugin_assets.KmsPluginPrefix, kms_plugin_assets.FileSystem()),
),
// TODO: How would we want to expose this kind of log to users when
// using recovery configs? Generally with normal CLI commands we
// don't print out all of these logs. We may want a logger with a
// custom writer behind our existing gate where we print nothing
// unless there is an error, then dump all of it.
configutil.WithLogger(hclog.NewNullLogger()),
)
if err != nil {
return nil, err
}
if wrapper == nil {
return nil, errors.New(`No "kms" block with purpose "recovery" found`)
}
if cleanupFunc != nil {
c.WrapperCleanupFunc = cleanupFunc
}
if ifWrapper, ok := wrapper.(wrapping.InitFinalizer); ok {
if err := ifWrapper.Init(c.Context); err != nil && !errors.Is(err, wrapping.ErrFunctionNotImplemented) {
return nil, fmt.Errorf("Error initializing kms: %w", err)
}
currCleanupFunc := c.WrapperCleanupFunc
c.WrapperCleanupFunc = func() error {
if err := ifWrapper.Finalize(context.Background()); err != nil && !errors.Is(err, wrapping.ErrFunctionNotImplemented) {
c.PrintCliError(fmt.Errorf("An error was encountered finalizing the kms: %w", err))
}
if currCleanupFunc != nil {
return currCleanupFunc()
}
return nil
}
}
c.client.SetRecoveryKmsWrapper(wrapper)
case c.FlagToken != "":
token, err := parseutil.MustParsePath(c.FlagToken)
switch {
case err == nil:
case errors.Is(err, parseutil.ErrNotParsed):
return nil, errors.New("Token flag must be used with env:// or file:// syntax")
default:
return nil, fmt.Errorf("error parsing token flag: %w", err)
}
c.client.SetToken(token)
case os.Getenv(envToken) != "":
// Backwards compat: allow reading from existing BOUNDARY_TOKEN env var
c.UI.Warn(`Direct usage of BOUNDARY_TOKEN env var is deprecated; please use "-token env://<env var name>" format, e.g. "-token env://BOUNDARY_TOKEN" to specify an env var to use.`)
c.client.SetToken(os.Getenv(envToken))
2021-04-08 20:54:32 -04:00
case c.client.Token() == "" && strings.ToLower(c.FlagKeyringType) != "none":
2020-10-21 09:00:17 -04:00
keyringType, tokenName, err := c.DiscoverKeyringTokenInfo()
if err != nil {
return nil, err
}
2020-10-21 09:00:17 -04:00
authToken := c.ReadTokenFromKeyring(keyringType, tokenName)
if authToken != nil {
c.client.SetToken(authToken.Token)
}
}
2020-05-04 23:04:55 -04:00
return c.client, nil
2020-02-12 13:53:27 -05:00
}
// If the first arg isn't a flag, extract it as the alias and return the remaining args
func ExtractAliasFromArgs(inArgs []string) (string, []string) {
if len(inArgs) > 0 && inArgs[0][0] != '-' {
return inArgs[0], inArgs[1:]
}
return "", inArgs
}
2020-02-12 13:53:27 -05:00
type FlagSetBit uint
const (
FlagSetNone FlagSetBit = 1 << iota
FlagSetHTTP
FlagSetClient
2020-02-12 13:53:27 -05:00
FlagSetOutputFormat
)
// FlagSet creates the flags for this command. The result is cached on the
// command to save performance on future calls.
2020-03-10 15:48:04 -04:00
func (c *Command) FlagSet(bit FlagSetBit) *FlagSets {
2020-02-12 13:53:27 -05:00
c.flagsOnce.Do(func() {
set := NewFlagSets(c.UI)
if bit&FlagSetHTTP != 0 {
2020-05-04 23:04:55 -04:00
f := set.NewFlagSet("Connection Options")
2020-02-12 13:53:27 -05:00
2020-05-04 23:04:55 -04:00
f.StringVar(&StringVar{
2020-05-05 11:19:42 -04:00
Name: FlagNameAddr,
Target: &c.FlagAddr,
2020-08-10 17:15:02 -04:00
EnvVar: api.EnvBoundaryAddr,
2020-02-12 13:53:27 -05:00
Completion: complete.PredictAnything,
2020-08-10 17:15:02 -04:00
Usage: "Addr of the Boundary controller, as a complete URL (e.g. https://boundary.example.com:9200).",
2020-05-04 23:04:55 -04:00
})
2020-02-12 13:53:27 -05:00
f.StringVar(&StringVar{
Name: FlagNameCACert,
2020-05-04 23:04:55 -04:00
Target: &c.flagTLSCACert,
2020-08-10 17:15:02 -04:00
EnvVar: api.EnvBoundaryCACert,
2020-02-12 13:53:27 -05:00
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,
2020-05-04 23:04:55 -04:00
Target: &c.flagTLSCAPath,
2020-08-10 17:15:02 -04:00
EnvVar: api.EnvBoundaryCAPath,
2020-02-12 13:53:27 -05:00
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,
2020-05-04 23:04:55 -04:00
Target: &c.flagTLSClientCert,
2020-08-10 17:15:02 -04:00
EnvVar: api.EnvBoundaryClientCert,
2020-02-12 13:53:27 -05:00
Completion: complete.PredictFiles("*"),
Usage: "Path on the local disk to a single PEM-encoded CA " +
2020-08-10 17:15:02 -04:00
"certificate to use for TLS authentication to the Boundary Controller. If " +
2020-02-12 13:53:27 -05:00
"this flag is specified, -client-key is also required.",
})
f.StringVar(&StringVar{
Name: FlagNameClientKey,
2020-05-04 23:04:55 -04:00
Target: &c.flagTLSClientKey,
2020-08-10 17:15:02 -04:00
EnvVar: api.EnvBoundaryClientKey,
2020-02-12 13:53:27 -05:00
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,
2020-08-10 17:15:02 -04:00
EnvVar: api.EnvBoundaryTLSServerName,
2020-02-12 13:53:27 -05:00
Completion: complete.PredictAnything,
2020-08-10 17:15:02 -04:00
Usage: "Name to use as the SNI host when connecting to the Boundary " +
2020-02-12 13:53:27 -05:00
"server via TLS.",
})
f.BoolVar(&BoolVar{
2020-05-04 23:04:55 -04:00
Name: FlagNameTLSInsecure,
Target: &c.flagTLSInsecure,
2020-08-10 17:15:02 -04:00
EnvVar: api.EnvBoundaryTLSInsecure,
2020-02-12 13:53:27 -05:00
Usage: "Disable verification of TLS certificates. Using this option " +
"is highly discouraged as it decreases the security of data " +
2020-08-10 17:15:02 -04:00
"transmissions to and from the Boundary server.",
2020-02-12 13:53:27 -05:00
})
}
if bit&FlagSetClient != 0 {
f := set.NewFlagSet("Client Options")
f.StringVar(&StringVar{
Name: "token-name",
Target: &c.FlagTokenName,
2020-09-25 17:06:53 -04:00
EnvVar: EnvTokenName,
2020-10-21 09:00:17 -04:00
Usage: `If specified, the given value will be used as the name when storing the token in the system credential store. This can allow switching user identities for different commands.`,
})
f.StringVar(&StringVar{
Name: "keyring-type",
Target: &c.FlagKeyringType,
Default: "auto",
EnvVar: EnvKeyringType,
Usage: `The type of keyring to use. Defaults to "auto" which will use the Windows credential manager, OSX keychain, or cross-platform password store depending on platform. Set to "none" to disable keyring functionality. Available types, depending on platform, are: "wincred", "keychain", "pass", and "secret-service".`,
})
2020-02-12 13:53:27 -05:00
f.StringVar(&StringVar{
Name: "token",
Target: &c.FlagToken,
Usage: `A URL pointing to a file on disk (file://) from which a token will be read or an env var (env://) from which the token will be read. Overrides the "token-name" parameter.`,
})
f.StringVar(&StringVar{
Name: "recovery-config",
Target: &c.FlagRecoveryConfig,
EnvVar: envRecoveryConfig,
Usage: `If specified, the given config file will be parsed for a "kms" block with purpose "recovery" and will use the recovery mechanism to authorize the call."`,
})
2020-02-12 13:53:27 -05:00
f.BoolVar(&BoolVar{
2020-05-04 23:04:55 -04:00
Name: "output-curl-string",
2023-06-11 20:40:47 -04:00
Target: &c.FlagOutputCurlString,
Usage: "Instead of executing the request, print an equivalent cURL command string and exit.",
2020-02-12 13:53:27 -05:00
})
f.BoolVar(&BoolVar{
Name: "skip-cache-daemon",
Target: &c.FlagSkipCacheDaemon,
Default: false,
EnvVar: envSkipCacheDaemon,
Usage: "Skips starting the caching daemon or sending the current used/retrieved token to the caching daemon.",
})
f.BoolVar(&BoolVar{
Name: "output-client-agent-cli-error",
Target: &c.FlagOutputClientAgentCliError,
Default: false,
EnvVar: EnvBoundaryClientAgentCliErrorOutput,
Usage: "Enables outputting CLI errors encountered for client-agent callbacks.",
})
f.BoolVar(&BoolVar{
2024-05-17 16:50:08 -07:00
Name: "skip-client-agent",
Target: &c.FlagSkipClientAgent,
Default: false,
2024-05-17 16:50:08 -07:00
EnvVar: envSkipClientAgent,
Usage: "Skips sending the auth token used for this command to the client agent if it is running.",
2024-04-26 13:48:56 -07:00
Hidden: true,
})
f.Uint16Var(&Uint16Var{
2024-05-17 16:50:08 -07:00
Name: "client-agent-port",
Target: &c.FlagClientAgentPort,
Default: 9300,
2024-05-17 16:50:08 -07:00
EnvVar: EnvClientAgentPort,
Usage: "The port on which the client agent is listening.",
2024-04-26 13:48:56 -07:00
Hidden: true,
})
2020-02-12 13:53:27 -05:00
}
2020-08-17 15:38:41 -04:00
if bit&FlagSetOutputFormat != 0 {
2020-02-12 13:53:27 -05:00
f := set.NewFlagSet("Output Options")
/*
f.BoolVar(&BoolVar{
Name: "verbose",
Target: &c.flagVerbose,
Completion: complete.PredictAnything,
Usage: "Turns on some extra verbosity in the command output.",
})
*/
2020-02-12 13:53:27 -05:00
if bit&FlagSetOutputFormat != 0 {
f.StringVar(&StringVar{
Name: "format",
Target: &c.flagFormat,
Default: "table",
2020-08-10 17:15:02 -04:00
EnvVar: EnvBoundaryCLIFormat,
2020-02-12 13:53:27 -05:00
Completion: complete.PredictSet("table", "json", "yaml"),
Usage: "Print the output in the given format. Valid formats are \"table\" or \"json\".",
2020-02-12 13:53:27 -05:00
})
}
}
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() {}
2024-02-14 17:14:31 -05:00
mainSet.SetOutput(io.Discard)
2020-02-12 13:53:27 -05:00
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.
2024-02-13 10:54:48 -05:00
func (fs *FlagSets) NewFlagSet(name string) *FlagSet {
2020-02-12 13:53:27 -05:00
flagSet := NewFlagSet(name)
2024-02-13 10:54:48 -05:00
flagSet.mainSet = fs.mainSet
flagSet.completions = fs.completions
fs.flagSets = append(fs.flagSets, flagSet)
2020-02-12 13:53:27 -05:00
return flagSet
}
// Completions returns the completions for this flag set.
2024-02-13 10:54:48 -05:00
func (fs *FlagSets) Completions() complete.Flags {
if fs == nil {
2020-09-21 20:25:16 -04:00
return nil
}
2024-02-13 10:54:48 -05:00
return fs.completions
2020-02-12 13:53:27 -05:00
}
// Parse parses the given flags, returning any errors.
2024-02-13 10:54:48 -05:00
func (fs *FlagSets) Parse(args []string) error {
return fs.mainSet.Parse(args)
2020-02-12 13:53:27 -05:00
}
// Parsed reports whether the command-line flags have been parsed.
2024-02-13 10:54:48 -05:00
func (fs *FlagSets) Parsed() bool {
return fs.mainSet.Parsed()
2020-02-12 13:53:27 -05:00
}
// Args returns the remaining args after parsing.
2024-02-13 10:54:48 -05:00
func (fs *FlagSets) Args() []string {
return fs.mainSet.Args()
2020-02-12 13:53:27 -05:00
}
// Visit visits the flags in lexicographical order, calling fn for each. It
// visits only those flags that have been set.
2024-02-13 10:54:48 -05:00
func (fs *FlagSets) Visit(fn func(*flag.Flag)) {
fs.mainSet.Visit(fn)
2020-02-12 13:53:27 -05:00
}
// 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)
}