From 95c507fad5cb3914300f584fea4871864e8cecd0 Mon Sep 17 00:00:00 2001 From: Todd Date: Mon, 25 Mar 2024 15:04:13 -0700 Subject: [PATCH] Add hidden ferry commands and auto adding tokens to ferry daemon (#4572) * Add boundary ferry command (#4462) * Add boundary ferry status (#4463) * Add sending auth token automatically to ferry daemon (#4465) * Hide ferry cmds, background error msgs, and dont retry background add-token requests (#4571) --- ...command_wrapper.go => wrapper_register.go} | 63 +-- internal/cmd/base/base.go | 21 + internal/cmd/commands.go | 416 +++++++++--------- internal/cmd/commands/ferry/addtoken.go | 75 ++++ internal/cmd/commands/ferry/ferry.go | 63 +++ internal/cmd/commands/ferry/status.go | 175 ++++++++ .../cmd/commands/ferry/wrapper_register.go | 42 ++ internal/cmd/ferry_cmd_darwin.go | 25 ++ internal/cmd/ferry_cmd_windows.go | 27 ++ internal/cmd/main.go | 6 +- internal/cmd/search_cmd_darwin.go | 8 - internal/cmd/search_cmd_freebsd.go | 8 - internal/cmd/search_cmd_linux.go | 8 - internal/cmd/search_cmd_windows.go | 8 - internal/cmd/wrapper/command_wrapper.go | 74 ++++ 15 files changed, 728 insertions(+), 291 deletions(-) rename internal/clientcache/cmd/daemon/{command_wrapper.go => wrapper_register.go} (62%) create mode 100644 internal/cmd/commands/ferry/addtoken.go create mode 100644 internal/cmd/commands/ferry/ferry.go create mode 100644 internal/cmd/commands/ferry/status.go create mode 100644 internal/cmd/commands/ferry/wrapper_register.go create mode 100644 internal/cmd/ferry_cmd_darwin.go create mode 100644 internal/cmd/ferry_cmd_windows.go create mode 100644 internal/cmd/wrapper/command_wrapper.go diff --git a/internal/clientcache/cmd/daemon/command_wrapper.go b/internal/clientcache/cmd/daemon/wrapper_register.go similarity index 62% rename from internal/clientcache/cmd/daemon/command_wrapper.go rename to internal/clientcache/cmd/daemon/wrapper_register.go index 37fd94838..72b93be4a 100644 --- a/internal/clientcache/cmd/daemon/command_wrapper.go +++ b/internal/clientcache/cmd/daemon/wrapper_register.go @@ -16,57 +16,31 @@ import ( "github.com/hashicorp/boundary/internal/clientcache/internal/daemon" "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/wrapper" "github.com/mitchellh/cli" ) -// Keep this interface aligned with the interface at internal/cmd/commands.go -type cacheEnabledCommand interface { - cli.Command - BaseCommand() *base.Command -} - -// CommandWrapper starts the boundary daemon after the command was Run and attempts -// to send the current persona to any running daemon. -type CommandWrapper struct { - cacheEnabledCommand -} - -// Wrap returns a cli.CommandFactory that returns a command wrapped in the CommandWrapper. -func Wrap(c cacheEnabledCommand) cli.CommandFactory { - return func() (cli.Command, error) { - return &CommandWrapper{ - cacheEnabledCommand: c, - }, nil +func init() { + if err := wrapper.RegisterSuccessfulCommandCallback("clientcache", hook); err != nil { + panic(err) } } -// Run runs the wrapped command and then attempts to start the boundary daemon and send -// the current persona -func (w *CommandWrapper) Run(args []string) int { - // potentially intercept the token in case it isn't stored in the keyring - var token string - w.cacheEnabledCommand.BaseCommand().Opts = append(w.cacheEnabledCommand.BaseCommand().Opts, base.WithInterceptedToken(&token)) - r := w.cacheEnabledCommand.Run(args) - if w.BaseCommand().FlagSkipCacheDaemon { - return r +// hook is the callback that is registered with the wrapper package to be called. +// The daemon is not started and the token is not added to the cache if the flag +// SkipCacheDaemon is set. +func hook(ctx context.Context, baseCmd *base.Command, token string) { + if baseCmd.FlagSkipCacheDaemon { + return } - - if r != base.CommandSuccess { - // if we were not successful in running our command, do not continue to - // start the daemon and add the token. - return r + if startDaemon(ctx, baseCmd) { + addTokenToCache(ctx, baseCmd, token) } - - ctx := context.Background() - if w.startDaemon(ctx) { - w.addTokenToCache(ctx, token) - } - return r } // startDaemon attempts to start a daemon and returns true if we have attempted to start // the daemon and either it was successful or it was already running. -func (w *CommandWrapper) startDaemon(ctx context.Context) bool { +func startDaemon(ctx context.Context, baseCmd *base.Command) bool { // Ignore errors related to checking if the process is already running since // this can fall back to running the process. if dotPath, err := DefaultDotDirectory(ctx); err == nil { @@ -79,7 +53,7 @@ func (w *CommandWrapper) startDaemon(ctx context.Context) bool { cmdName, err := os.Executable() if err != nil { - w.BaseCommand().UI.Error(fmt.Sprintf("unable to find boundary binary for daemon startup: %s", err.Error())) + baseCmd.UI.Error(fmt.Sprintf("unable to find boundary binary for daemon startup: %s", err.Error())) return false } @@ -105,13 +79,13 @@ func silentUi() *cli.BasicUi { // addTokenToCache runs AddTokenCommand with the token used in, or retrieved by // the wrapped command. -func (w *CommandWrapper) addTokenToCache(ctx context.Context, token string) bool { - com := AddTokenCommand{Command: base.NewCommand(w.BaseCommand().UI)} - client, err := w.BaseCommand().Client() +func addTokenToCache(ctx context.Context, baseCmd *base.Command, token string) bool { + com := AddTokenCommand{Command: base.NewCommand(baseCmd.UI)} + client, err := baseCmd.Client() if err != nil { return false } - keyringType, tokName, err := w.BaseCommand().DiscoverKeyringTokenInfo() + keyringType, tokName, err := baseCmd.DiscoverKeyringTokenInfo() if err != nil && token == "" { return false } @@ -138,7 +112,6 @@ func (w *CommandWrapper) addTokenToCache(ctx context.Context, token string) bool // provided context is done. It returns an error if the unix socket is not found // before the context is done. func waitForDaemon(ctx context.Context) error { - const op = "daemon.waitForDaemon" dotPath, err := DefaultDotDirectory(ctx) if err != nil { return err diff --git a/internal/cmd/base/base.go b/internal/cmd/base/base.go index e6b324c27..082d04233 100644 --- a/internal/cmd/base/base.go +++ b/internal/cmd/base/base.go @@ -68,6 +68,8 @@ const ( EnvKeyringType = "BOUNDARY_KEYRING_TYPE" envRecoveryConfig = "BOUNDARY_RECOVERY_CONFIG" envSkipCacheDaemon = "BOUNDARY_SKIP_CACHE_DAEMON" + envSkipFerry = "BOUNDARY_SKIP_FERRY_DAEMON" + EnvFerryDaemonPort = "BOUNDARY_FERRY_LISTENING_PORT" StoredTokenName = "HashiCorp Boundary Auth Token" ) @@ -106,6 +108,9 @@ type Command struct { FlagRecoveryConfig string FlagOutputCurlString bool FlagSkipCacheDaemon bool + FlagSkipFerry bool + + FlagFerryDaemonPort uint FlagScopeId string FlagScopeName string @@ -479,6 +484,22 @@ func (c *Command) FlagSet(bit FlagSetBit) *FlagSets { EnvVar: envSkipCacheDaemon, Usage: "Skips starting the caching daemon or sending the current used/retrieved token to the caching daemon.", }) + + f.BoolVar(&BoolVar{ + Name: "skip-ferry-daemon", + Target: &c.FlagSkipFerry, + Default: false, + EnvVar: envSkipFerry, + Usage: "Skips sending the auth token used for this command to the ferry daemon if it is running.", + }) + + f.UintVar(&UintVar{ + Name: "ferry-port", + Target: &c.FlagFerryDaemonPort, + Default: 9300, + EnvVar: EnvFerryDaemonPort, + Usage: "The port on which the ferry daemon is listening.", + }) } if bit&FlagSetOutputFormat != 0 { diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index 9f48605b4..438755507 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -37,6 +37,7 @@ import ( "github.com/hashicorp/boundary/internal/cmd/commands/userscmd" "github.com/hashicorp/boundary/internal/cmd/commands/version" "github.com/hashicorp/boundary/internal/cmd/commands/workerscmd" + "github.com/hashicorp/boundary/internal/cmd/wrapper" "github.com/mitchellh/cli" ) @@ -70,19 +71,19 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { }, nil }, - "authenticate": clientCacheWrapper( + "authenticate": wrapper.Wrap( &authenticate.Command{ Command: base.NewCommand(ui, opts...), }), - "authenticate password": clientCacheWrapper( + "authenticate password": wrapper.Wrap( &authenticate.PasswordCommand{ Command: base.NewCommand(ui, opts...), }), - "authenticate oidc": clientCacheWrapper( + "authenticate oidc": wrapper.Wrap( &authenticate.OidcCommand{ Command: base.NewCommand(ui, opts...), }), - "authenticate ldap": clientCacheWrapper( + "authenticate ldap": wrapper.Wrap( &authenticate.LdapCommand{ Command: base.NewCommand(ui, opts...), }), @@ -92,67 +93,67 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "accounts read": clientCacheWrapper( + "accounts read": wrapper.Wrap( &accountscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "accounts delete": clientCacheWrapper( + "accounts delete": wrapper.Wrap( &accountscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "accounts list": clientCacheWrapper( + "accounts list": wrapper.Wrap( &accountscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "accounts set-password": clientCacheWrapper( + "accounts set-password": wrapper.Wrap( &accountscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "set-password", }), - "accounts change-password": clientCacheWrapper( + "accounts change-password": wrapper.Wrap( &accountscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "change-password", }), - "accounts create": clientCacheWrapper( + "accounts create": wrapper.Wrap( &accountscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "accounts create password": clientCacheWrapper( + "accounts create password": wrapper.Wrap( &accountscmd.PasswordCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "accounts create oidc": clientCacheWrapper( + "accounts create oidc": wrapper.Wrap( &accountscmd.OidcCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "accounts create ldap": clientCacheWrapper( + "accounts create ldap": wrapper.Wrap( &accountscmd.LdapCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "accounts update": clientCacheWrapper( + "accounts update": wrapper.Wrap( &accountscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "accounts update password": clientCacheWrapper( + "accounts update password": wrapper.Wrap( &accountscmd.PasswordCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "accounts update oidc": clientCacheWrapper( + "accounts update oidc": wrapper.Wrap( &accountscmd.OidcCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "accounts update ldap": clientCacheWrapper( + "accounts update ldap": wrapper.Wrap( &accountscmd.LdapCommand{ Command: base.NewCommand(ui, opts...), Func: "update", @@ -163,37 +164,37 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "aliases read": clientCacheWrapper( + "aliases read": wrapper.Wrap( &aliasescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "aliases delete": clientCacheWrapper( + "aliases delete": wrapper.Wrap( &aliasescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "aliases list": clientCacheWrapper( + "aliases list": wrapper.Wrap( &aliasescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "aliases create": clientCacheWrapper( + "aliases create": wrapper.Wrap( &aliasescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "aliases update": clientCacheWrapper( + "aliases update": wrapper.Wrap( &aliasescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "aliases create target": clientCacheWrapper( + "aliases create target": wrapper.Wrap( &aliasescmd.TargetCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "aliases update target": clientCacheWrapper( + "aliases update target": wrapper.Wrap( &aliasescmd.TargetCommand{ Command: base.NewCommand(ui, opts...), Func: "update", @@ -204,62 +205,62 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "auth-methods read": clientCacheWrapper( + "auth-methods read": wrapper.Wrap( &authmethodscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "auth-methods delete": clientCacheWrapper( + "auth-methods delete": wrapper.Wrap( &authmethodscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "auth-methods list": clientCacheWrapper( + "auth-methods list": wrapper.Wrap( &authmethodscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "auth-methods create": clientCacheWrapper( + "auth-methods create": wrapper.Wrap( &authmethodscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "auth-methods create password": clientCacheWrapper( + "auth-methods create password": wrapper.Wrap( &authmethodscmd.PasswordCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "auth-methods create oidc": clientCacheWrapper( + "auth-methods create oidc": wrapper.Wrap( &authmethodscmd.OidcCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "auth-methods create ldap": clientCacheWrapper( + "auth-methods create ldap": wrapper.Wrap( &authmethodscmd.LdapCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "auth-methods update": clientCacheWrapper( + "auth-methods update": wrapper.Wrap( &authmethodscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "auth-methods update password": clientCacheWrapper( + "auth-methods update password": wrapper.Wrap( &authmethodscmd.PasswordCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "auth-methods update oidc": clientCacheWrapper( + "auth-methods update oidc": wrapper.Wrap( &authmethodscmd.OidcCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "auth-methods update ldap": clientCacheWrapper( + "auth-methods update ldap": wrapper.Wrap( &authmethodscmd.LdapCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "auth-methods change-state oidc": clientCacheWrapper( + "auth-methods change-state oidc": wrapper.Wrap( &authmethodscmd.OidcCommand{ Command: base.NewCommand(ui, opts...), Func: "change-state", @@ -270,17 +271,17 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "auth-tokens read": clientCacheWrapper( + "auth-tokens read": wrapper.Wrap( &authtokenscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "auth-tokens delete": clientCacheWrapper( + "auth-tokens delete": wrapper.Wrap( &authtokenscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "auth-tokens list": clientCacheWrapper( + "auth-tokens list": wrapper.Wrap( &authtokenscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", @@ -291,7 +292,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "billing monthly-active-users": clientCacheWrapper( + "billing monthly-active-users": wrapper.Wrap( &billingcmd.Command{ Command: base.NewCommand(ui), Func: "monthly-active-users", @@ -339,32 +340,32 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { }, nil }, - "connect": clientCacheWrapper( + "connect": wrapper.Wrap( &connect.Command{ Command: base.NewCommand(ui, opts...), Func: "connect", }), - "connect http": clientCacheWrapper( + "connect http": wrapper.Wrap( &connect.Command{ Command: base.NewCommand(ui, opts...), Func: "http", }), - "connect kube": clientCacheWrapper( + "connect kube": wrapper.Wrap( &connect.Command{ Command: base.NewCommand(ui, opts...), Func: "kube", }), - "connect postgres": clientCacheWrapper( + "connect postgres": wrapper.Wrap( &connect.Command{ Command: base.NewCommand(ui, opts...), Func: "postgres", }), - "connect rdp": clientCacheWrapper( + "connect rdp": wrapper.Wrap( &connect.Command{ Command: base.NewCommand(ui, opts...), Func: "rdp", }), - "connect ssh": clientCacheWrapper( + "connect ssh": wrapper.Wrap( &connect.Command{ Command: base.NewCommand(ui, opts...), Func: "ssh", @@ -391,57 +392,57 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "credential-libraries read": clientCacheWrapper( + "credential-libraries read": wrapper.Wrap( &credentiallibrariescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "credential-libraries delete": clientCacheWrapper( + "credential-libraries delete": wrapper.Wrap( &credentiallibrariescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "credential-libraries list": clientCacheWrapper( + "credential-libraries list": wrapper.Wrap( &credentiallibrariescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "credential-libraries create": clientCacheWrapper( + "credential-libraries create": wrapper.Wrap( &credentiallibrariescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "credential-libraries create vault": clientCacheWrapper( + "credential-libraries create vault": wrapper.Wrap( &credentiallibrariescmd.VaultCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "credential-libraries create vault-generic": clientCacheWrapper( + "credential-libraries create vault-generic": wrapper.Wrap( &credentiallibrariescmd.VaultGenericCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "credential-libraries create vault-ssh-certificate": clientCacheWrapper( + "credential-libraries create vault-ssh-certificate": wrapper.Wrap( &credentiallibrariescmd.VaultSshCertificateCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "credential-libraries update": clientCacheWrapper( + "credential-libraries update": wrapper.Wrap( &credentiallibrariescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "credential-libraries update vault": clientCacheWrapper( + "credential-libraries update vault": wrapper.Wrap( &credentiallibrariescmd.VaultGenericCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "credential-libraries update vault-generic": clientCacheWrapper( + "credential-libraries update vault-generic": wrapper.Wrap( &credentiallibrariescmd.VaultGenericCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "credential-libraries update vault-ssh-certificate": clientCacheWrapper( + "credential-libraries update vault-ssh-certificate": wrapper.Wrap( &credentiallibrariescmd.VaultSshCertificateCommand{ Command: base.NewCommand(ui, opts...), Func: "update", @@ -452,47 +453,47 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "credential-stores read": clientCacheWrapper( + "credential-stores read": wrapper.Wrap( &credentialstorescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "credential-stores delete": clientCacheWrapper( + "credential-stores delete": wrapper.Wrap( &credentialstorescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "credential-stores list": clientCacheWrapper( + "credential-stores list": wrapper.Wrap( &credentialstorescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "credential-stores create": clientCacheWrapper( + "credential-stores create": wrapper.Wrap( &credentialstorescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "credential-stores create vault": clientCacheWrapper( + "credential-stores create vault": wrapper.Wrap( &credentialstorescmd.VaultCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "credential-stores create static": clientCacheWrapper( + "credential-stores create static": wrapper.Wrap( &credentialstorescmd.StaticCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "credential-stores update": clientCacheWrapper( + "credential-stores update": wrapper.Wrap( &credentialstorescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "credential-stores update vault": clientCacheWrapper( + "credential-stores update vault": wrapper.Wrap( &credentialstorescmd.VaultCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "credential-stores update static": clientCacheWrapper( + "credential-stores update static": wrapper.Wrap( &credentialstorescmd.StaticCommand{ Command: base.NewCommand(ui, opts...), Func: "update", @@ -503,57 +504,57 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "credentials read": clientCacheWrapper( + "credentials read": wrapper.Wrap( &credentialscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "credentials delete": clientCacheWrapper( + "credentials delete": wrapper.Wrap( &credentialscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "credentials list": clientCacheWrapper( + "credentials list": wrapper.Wrap( &credentialscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "credentials create": clientCacheWrapper( + "credentials create": wrapper.Wrap( &credentialscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "credentials create username-password": clientCacheWrapper( + "credentials create username-password": wrapper.Wrap( &credentialscmd.UsernamePasswordCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "credentials create ssh-private-key": clientCacheWrapper( + "credentials create ssh-private-key": wrapper.Wrap( &credentialscmd.SshPrivateKeyCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "credentials create json": clientCacheWrapper( + "credentials create json": wrapper.Wrap( &credentialscmd.JsonCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "credentials update": clientCacheWrapper( + "credentials update": wrapper.Wrap( &credentialscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "credentials update username-password": clientCacheWrapper( + "credentials update username-password": wrapper.Wrap( &credentialscmd.UsernamePasswordCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "credentials update ssh-private-key": clientCacheWrapper( + "credentials update ssh-private-key": wrapper.Wrap( &credentialscmd.SshPrivateKeyCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "credentials update json": clientCacheWrapper( + "credentials update json": wrapper.Wrap( &credentialscmd.JsonCommand{ Command: base.NewCommand(ui, opts...), Func: "update", @@ -573,47 +574,54 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { }, nil }, + "ferry": func() (cli.Command, error) { + return &unsupported.UnsupportedCommand{ + Command: base.NewCommand(ui, opts...), + CommandName: "ferry", + }, nil + }, + "groups": func() (cli.Command, error) { return &groupscmd.Command{ Command: base.NewCommand(ui, opts...), }, nil }, - "groups create": clientCacheWrapper( + "groups create": wrapper.Wrap( &groupscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "groups update": clientCacheWrapper( + "groups update": wrapper.Wrap( &groupscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "groups read": clientCacheWrapper( + "groups read": wrapper.Wrap( &groupscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "groups delete": clientCacheWrapper( + "groups delete": wrapper.Wrap( &groupscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "groups list": clientCacheWrapper( + "groups list": wrapper.Wrap( &groupscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "groups add-members": clientCacheWrapper( + "groups add-members": wrapper.Wrap( &groupscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "add-members", }), - "groups set-members": clientCacheWrapper( + "groups set-members": wrapper.Wrap( &groupscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "set-members", }), - "groups remove-members": clientCacheWrapper( + "groups remove-members": wrapper.Wrap( &groupscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "remove-members", @@ -624,47 +632,47 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "host-catalogs read": clientCacheWrapper( + "host-catalogs read": wrapper.Wrap( &hostcatalogscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "host-catalogs delete": clientCacheWrapper( + "host-catalogs delete": wrapper.Wrap( &hostcatalogscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "host-catalogs list": clientCacheWrapper( + "host-catalogs list": wrapper.Wrap( &hostcatalogscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "host-catalogs create": clientCacheWrapper( + "host-catalogs create": wrapper.Wrap( &hostcatalogscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "host-catalogs create static": clientCacheWrapper( + "host-catalogs create static": wrapper.Wrap( &hostcatalogscmd.StaticCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "host-catalogs create plugin": clientCacheWrapper( + "host-catalogs create plugin": wrapper.Wrap( &hostcatalogscmd.PluginCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "host-catalogs update": clientCacheWrapper( + "host-catalogs update": wrapper.Wrap( &hostcatalogscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "host-catalogs update static": clientCacheWrapper( + "host-catalogs update static": wrapper.Wrap( &hostcatalogscmd.StaticCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "host-catalogs update plugin": clientCacheWrapper( + "host-catalogs update plugin": wrapper.Wrap( &hostcatalogscmd.PluginCommand{ Command: base.NewCommand(ui, opts...), Func: "update", @@ -675,62 +683,62 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "host-sets read": clientCacheWrapper( + "host-sets read": wrapper.Wrap( &hostsetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "host-sets delete": clientCacheWrapper( + "host-sets delete": wrapper.Wrap( &hostsetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "host-sets list": clientCacheWrapper( + "host-sets list": wrapper.Wrap( &hostsetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "host-sets create": clientCacheWrapper( + "host-sets create": wrapper.Wrap( &hostsetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "host-sets create static": clientCacheWrapper( + "host-sets create static": wrapper.Wrap( &hostsetscmd.StaticCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "host-sets create plugin": clientCacheWrapper( + "host-sets create plugin": wrapper.Wrap( &hostsetscmd.PluginCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "host-sets update": clientCacheWrapper( + "host-sets update": wrapper.Wrap( &hostsetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "host-sets update static": clientCacheWrapper( + "host-sets update static": wrapper.Wrap( &hostsetscmd.StaticCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "host-sets update plugin": clientCacheWrapper( + "host-sets update plugin": wrapper.Wrap( &hostsetscmd.PluginCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "host-sets add-hosts": clientCacheWrapper( + "host-sets add-hosts": wrapper.Wrap( &hostsetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "add-hosts", }), - "host-sets remove-hosts": clientCacheWrapper( + "host-sets remove-hosts": wrapper.Wrap( &hostsetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "remove-hosts", }), - "host-sets set-hosts": clientCacheWrapper( + "host-sets set-hosts": wrapper.Wrap( &hostsetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "set-hosts", @@ -741,37 +749,37 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "hosts read": clientCacheWrapper( + "hosts read": wrapper.Wrap( &hostscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "hosts delete": clientCacheWrapper( + "hosts delete": wrapper.Wrap( &hostscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "hosts list": clientCacheWrapper( + "hosts list": wrapper.Wrap( &hostscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "hosts create": clientCacheWrapper( + "hosts create": wrapper.Wrap( &hostscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "hosts create static": clientCacheWrapper( + "hosts create static": wrapper.Wrap( &hostscmd.StaticCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "hosts update": clientCacheWrapper( + "hosts update": wrapper.Wrap( &hostscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "hosts update static": clientCacheWrapper( + "hosts update static": wrapper.Wrap( &hostscmd.StaticCommand{ Command: base.NewCommand(ui, opts...), Func: "update", @@ -788,47 +796,47 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "managed-groups read": clientCacheWrapper( + "managed-groups read": wrapper.Wrap( &managedgroupscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "managed-groups delete": clientCacheWrapper( + "managed-groups delete": wrapper.Wrap( &managedgroupscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "managed-groups list": clientCacheWrapper( + "managed-groups list": wrapper.Wrap( &managedgroupscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "managed-groups create": clientCacheWrapper( + "managed-groups create": wrapper.Wrap( &managedgroupscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "managed-groups create oidc": clientCacheWrapper( + "managed-groups create oidc": wrapper.Wrap( &managedgroupscmd.OidcCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "managed-groups create ldap": clientCacheWrapper( + "managed-groups create ldap": wrapper.Wrap( &managedgroupscmd.LdapCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "managed-groups update": clientCacheWrapper( + "managed-groups update": wrapper.Wrap( &managedgroupscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "managed-groups update oidc": clientCacheWrapper( + "managed-groups update oidc": wrapper.Wrap( &managedgroupscmd.OidcCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "managed-groups update ldap": clientCacheWrapper( + "managed-groups update ldap": wrapper.Wrap( &managedgroupscmd.LdapCommand{ Command: base.NewCommand(ui, opts...), Func: "update", @@ -894,72 +902,72 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "roles create": clientCacheWrapper( + "roles create": wrapper.Wrap( &rolescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "roles update": clientCacheWrapper( + "roles update": wrapper.Wrap( &rolescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "roles read": clientCacheWrapper( + "roles read": wrapper.Wrap( &rolescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "roles delete": clientCacheWrapper( + "roles delete": wrapper.Wrap( &rolescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "roles list": clientCacheWrapper( + "roles list": wrapper.Wrap( &rolescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "roles add-principals": clientCacheWrapper( + "roles add-principals": wrapper.Wrap( &rolescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "add-principals", }), - "roles set-principals": clientCacheWrapper( + "roles set-principals": wrapper.Wrap( &rolescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "set-principals", }), - "roles remove-principals": clientCacheWrapper( + "roles remove-principals": wrapper.Wrap( &rolescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "remove-principals", }), - "roles add-grants": clientCacheWrapper( + "roles add-grants": wrapper.Wrap( &rolescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "add-grants", }), - "roles set-grants": clientCacheWrapper( + "roles set-grants": wrapper.Wrap( &rolescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "set-grants", }), - "roles remove-grants": clientCacheWrapper( + "roles remove-grants": wrapper.Wrap( &rolescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "remove-grants", }), - "roles add-grant-scopes": clientCacheWrapper( + "roles add-grant-scopes": wrapper.Wrap( &rolescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "add-grant-scopes", }), - "roles set-grant-scopes": clientCacheWrapper( + "roles set-grant-scopes": wrapper.Wrap( &rolescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "set-grant-scopes", }), - "roles remove-grant-scopes": clientCacheWrapper( + "roles remove-grant-scopes": wrapper.Wrap( &rolescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "remove-grant-scopes", @@ -970,53 +978,53 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "scopes create": clientCacheWrapper( + "scopes create": wrapper.Wrap( &scopescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "scopes read": clientCacheWrapper( + "scopes read": wrapper.Wrap( &scopescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "scopes update": clientCacheWrapper( + "scopes update": wrapper.Wrap( &scopescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "scopes delete": clientCacheWrapper( + "scopes delete": wrapper.Wrap( &scopescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "scopes list": clientCacheWrapper( + "scopes list": wrapper.Wrap( &scopescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "scopes list-keys": clientCacheWrapper( + "scopes list-keys": wrapper.Wrap( &scopescmd.ListKeysCommand{ Command: base.NewCommand(ui, opts...), }), - "scopes rotate-keys": clientCacheWrapper( + "scopes rotate-keys": wrapper.Wrap( &scopescmd.RotateKeysCommand{ Command: base.NewCommand(ui, opts...), }), - "scopes list-key-version-destruction-jobs": clientCacheWrapper( + "scopes list-key-version-destruction-jobs": wrapper.Wrap( &scopescmd.ListKeyVersionDestructionJobsCommand{ Command: base.NewCommand(ui, opts...), }), - "scopes destroy-key-version": clientCacheWrapper( + "scopes destroy-key-version": wrapper.Wrap( &scopescmd.DestroyKeyVersionCommand{ Command: base.NewCommand(ui, opts...), }), - "scopes attach-storage-policy": clientCacheWrapper( + "scopes attach-storage-policy": wrapper.Wrap( &scopescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "attach-storage-policy", }), - "scopes detach-storage-policy": clientCacheWrapper( + "scopes detach-storage-policy": wrapper.Wrap( &scopescmd.Command{ Command: base.NewCommand(ui, opts...), Func: "detach-storage-policy", @@ -1034,17 +1042,17 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "sessions read": clientCacheWrapper( + "sessions read": wrapper.Wrap( &sessionscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "sessions list": clientCacheWrapper( + "sessions list": wrapper.Wrap( &sessionscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "sessions cancel": clientCacheWrapper( + "sessions cancel": wrapper.Wrap( &sessionscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "cancel", @@ -1055,26 +1063,26 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "session-recordings read": clientCacheWrapper( + "session-recordings read": wrapper.Wrap( &sessionrecordingscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "session-recordings list": clientCacheWrapper( + "session-recordings list": wrapper.Wrap( &sessionrecordingscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "session-recordings download": clientCacheWrapper( + "session-recordings download": wrapper.Wrap( &sessionrecordingscmd.DownloadCommand{ Command: base.NewCommand(ui, opts...), }), - "session-recordings delete": clientCacheWrapper( + "session-recordings delete": wrapper.Wrap( &sessionrecordingscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "session-recordings reapply-storage-policy": clientCacheWrapper( + "session-recordings reapply-storage-policy": wrapper.Wrap( &sessionrecordingscmd.ReApplyStoragePolicyCommand{ Command: base.NewCommand(ui, opts...), }), @@ -1084,27 +1092,27 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "storage-buckets read": clientCacheWrapper( + "storage-buckets read": wrapper.Wrap( &storagebucketscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "storage-buckets delete": clientCacheWrapper( + "storage-buckets delete": wrapper.Wrap( &storagebucketscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "storage-buckets list": clientCacheWrapper( + "storage-buckets list": wrapper.Wrap( &storagebucketscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "storage-buckets create": clientCacheWrapper( + "storage-buckets create": wrapper.Wrap( &storagebucketscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "storage-buckets update": clientCacheWrapper( + "storage-buckets update": wrapper.Wrap( &storagebucketscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", @@ -1115,82 +1123,82 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "targets authorize-session": clientCacheWrapper( + "targets authorize-session": wrapper.Wrap( &targetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "authorize-session", }), - "targets read": clientCacheWrapper( + "targets read": wrapper.Wrap( &targetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "targets delete": clientCacheWrapper( + "targets delete": wrapper.Wrap( &targetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "targets list": clientCacheWrapper( + "targets list": wrapper.Wrap( &targetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "targets create": clientCacheWrapper( + "targets create": wrapper.Wrap( &targetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "targets create tcp": clientCacheWrapper( + "targets create tcp": wrapper.Wrap( &targetscmd.TcpCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "targets create ssh": clientCacheWrapper( + "targets create ssh": wrapper.Wrap( &targetscmd.SshCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "targets update": clientCacheWrapper( + "targets update": wrapper.Wrap( &targetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "targets update tcp": clientCacheWrapper( + "targets update tcp": wrapper.Wrap( &targetscmd.TcpCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "targets update ssh": clientCacheWrapper( + "targets update ssh": wrapper.Wrap( &targetscmd.SshCommand{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "targets add-host-sources": clientCacheWrapper( + "targets add-host-sources": wrapper.Wrap( &targetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "add-host-sources", }), - "targets remove-host-sources": clientCacheWrapper( + "targets remove-host-sources": wrapper.Wrap( &targetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "remove-host-sources", }), - "targets set-host-sources": clientCacheWrapper( + "targets set-host-sources": wrapper.Wrap( &targetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "set-host-sources", }), - "targets add-credential-sources": clientCacheWrapper( + "targets add-credential-sources": wrapper.Wrap( &targetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "add-credential-sources", }), - "targets remove-credential-sources": clientCacheWrapper( + "targets remove-credential-sources": wrapper.Wrap( &targetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "remove-credential-sources", }), - "targets set-credential-sources": clientCacheWrapper( + "targets set-credential-sources": wrapper.Wrap( &targetscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "set-credential-sources", @@ -1208,42 +1216,42 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "users create": clientCacheWrapper( + "users create": wrapper.Wrap( &userscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "users read": clientCacheWrapper( + "users read": wrapper.Wrap( &userscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "users update": clientCacheWrapper( + "users update": wrapper.Wrap( &userscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "users delete": clientCacheWrapper( + "users delete": wrapper.Wrap( &userscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "users list": clientCacheWrapper( + "users list": wrapper.Wrap( &userscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "users add-accounts": clientCacheWrapper( + "users add-accounts": wrapper.Wrap( &userscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "add-accounts", }), - "users set-accounts": clientCacheWrapper( + "users set-accounts": wrapper.Wrap( &userscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "set-accounts", }), - "users remove-accounts": clientCacheWrapper( + "users remove-accounts": wrapper.Wrap( &userscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "remove-accounts", @@ -1254,66 +1262,66 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Command: base.NewCommand(ui, opts...), }, nil }, - "workers create": clientCacheWrapper( + "workers create": wrapper.Wrap( &workerscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "workers create worker-led": clientCacheWrapper( + "workers create worker-led": wrapper.Wrap( &workerscmd.WorkerLedCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "workers create controller-led": clientCacheWrapper( + "workers create controller-led": wrapper.Wrap( &workerscmd.ControllerLedCommand{ Command: base.NewCommand(ui, opts...), Func: "create", }), - "workers read": clientCacheWrapper( + "workers read": wrapper.Wrap( &workerscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "workers update": clientCacheWrapper( + "workers update": wrapper.Wrap( &workerscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "update", }), - "workers delete": clientCacheWrapper( + "workers delete": wrapper.Wrap( &workerscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "delete", }), - "workers list": clientCacheWrapper( + "workers list": wrapper.Wrap( &workerscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "list", }), - "workers add-worker-tags": clientCacheWrapper( + "workers add-worker-tags": wrapper.Wrap( &workerscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "add-worker-tags", }), - "workers set-worker-tags": clientCacheWrapper( + "workers set-worker-tags": wrapper.Wrap( &workerscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "set-worker-tags", }), - "workers remove-worker-tags": clientCacheWrapper( + "workers remove-worker-tags": wrapper.Wrap( &workerscmd.Command{ Command: base.NewCommand(ui, opts...), Func: "remove-worker-tags", }), - "workers certificate-authority": clientCacheWrapper( + "workers certificate-authority": wrapper.Wrap( &workerscmd.WorkerCACommand{ Command: base.NewCommand(ui, opts...), }), - "workers certificate-authority read": clientCacheWrapper( + "workers certificate-authority read": wrapper.Wrap( &workerscmd.WorkerCACommand{ Command: base.NewCommand(ui, opts...), Func: "read", }), - "workers certificate-authority reinitialize": clientCacheWrapper( + "workers certificate-authority reinitialize": wrapper.Wrap( &workerscmd.WorkerCACommand{ Command: base.NewCommand(ui, opts...), Func: "reinitialize", @@ -1328,17 +1336,3 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { } var extraCommandsFuncs []func(ui, serverCmdUi cli.Ui, runOpts *RunOptions) - -// Keep this interface aligned with the interface at internal/clientcache/cmd/daemon/command_wrapper.go -type cacheEnabledCommand interface { - cli.Command - BaseCommand() *base.Command -} - -// clientCacheWrapper wraps all short lived, non server, command factories. -// The default func is a noop. -var clientCacheWrapper = func(c cacheEnabledCommand) cli.CommandFactory { - return func() (cli.Command, error) { - return c, nil - } -} diff --git a/internal/cmd/commands/ferry/addtoken.go b/internal/cmd/commands/ferry/addtoken.go new file mode 100644 index 000000000..9920e0af3 --- /dev/null +++ b/internal/cmd/commands/ferry/addtoken.go @@ -0,0 +1,75 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package ferry + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "strings" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/go-retryablehttp" +) + +// userTokenToAdd is the request body to this handler. +type UpsertTokenRequest struct { + // BoundaryAddr is a required field for all requests + BoundaryAddr string `json:"boundary_addr,omitempty"` + // The raw auth token for this user. + Token string `json:"token,omitempty"` +} + +// addToken builds the UpsertTokenRequest using the client's address and token. +// It then sends the request to the ferry daemon. +// The passed in cli.Ui is used to print out any errors when looking up the +// auth token from the keyring. This allows background operations calling this +// method to pass in a silent UI to suppress any output. +func addToken(ctx context.Context, apiClient *api.Client, port uint) (*api.Response, *api.Error, error) { + pa := UpsertTokenRequest{ + BoundaryAddr: apiClient.Addr(), + } + token := apiClient.Token() + if token == "" { + return nil, nil, errors.New("The client auth token is empty.") + } + if parts := strings.Split(token, "_"); len(parts) != 3 { + return nil, nil, errors.New("The client provided auth token is not in the proper format.") + } + pa.Token = token + + client := retryablehttp.NewClient() + client.Logger = nil + client.RetryWaitMin = 100 * time.Millisecond + client.RetryWaitMax = 1500 * time.Millisecond + + // TODO (ICU-13140): Until we release ferry feature, do not retry attempts + // to connect to the ferry daemon since it adds a noticeably long delay to + // the command. + client.RetryMax = 0 + + req, err := retryablehttp.NewRequestWithContext(ctx, "POST", ferryUrl(port, "v1/tokens"), + retryablehttp.ReaderFunc(func() (io.Reader, error) { + b, err := json.Marshal(&pa) + if err != nil { + return nil, fmt.Errorf("error marshaling body: %w", err) + } + return bytes.NewReader(b), nil + })) + if err != nil { + return nil, nil, err + } + req.Header.Set("content-type", "application/json") + resp, err := client.Do(req) + if err != nil { + return nil, nil, fmt.Errorf("Error when sending request to the ferry daemon: %w.", err) + } + apiResp := api.NewResponse(resp) + apiErr, err := apiResp.Decode(nil) + return apiResp, apiErr, err +} diff --git a/internal/cmd/commands/ferry/ferry.go b/internal/cmd/commands/ferry/ferry.go new file mode 100644 index 000000000..895516355 --- /dev/null +++ b/internal/cmd/commands/ferry/ferry.go @@ -0,0 +1,63 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package ferry + +import ( + "fmt" + "strings" + + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +var ( + _ cli.Command = (*FerryCommand)(nil) + _ cli.CommandAutocomplete = (*FerryCommand)(nil) +) + +type FerryCommand struct { + *base.Command +} + +func (c *FerryCommand) Synopsis() string { + return "Manages the Boundary ferry daemon" +} + +func (c *FerryCommand) Help() string { + helpText := ` +Usage: boundary ferry [sub command] [options] + + This command allows interacting with the Boundary ferry daemon. + + Get the status of the daemon: + + $ boundary ferry status + + For a full list of examples, please see the documentation. + +` + return strings.TrimSpace(helpText) +} + +func (c *FerryCommand) Flags() *base.FlagSets { + return c.FlagSet(base.FlagSetNone) +} + +func (c *FerryCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *FerryCommand) AutocompleteFlags() complete.Flags { + return c.Flags().Completions() +} + +func (c *FerryCommand) Run(args []string) int { + return cli.RunResultHelp +} + +// ferryUrl constructs the full URL for a ferry request given a port and path. +func ferryUrl(port uint, path string) string { + return fmt.Sprintf("http://127.0.0.1:%d/%s", port, path) +} diff --git a/internal/cmd/commands/ferry/status.go b/internal/cmd/commands/ferry/status.go new file mode 100644 index 000000000..d04a73225 --- /dev/null +++ b/internal/cmd/commands/ferry/status.go @@ -0,0 +1,175 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package ferry + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/go-retryablehttp" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +var ( + _ cli.Command = (*StatusCommand)(nil) + _ cli.CommandAutocomplete = (*StatusCommand)(nil) +) + +type StatusCommand struct { + *base.Command +} + +func (c *StatusCommand) Synopsis() string { + return "Get the status information of the running boundary ferry daemon" +} + +func (c *StatusCommand) Help() string { + helpText := ` +Usage: boundary ferry status [options] + + Get the status of the boundary ferry daemon: + + $ boundary ferry status + + For a full list of examples, please see the documentation. + +` + c.Flags().Help() + return strings.TrimSpace(helpText) +} + +func (c *StatusCommand) Flags() *base.FlagSets { + set := c.FlagSet(base.FlagSetOutputFormat) + f := set.NewFlagSet("Client Options") + + f.BoolVar(&base.BoolVar{ + Name: "output-curl-string", + Target: &c.FlagOutputCurlString, + Usage: "Instead of executing the request, print an equivalent cURL command string and exit.", + }) + + f.UintVar(&base.UintVar{ + Name: "ferry-port", + Target: &c.FlagFerryDaemonPort, + Default: 9300, + EnvVar: base.EnvFerryDaemonPort, + Usage: "The port on which the ferry daemon is listening.", + }) + + return set +} + +func (c *StatusCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *StatusCommand) AutocompleteFlags() complete.Flags { + return c.Flags().Completions() +} + +func (c *StatusCommand) Run(args []string) int { + ctx := c.Context + f := c.Flags() + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + resp, result, apiErr, err := c.Status(ctx) + if err != nil { + c.PrintCliError(err) + return base.CommandCliError + } + if apiErr != nil { + c.PrintApiError(apiErr, "Error from ferry daemon when getting its status") + return base.CommandApiError + } + + switch base.Format(c.UI) { + case "json": + if ok := c.PrintJsonItem(resp); !ok { + return base.CommandCliError + } + default: + c.UI.Output(printStatusTable(result)) + } + return base.CommandSuccess +} + +type GetStatusResponse struct { + BoundaryAddr string `json:"boundary_addr"` + AuthTokenId string `json:"auth_token_id"` + AuthTokenExpiry time.Time `json:"auth_token_expiry"` + Version string `json:"version"` + Errors []string `json:"errors"` + Warnings []string `json:"warnings"` +} + +func (c *StatusCommand) Status(ctx context.Context) (*api.Response, *GetStatusResponse, *api.Error, error) { + const op = "ferry.(StatusCommand).Status" + client := retryablehttp.NewClient() + client.Logger = nil + client.RetryWaitMin = 100 * time.Millisecond + client.RetryWaitMax = 1500 * time.Millisecond + + req, err := retryablehttp.NewRequestWithContext(ctx, "GET", ferryUrl(c.FlagFerryDaemonPort, "v1/status"), nil) + if err != nil { + return nil, nil, nil, err + } + req.Header.Set("content-type", "application/json") + + if c.FlagOutputCurlString { + api.LastOutputStringError = &api.OutputStringError{Request: req} + return nil, nil, nil, api.LastOutputStringError + } + + resp, err := client.Do(req) + if err != nil { + return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("client do failed")) + } + apiResp := api.NewResponse(resp) + + res := &GetStatusResponse{} + apiErr, err := apiResp.Decode(&res) + if err != nil { + return nil, nil, nil, fmt.Errorf("Error when sending request to the ferry daemon: %w.", err) + } + if apiErr != nil { + return apiResp, nil, apiErr, nil + } + return apiResp, res, nil, nil +} + +func printStatusTable(status *GetStatusResponse) string { + nonAttributeMap := map[string]any{ + "Address": status.BoundaryAddr, + "Auth Token Id": status.AuthTokenId, + "Auth Token Expiration": time.Until(status.AuthTokenExpiry).Round(time.Second).String(), + "Version": status.Version, + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) + + ret := []string{ + "", + "Status:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + } + + if len(status.Errors) > 0 { + ret = append(ret, " Recent errors:") + ret = append(ret, base.WrapSlice(4, status.Errors)) + } + + if len(status.Warnings) > 0 { + ret = append(ret, " Recent warnings:") + ret = append(ret, base.WrapSlice(4, status.Warnings)) + } + return base.WrapForHelpText(ret) +} diff --git a/internal/cmd/commands/ferry/wrapper_register.go b/internal/cmd/commands/ferry/wrapper_register.go new file mode 100644 index 000000000..2f94c08ae --- /dev/null +++ b/internal/cmd/commands/ferry/wrapper_register.go @@ -0,0 +1,42 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package ferry + +import ( + "context" + + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/wrapper" +) + +// TODO (ICU-13140): Remove this and re-enable error output for background +// ferry daemon token sending. +const allowErrorOutput = false + +func init() { + if err := wrapper.RegisterSuccessfulCommandCallback("ferry", hook); err != nil { + panic(err) + } +} + +func hook(ctx context.Context, baseCmd *base.Command, token string) { + if baseCmd.FlagSkipFerry { + return + } + client, err := baseCmd.Client() + if err != nil && allowErrorOutput { + baseCmd.PrintCliError(err) + return + } + if token != "" { + client.SetToken(token) + } + _, apiErr, err := addToken(ctx, client, baseCmd.FlagFerryDaemonPort) + if err != nil && allowErrorOutput { + baseCmd.PrintCliError(err) + } + if apiErr != nil && allowErrorOutput { + baseCmd.PrintApiError(apiErr, "sending token to ferry daemon in the background") + } +} diff --git a/internal/cmd/ferry_cmd_darwin.go b/internal/cmd/ferry_cmd_darwin.go new file mode 100644 index 000000000..91568037f --- /dev/null +++ b/internal/cmd/ferry_cmd_darwin.go @@ -0,0 +1,25 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package cmd + +import ( + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/commands/ferry" + "github.com/mitchellh/cli" +) + +func init() { + extraCommandsFuncs = append(extraCommandsFuncs, func(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { + Commands["ferry"] = func() (cli.Command, error) { + return &ferry.FerryCommand{ + Command: base.NewCommand(ui), + }, nil + } + Commands["ferry status"] = func() (cli.Command, error) { + return &ferry.StatusCommand{ + Command: base.NewCommand(ui), + }, nil + } + }) +} diff --git a/internal/cmd/ferry_cmd_windows.go b/internal/cmd/ferry_cmd_windows.go new file mode 100644 index 000000000..55df18e3b --- /dev/null +++ b/internal/cmd/ferry_cmd_windows.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +//go:build amd64 || arm64 + +package cmd + +import ( + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/commands/ferry" + "github.com/mitchellh/cli" +) + +func init() { + extraCommandsFuncs = append(extraCommandsFuncs, func(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { + Commands["ferry"] = func() (cli.Command, error) { + return &ferry.FerryCommand{ + Command: base.NewCommand(ui), + }, nil + } + Commands["ferry status"] = func() (cli.Command, error) { + return &ferry.StatusCommand{ + Command: base.NewCommand(ui), + }, nil + } + }) +} diff --git a/internal/cmd/main.go b/internal/cmd/main.go index 4226f46cc..a266a90a8 100644 --- a/internal/cmd/main.go +++ b/internal/cmd/main.go @@ -241,7 +241,7 @@ func RunCustom(args []string, runOpts *RunOptions) (exitCode int) { initCommands(ui, serverCmdUi, runOpts) - hiddenCommands := []string{"version"} + hiddenCommands := []string{"version", "ferry", "ferry status"} cli := &cli.CLI{ Name: "boundary", @@ -294,11 +294,11 @@ func groupedHelpFunc(f cli.HelpFunc) cli.HelpFunc { fmt.Fprintf(tw, "Usage: boundary [args]\n") genericCommands := make([]string, 0, 3) - clientCommands := make([]string, 0, 6) + clientCommands := make([]string, 0, 9) typeSpecificCommands := make([]string, 0, len(commands)-cap(genericCommands)-cap(clientCommands)) for k := range commands { switch k { - case "authenticate", "config", "connect", "daemon", "dev", "logout", "search", "server": + case "authenticate", "config", "connect", "daemon", "dev", "ferry", "logout", "search", "server": clientCommands = append(clientCommands, k) case "read", "update", "delete": genericCommands = append(genericCommands, k) diff --git a/internal/cmd/search_cmd_darwin.go b/internal/cmd/search_cmd_darwin.go index a909630ed..97c00bb6f 100644 --- a/internal/cmd/search_cmd_darwin.go +++ b/internal/cmd/search_cmd_darwin.go @@ -11,8 +11,6 @@ import ( ) func init() { - clientCacheWrapper = daemonWrap - extraCommandsFuncs = append(extraCommandsFuncs, func(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Commands["daemon"] = func() (cli.Command, error) { return &daemon.DaemonCommand{ @@ -46,9 +44,3 @@ func init() { } }) } - -// daemonWrap wraps the provided cacheEnabledCommand with a daemon writer which -// conditionally starts the client side daemon after the command completes -func daemonWrap(c cacheEnabledCommand) cli.CommandFactory { - return daemon.Wrap(c) -} diff --git a/internal/cmd/search_cmd_freebsd.go b/internal/cmd/search_cmd_freebsd.go index 7a36c35a5..ad54e7fd4 100644 --- a/internal/cmd/search_cmd_freebsd.go +++ b/internal/cmd/search_cmd_freebsd.go @@ -13,8 +13,6 @@ import ( ) func init() { - clientCacheWrapper = daemonWrap - extraCommandsFuncs = append(extraCommandsFuncs, func(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Commands["daemon"] = func() (cli.Command, error) { return &daemon.DaemonCommand{ @@ -48,9 +46,3 @@ func init() { } }) } - -// daemonWrap wraps the provided cacheEnabledCommand with a daemon writer which -// conditionally starts the client side daemon after the command completes -func daemonWrap(c cacheEnabledCommand) cli.CommandFactory { - return daemon.Wrap(c) -} diff --git a/internal/cmd/search_cmd_linux.go b/internal/cmd/search_cmd_linux.go index a909630ed..97c00bb6f 100644 --- a/internal/cmd/search_cmd_linux.go +++ b/internal/cmd/search_cmd_linux.go @@ -11,8 +11,6 @@ import ( ) func init() { - clientCacheWrapper = daemonWrap - extraCommandsFuncs = append(extraCommandsFuncs, func(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Commands["daemon"] = func() (cli.Command, error) { return &daemon.DaemonCommand{ @@ -46,9 +44,3 @@ func init() { } }) } - -// daemonWrap wraps the provided cacheEnabledCommand with a daemon writer which -// conditionally starts the client side daemon after the command completes -func daemonWrap(c cacheEnabledCommand) cli.CommandFactory { - return daemon.Wrap(c) -} diff --git a/internal/cmd/search_cmd_windows.go b/internal/cmd/search_cmd_windows.go index 7a36c35a5..ad54e7fd4 100644 --- a/internal/cmd/search_cmd_windows.go +++ b/internal/cmd/search_cmd_windows.go @@ -13,8 +13,6 @@ import ( ) func init() { - clientCacheWrapper = daemonWrap - extraCommandsFuncs = append(extraCommandsFuncs, func(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Commands["daemon"] = func() (cli.Command, error) { return &daemon.DaemonCommand{ @@ -48,9 +46,3 @@ func init() { } }) } - -// daemonWrap wraps the provided cacheEnabledCommand with a daemon writer which -// conditionally starts the client side daemon after the command completes -func daemonWrap(c cacheEnabledCommand) cli.CommandFactory { - return daemon.Wrap(c) -} diff --git a/internal/cmd/wrapper/command_wrapper.go b/internal/cmd/wrapper/command_wrapper.go new file mode 100644 index 000000000..48fe608a3 --- /dev/null +++ b/internal/cmd/wrapper/command_wrapper.go @@ -0,0 +1,74 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package wrapper + +import ( + "context" + "errors" + "sync" + + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/mitchellh/cli" +) + +var ErrCallbackAlreadyRegistered = errors.New("registered callback already exists") + +// PostSuccessfulCommandCallback is a function that should be run after the +// wrapped command completes successfully. token will contain the value for any +// new token obtained during the running of the command +type PostSuccessfulCommandCallback func(ctx context.Context, badeCmd *base.Command, token string) + +var callbacks sync.Map + +// RegisterSuccessfulCommandCallback registers a callback to be run after a +// successful command. +func RegisterSuccessfulCommandCallback(name string, fn PostSuccessfulCommandCallback) error { + if _, existed := callbacks.Swap(name, fn); existed { + return ErrCallbackAlreadyRegistered + } + return nil +} + +type WrappableCommand interface { + cli.Command + BaseCommand() *base.Command +} + +// CommandWrapper starts the boundary daemon after the command was Run and attempts +// to send the current persona to any running daemon. +type CommandWrapper struct { + WrappableCommand +} + +// Wrap returns a cli.CommandFactory that returns a command wrapped in the CommandWrapper. +func Wrap(c WrappableCommand) cli.CommandFactory { + return func() (cli.Command, error) { + return &CommandWrapper{ + WrappableCommand: c, + }, nil + } +} + +// Run runs the wrapped command and then attempts to start the boundary daemon and send +// the current persona +func (w *CommandWrapper) Run(args []string) int { + // potentially intercept the token in case it isn't stored in the keyring + var token string + w.BaseCommand().Opts = append(w.BaseCommand().Opts, base.WithInterceptedToken(&token)) + r := w.WrappableCommand.Run(args) + + if r != base.CommandSuccess { + // if we were not successful in running our command, do not continue to + // start the daemon and add the token. + return r + } + + ctx := context.Background() + callbacks.Range(func(key, value any) bool { + val := value.(PostSuccessfulCommandCallback) + val(ctx, w.BaseCommand(), token) + return true + }) + return r +}