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"
2023-09-17 19:35:43 -04:00
"errors"
2020-02-12 13:53:27 -05:00
"flag"
"fmt"
"io"
2020-05-04 23:04:55 -04:00
"os"
2020-06-24 13:08:13 -07:00
"os/signal"
2020-02-12 13:53:27 -05:00
"regexp"
"strings"
"sync"
2020-06-24 13:08:13 -07:00
"syscall"
2020-02-12 13:53:27 -05:00
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/api"
2022-03-16 17:04:07 -04:00
"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"
2022-03-16 17:04:07 -04:00
"github.com/hashicorp/go-hclog"
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
configutil "github.com/hashicorp/go-secure-stdlib/configutil/v2"
2022-08-05 16:08:48 -04:00
"github.com/hashicorp/go-secure-stdlib/parseutil"
2022-03-16 17:04:07 -04:00
"github.com/hashicorp/go-secure-stdlib/pluginutil/v2"
2020-02-12 13:53:27 -05:00
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
2021-11-18 11:53:45 -05:00
type EnabledPlugin uint
const (
EnabledPluginUnknown EnabledPlugin = iota
2023-04-08 16:15:14 -07:00
EnabledPluginLoopback
EnabledPluginAws
2021-11-18 11:53:45 -05:00
EnabledPluginHostAzure
2024-04-12 11:28:40 -04:00
EnabledPluginMinio
2024-11-08 11:09:29 -05:00
EnabledPluginGCP
2021-11-18 11:53:45 -05:00
)
2024-04-12 11:28:40 -04:00
// MinioEnabled controls if the Minio storage plugin should be initiated or not
var MinioEnabled bool
2021-11-18 11:53:45 -05:00
func ( e EnabledPlugin ) String () string {
switch e {
2023-04-08 16:15:14 -07:00
case EnabledPluginLoopback :
2021-11-18 16:01:15 -05:00
return "Loopback"
2023-04-08 16:15:14 -07:00
case EnabledPluginAws :
2021-11-18 11:53:45 -05:00
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"
2021-11-18 11:53:45 -05:00
default :
return ""
}
}
2021-03-04 20:05:17 -05:00
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
2024-09-20 12:33:48 -04:00
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]+` )
2020-08-20 13:34:03 -04:00
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
2023-11-01 12:45:35 -07:00
Opts [] Option
2020-02-12 13:53:27 -05:00
flags * FlagSets
flagsOnce sync . Once
2024-01-26 15:56:41 -08:00
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
2024-09-20 12:33:48 -04:00
flagFormat string
FlagToken string
FlagTokenName string
FlagKeyringType string
FlagRecoveryConfig string
FlagOutputCurlString bool
FlagSkipCacheDaemon bool
FlagSkipClientAgent bool
FlagOutputClientAgentCliError bool
2024-03-25 15:04:13 -07:00
2024-08-20 14:43:51 +01:00
FlagClientAgentPort uint16
2020-05-04 23:04:55 -04:00
2021-06-22 22:26:26 -04:00
FlagScopeId string
FlagScopeName string
2021-10-07 15:30:39 -04:00
FlagPluginId string
FlagPluginName string
2021-06-22 22:26:26 -04:00
FlagId string
FlagName string
FlagDescription string
FlagAuthMethodId string
FlagHostCatalogId string
FlagCredentialStoreId string
2024-08-16 15:28:20 +01:00
FlagVersion int64
2021-06-22 22:26:26 -04:00
FlagRecursive bool
FlagFilter string
2022-08-26 13:37:26 -07:00
FlagTags map [ string ][] string
2023-05-20 05:45:39 -04:00
FlagOutputFile string // the output file for the command
FlagNoClobber bool // Don't clobber the output file
2020-08-13 11:20:22 -04:00
2021-10-07 15:30:39 -04:00
// 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
2022-03-16 17:04:07 -04:00
// 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
}
2020-06-24 13:08:13 -07:00
// New returns a new instance of a base.Command type
2023-11-15 19:55:46 -05:00
func NewCommand ( ui cli . Ui , opt ... Option ) * Command {
2023-12-07 16:03:28 -08:00
opts := GetOpts ( opt ... )
2020-06-24 13:08:13 -07:00
ctx , cancel := context . WithCancel ( context . Background ())
ret := & Command {
UI : ui ,
ShutdownCh : MakeShutdownCh (),
Context : ctx ,
2023-11-15 19:55:46 -05:00
FlagId : opts . withImplicitId ,
2020-06-24 13:08:13 -07:00
}
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
}
2020-06-24 13:08:13 -07:00
// 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 {}{}
}
2020-06-24 13:08:13 -07:00
}()
return resultCh
}
2023-11-01 12:45:35 -07:00
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 ) {
2021-04-23 12:45:08 -05:00
// Read the cached client if present
2020-05-04 23:04:55 -04:00
if c . client != nil {
return c . client , nil
}
2023-11-01 12:45:35 -07:00
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
}
2024-01-26 15:56:41 -08:00
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 {
2023-09-17 19:35:43 -04:00
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 )
}
2020-08-20 13:34:03 -04:00
switch {
2022-08-05 19:27:14 -04:00
case opts . withNoTokenValue :
c . client . SetToken ( "" )
2020-08-20 13:34:03 -04:00
case c . FlagRecoveryConfig != "" :
2022-03-16 17:04:07 -04:00
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 ()),
)
2020-08-20 13:34:03 -04:00
if err != nil {
return nil , err
2020-07-14 14:32:47 -07:00
}
2020-08-27 16:38:44 -04:00
if wrapper == nil {
return nil , errors . New ( `No "kms" block with purpose "recovery" found` )
}
2022-03-16 17:04:07 -04:00
if cleanupFunc != nil {
c . WrapperCleanupFunc = cleanupFunc
2020-08-20 13:34:03 -04:00
}
2022-03-16 17:04:07 -04:00
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 ()
2020-10-01 01:52:08 -04:00
}
2022-03-16 17:04:07 -04:00
return nil
}
}
2020-10-01 01:52:08 -04:00
2020-09-21 12:20:44 -04:00
c . client . SetRecoveryKmsWrapper ( wrapper )
2020-08-20 13:34:03 -04:00
2022-08-05 16:08:48 -04:00
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 )
2022-08-05 19:27:14 -04:00
case os . Getenv ( envToken ) != "" :
2022-08-05 16:08:48 -04:00
// Backwards compat: allow reading from existing BOUNDARY_TOKEN env var
2022-08-05 19:27:14 -04:00
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.` )
2022-08-05 16:08:48 -04:00
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-08-25 00:20:50 -04:00
}
2020-10-21 09:00:17 -04:00
authToken := c . ReadTokenFromKeyring ( keyringType , tokenName )
if authToken != nil {
c . client . SetToken ( authToken . Token )
2020-07-14 14:32:47 -07:00
}
}
2020-05-04 23:04:55 -04:00
return c . client , nil
2020-02-12 13:53:27 -05:00
}
2024-02-13 15:56:13 -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
2020-07-14 14:32:47 -07:00
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 ,
2024-01-26 15:56:41 -08:00
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
})
2020-07-14 14:32:47 -07: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-07-14 14:32:47 -07:00
})
2020-02-12 13:53:27 -05:00
2022-08-05 16:08:48 -04: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.` ,
})
2020-08-20 13:34:03 -04:00
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 ,
2020-09-19 00:18:17 -04:00
Usage : "Instead of executing the request, print an equivalent cURL command string and exit." ,
2020-02-12 13:53:27 -05:00
})
2023-11-01 12:45:35 -07: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." ,
})
2024-03-25 15:04:13 -07:00
2024-09-20 12:33:48 -04:00
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." ,
})
2024-03-25 15:04:13 -07:00
f . BoolVar ( & BoolVar {
2024-05-17 16:50:08 -07:00
Name : "skip-client-agent" ,
Target : & c . FlagSkipClientAgent ,
2024-03-25 15:04:13 -07:00
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 ,
2024-03-25 15:04:13 -07:00
})
2024-08-20 14:43:51 +01:00
f . Uint16Var ( & Uint16Var {
2024-05-17 16:50:08 -07:00
Name : "client-agent-port" ,
Target : & c . FlagClientAgentPort ,
2024-03-25 15:04:13 -07:00
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 ,
2024-03-25 15:04:13 -07:00
})
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" )
2020-09-16 08:50:43 -04:00
/*
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" ),
2020-09-19 00:18:17 -04:00
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 )
}