From 341e7aff0d32e1016218fa5351cd2e639c14262f Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 23 Feb 2021 19:06:39 -0500 Subject: [PATCH] CLI Generation (#953) --- Makefile | 7 +- internal/cmd/commands.go | 226 ++-- internal/cmd/commands/accounts/account.go | 407 ------ internal/cmd/commands/accounts/funcs.go | 59 - internal/cmd/commands/accounts/password.go | 241 ---- .../cmd/commands/accountscmd/accounts.gen.go | 321 +++++ internal/cmd/commands/accountscmd/funcs.go | 277 +++++ .../accountscmd/password_accounts.gen.go | 238 ++++ .../commands/accountscmd/password_funcs.go | 123 ++ .../cmd/commands/authmethods/authmethod.go | 288 ----- internal/cmd/commands/authmethods/funcs.go | 92 -- internal/cmd/commands/authmethods/password.go | 219 ---- .../authmethodscmd/authmethods.gen.go | 288 +++++ internal/cmd/commands/authmethodscmd/funcs.go | 178 +++ .../password_authmethods.gen.go | 243 ++++ .../commands/authmethodscmd/password_funcs.go | 117 ++ .../cmd/commands/authtokens/authtokens.go | 224 ---- internal/cmd/commands/authtokens/funcs.go | 41 - .../commands/authtokenscmd/authtokens.gen.go | 284 +++++ internal/cmd/commands/authtokenscmd/funcs.go | 87 ++ internal/cmd/commands/groups/funcs.go | 141 --- internal/cmd/commands/groups/group.go | 302 ----- internal/cmd/commands/groupscmd/funcs.go | 254 ++++ internal/cmd/commands/groupscmd/groups.gen.go | 362 ++++++ internal/cmd/commands/hostcatalogs/funcs.go | 77 -- .../cmd/commands/hostcatalogs/hostcatalog.go | 284 ----- internal/cmd/commands/hostcatalogs/static.go | 174 --- .../cmd/commands/hostcatalogscmd/funcs.go | 173 +++ .../hostcatalogscmd/hostcatalogs.gen.go | 288 +++++ .../commands/hostcatalogscmd/static_funcs.go | 33 + .../static_hostcatalogs.gen.go | 241 ++++ internal/cmd/commands/hosts/funcs.go | 65 - internal/cmd/commands/hosts/host.go | 271 ---- internal/cmd/commands/hosts/static.go | 201 --- internal/cmd/commands/hostscmd/funcs.go | 153 +++ internal/cmd/commands/hostscmd/hosts.gen.go | 299 +++++ .../cmd/commands/hostscmd/static_funcs.go | 86 ++ .../cmd/commands/hostscmd/static_hosts.gen.go | 238 ++++ internal/cmd/commands/hostsets/funcs.go | 65 - internal/cmd/commands/hostsets/hostset.go | 366 ------ internal/cmd/commands/hostsets/static.go | 174 --- internal/cmd/commands/hostsetscmd/funcs.go | 272 ++++ .../cmd/commands/hostsetscmd/hostsets.gen.go | 329 +++++ .../cmd/commands/hostsetscmd/static_funcs.go | 35 + .../hostsetscmd/static_hostsets.gen.go | 236 ++++ internal/cmd/commands/roles/funcs.go | 151 --- internal/cmd/commands/roles/role.go | 356 ------ internal/cmd/commands/rolescmd/funcs.go | 359 ++++++ internal/cmd/commands/rolescmd/roles.gen.go | 386 ++++++ internal/cmd/commands/scopes/funcs.go | 63 - internal/cmd/commands/scopes/scope.go | 282 ----- internal/cmd/commands/scopescmd/funcs.go | 159 +++ internal/cmd/commands/scopescmd/scopes.gen.go | 336 +++++ internal/cmd/commands/sessions/funcs.go | 107 -- internal/cmd/commands/sessions/session.go | 223 ---- internal/cmd/commands/sessionscmd/funcs.go | 205 +++ .../cmd/commands/sessionscmd/sessions.gen.go | 259 ++++ internal/cmd/commands/targets/funcs.go | 169 --- internal/cmd/commands/targets/target.go | 498 -------- internal/cmd/commands/targets/tcp.go | 268 ---- internal/cmd/commands/targetscmd/funcs.go | 492 ++++++++ .../cmd/commands/targetscmd/targets.gen.go | 335 +++++ internal/cmd/commands/targetscmd/tcp_funcs.go | 154 +++ .../commands/targetscmd/tcp_targets.gen.go | 243 ++++ internal/cmd/commands/users/funcs.go | 135 -- internal/cmd/commands/users/user.go | 302 ----- internal/cmd/commands/userscmd/funcs.go | 255 ++++ internal/cmd/commands/userscmd/users.gen.go | 362 ++++++ internal/cmd/gencli/Makefile | 12 + internal/cmd/gencli/input.go | 308 +++++ internal/cmd/gencli/main.go | 5 + internal/cmd/gencli/templates.go | 463 +++++++ internal/tests/cli/boundary/sessions.bats | 4 +- website/content/docs/api-clients/desktop.mdx | 44 +- .../docs/concepts/security/permissions.mdx | 1106 ++++++++++------- website/content/docs/configuration/worker.mdx | 24 +- .../docs/installing/no-gen-resources.mdx | 20 +- 77 files changed, 10303 insertions(+), 6861 deletions(-) delete mode 100644 internal/cmd/commands/accounts/account.go delete mode 100644 internal/cmd/commands/accounts/funcs.go delete mode 100644 internal/cmd/commands/accounts/password.go create mode 100644 internal/cmd/commands/accountscmd/accounts.gen.go create mode 100644 internal/cmd/commands/accountscmd/funcs.go create mode 100644 internal/cmd/commands/accountscmd/password_accounts.gen.go create mode 100644 internal/cmd/commands/accountscmd/password_funcs.go delete mode 100644 internal/cmd/commands/authmethods/authmethod.go delete mode 100644 internal/cmd/commands/authmethods/funcs.go delete mode 100644 internal/cmd/commands/authmethods/password.go create mode 100644 internal/cmd/commands/authmethodscmd/authmethods.gen.go create mode 100644 internal/cmd/commands/authmethodscmd/funcs.go create mode 100644 internal/cmd/commands/authmethodscmd/password_authmethods.gen.go create mode 100644 internal/cmd/commands/authmethodscmd/password_funcs.go delete mode 100644 internal/cmd/commands/authtokens/authtokens.go delete mode 100644 internal/cmd/commands/authtokens/funcs.go create mode 100644 internal/cmd/commands/authtokenscmd/authtokens.gen.go create mode 100644 internal/cmd/commands/authtokenscmd/funcs.go delete mode 100644 internal/cmd/commands/groups/funcs.go delete mode 100644 internal/cmd/commands/groups/group.go create mode 100644 internal/cmd/commands/groupscmd/funcs.go create mode 100644 internal/cmd/commands/groupscmd/groups.gen.go delete mode 100644 internal/cmd/commands/hostcatalogs/funcs.go delete mode 100644 internal/cmd/commands/hostcatalogs/hostcatalog.go delete mode 100644 internal/cmd/commands/hostcatalogs/static.go create mode 100644 internal/cmd/commands/hostcatalogscmd/funcs.go create mode 100644 internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go create mode 100644 internal/cmd/commands/hostcatalogscmd/static_funcs.go create mode 100644 internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go delete mode 100644 internal/cmd/commands/hosts/funcs.go delete mode 100644 internal/cmd/commands/hosts/host.go delete mode 100644 internal/cmd/commands/hosts/static.go create mode 100644 internal/cmd/commands/hostscmd/funcs.go create mode 100644 internal/cmd/commands/hostscmd/hosts.gen.go create mode 100644 internal/cmd/commands/hostscmd/static_funcs.go create mode 100644 internal/cmd/commands/hostscmd/static_hosts.gen.go delete mode 100644 internal/cmd/commands/hostsets/funcs.go delete mode 100644 internal/cmd/commands/hostsets/hostset.go delete mode 100644 internal/cmd/commands/hostsets/static.go create mode 100644 internal/cmd/commands/hostsetscmd/funcs.go create mode 100644 internal/cmd/commands/hostsetscmd/hostsets.gen.go create mode 100644 internal/cmd/commands/hostsetscmd/static_funcs.go create mode 100644 internal/cmd/commands/hostsetscmd/static_hostsets.gen.go delete mode 100644 internal/cmd/commands/roles/funcs.go delete mode 100644 internal/cmd/commands/roles/role.go create mode 100644 internal/cmd/commands/rolescmd/funcs.go create mode 100644 internal/cmd/commands/rolescmd/roles.gen.go delete mode 100644 internal/cmd/commands/scopes/funcs.go delete mode 100644 internal/cmd/commands/scopes/scope.go create mode 100644 internal/cmd/commands/scopescmd/funcs.go create mode 100644 internal/cmd/commands/scopescmd/scopes.gen.go delete mode 100644 internal/cmd/commands/sessions/funcs.go delete mode 100644 internal/cmd/commands/sessions/session.go create mode 100644 internal/cmd/commands/sessionscmd/funcs.go create mode 100644 internal/cmd/commands/sessionscmd/sessions.gen.go delete mode 100644 internal/cmd/commands/targets/funcs.go delete mode 100644 internal/cmd/commands/targets/target.go delete mode 100644 internal/cmd/commands/targets/tcp.go create mode 100644 internal/cmd/commands/targetscmd/funcs.go create mode 100644 internal/cmd/commands/targetscmd/targets.gen.go create mode 100644 internal/cmd/commands/targetscmd/tcp_funcs.go create mode 100644 internal/cmd/commands/targetscmd/tcp_targets.gen.go delete mode 100644 internal/cmd/commands/users/funcs.go delete mode 100644 internal/cmd/commands/users/user.go create mode 100644 internal/cmd/commands/userscmd/funcs.go create mode 100644 internal/cmd/commands/userscmd/users.gen.go create mode 100644 internal/cmd/gencli/Makefile create mode 100644 internal/cmd/gencli/input.go create mode 100644 internal/cmd/gencli/main.go create mode 100644 internal/cmd/gencli/templates.go diff --git a/Makefile b/Makefile index fc4720db6..1c8615d5d 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,9 @@ export GEN_BASEPATH := $(shell pwd) api: $(MAKE) --environment-overrides -C internal/api/genapi api +cli: + $(MAKE) --environment-overrides -C internal/cmd/gencli cli + tools: go generate -tags tools tools/tools.go @@ -74,7 +77,7 @@ endif perms-table: @go run internal/website/permstable/permstable.go -gen: cleangen proto api migrations perms-table fmt +gen: cleangen proto api cli migrations perms-table fmt migrations: $(MAKE) --environment-overrides -C internal/db/schema/migrations/generate migrations @@ -182,7 +185,7 @@ docker-publish: docker push $(IMAGE_TAG) docker push hashicorp/boundary:latest -.PHONY: api tools gen migrations proto website ci-config ci-verify set-ui-version docker docker-build docker-build-dev docker-publish +.PHONY: api cli tools gen migrations proto website ci-config ci-verify set-ui-version docker docker-build docker-build-dev docker-publish .NOTPARALLEL: diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index a3b7a56f3..40b147cc6 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -6,24 +6,24 @@ import ( "syscall" "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/commands/accounts" + "github.com/hashicorp/boundary/internal/cmd/commands/accountscmd" "github.com/hashicorp/boundary/internal/cmd/commands/authenticate" - "github.com/hashicorp/boundary/internal/cmd/commands/authmethods" - "github.com/hashicorp/boundary/internal/cmd/commands/authtokens" + "github.com/hashicorp/boundary/internal/cmd/commands/authmethodscmd" + "github.com/hashicorp/boundary/internal/cmd/commands/authtokenscmd" "github.com/hashicorp/boundary/internal/cmd/commands/config" "github.com/hashicorp/boundary/internal/cmd/commands/connect" "github.com/hashicorp/boundary/internal/cmd/commands/database" "github.com/hashicorp/boundary/internal/cmd/commands/dev" - "github.com/hashicorp/boundary/internal/cmd/commands/groups" - "github.com/hashicorp/boundary/internal/cmd/commands/hostcatalogs" - "github.com/hashicorp/boundary/internal/cmd/commands/hosts" - "github.com/hashicorp/boundary/internal/cmd/commands/hostsets" - "github.com/hashicorp/boundary/internal/cmd/commands/roles" - "github.com/hashicorp/boundary/internal/cmd/commands/scopes" + "github.com/hashicorp/boundary/internal/cmd/commands/groupscmd" + "github.com/hashicorp/boundary/internal/cmd/commands/hostcatalogscmd" + "github.com/hashicorp/boundary/internal/cmd/commands/hostscmd" + "github.com/hashicorp/boundary/internal/cmd/commands/hostsetscmd" + "github.com/hashicorp/boundary/internal/cmd/commands/rolescmd" + "github.com/hashicorp/boundary/internal/cmd/commands/scopescmd" "github.com/hashicorp/boundary/internal/cmd/commands/server" - "github.com/hashicorp/boundary/internal/cmd/commands/sessions" - "github.com/hashicorp/boundary/internal/cmd/commands/targets" - "github.com/hashicorp/boundary/internal/cmd/commands/users" + "github.com/hashicorp/boundary/internal/cmd/commands/sessionscmd" + "github.com/hashicorp/boundary/internal/cmd/commands/targetscmd" + "github.com/hashicorp/boundary/internal/cmd/commands/userscmd" "github.com/hashicorp/boundary/internal/cmd/commands/version" "github.com/mitchellh/cli" @@ -66,132 +66,132 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { }, "accounts": func() (cli.Command, error) { - return &accounts.Command{ + return &accountscmd.Command{ Command: base.NewCommand(ui), }, nil }, "accounts read": func() (cli.Command, error) { - return &accounts.Command{ + return &accountscmd.Command{ Command: base.NewCommand(ui), Func: "read", }, nil }, "accounts delete": func() (cli.Command, error) { - return &accounts.Command{ + return &accountscmd.Command{ Command: base.NewCommand(ui), Func: "delete", }, nil }, "accounts list": func() (cli.Command, error) { - return &accounts.Command{ + return &accountscmd.Command{ Command: base.NewCommand(ui), Func: "list", }, nil }, "accounts set-password": func() (cli.Command, error) { - return &accounts.Command{ + return &accountscmd.Command{ Command: base.NewCommand(ui), Func: "set-password", }, nil }, "accounts change-password": func() (cli.Command, error) { - return &accounts.Command{ + return &accountscmd.Command{ Command: base.NewCommand(ui), Func: "change-password", }, nil }, "accounts create": func() (cli.Command, error) { - return &accounts.Command{ + return &accountscmd.Command{ Command: base.NewCommand(ui), Func: "create", }, nil }, "accounts create password": func() (cli.Command, error) { - return &accounts.PasswordCommand{ + return &accountscmd.PasswordCommand{ Command: base.NewCommand(ui), Func: "create", }, nil }, "accounts update": func() (cli.Command, error) { - return &accounts.Command{ + return &accountscmd.Command{ Command: base.NewCommand(ui), Func: "update", }, nil }, "accounts update password": func() (cli.Command, error) { - return &accounts.PasswordCommand{ + return &accountscmd.PasswordCommand{ Command: base.NewCommand(ui), Func: "update", }, nil }, "auth-methods": func() (cli.Command, error) { - return &authmethods.Command{ + return &authmethodscmd.Command{ Command: base.NewCommand(ui), }, nil }, "auth-methods read": func() (cli.Command, error) { - return &authmethods.Command{ + return &authmethodscmd.Command{ Command: base.NewCommand(ui), Func: "read", }, nil }, "auth-methods delete": func() (cli.Command, error) { - return &authmethods.Command{ + return &authmethodscmd.Command{ Command: base.NewCommand(ui), Func: "delete", }, nil }, "auth-methods list": func() (cli.Command, error) { - return &authmethods.Command{ + return &authmethodscmd.Command{ Command: base.NewCommand(ui), Func: "list", }, nil }, "auth-methods create": func() (cli.Command, error) { - return &authmethods.Command{ + return &authmethodscmd.Command{ Command: base.NewCommand(ui), Func: "create", }, nil }, "auth-methods create password": func() (cli.Command, error) { - return &authmethods.PasswordCommand{ + return &authmethodscmd.PasswordCommand{ Command: base.NewCommand(ui), Func: "create", }, nil }, "auth-methods update": func() (cli.Command, error) { - return &authmethods.Command{ + return &authmethodscmd.Command{ Command: base.NewCommand(ui), Func: "update", }, nil }, "auth-methods update password": func() (cli.Command, error) { - return &authmethods.PasswordCommand{ + return &authmethodscmd.PasswordCommand{ Command: base.NewCommand(ui), Func: "update", }, nil }, "auth-tokens": func() (cli.Command, error) { - return &authtokens.Command{ + return &authtokenscmd.Command{ Command: base.NewCommand(ui), }, nil }, "auth-tokens read": func() (cli.Command, error) { - return &authtokens.Command{ + return &authtokenscmd.Command{ Command: base.NewCommand(ui), Func: "read", }, nil }, "auth-tokens delete": func() (cli.Command, error) { - return &authtokens.Command{ + return &authtokenscmd.Command{ Command: base.NewCommand(ui), Func: "delete", }, nil }, "auth-tokens list": func() (cli.Command, error) { - return &authtokens.Command{ + return &authtokenscmd.Command{ Command: base.NewCommand(ui), Func: "list", }, nil @@ -293,474 +293,474 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { }, "groups": func() (cli.Command, error) { - return &groups.Command{ + return &groupscmd.Command{ Command: base.NewCommand(ui), }, nil }, "groups create": func() (cli.Command, error) { - return &groups.Command{ + return &groupscmd.Command{ Command: base.NewCommand(ui), Func: "create", }, nil }, "groups update": func() (cli.Command, error) { - return &groups.Command{ + return &groupscmd.Command{ Command: base.NewCommand(ui), Func: "update", }, nil }, "groups read": func() (cli.Command, error) { - return &groups.Command{ + return &groupscmd.Command{ Command: base.NewCommand(ui), Func: "read", }, nil }, "groups delete": func() (cli.Command, error) { - return &groups.Command{ + return &groupscmd.Command{ Command: base.NewCommand(ui), Func: "delete", }, nil }, "groups list": func() (cli.Command, error) { - return &groups.Command{ + return &groupscmd.Command{ Command: base.NewCommand(ui), Func: "list", }, nil }, "groups add-members": func() (cli.Command, error) { - return &groups.Command{ + return &groupscmd.Command{ Command: base.NewCommand(ui), Func: "add-members", }, nil }, "groups set-members": func() (cli.Command, error) { - return &groups.Command{ + return &groupscmd.Command{ Command: base.NewCommand(ui), Func: "set-members", }, nil }, "groups remove-members": func() (cli.Command, error) { - return &groups.Command{ + return &groupscmd.Command{ Command: base.NewCommand(ui), Func: "remove-members", }, nil }, "host-catalogs": func() (cli.Command, error) { - return &hostcatalogs.Command{ + return &hostcatalogscmd.Command{ Command: base.NewCommand(ui), }, nil }, "host-catalogs read": func() (cli.Command, error) { - return &hostcatalogs.Command{ + return &hostcatalogscmd.Command{ Command: base.NewCommand(ui), Func: "read", }, nil }, "host-catalogs delete": func() (cli.Command, error) { - return &hostcatalogs.Command{ + return &hostcatalogscmd.Command{ Command: base.NewCommand(ui), Func: "delete", }, nil }, "host-catalogs list": func() (cli.Command, error) { - return &hostcatalogs.Command{ + return &hostcatalogscmd.Command{ Command: base.NewCommand(ui), Func: "list", }, nil }, "host-catalogs create": func() (cli.Command, error) { - return &hostcatalogs.Command{ + return &hostcatalogscmd.Command{ Command: base.NewCommand(ui), Func: "create", }, nil }, "host-catalogs create static": func() (cli.Command, error) { - return &hostcatalogs.StaticCommand{ + return &hostcatalogscmd.StaticCommand{ Command: base.NewCommand(ui), Func: "create", }, nil }, "host-catalogs update": func() (cli.Command, error) { - return &hostcatalogs.Command{ + return &hostcatalogscmd.Command{ Command: base.NewCommand(ui), Func: "update", }, nil }, "host-catalogs update static": func() (cli.Command, error) { - return &hostcatalogs.StaticCommand{ + return &hostcatalogscmd.StaticCommand{ Command: base.NewCommand(ui), Func: "update", }, nil }, "host-sets": func() (cli.Command, error) { - return &hostsets.Command{ + return &hostsetscmd.Command{ Command: base.NewCommand(ui), }, nil }, "host-sets read": func() (cli.Command, error) { - return &hostsets.Command{ + return &hostsetscmd.Command{ Command: base.NewCommand(ui), Func: "read", }, nil }, "host-sets delete": func() (cli.Command, error) { - return &hostsets.Command{ + return &hostsetscmd.Command{ Command: base.NewCommand(ui), Func: "delete", }, nil }, "host-sets list": func() (cli.Command, error) { - return &hostsets.Command{ + return &hostsetscmd.Command{ Command: base.NewCommand(ui), Func: "list", }, nil }, "host-sets create": func() (cli.Command, error) { - return &hostsets.Command{ + return &hostsetscmd.Command{ Command: base.NewCommand(ui), Func: "create", }, nil }, "host-sets create static": func() (cli.Command, error) { - return &hostsets.StaticCommand{ + return &hostsetscmd.StaticCommand{ Command: base.NewCommand(ui), Func: "create", }, nil }, "host-sets update": func() (cli.Command, error) { - return &hostsets.Command{ + return &hostsetscmd.Command{ Command: base.NewCommand(ui), Func: "update", }, nil }, "host-sets update static": func() (cli.Command, error) { - return &hostsets.StaticCommand{ + return &hostsetscmd.StaticCommand{ Command: base.NewCommand(ui), Func: "update", }, nil }, "host-sets add-hosts": func() (cli.Command, error) { - return &hostsets.Command{ + return &hostsetscmd.Command{ Command: base.NewCommand(ui), Func: "add-hosts", }, nil }, "host-sets remove-hosts": func() (cli.Command, error) { - return &hostsets.Command{ + return &hostsetscmd.Command{ Command: base.NewCommand(ui), Func: "remove-hosts", }, nil }, "host-sets set-hosts": func() (cli.Command, error) { - return &hostsets.Command{ + return &hostsetscmd.Command{ Command: base.NewCommand(ui), Func: "set-hosts", }, nil }, "hosts": func() (cli.Command, error) { - return &hosts.Command{ + return &hostscmd.Command{ Command: base.NewCommand(ui), }, nil }, "hosts read": func() (cli.Command, error) { - return &hosts.Command{ + return &hostscmd.Command{ Command: base.NewCommand(ui), Func: "read", }, nil }, "hosts delete": func() (cli.Command, error) { - return &hosts.Command{ + return &hostscmd.Command{ Command: base.NewCommand(ui), Func: "delete", }, nil }, "hosts list": func() (cli.Command, error) { - return &hosts.Command{ + return &hostscmd.Command{ Command: base.NewCommand(ui), Func: "list", }, nil }, "hosts create": func() (cli.Command, error) { - return &hosts.Command{ + return &hostscmd.Command{ Command: base.NewCommand(ui), Func: "create", }, nil }, "hosts create static": func() (cli.Command, error) { - return &hosts.StaticCommand{ + return &hostscmd.StaticCommand{ Command: base.NewCommand(ui), Func: "create", }, nil }, "hosts update": func() (cli.Command, error) { - return &hosts.Command{ + return &hostscmd.Command{ Command: base.NewCommand(ui), Func: "update", }, nil }, "hosts update static": func() (cli.Command, error) { - return &hosts.StaticCommand{ + return &hostscmd.StaticCommand{ Command: base.NewCommand(ui), Func: "update", }, nil }, "roles": func() (cli.Command, error) { - return &roles.Command{ + return &rolescmd.Command{ Command: base.NewCommand(ui), }, nil }, "roles create": func() (cli.Command, error) { - return &roles.Command{ + return &rolescmd.Command{ Command: base.NewCommand(ui), Func: "create", }, nil }, "roles update": func() (cli.Command, error) { - return &roles.Command{ + return &rolescmd.Command{ Command: base.NewCommand(ui), Func: "update", }, nil }, "roles read": func() (cli.Command, error) { - return &roles.Command{ + return &rolescmd.Command{ Command: base.NewCommand(ui), Func: "read", }, nil }, "roles delete": func() (cli.Command, error) { - return &roles.Command{ + return &rolescmd.Command{ Command: base.NewCommand(ui), Func: "delete", }, nil }, "roles list": func() (cli.Command, error) { - return &roles.Command{ + return &rolescmd.Command{ Command: base.NewCommand(ui), Func: "list", }, nil }, "roles add-principals": func() (cli.Command, error) { - return &roles.Command{ + return &rolescmd.Command{ Command: base.NewCommand(ui), Func: "add-principals", }, nil }, "roles set-principals": func() (cli.Command, error) { - return &roles.Command{ + return &rolescmd.Command{ Command: base.NewCommand(ui), Func: "set-principals", }, nil }, "roles remove-principals": func() (cli.Command, error) { - return &roles.Command{ + return &rolescmd.Command{ Command: base.NewCommand(ui), Func: "remove-principals", }, nil }, "roles add-grants": func() (cli.Command, error) { - return &roles.Command{ + return &rolescmd.Command{ Command: base.NewCommand(ui), Func: "add-grants", }, nil }, "roles set-grants": func() (cli.Command, error) { - return &roles.Command{ + return &rolescmd.Command{ Command: base.NewCommand(ui), Func: "set-grants", }, nil }, "roles remove-grants": func() (cli.Command, error) { - return &roles.Command{ + return &rolescmd.Command{ Command: base.NewCommand(ui), Func: "remove-grants", }, nil }, "scopes": func() (cli.Command, error) { - return &scopes.Command{ + return &scopescmd.Command{ Command: base.NewCommand(ui), }, nil }, "scopes create": func() (cli.Command, error) { - return &scopes.Command{ + return &scopescmd.Command{ Command: base.NewCommand(ui), Func: "create", }, nil }, "scopes read": func() (cli.Command, error) { - return &scopes.Command{ + return &scopescmd.Command{ Command: base.NewCommand(ui), Func: "read", }, nil }, "scopes update": func() (cli.Command, error) { - return &scopes.Command{ + return &scopescmd.Command{ Command: base.NewCommand(ui), Func: "update", }, nil }, "scopes delete": func() (cli.Command, error) { - return &scopes.Command{ + return &scopescmd.Command{ Command: base.NewCommand(ui), Func: "delete", }, nil }, "scopes list": func() (cli.Command, error) { - return &scopes.Command{ + return &scopescmd.Command{ Command: base.NewCommand(ui), Func: "list", }, nil }, "sessions": func() (cli.Command, error) { - return &sessions.Command{ + return &sessionscmd.Command{ Command: base.NewCommand(ui), }, nil }, "sessions read": func() (cli.Command, error) { - return &sessions.Command{ + return &sessionscmd.Command{ Command: base.NewCommand(ui), Func: "read", }, nil }, "sessions list": func() (cli.Command, error) { - return &sessions.Command{ + return &sessionscmd.Command{ Command: base.NewCommand(ui), Func: "list", }, nil }, "sessions cancel": func() (cli.Command, error) { - return &sessions.Command{ + return &sessionscmd.Command{ Command: base.NewCommand(ui), Func: "cancel", }, nil }, "targets": func() (cli.Command, error) { - return &targets.Command{ + return &targetscmd.Command{ Command: base.NewCommand(ui), }, nil }, "targets authorize-session": func() (cli.Command, error) { - return &targets.Command{ + return &targetscmd.Command{ Command: base.NewCommand(ui), Func: "authorize-session", }, nil }, "targets read": func() (cli.Command, error) { - return &targets.Command{ + return &targetscmd.Command{ Command: base.NewCommand(ui), Func: "read", }, nil }, "targets delete": func() (cli.Command, error) { - return &targets.Command{ + return &targetscmd.Command{ Command: base.NewCommand(ui), Func: "delete", }, nil }, "targets list": func() (cli.Command, error) { - return &targets.Command{ + return &targetscmd.Command{ Command: base.NewCommand(ui), Func: "list", }, nil }, "targets create": func() (cli.Command, error) { - return &targets.Command{ + return &targetscmd.Command{ Command: base.NewCommand(ui), Func: "create", }, nil }, "targets create tcp": func() (cli.Command, error) { - return &targets.TcpCommand{ + return &targetscmd.TcpCommand{ Command: base.NewCommand(ui), Func: "create", }, nil }, "targets update": func() (cli.Command, error) { - return &targets.Command{ + return &targetscmd.Command{ Command: base.NewCommand(ui), Func: "update", }, nil }, "targets update tcp": func() (cli.Command, error) { - return &targets.TcpCommand{ + return &targetscmd.TcpCommand{ Command: base.NewCommand(ui), Func: "update", }, nil }, "targets add-host-sets": func() (cli.Command, error) { - return &targets.Command{ + return &targetscmd.Command{ Command: base.NewCommand(ui), Func: "add-host-sets", }, nil }, "targets remove-host-sets": func() (cli.Command, error) { - return &targets.Command{ + return &targetscmd.Command{ Command: base.NewCommand(ui), Func: "remove-host-sets", }, nil }, "targets set-host-sets": func() (cli.Command, error) { - return &targets.Command{ + return &targetscmd.Command{ Command: base.NewCommand(ui), Func: "set-host-sets", }, nil }, "users": func() (cli.Command, error) { - return &users.Command{ + return &userscmd.Command{ Command: base.NewCommand(ui), }, nil }, "users create": func() (cli.Command, error) { - return &users.Command{ + return &userscmd.Command{ Command: base.NewCommand(ui), Func: "create", }, nil }, "users read": func() (cli.Command, error) { - return &users.Command{ + return &userscmd.Command{ Command: base.NewCommand(ui), Func: "read", }, nil }, "users update": func() (cli.Command, error) { - return &users.Command{ + return &userscmd.Command{ Command: base.NewCommand(ui), Func: "update", }, nil }, "users delete": func() (cli.Command, error) { - return &users.Command{ + return &userscmd.Command{ Command: base.NewCommand(ui), Func: "delete", }, nil }, "users list": func() (cli.Command, error) { - return &users.Command{ + return &userscmd.Command{ Command: base.NewCommand(ui), Func: "list", }, nil }, "users add-accounts": func() (cli.Command, error) { - return &users.Command{ + return &userscmd.Command{ Command: base.NewCommand(ui), Func: "add-accounts", }, nil }, "users set-accounts": func() (cli.Command, error) { - return &users.Command{ + return &userscmd.Command{ Command: base.NewCommand(ui), Func: "set-accounts", }, nil }, "users remove-accounts": func() (cli.Command, error) { - return &users.Command{ + return &userscmd.Command{ Command: base.NewCommand(ui), Func: "remove-accounts", }, nil diff --git a/internal/cmd/commands/accounts/account.go b/internal/cmd/commands/accounts/account.go deleted file mode 100644 index 06bd1d2de..000000000 --- a/internal/cmd/commands/accounts/account.go +++ /dev/null @@ -1,407 +0,0 @@ -package accounts - -import ( - "fmt" - "net/http" - "os" - "strings" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/accounts" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/internal/types/resource" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/hashicorp/vault/sdk/helper/password" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*Command)(nil) - _ cli.CommandAutocomplete = (*Command)(nil) -) - -type Command struct { - *base.Command - - Func string - - flagPassword string - flagCurrentPassword string - flagNewPassword string -} - -func (c *Command) Synopsis() string { - switch c.Func { - case "set-password": - return "Directly set the password on an account resource" - case "change-password": - return "Change the password on an account resource" - default: - return common.SynopsisFunc(c.Func, "account") - } -} - -var flagsMap = map[string][]string{ - "read": {"id"}, - "delete": {"id"}, - "list": {"auth-method-id"}, - "set-password": {"id", "password", "version"}, - "change-password": {"id", "current-password", "new-password", "version"}, -} - -func (c *Command) Help() string { - helpMap := common.HelpMap("account") - var helpStr string - switch c.Func { - case "": - return base.WrapForHelpText([]string{ - "Usage: boundary accounts [sub command] [options] [args]", - "", - " This command allows operations on Boundary account resources. Example:", - "", - " Read a account:", - "", - ` $ boundary accounts read -id apw_1234567890`, - "", - " Please see the accounts subcommand help for detailed usage information.", - }) - case "create": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary accounts create [type] [sub command] [options] [args]", - "", - " This command allows create operations on Boundary account resources. Example:", - "", - " Create a password-type account:", - "", - ` $ boundary accounts create password -name prodops -description "For ProdOps usage"`, - "", - " Please see the typed subcommand help for detailed usage information.", - }) - case "update": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary accounts update [type] [sub command] [options] [args]", - "", - " This command allows update operations on Boundary account resources. Example:", - "", - " Update a password-type account:", - "", - ` $ boundary accounts update password -id apw_1234567890 -name devops -description "For DevOps usage"`, - "", - " Please see the typed subcommand help for detailed usage information.", - }) - case "set-password": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary accounts set-password [sub command] [options] [args]", - "", - " This command allows setting the password on account-type resources, if the types match and the operation is allowed by the given account type. Example:", - "", - " Set the password on a password-type account:", - "", - ` $ boundary accounts set-password -id apw_1234567890 -password `, - "", - "", - }) - case "change-password": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary accounts change-password [sub command] [options] [args]", - "", - " This command allows changing the password (with verification of the current password) on account-type resources, if the types match and the operation is allowed by the given account type. Example:", - "", - " Change the password on a password-type account:", - "", - ` $ boundary accounts change-password -id apw_1234567890 -current-password -new-password `, - "", - "", - }) - default: - helpStr = helpMap[c.Func]() - } - return helpStr + c.Flags().Help() -} - -func (c *Command) Flags() *base.FlagSets { - if len(flagsMap[c.Func]) == 0 { - return c.FlagSet(base.FlagSetNone) - } - - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, resource.Account.String(), flagsMap[c.Func]) - - for _, name := range flagsMap[c.Func] { - switch name { - case "password": - f.StringVar(&base.StringVar{ - Name: "password", - Target: &c.flagPassword, - Usage: "The password for the account. If not specified, the command will prompt for the password to be entered in a non-echoing way.", - }) - case "current-password": - f.StringVar(&base.StringVar{ - Name: "current-password", - Target: &c.flagCurrentPassword, - Usage: "The current password for the account. If not specified, the command will prompt for the password to be entered in a non-echoing way.", - }) - case "new-password": - f.StringVar(&base.StringVar{ - Name: "new-password", - Target: &c.flagNewPassword, - Usage: "The new password for the account. If not specified, the command will prompt for the password to be entered in a non-echoing way.", - }) - } - } - - return set -} - -func (c *Command) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *Command) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *Command) Run(args []string) int { - switch c.Func { - case "", "create", "update": - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(flagsMap[c.Func], "auth-method-id") && c.FlagAuthMethodId == "" { - c.UI.Error("Auth Method ID must be passed in via -auth-method-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []accounts.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, accounts.DefaultName()) - default: - opts = append(opts, accounts.WithName(c.FlagName)) - } - - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, accounts.DefaultDescription()) - default: - opts = append(opts, accounts.WithDescription(c.FlagDescription)) - } - - if strutil.StrListContains(flagsMap[c.Func], "password") && c.flagPassword == "" { - fmt.Print("Password is not set as flag, please enter it now (will be hidden): ") - value, err := password.Read(os.Stdin) - fmt.Print("\n") - if err != nil { - c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error())) - return 2 - } - fmt.Print("Please enter it one more time for confirmation: ") - confirmation, err := password.Read(os.Stdin) - fmt.Print("\n") - if err != nil { - c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error())) - return 2 - } - if strings.TrimSpace(value) != strings.TrimSpace(confirmation) { - c.UI.Error("Entered password and confirmation value did not match.") - return 2 - } - c.flagPassword = strings.TrimSpace(value) - } - - if strutil.StrListContains(flagsMap[c.Func], "current-password") && c.flagCurrentPassword == "" { - fmt.Print("Current password is not set as flag, please enter it now (will be hidden): ") - value, err := password.Read(os.Stdin) - fmt.Print("\n") - if err != nil { - c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error())) - return 2 - } - c.flagCurrentPassword = strings.TrimSpace(value) - } - - if strutil.StrListContains(flagsMap[c.Func], "new-password") && c.flagNewPassword == "" { - fmt.Print("New password is not set as flag, please enter it now (will be hidden): ") - value, err := password.Read(os.Stdin) - fmt.Print("\n") - if err != nil { - c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error())) - return 2 - } - fmt.Print("Please enter it one more time for confirmation: ") - confirmation, err := password.Read(os.Stdin) - fmt.Print("\n") - if err != nil { - c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error())) - return 2 - } - if strings.TrimSpace(value) != strings.TrimSpace(confirmation) { - c.UI.Error("Entered password and confirmation value did not match.") - return 2 - } - c.flagNewPassword = strings.TrimSpace(value) - } - - // Perform check-and-set when needed - var version uint32 - switch c.Func { - case "create": - // These don't update so don't need the existing version - default: - switch c.FlagVersion { - case 0: - opts = append(opts, accounts.WithAutomaticVersioning(true)) - default: - version = uint32(c.FlagVersion) - } - } - - accountClient := accounts.NewClient(client) - - existed := true - var result api.GenericResult - var listResult api.GenericListResult - - switch c.Func { - case "read": - result, err = accountClient.Read(c.Context, c.FlagId, opts...) - case "delete": - _, err = accountClient.Delete(c.Context, c.FlagId, opts...) - if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { - existed = false - err = nil - } - case "list": - listResult, err = accountClient.List(c.Context, c.FlagAuthMethodId, opts...) - case "set-password": - result, err = accountClient.SetPassword(c.Context, c.FlagId, c.flagPassword, version, opts...) - case "change-password": - result, err = accountClient.ChangePassword(c.Context, c.FlagId, c.flagCurrentPassword, c.flagNewPassword, version, opts...) - } - - plural := "account" - if c.Func == "list" { - plural = "accounts" - } - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - switch c.Func { - case "delete": - switch base.Format(c.UI) { - case "json": - c.UI.Output("null") - case "table": - output := "The delete operation completed successfully" - switch existed { - case true: - output += "." - default: - output += ", however the resource did not exist at the time." - } - c.UI.Output(output) - } - return 0 - - case "list": - accounts := listResult.GetItems().([]*accounts.Account) - switch base.Format(c.UI) { - case "json": - if len(accounts) == 0 { - c.UI.Output("null") - return 0 - } - b, err := base.JsonFormatter{}.Format(accounts) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - - case "table": - if len(accounts) == 0 { - c.UI.Output("No accounts found") - return 0 - } - var output []string - output = []string{ - "", - "Account information:", - } - for i, m := range accounts { - if i > 0 { - output = append(output, "") - } - if true { - output = append(output, - fmt.Sprintf(" ID: %s", m.Id), - fmt.Sprintf(" Version: %d", m.Version), - fmt.Sprintf(" Type: %s", m.Type), - ) - } - if m.Name != "" { - output = append(output, - fmt.Sprintf(" Name: %s", m.Name), - ) - } - if m.Description != "" { - output = append(output, - fmt.Sprintf(" Description: %s", m.Description), - ) - } - if len(m.AuthorizedActions) > 0 { - output = append(output, - " Authorized Actions:", - base.WrapSlice(6, m.AuthorizedActions), - ) - } - } - c.UI.Output(base.WrapForHelpText(output)) - } - return 0 - } - - account := result.GetItem().(*accounts.Account) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateAccountTableOutput(account)) - case "json": - b, err := base.JsonFormatter{}.Format(account) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/accounts/funcs.go b/internal/cmd/commands/accounts/funcs.go deleted file mode 100644 index 3716477ab..000000000 --- a/internal/cmd/commands/accounts/funcs.go +++ /dev/null @@ -1,59 +0,0 @@ -package accounts - -import ( - "time" - - "github.com/hashicorp/boundary/api/accounts" - "github.com/hashicorp/boundary/internal/cmd/base" -) - -func generateAccountTableOutput(in *accounts.Account) string { - nonAttributeMap := map[string]interface{}{ - "ID": in.Id, - "Version": in.Version, - "Type": in.Type, - "Created Time": in.CreatedTime.Local().Format(time.RFC1123), - "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), - "Auth Method ID": in.AuthMethodId, - } - - if in.Name != "" { - nonAttributeMap["Name"] = in.Name - } - if in.Description != "" { - nonAttributeMap["Description"] = in.Description - } - - maxLength := base.MaxAttributesLength(nonAttributeMap, in.Attributes, keySubstMap) - - ret := []string{ - "", - "Account information:", - base.WrapMap(2, maxLength+2, nonAttributeMap), - "", - " Scope:", - base.ScopeInfoForOutput(in.Scope, maxLength), - } - - if len(in.AuthorizedActions) > 0 { - ret = append(ret, - "", - " Authorized Actions:", - base.WrapSlice(4, in.AuthorizedActions), - ) - } - - if len(in.Attributes) > 0 { - ret = append(ret, - "", - " Attributes:", - base.WrapMap(4, maxLength, in.Attributes), - ) - } - - return base.WrapForHelpText(ret) -} - -var keySubstMap = map[string]string{ - "login_name": "Login Name", -} diff --git a/internal/cmd/commands/accounts/password.go b/internal/cmd/commands/accounts/password.go deleted file mode 100644 index e5bb3a6e6..000000000 --- a/internal/cmd/commands/accounts/password.go +++ /dev/null @@ -1,241 +0,0 @@ -package accounts - -import ( - "fmt" - "net/textproto" - "os" - "strings" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/accounts" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/hashicorp/vault/sdk/helper/password" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*PasswordCommand)(nil) - _ cli.CommandAutocomplete = (*PasswordCommand)(nil) -) - -type PasswordCommand struct { - *base.Command - - Func string - - flagLoginName string - flagPassword string -} - -func (c *PasswordCommand) Synopsis() string { - return fmt.Sprintf("%s a password-type account", textproto.CanonicalMIMEHeaderKey(c.Func)) -} - -var passwordFlagsMap = map[string][]string{ - "create": {"auth-method-id", "name", "description", "login-name", "password"}, - "update": {"id", "name", "description", "version", "login-name"}, -} - -func (c *PasswordCommand) Help() string { - var info string - switch c.Func { - case "create": - info = base.WrapForHelpText([]string{ - "Usage: boundary accounts create password [options] [args]", - "", - " Create a password-type account. Example:", - "", - ` $ boundary accounts create password -login-name prodops -description "Password account for ProdOps"`, - "", - "", - }) - - case "update": - info = base.WrapForHelpText([]string{ - "Usage: boundary accounts update password [options] [args]", - "", - " Update a password-type account given its ID. Example:", - "", - ` $ boundary accounts update password -id apw_1234567890 -name "devops" -description "Password account for DevOps"`, - "", - "", - }) - } - return info + c.Flags().Help() -} - -func (c *PasswordCommand) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - - if len(passwordFlagsMap[c.Func]) > 0 { - common.PopulateCommonFlags(c.Command, f, "password-type account", passwordFlagsMap[c.Func]) - } - - f = set.NewFlagSet("Password Account Options") - - for _, name := range passwordFlagsMap[c.Func] { - switch name { - case "login-name": - f.StringVar(&base.StringVar{ - Name: "login-name", - Target: &c.flagLoginName, - Usage: "The login name for the account", - }) - case "password": - f.StringVar(&base.StringVar{ - Name: "password", - Target: &c.flagPassword, - Usage: "The password for the account. If not specified, the command will prompt for the password to be entered in a non-echoing way.", - }) - } - } - - return set -} - -func (c *PasswordCommand) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *PasswordCommand) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *PasswordCommand) Run(args []string) int { - if c.Func == "" { - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(passwordFlagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(passwordFlagsMap[c.Func], "auth-method-id") && c.FlagAuthMethodId == "" { - c.UI.Error("Auth Method ID must be passed in via -auth-method-id") - return 1 - } - if c.Func == "create" && c.flagLoginName == "" { - c.UI.Error("Login Name must be passed in via -login-name") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []accounts.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, accounts.DefaultName()) - default: - opts = append(opts, accounts.WithName(c.FlagName)) - } - - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, accounts.DefaultDescription()) - default: - opts = append(opts, accounts.WithDescription(c.FlagDescription)) - } - - switch c.flagLoginName { - case "": - case "null": - opts = append(opts, accounts.DefaultPasswordAccountLoginName()) - default: - opts = append(opts, accounts.WithPasswordAccountLoginName(c.flagLoginName)) - } - - if strutil.StrListContains(passwordFlagsMap[c.Func], "password") { - switch c.flagPassword { - case "": - fmt.Print("Password is not set as flag, please enter it now (will be hidden): ") - value, err := password.Read(os.Stdin) - fmt.Print("\n") - if err != nil { - c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error())) - return 2 - } - fmt.Print("Please enter it one more time for confirmation: ") - confirmation, err := password.Read(os.Stdin) - fmt.Print("\n") - if err != nil { - c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error())) - return 2 - } - if strings.TrimSpace(value) != strings.TrimSpace(confirmation) { - c.UI.Error("Entered password and confirmation value did not match.") - return 2 - } - opts = append(opts, accounts.WithPasswordAccountPassword(strings.TrimSpace(value))) - default: - opts = append(opts, accounts.WithPasswordAccountPassword(c.flagPassword)) - } - } - - accountClient := accounts.NewClient(client) - - // Perform check-and-set when needed - var version uint32 - switch c.Func { - case "create": - // These don't update so don't need the existing version - default: - switch c.FlagVersion { - case 0: - opts = append(opts, accounts.WithAutomaticVersioning(true)) - default: - version = uint32(c.FlagVersion) - } - } - - var result api.GenericResult - - switch c.Func { - case "create": - result, err = accountClient.Create(c.Context, c.FlagAuthMethodId, opts...) - case "update": - result, err = accountClient.Update(c.Context, c.FlagId, version, opts...) - } - - plural := "password-type account" - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - account := result.GetItem().(*accounts.Account) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateAccountTableOutput(account)) - case "json": - b, err := base.JsonFormatter{}.Format(account) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/accountscmd/accounts.gen.go b/internal/cmd/commands/accountscmd/accounts.gen.go new file mode 100644 index 000000000..7ddd3247a --- /dev/null +++ b/internal/cmd/commands/accountscmd/accounts.gen.go @@ -0,0 +1,321 @@ +// Code generated by "make api"; DO NOT EDIT. +package accountscmd + +import ( + "fmt" + "net/http" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/accounts" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string + + extraCmdVars +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "account" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("account") + + switch c.Func { + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "delete": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = c.extraHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "read": {"id"}, + + "delete": {"id"}, + + "list": {"auth-method-id"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "account", flagsMap[c.Func]) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + + case "create", "update": + return cli.RunResultHelp + + } + + c.plural = "account" + switch c.Func { + case "list": + c.plural = "accounts" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []accounts.Option + + if strutil.StrListContains(flagsMap[c.Func], "auth-method-id") { + switch c.Func { + case "list": + if c.FlagAuthMethodId == "" { + c.UI.Error("AuthMethod ID must be passed in via -auth-method-id or BOUNDARY_AUTH_METHOD_ID") + return 1 + } + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + accountsClient := accounts.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, accounts.DefaultName()) + default: + opts = append(opts, accounts.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, accounts.DefaultDescription()) + default: + opts = append(opts, accounts.WithDescription(c.FlagDescription)) + } + + var version uint32 + + switch c.Func { + + case "change-password": + switch c.FlagVersion { + case 0: + opts = append(opts, accounts.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "set-password": + switch c.FlagVersion { + case 0: + opts = append(opts, accounts.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + } + + if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + existed := true + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "read": + result, err = accountsClient.Read(c.Context, c.FlagId, opts...) + + case "delete": + _, err = accountsClient.Delete(c.Context, c.FlagId, opts...) + if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { + existed = false + err = nil + } + + case "list": + listResult, err = accountsClient.List(c.Context, c.FlagAuthMethodId, opts...) + + } + + result, err = executeExtraActions(c, result, err, accountsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + c.UI.Output(fmt.Sprintf("{ \"existed\": %t }", existed)) + + case "table": + output := "The delete operation completed successfully" + switch existed { + case true: + output += "." + default: + output += ", however the resource did not exist at the time." + } + c.UI.Output(output) + } + + return 0 + + case "list": + listedItems := listResult.GetItems().([]*accounts.Account) + switch base.Format(c.UI) { + case "json": + switch { + + case len(listedItems) == 0: + c.UI.Output("null") + + default: + b, err := base.JsonFormatter{}.Format(listedItems) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + case "table": + c.UI.Output(c.printListTable(listedItems)) + } + + return 0 + + } + + item := result.GetItem().(*accounts.Account) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *[]accounts.Option) int { return 0 } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *accounts.Client, _ uint32, _ []accounts.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/accountscmd/funcs.go b/internal/cmd/commands/accountscmd/funcs.go new file mode 100644 index 000000000..3c505bbbb --- /dev/null +++ b/internal/cmd/commands/accountscmd/funcs.go @@ -0,0 +1,277 @@ +package accountscmd + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/accounts" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/hashicorp/vault/sdk/helper/password" +) + +func init() { + extraActionsFlagsMapFunc = extraActionsFlagsMapFuncImpl + extraSynopsisFunc = extraSynopsisFuncImpl + extraFlagsFunc = extraFlagsFuncImpl + extraFlagsHandlingFunc = extraFlagsHandlingFuncImpl + executeExtraActions = executeExtraActionsImpl +} + +type extraCmdVars struct { + flagPassword string + flagCurrentPassword string + flagNewPassword string +} + +func extraActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "change-password": {"id", "current-password", "new-password", "version"}, + "set-password": {"id", "password", "version"}, + } +} + +func extraSynopsisFuncImpl(c *Command) string { + switch c.Func { + case "change-password": + return "Change the password on an account resource" + + case "set-password": + return "Directly set the password on an account resource" + + default: + return "" + } +} + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "": + return base.WrapForHelpText([]string{ + "Usage: boundary accounts [sub command] [options] [args]", + "", + " This command allows operations on Boundary account resources. Example:", + "", + " Read a account:", + "", + ` $ boundary accounts read -id apw_1234567890`, + "", + " Please see the accounts subcommand help for detailed usage information.", + }) + case "change-password": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary accounts change-password [options] [args]", + "", + " This command allows changing the password (with verification of the current password) on account-type resources, if the types match and the operation is allowed by the given account type. Example:", + "", + " Change the password on a password-type account:", + "", + ` $ boundary accounts change-password -id apw_1234567890 -current-password -new-password `, + "", + "", + }) + case "set-password": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary accounts set-password [options] [args]", + "", + " This command allows setting the password on account-type resources, if the types match and the operation is allowed by the given account type. Example:", + "", + " Set the password on a password-type account:", + "", + ` $ boundary accounts set-password -id apw_1234567890 -password `, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} + +func extraFlagsFuncImpl(c *Command, _ *base.FlagSets, f *base.FlagSet) { + for _, name := range flagsMap[c.Func] { + switch name { + case "password": + f.StringVar(&base.StringVar{ + Name: "password", + Target: &c.flagPassword, + Usage: "The password for the account. If not specified, the command will prompt for the password to be entered in a non-echoing way.", + }) + case "current-password": + f.StringVar(&base.StringVar{ + Name: "current-password", + Target: &c.flagCurrentPassword, + Usage: "The current password for the account. If not specified, the command will prompt for the password to be entered in a non-echoing way.", + }) + case "new-password": + f.StringVar(&base.StringVar{ + Name: "new-password", + Target: &c.flagNewPassword, + Usage: "The new password for the account. If not specified, the command will prompt for the password to be entered in a non-echoing way.", + }) + } + } +} + +func extraFlagsHandlingFuncImpl(c *Command, opts *[]accounts.Option) int { + if strutil.StrListContains(flagsMap[c.Func], "password") && c.flagPassword == "" { + fmt.Print("Password is not set as flag, please enter it now (will be hidden): ") + value, err := password.Read(os.Stdin) + fmt.Print("\n") + if err != nil { + c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error())) + return 2 + } + fmt.Print("Please enter it one more time for confirmation: ") + confirmation, err := password.Read(os.Stdin) + fmt.Print("\n") + if err != nil { + c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error())) + return 2 + } + if strings.TrimSpace(value) != strings.TrimSpace(confirmation) { + c.UI.Error("Entered password and confirmation value did not match.") + return 2 + } + c.flagPassword = strings.TrimSpace(value) + } + + if strutil.StrListContains(flagsMap[c.Func], "current-password") && c.flagCurrentPassword == "" { + fmt.Print("Current password is not set as flag, please enter it now (will be hidden): ") + value, err := password.Read(os.Stdin) + fmt.Print("\n") + if err != nil { + c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error())) + return 2 + } + fmt.Print("Please enter it one more time for confirmation: ") + confirmation, err := password.Read(os.Stdin) + fmt.Print("\n") + if err != nil { + c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error())) + return 2 + } + if strings.TrimSpace(value) != strings.TrimSpace(confirmation) { + c.UI.Error("Entered password and confirmation value did not match.") + return 2 + } + c.flagCurrentPassword = strings.TrimSpace(value) + } + + if strutil.StrListContains(flagsMap[c.Func], "new-password") && c.flagNewPassword == "" { + fmt.Print("New password is not set as flag, please enter it now (will be hidden): ") + value, err := password.Read(os.Stdin) + fmt.Print("\n") + if err != nil { + c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error())) + return 2 + } + c.flagNewPassword = strings.TrimSpace(value) + } + + return 0 +} + +func executeExtraActionsImpl(c *Command, origResult api.GenericResult, origError error, accountClient *accounts.Client, version uint32, opts []accounts.Option) (api.GenericResult, error) { + switch c.Func { + case "set-password": + return accountClient.SetPassword(c.Context, c.FlagId, c.flagPassword, version, opts...) + case "change-password": + return accountClient.ChangePassword(c.Context, c.FlagId, c.flagCurrentPassword, c.flagNewPassword, version, opts...) + } + return origResult, origError +} + +func (c *Command) printListTable(items []*accounts.Account) string { + if len(items) == 0 { + return "No accounts found" + } + var output []string + output = []string{ + "", + "Account information:", + } + for i, m := range items { + if i > 0 { + output = append(output, "") + } + if true { + output = append(output, + fmt.Sprintf(" ID: %s", m.Id), + fmt.Sprintf(" Version: %d", m.Version), + fmt.Sprintf(" Type: %s", m.Type), + ) + } + if m.Name != "" { + output = append(output, + fmt.Sprintf(" Name: %s", m.Name), + ) + } + if m.Description != "" { + output = append(output, + fmt.Sprintf(" Description: %s", m.Description), + ) + } + if len(m.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, m.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(item *accounts.Account) string { + nonAttributeMap := map[string]interface{}{ + "ID": item.Id, + "Version": item.Version, + "Type": item.Type, + "Created Time": item.CreatedTime.Local().Format(time.RFC1123), + "Updated Time": item.UpdatedTime.Local().Format(time.RFC1123), + "Auth Method ID": item.AuthMethodId, + } + + if item.Name != "" { + nonAttributeMap["Name"] = item.Name + } + if item.Description != "" { + nonAttributeMap["Description"] = item.Description + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap) + + ret := []string{ + "", + "Account information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + "", + " Scope:", + base.ScopeInfoForOutput(item.Scope, maxLength), + } + + if len(item.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, item.AuthorizedActions), + ) + } + + if len(item.Attributes) > 0 { + ret = append(ret, + "", + " Attributes:", + base.WrapMap(4, maxLength, item.Attributes), + ) + } + + return base.WrapForHelpText(ret) +} + +var keySubstMap = map[string]string{ + "login_name": "Login Name", +} diff --git a/internal/cmd/commands/accountscmd/password_accounts.gen.go b/internal/cmd/commands/accountscmd/password_accounts.gen.go new file mode 100644 index 000000000..912a6d1f3 --- /dev/null +++ b/internal/cmd/commands/accountscmd/password_accounts.gen.go @@ -0,0 +1,238 @@ +// Code generated by "make api"; DO NOT EDIT. +package accountscmd + +import ( + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/accounts" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initPasswordFlags() { + flagsOnce.Do(func() { + extraFlags := extraPasswordActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsPasswordMap[k] = append(flagsPasswordMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*PasswordCommand)(nil) + _ cli.CommandAutocomplete = (*PasswordCommand)(nil) +) + +type PasswordCommand struct { + *base.Command + + Func string + + plural string + + extraPasswordCmdVars +} + +func (c *PasswordCommand) AutocompleteArgs() complete.Predictor { + initPasswordFlags() + return complete.PredictAnything +} + +func (c *PasswordCommand) AutocompleteFlags() complete.Flags { + initPasswordFlags() + return c.Flags().Completions() +} + +func (c *PasswordCommand) Synopsis() string { + if extra := extraPasswordSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "account" + + synopsisStr = fmt.Sprintf("%s %s", "password-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *PasswordCommand) Help() string { + initPasswordFlags() + + var helpStr string + helpMap := common.HelpMap("account") + + switch c.Func { + default: + + helpStr = c.extraPasswordHelpFunc(helpMap) + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsPasswordMap = map[string][]string{ + + "create": {"auth-method-id", "name", "description"}, + + "update": {"id", "name", "description", "version"}, +} + +func (c *PasswordCommand) Flags() *base.FlagSets { + if len(flagsPasswordMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "password-type account", flagsPasswordMap[c.Func]) + + extraPasswordFlagsFunc(c, set, f) + + return set +} + +func (c *PasswordCommand) Run(args []string) int { + initPasswordFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "password-type account" + switch c.Func { + case "list": + c.plural = "password-type accounts" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsPasswordMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []accounts.Option + + if strutil.StrListContains(flagsPasswordMap[c.Func], "auth-method-id") { + switch c.Func { + case "create": + if c.FlagAuthMethodId == "" { + c.UI.Error("AuthMethod ID must be passed in via -auth-method-id or BOUNDARY_AUTH_METHOD_ID") + return 1 + } + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + accountsClient := accounts.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, accounts.DefaultName()) + default: + opts = append(opts, accounts.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, accounts.DefaultDescription()) + default: + opts = append(opts, accounts.WithDescription(c.FlagDescription)) + } + + var version uint32 + + switch c.Func { + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, accounts.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } + + if ret := extraPasswordFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + var result api.GenericResult + + switch c.Func { + + case "create": + result, err = accountsClient.Create(c.Context, c.FlagAuthMethodId, opts...) + + case "update": + result, err = accountsClient.Update(c.Context, c.FlagId, version, opts...) + + } + + result, err = executeExtraPasswordActions(c, result, err, accountsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomPasswordActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + } + + item := result.GetItem().(*accounts.Account) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + extraPasswordActionsFlagsMapFunc = func() map[string][]string { return nil } + extraPasswordSynopsisFunc = func(*PasswordCommand) string { return "" } + extraPasswordFlagsFunc = func(*PasswordCommand, *base.FlagSets, *base.FlagSet) {} + extraPasswordFlagsHandlingFunc = func(*PasswordCommand, *[]accounts.Option) int { return 0 } + executeExtraPasswordActions = func(_ *PasswordCommand, inResult api.GenericResult, inErr error, _ *accounts.Client, _ uint32, _ []accounts.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomPasswordActionOutput = func(*PasswordCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/accountscmd/password_funcs.go b/internal/cmd/commands/accountscmd/password_funcs.go new file mode 100644 index 000000000..3359063fd --- /dev/null +++ b/internal/cmd/commands/accountscmd/password_funcs.go @@ -0,0 +1,123 @@ +package accountscmd + +import ( + "fmt" + "os" + "strings" + + "github.com/hashicorp/boundary/api/accounts" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/hashicorp/vault/sdk/helper/password" +) + +func init() { + extraPasswordActionsFlagsMapFunc = extraPasswordActionsFlagsMapFuncImpl + extraPasswordFlagsFunc = extraPasswordFlagsFuncImpl + extraPasswordFlagsHandlingFunc = extraPasswordFlagsHandlingFuncImpl +} + +func extraPasswordActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "create": {"login-name", "password"}, + "update": {"login-name"}, + } +} + +type extraPasswordCmdVars struct { + flagLoginName string + flagPassword string +} + +func (c *PasswordCommand) extraPasswordHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary accounts create password [options] [args]", + "", + " Create a password-type account. Example:", + "", + ` $ boundary accounts create password -login-name prodops -description "Password account for ProdOps"`, + "", + "", + }) + + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary accounts update password [options] [args]", + "", + " Update a password-type account given its ID. Example:", + "", + ` $ boundary accounts update password -id apw_1234567890 -name "devops" -description "Password account for DevOps"`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} + +func extraPasswordFlagsFuncImpl(c *PasswordCommand, set *base.FlagSets, f *base.FlagSet) { + f = set.NewFlagSet("Password Account Options") + + for _, name := range flagsPasswordMap[c.Func] { + switch name { + case "login-name": + f.StringVar(&base.StringVar{ + Name: "login-name", + Target: &c.flagLoginName, + Usage: "The login name for the account", + }) + case "password": + f.StringVar(&base.StringVar{ + Name: "password", + Target: &c.flagPassword, + Usage: "The password for the account. If not specified, the command will prompt for the password to be entered in a non-echoing way.", + }) + } + } +} + +func extraPasswordFlagsHandlingFuncImpl(c *PasswordCommand, opts *[]accounts.Option) int { + if c.Func == "create" && c.flagLoginName == "" { + c.UI.Error("Login Name must be passed in via -login-name") + return 1 + } + + switch c.flagLoginName { + case "": + case "null": + *opts = append(*opts, accounts.DefaultPasswordAccountLoginName()) + default: + *opts = append(*opts, accounts.WithPasswordAccountLoginName(c.flagLoginName)) + } + + if strutil.StrListContains(flagsPasswordMap[c.Func], "password") { + switch c.flagPassword { + case "": + fmt.Print("Password is not set as flag, please enter it now (will be hidden): ") + value, err := password.Read(os.Stdin) + fmt.Print("\n") + if err != nil { + c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error())) + return 2 + } + fmt.Print("Please enter it one more time for confirmation: ") + confirmation, err := password.Read(os.Stdin) + fmt.Print("\n") + if err != nil { + c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error())) + return 2 + } + if strings.TrimSpace(value) != strings.TrimSpace(confirmation) { + c.UI.Error("Entered password and confirmation value did not match.") + return 2 + } + *opts = append(*opts, accounts.WithPasswordAccountPassword(strings.TrimSpace(value))) + default: + *opts = append(*opts, accounts.WithPasswordAccountPassword(c.flagPassword)) + } + } + + return 0 +} diff --git a/internal/cmd/commands/authmethods/authmethod.go b/internal/cmd/commands/authmethods/authmethod.go deleted file mode 100644 index 69431e1b9..000000000 --- a/internal/cmd/commands/authmethods/authmethod.go +++ /dev/null @@ -1,288 +0,0 @@ -package authmethods - -import ( - "fmt" - "net/http" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/authmethods" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/internal/types/resource" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*Command)(nil) - _ cli.CommandAutocomplete = (*Command)(nil) -) - -type Command struct { - *base.Command - - Func string -} - -func (c *Command) Synopsis() string { - return common.SynopsisFunc(c.Func, "auth method") -} - -var flagsMap = map[string][]string{ - "read": {"id"}, - "delete": {"id"}, - "list": {"scope-id", "recursive"}, -} - -func (c *Command) Help() string { - helpMap := common.HelpMap("auth method") - var helpStr string - switch c.Func { - case "": - return base.WrapForHelpText([]string{ - "Usage: boundary auth-methods [sub command] [options] [args]", - "", - " This command allows operations on Boundary auth method resources. Example:", - "", - " Read an auth method:", - "", - ` $ boundary auth-methods read -id ampw_1234567890`, - "", - " Please see the auth-methods subcommand help for detailed usage information.", - }) - case "create": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary auth-methods create [type] [sub command] [options] [args]", - "", - " This command allows create operations on Boundary auth method resources. Example:", - "", - " Create a password-type auth method:", - "", - ` $ boundary auth-methods create password -name prodops -description "For ProdOps usage"`, - "", - " Please see the typed subcommand help for detailed usage information.", - }) - case "update": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary auth-methods update [type] [sub command] [options] [args]", - "", - " This command allows update operations on Boundary auth method resources. Example:", - "", - " Update a password-type auth method:", - "", - ` $ boundary auth-methods update password -id ampw_1234567890 -name devops -description "For DevOps usage"`, - "", - " Please see the typed subcommand help for detailed usage information.", - }) - default: - helpStr = helpMap[c.Func]() - } - return helpStr + c.Flags().Help() -} - -func (c *Command) Flags() *base.FlagSets { - if len(flagsMap[c.Func]) == 0 { - return c.FlagSet(base.FlagSetNone) - } - - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, resource.AuthMethod.String(), flagsMap[c.Func]) - - return set -} - -func (c *Command) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *Command) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *Command) Run(args []string) int { - switch c.Func { - case "", "create", "update": - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(flagsMap[c.Func], "scope-id") && c.FlagScopeId == "" { - c.UI.Error("Scope ID must be passed in via -scope-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []authmethods.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, authmethods.DefaultName()) - default: - opts = append(opts, authmethods.WithName(c.FlagName)) - } - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, authmethods.DefaultDescription()) - default: - opts = append(opts, authmethods.WithDescription(c.FlagDescription)) - } - switch c.FlagRecursive { - case true: - opts = append(opts, authmethods.WithRecursive(true)) - } - - authmethodClient := authmethods.NewClient(client) - - existed := true - var result api.GenericResult - var listResult api.GenericListResult - - switch c.Func { - case "read": - result, err = authmethodClient.Read(c.Context, c.FlagId, opts...) - case "delete": - _, err = authmethodClient.Delete(c.Context, c.FlagId, opts...) - if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { - existed = false - err = nil - } - case "list": - listResult, err = authmethodClient.List(c.Context, c.FlagScopeId, opts...) - } - - plural := "auth method" - if c.Func == "list" { - plural = "auth methods" - } - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - switch c.Func { - case "delete": - switch base.Format(c.UI) { - case "json": - c.UI.Output("null") - case "table": - output := "The delete operation completed successfully" - switch existed { - case true: - output += "." - default: - output += ", however the resource did not exist at the time." - } - c.UI.Output(output) - } - return 0 - - case "list": - listedMethods := listResult.GetItems().([]*authmethods.AuthMethod) - switch base.Format(c.UI) { - case "json": - if len(listedMethods) == 0 { - c.UI.Output("null") - return 0 - } - b, err := base.JsonFormatter{}.Format(listedMethods) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - - case "table": - if len(listedMethods) == 0 { - c.UI.Output("No auth methods found") - return 0 - } - var output []string - output = []string{ - "", - "Auth Method information:", - } - for i, m := range listedMethods { - if i > 0 { - output = append(output, "") - } - if true { - output = append(output, - fmt.Sprintf(" ID: %s", m.Id), - ) - } - if c.FlagRecursive { - output = append(output, - fmt.Sprintf(" Scope ID: %s", m.Scope.Id), - ) - } - if true { - output = append(output, - fmt.Sprintf(" Version: %d", m.Version), - ) - } - if m.Name != "" { - output = append(output, - fmt.Sprintf(" Name: %s", m.Name), - ) - } - if m.Description != "" { - output = append(output, - fmt.Sprintf(" Description: %s", m.Description), - ) - } - if true { - output = append(output, - fmt.Sprintf(" Type: %s", m.Type), - fmt.Sprintf(" Version: %d", m.Version), - ) - } - if len(m.AuthorizedActions) > 0 { - output = append(output, - " Authorized Actions:", - base.WrapSlice(6, m.AuthorizedActions), - ) - } - } - c.UI.Output(base.WrapForHelpText(output)) - } - return 0 - } - - method := result.GetItem().(*authmethods.AuthMethod) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateAuthMethodTableOutput(method)) - case "json": - b, err := base.JsonFormatter{}.Format(method) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/authmethods/funcs.go b/internal/cmd/commands/authmethods/funcs.go deleted file mode 100644 index aa1351a91..000000000 --- a/internal/cmd/commands/authmethods/funcs.go +++ /dev/null @@ -1,92 +0,0 @@ -package authmethods - -import ( - "fmt" - "sort" - "time" - - "github.com/hashicorp/boundary/api/authmethods" - "github.com/hashicorp/boundary/internal/cmd/base" -) - -func addPasswordFlags(c *PasswordCommand, f *base.FlagSet) { - f.StringVar(&base.StringVar{ - Name: "min-login-name-length", - Target: &c.flagMinLoginNameLength, - Usage: "The minimum length of login names", - }) - f.StringVar(&base.StringVar{ - Name: "min-password-length", - Target: &c.flagMinPasswordLength, - Usage: "The minimum length of passwords", - }) -} - -func generateAuthMethodTableOutput(in *authmethods.AuthMethod) string { - nonAttributeMap := map[string]interface{}{ - "ID": in.Id, - "Version": in.Version, - "Type": in.Type, - "Created Time": in.CreatedTime.Local().Format(time.RFC1123), - "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), - } - - if in.Name != "" { - nonAttributeMap["Name"] = in.Name - } - if in.Description != "" { - nonAttributeMap["Description"] = in.Description - } - - maxLength := base.MaxAttributesLength(nonAttributeMap, in.Attributes, keySubstMap) - - ret := []string{ - "", - "Auth Method information:", - base.WrapMap(2, maxLength+2, nonAttributeMap), - "", - " Scope:", - base.ScopeInfoForOutput(in.Scope, maxLength), - } - - if len(in.AuthorizedActions) > 0 { - ret = append(ret, - "", - " Authorized Actions:", - base.WrapSlice(4, in.AuthorizedActions), - ) - } - - if len(in.AuthorizedCollectionActions) > 0 { - keys := make([]string, 0, len(in.AuthorizedCollectionActions)) - for k := range in.AuthorizedCollectionActions { - keys = append(keys, k) - } - sort.Strings(keys) - ret = append(ret, - "", - " Authorized Actions on Auth Method's Collections:", - ) - for _, key := range keys { - ret = append(ret, - fmt.Sprintf(" %ss:", key), - base.WrapSlice(6, in.AuthorizedCollectionActions[key]), - ) - } - } - - if len(in.Attributes) > 0 { - ret = append(ret, - "", - " Attributes:", - base.WrapMap(4, maxLength, in.Attributes), - ) - } - - return base.WrapForHelpText(ret) -} - -var keySubstMap = map[string]string{ - "min_login_name_length": "Minimum Login Name Length", - "min_password_length": "Minimum Password Length", -} diff --git a/internal/cmd/commands/authmethods/password.go b/internal/cmd/commands/authmethods/password.go deleted file mode 100644 index 662212e12..000000000 --- a/internal/cmd/commands/authmethods/password.go +++ /dev/null @@ -1,219 +0,0 @@ -package authmethods - -import ( - "fmt" - "net/textproto" - "strconv" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/authmethods" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*PasswordCommand)(nil) - _ cli.CommandAutocomplete = (*PasswordCommand)(nil) -) - -type PasswordCommand struct { - *base.Command - - Func string - - flagMinLoginNameLength string - flagMinPasswordLength string -} - -func (c *PasswordCommand) Synopsis() string { - return fmt.Sprintf("%s a password type auth-method", textproto.CanonicalMIMEHeaderKey(c.Func)) -} - -var passwordFlagsMap = map[string][]string{ - "create": {"scope-id", "name", "description"}, - "update": {"id", "name", "description", "version"}, -} - -func (c *PasswordCommand) Help() string { - var info string - switch c.Func { - case "create": - info = base.WrapForHelpText([]string{ - "Usage: boundary auth-methods create password [options] [args]", - "", - " Create a password-type auth method. Example:", - "", - ` $ boundary auth-methods create password -name prodops -description "Password auth-method for ProdOps"`, - "", - "", - }) - - case "update": - info = base.WrapForHelpText([]string{ - "Usage: boundary auth-methods update password [options] [args]", - "", - " Update a password-type auth method given its ID. Example:", - "", - ` $ boundary auth-methods update password -id ampw_1234567890 -name "devops" -description "Password auth-method for DevOps"`, - "", - "", - }) - } - return info + c.Flags().Help() -} - -func (c *PasswordCommand) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - - f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "password-type auth method", passwordFlagsMap[c.Func]) - - f = set.NewFlagSet("Password Auth-Method Options") - addPasswordFlags(c, f) - - return set -} - -func (c *PasswordCommand) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *PasswordCommand) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *PasswordCommand) Run(args []string) int { - if c.Func == "" { - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(passwordFlagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(passwordFlagsMap[c.Func], "scope-id") && c.FlagScopeId == "" { - c.UI.Error("Scope ID must be passed in via -scope-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []authmethods.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, authmethods.DefaultName()) - default: - opts = append(opts, authmethods.WithName(c.FlagName)) - } - - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, authmethods.DefaultDescription()) - default: - opts = append(opts, authmethods.WithDescription(c.FlagDescription)) - } - - var attributes map[string]interface{} - addAttribute := func(name string, value interface{}) { - if attributes == nil { - attributes = make(map[string]interface{}) - } - attributes[name] = value - } - switch c.flagMinLoginNameLength { - case "": - case "null": - addAttribute("min_login_name_length", nil) - default: - length, err := strconv.ParseUint(c.flagMinLoginNameLength, 10, 32) - if err != nil { - c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagMinLoginNameLength, err)) - return 1 - } - addAttribute("min_login_name_length", uint32(length)) - } - - switch c.flagMinPasswordLength { - case "": - case "null": - addAttribute("min_password_length", nil) - default: - length, err := strconv.ParseUint(c.flagMinPasswordLength, 10, 32) - if err != nil { - c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagMinPasswordLength, err)) - return 1 - } - addAttribute("min_password_length", uint32(length)) - } - - if attributes != nil { - opts = append(opts, authmethods.WithAttributes(attributes)) - } - - authmethodClient := authmethods.NewClient(client) - - // Perform check-and-set when needed - var version uint32 - switch c.Func { - case "create": - // These don't update so don't need the existing version - default: - switch c.FlagVersion { - case 0: - opts = append(opts, authmethods.WithAutomaticVersioning(true)) - default: - version = uint32(c.FlagVersion) - } - } - - var result api.GenericResult - - switch c.Func { - case "create": - result, err = authmethodClient.Create(c.Context, "password", c.FlagScopeId, opts...) - case "update": - result, err = authmethodClient.Update(c.Context, c.FlagId, version, opts...) - } - - plural := "password-type auth-method" - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - method := result.GetItem().(*authmethods.AuthMethod) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateAuthMethodTableOutput(method)) - case "json": - b, err := base.JsonFormatter{}.Format(method) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/authmethodscmd/authmethods.gen.go b/internal/cmd/commands/authmethodscmd/authmethods.gen.go new file mode 100644 index 000000000..d89a22105 --- /dev/null +++ b/internal/cmd/commands/authmethodscmd/authmethods.gen.go @@ -0,0 +1,288 @@ +// Code generated by "make api"; DO NOT EDIT. +package authmethodscmd + +import ( + "fmt" + "net/http" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/authmethods" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "auth method" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("auth method") + + switch c.Func { + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "delete": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = c.extraHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "read": {"id"}, + + "delete": {"id"}, + + "list": {"scope-id", "recursive"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "auth method", flagsMap[c.Func]) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + + case "create", "update": + return cli.RunResultHelp + + } + + c.plural = "auth method" + switch c.Func { + case "list": + c.plural = "auth methods" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []authmethods.Option + + if strutil.StrListContains(flagsMap[c.Func], "scope-id") { + switch c.Func { + case "list": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + authmethodsClient := authmethods.NewClient(client) + + switch c.FlagRecursive { + case true: + opts = append(opts, authmethods.WithRecursive(true)) + } + + var version uint32 + + if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + existed := true + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "read": + result, err = authmethodsClient.Read(c.Context, c.FlagId, opts...) + + case "delete": + _, err = authmethodsClient.Delete(c.Context, c.FlagId, opts...) + if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { + existed = false + err = nil + } + + case "list": + listResult, err = authmethodsClient.List(c.Context, c.FlagScopeId, opts...) + + } + + result, err = executeExtraActions(c, result, err, authmethodsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + c.UI.Output(fmt.Sprintf("{ \"existed\": %t }", existed)) + + case "table": + output := "The delete operation completed successfully" + switch existed { + case true: + output += "." + default: + output += ", however the resource did not exist at the time." + } + c.UI.Output(output) + } + + return 0 + + case "list": + listedItems := listResult.GetItems().([]*authmethods.AuthMethod) + switch base.Format(c.UI) { + case "json": + switch { + + case len(listedItems) == 0: + c.UI.Output("null") + + default: + b, err := base.JsonFormatter{}.Format(listedItems) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + case "table": + c.UI.Output(c.printListTable(listedItems)) + } + + return 0 + + } + + item := result.GetItem().(*authmethods.AuthMethod) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *[]authmethods.Option) int { return 0 } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *authmethods.Client, _ uint32, _ []authmethods.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/authmethodscmd/funcs.go b/internal/cmd/commands/authmethodscmd/funcs.go new file mode 100644 index 000000000..56cdf752a --- /dev/null +++ b/internal/cmd/commands/authmethodscmd/funcs.go @@ -0,0 +1,178 @@ +package authmethodscmd + +import ( + "fmt" + "sort" + "time" + + "github.com/hashicorp/boundary/api/authmethods" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "": + return base.WrapForHelpText([]string{ + "Usage: boundary auth-methods [sub command] [options] [args]", + "", + " This command allows operations on Boundary auth method resources. Example:", + "", + " Read an auth method:", + "", + ` $ boundary auth-methods read -id ampw_1234567890`, + "", + " Please see the auth-methods subcommand help for detailed usage information.", + }) + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary auth-methods create [type] [sub command] [options] [args]", + "", + " This command allows create operations on Boundary auth method resources. Example:", + "", + " Create a password-type auth method:", + "", + ` $ boundary auth-methods create password -name prodops -description "For ProdOps usage"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary auth-methods update [type] [sub command] [options] [args]", + "", + " This command allows update operations on Boundary auth method resources. Example:", + "", + " Update a password-type auth method:", + "", + ` $ boundary auth-methods update password -id ampw_1234567890 -name devops -description "For DevOps usage"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + } + return helpStr + c.Flags().Help() +} + +func (c *Command) printListTable(items []*authmethods.AuthMethod) string { + if len(items) == 0 { + return "No auth methods found" + } + + var output []string + output = []string{ + "", + "Auth Method information:", + } + for i, m := range items { + if i > 0 { + output = append(output, "") + } + if true { + output = append(output, + fmt.Sprintf(" ID: %s", m.Id), + ) + } + if c.FlagRecursive { + output = append(output, + fmt.Sprintf(" Scope ID: %s", m.Scope.Id), + ) + } + if true { + output = append(output, + fmt.Sprintf(" Version: %d", m.Version), + ) + } + if m.Name != "" { + output = append(output, + fmt.Sprintf(" Name: %s", m.Name), + ) + } + if m.Description != "" { + output = append(output, + fmt.Sprintf(" Description: %s", m.Description), + ) + } + if true { + output = append(output, + fmt.Sprintf(" Type: %s", m.Type), + fmt.Sprintf(" Version: %d", m.Version), + ) + } + if len(m.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, m.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(in *authmethods.AuthMethod) string { + nonAttributeMap := map[string]interface{}{ + "ID": in.Id, + "Version": in.Version, + "Type": in.Type, + "Created Time": in.CreatedTime.Local().Format(time.RFC1123), + "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), + } + + if in.Name != "" { + nonAttributeMap["Name"] = in.Name + } + if in.Description != "" { + nonAttributeMap["Description"] = in.Description + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, in.Attributes, keySubstMap) + + ret := []string{ + "", + "Auth Method information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + "", + " Scope:", + base.ScopeInfoForOutput(in.Scope, maxLength), + } + + if len(in.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, in.AuthorizedActions), + ) + } + + if len(in.AuthorizedCollectionActions) > 0 { + keys := make([]string, 0, len(in.AuthorizedCollectionActions)) + for k := range in.AuthorizedCollectionActions { + keys = append(keys, k) + } + sort.Strings(keys) + ret = append(ret, + "", + " Authorized Actions on Auth Method's Collections:", + ) + for _, key := range keys { + ret = append(ret, + fmt.Sprintf(" %ss:", key), + base.WrapSlice(6, in.AuthorizedCollectionActions[key]), + ) + } + } + + if len(in.Attributes) > 0 { + ret = append(ret, + "", + " Attributes:", + base.WrapMap(4, maxLength, in.Attributes), + ) + } + + return base.WrapForHelpText(ret) +} + +var keySubstMap = map[string]string{ + "min_login_name_length": "Minimum Login Name Length", + "min_password_length": "Minimum Password Length", +} diff --git a/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go b/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go new file mode 100644 index 000000000..6c8a77375 --- /dev/null +++ b/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go @@ -0,0 +1,243 @@ +// Code generated by "make api"; DO NOT EDIT. +package authmethodscmd + +import ( + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/authmethods" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initPasswordFlags() { + flagsOnce.Do(func() { + extraFlags := extraPasswordActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsPasswordMap[k] = append(flagsPasswordMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*PasswordCommand)(nil) + _ cli.CommandAutocomplete = (*PasswordCommand)(nil) +) + +type PasswordCommand struct { + *base.Command + + Func string + + plural string + + extraPasswordCmdVars +} + +func (c *PasswordCommand) AutocompleteArgs() complete.Predictor { + initPasswordFlags() + return complete.PredictAnything +} + +func (c *PasswordCommand) AutocompleteFlags() complete.Flags { + initPasswordFlags() + return c.Flags().Completions() +} + +func (c *PasswordCommand) Synopsis() string { + if extra := extraPasswordSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "auth method" + + synopsisStr = fmt.Sprintf("%s %s", "password-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *PasswordCommand) Help() string { + initPasswordFlags() + + var helpStr string + helpMap := common.HelpMap("auth method") + + switch c.Func { + default: + + helpStr = c.extraPasswordHelpFunc(helpMap) + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsPasswordMap = map[string][]string{ + + "create": {"scope-id", "name", "description"}, + + "update": {"id", "name", "description", "version"}, +} + +func (c *PasswordCommand) Flags() *base.FlagSets { + if len(flagsPasswordMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "password-type auth method", flagsPasswordMap[c.Func]) + + extraPasswordFlagsFunc(c, set, f) + + return set +} + +func (c *PasswordCommand) Run(args []string) int { + initPasswordFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "password-type auth method" + switch c.Func { + case "list": + c.plural = "password-type auth methods" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsPasswordMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []authmethods.Option + + if strutil.StrListContains(flagsPasswordMap[c.Func], "scope-id") { + switch c.Func { + case "create": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + authmethodsClient := authmethods.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, authmethods.DefaultName()) + default: + opts = append(opts, authmethods.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, authmethods.DefaultDescription()) + default: + opts = append(opts, authmethods.WithDescription(c.FlagDescription)) + } + + switch c.FlagRecursive { + case true: + opts = append(opts, authmethods.WithRecursive(true)) + } + + var version uint32 + + switch c.Func { + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, authmethods.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } + + if ret := extraPasswordFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + var result api.GenericResult + + switch c.Func { + + case "create": + result, err = authmethodsClient.Create(c.Context, "password", c.FlagScopeId, opts...) + + case "update": + result, err = authmethodsClient.Update(c.Context, c.FlagId, version, opts...) + + } + + result, err = executeExtraPasswordActions(c, result, err, authmethodsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomPasswordActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + } + + item := result.GetItem().(*authmethods.AuthMethod) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + extraPasswordActionsFlagsMapFunc = func() map[string][]string { return nil } + extraPasswordSynopsisFunc = func(*PasswordCommand) string { return "" } + extraPasswordFlagsFunc = func(*PasswordCommand, *base.FlagSets, *base.FlagSet) {} + extraPasswordFlagsHandlingFunc = func(*PasswordCommand, *[]authmethods.Option) int { return 0 } + executeExtraPasswordActions = func(_ *PasswordCommand, inResult api.GenericResult, inErr error, _ *authmethods.Client, _ uint32, _ []authmethods.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomPasswordActionOutput = func(*PasswordCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/authmethodscmd/password_funcs.go b/internal/cmd/commands/authmethodscmd/password_funcs.go new file mode 100644 index 000000000..abd46c19e --- /dev/null +++ b/internal/cmd/commands/authmethodscmd/password_funcs.go @@ -0,0 +1,117 @@ +package authmethodscmd + +import ( + "fmt" + "strconv" + + "github.com/hashicorp/boundary/api/authmethods" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func init() { + extraPasswordActionsFlagsMapFunc = extraPasswordActionsFlagsMapFuncImpl + extraPasswordFlagsFunc = extraPasswordFlagsFuncImpl + extraPasswordFlagsHandlingFunc = extraPasswordFlagHandlingFuncImpl +} + +type extraPasswordCmdVars struct { + flagMinLoginNameLength string + flagMinPasswordLength string +} + +func extraPasswordActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "create": {"min-login-name-length", "min-password-length"}, + "update": {"min-login-name-length", "min-password-length"}, + } +} + +func (c *PasswordCommand) extraPasswordHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary auth-methods create password [options] [args]", + "", + " Create a password-type auth method. Example:", + "", + ` $ boundary auth-methods create password -name prodops -description "Password auth-method for ProdOps"`, + "", + "", + }) + + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary auth-methods update password [options] [args]", + "", + " Update a password-type auth method given its ID. Example:", + "", + ` $ boundary auth-methods update password -id ampw_1234567890 -name "devops" -description "Password auth-method for DevOps"`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} + +func extraPasswordFlagsFuncImpl(c *PasswordCommand, set *base.FlagSets, f *base.FlagSet) { + f = set.NewFlagSet("Password Auth Method Options") + + for _, name := range flagsPasswordMap[c.Func] { + switch name { + case "min-login-name-length": + f.StringVar(&base.StringVar{ + Name: "min-login-name-length", + Target: &c.flagMinLoginNameLength, + Usage: "The minimum length of login names", + }) + case "min-password-length": + f.StringVar(&base.StringVar{ + Name: "min-password-length", + Target: &c.flagMinPasswordLength, + Usage: "The minimum length of passwords", + }) + } + } +} + +func extraPasswordFlagHandlingFuncImpl(c *PasswordCommand, opts *[]authmethods.Option) int { + var attributes map[string]interface{} + addAttribute := func(name string, value interface{}) { + if attributes == nil { + attributes = make(map[string]interface{}) + } + attributes[name] = value + } + switch c.flagMinLoginNameLength { + case "": + case "null": + addAttribute("min_login_name_length", nil) + default: + length, err := strconv.ParseUint(c.flagMinLoginNameLength, 10, 32) + if err != nil { + c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagMinLoginNameLength, err)) + return 1 + } + addAttribute("min_login_name_length", uint32(length)) + } + + switch c.flagMinPasswordLength { + case "": + case "null": + addAttribute("min_password_length", nil) + default: + length, err := strconv.ParseUint(c.flagMinPasswordLength, 10, 32) + if err != nil { + c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagMinPasswordLength, err)) + return 1 + } + addAttribute("min_password_length", uint32(length)) + } + + if attributes != nil { + *opts = append(*opts, authmethods.WithAttributes(attributes)) + } + + return 0 +} diff --git a/internal/cmd/commands/authtokens/authtokens.go b/internal/cmd/commands/authtokens/authtokens.go deleted file mode 100644 index 6de62ece1..000000000 --- a/internal/cmd/commands/authtokens/authtokens.go +++ /dev/null @@ -1,224 +0,0 @@ -package authtokens - -import ( - "fmt" - "net/http" - "time" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/authtokens" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/internal/types/resource" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*Command)(nil) - _ cli.CommandAutocomplete = (*Command)(nil) -) - -type Command struct { - *base.Command - - Func string -} - -func (c *Command) Synopsis() string { - return common.SynopsisFunc(c.Func, "auth token") -} - -var flagsMap = map[string][]string{ - "read": {"id"}, - "delete": {"id"}, - "list": {"scope-id", "recursive"}, -} - -func (c *Command) Help() string { - helpMap := common.HelpMap("auth token") - if c.Func == "" { - return helpMap["base"]() - } - return helpMap[c.Func]() + c.Flags().Help() -} - -func (c *Command) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - - if len(flagsMap[c.Func]) > 0 { - f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, resource.AuthToken.String(), flagsMap[c.Func]) - } - - return set -} - -func (c *Command) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *Command) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *Command) Run(args []string) int { - if c.Func == "" { - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(flagsMap[c.Func], "scope-id") && c.FlagScopeId == "" { - c.UI.Error("Scope ID must be passed in via -scope-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - authtokenClient := authtokens.NewClient(client) - - var opts []authtokens.Option - - switch c.FlagRecursive { - case true: - opts = append(opts, authtokens.WithRecursive(true)) - } - - existed := true - var result api.GenericResult - var listResult api.GenericListResult - - switch c.Func { - case "read": - result, err = authtokenClient.Read(c.Context, c.FlagId, opts...) - case "delete": - _, err = authtokenClient.Delete(c.Context, c.FlagId, opts...) - if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { - existed = false - err = nil - } - case "list": - listResult, err = authtokenClient.List(c.Context, c.FlagScopeId, opts...) - } - - plural := "auth token" - if c.Func == "list" { - plural = "auth tokens" - } - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - switch c.Func { - case "delete": - switch base.Format(c.UI) { - case "json": - c.UI.Output("null") - case "table": - output := "The delete operation completed successfully" - switch existed { - case true: - output += "." - default: - output += ", however the resource did not exist at the time." - } - c.UI.Output(output) - } - return 0 - - case "list": - listedTokens := listResult.GetItems().([]*authtokens.AuthToken) - switch base.Format(c.UI) { - case "json": - if len(listedTokens) == 0 { - c.UI.Output("null") - return 0 - } - b, err := base.JsonFormatter{}.Format(listedTokens) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - - case "table": - if len(listedTokens) == 0 { - c.UI.Output("No auth tokens found") - return 0 - } - var output []string - output = []string{ - "", - "Auth Token information:", - } - for i, t := range listedTokens { - if i > 0 { - output = append(output, "") - } - if true { - output = append(output, - fmt.Sprintf(" ID: %s", t.Id), - ) - } - if c.FlagRecursive { - output = append(output, - fmt.Sprintf(" Scope ID: %s", t.Scope.Id), - ) - } - if true { - output = append(output, - fmt.Sprintf(" Approximate Last Used Time: %s", t.ApproximateLastUsedTime.Local().Format(time.RFC1123)), - fmt.Sprintf(" Auth Method ID: %s", t.AuthMethodId), - fmt.Sprintf(" Created Time: %s", t.CreatedTime.Local().Format(time.RFC1123)), - fmt.Sprintf(" Expiration Time: %s", t.ExpirationTime.Local().Format(time.RFC1123)), - fmt.Sprintf(" Updated Time: %s", t.UpdatedTime.Local().Format(time.RFC1123)), - fmt.Sprintf(" User ID: %s", t.UserId), - ) - } - if len(t.AuthorizedActions) > 0 { - output = append(output, - " Authorized Actions:", - base.WrapSlice(6, t.AuthorizedActions), - ) - } - } - c.UI.Output(base.WrapForHelpText(output)) - } - return 0 - } - - token := result.GetItem().(*authtokens.AuthToken) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateAuthTokenTableOutput(token)) - case "json": - b, err := base.JsonFormatter{}.Format(token) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/authtokens/funcs.go b/internal/cmd/commands/authtokens/funcs.go deleted file mode 100644 index d79dc9b55..000000000 --- a/internal/cmd/commands/authtokens/funcs.go +++ /dev/null @@ -1,41 +0,0 @@ -package authtokens - -import ( - "time" - - "github.com/hashicorp/boundary/api/authtokens" - "github.com/hashicorp/boundary/internal/cmd/base" -) - -func generateAuthTokenTableOutput(in *authtokens.AuthToken) string { - nonAttributeMap := map[string]interface{}{ - "ID": in.Id, - "Auth Method ID": in.AuthMethodId, - "User ID": in.UserId, - "Created Time": in.CreatedTime.Local().Format(time.RFC1123), - "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), - "Expiration Time": in.ExpirationTime.Local().Format(time.RFC1123), - "Approximate Last Used Time": in.ApproximateLastUsedTime.Local().Format(time.RFC1123), - } - - maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) - - ret := []string{ - "", - "Auth Token information:", - base.WrapMap(2, maxLength+2, nonAttributeMap), - "", - " Scope:", - base.ScopeInfoForOutput(in.Scope, maxLength), - } - - if len(in.AuthorizedActions) > 0 { - ret = append(ret, - "", - " Authorized Actions:", - base.WrapSlice(4, in.AuthorizedActions), - ) - } - - return base.WrapForHelpText(ret) -} diff --git a/internal/cmd/commands/authtokenscmd/authtokens.gen.go b/internal/cmd/commands/authtokenscmd/authtokens.gen.go new file mode 100644 index 000000000..947bbaf7a --- /dev/null +++ b/internal/cmd/commands/authtokenscmd/authtokens.gen.go @@ -0,0 +1,284 @@ +// Code generated by "make api"; DO NOT EDIT. +package authtokenscmd + +import ( + "fmt" + "net/http" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/authtokens" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "auth token" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("auth token") + + switch c.Func { + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "delete": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = helpMap["base"]() + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "read": {"id"}, + + "delete": {"id"}, + + "list": {"scope-id", "recursive"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "auth token", flagsMap[c.Func]) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "auth token" + switch c.Func { + case "list": + c.plural = "auth tokens" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []authtokens.Option + + if strutil.StrListContains(flagsMap[c.Func], "scope-id") { + switch c.Func { + case "list": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + authtokensClient := authtokens.NewClient(client) + + switch c.FlagRecursive { + case true: + opts = append(opts, authtokens.WithRecursive(true)) + } + + var version uint32 + + if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + existed := true + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "read": + result, err = authtokensClient.Read(c.Context, c.FlagId, opts...) + + case "delete": + _, err = authtokensClient.Delete(c.Context, c.FlagId, opts...) + if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { + existed = false + err = nil + } + + case "list": + listResult, err = authtokensClient.List(c.Context, c.FlagScopeId, opts...) + + } + + result, err = executeExtraActions(c, result, err, authtokensClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + c.UI.Output(fmt.Sprintf("{ \"existed\": %t }", existed)) + + case "table": + output := "The delete operation completed successfully" + switch existed { + case true: + output += "." + default: + output += ", however the resource did not exist at the time." + } + c.UI.Output(output) + } + + return 0 + + case "list": + listedItems := listResult.GetItems().([]*authtokens.AuthToken) + switch base.Format(c.UI) { + case "json": + switch { + + case len(listedItems) == 0: + c.UI.Output("null") + + default: + b, err := base.JsonFormatter{}.Format(listedItems) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + case "table": + c.UI.Output(c.printListTable(listedItems)) + } + + return 0 + + } + + item := result.GetItem().(*authtokens.AuthToken) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *[]authtokens.Option) int { return 0 } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *authtokens.Client, _ uint32, _ []authtokens.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/authtokenscmd/funcs.go b/internal/cmd/commands/authtokenscmd/funcs.go new file mode 100644 index 000000000..cfc6273a2 --- /dev/null +++ b/internal/cmd/commands/authtokenscmd/funcs.go @@ -0,0 +1,87 @@ +package authtokenscmd + +import ( + "fmt" + "time" + + "github.com/hashicorp/boundary/api/authtokens" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func (c *Command) printListTable(items []*authtokens.AuthToken) string { + if len(items) == 0 { + return "No auth tokens found" + } + + var output []string + output = []string{ + "", + "Auth Token information:", + } + for i, t := range items { + if i > 0 { + output = append(output, "") + } + if true { + output = append(output, + fmt.Sprintf(" ID: %s", t.Id), + ) + } + if c.FlagRecursive { + output = append(output, + fmt.Sprintf(" Scope ID: %s", t.Scope.Id), + ) + } + if true { + output = append(output, + fmt.Sprintf(" Approximate Last Used Time: %s", t.ApproximateLastUsedTime.Local().Format(time.RFC1123)), + fmt.Sprintf(" Auth Method ID: %s", t.AuthMethodId), + fmt.Sprintf(" Created Time: %s", t.CreatedTime.Local().Format(time.RFC1123)), + fmt.Sprintf(" Expiration Time: %s", t.ExpirationTime.Local().Format(time.RFC1123)), + fmt.Sprintf(" Updated Time: %s", t.UpdatedTime.Local().Format(time.RFC1123)), + fmt.Sprintf(" User ID: %s", t.UserId), + ) + } + if len(t.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, t.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(in *authtokens.AuthToken) string { + nonAttributeMap := map[string]interface{}{ + "ID": in.Id, + "Auth Method ID": in.AuthMethodId, + "User ID": in.UserId, + "Created Time": in.CreatedTime.Local().Format(time.RFC1123), + "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), + "Expiration Time": in.ExpirationTime.Local().Format(time.RFC1123), + "Approximate Last Used Time": in.ApproximateLastUsedTime.Local().Format(time.RFC1123), + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) + + ret := []string{ + "", + "Auth Token information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + "", + " Scope:", + base.ScopeInfoForOutput(in.Scope, maxLength), + } + + if len(in.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, in.AuthorizedActions), + ) + } + + return base.WrapForHelpText(ret) +} diff --git a/internal/cmd/commands/groups/funcs.go b/internal/cmd/commands/groups/funcs.go deleted file mode 100644 index b27cebaa0..000000000 --- a/internal/cmd/commands/groups/funcs.go +++ /dev/null @@ -1,141 +0,0 @@ -package groups - -import ( - "fmt" - "strings" - "time" - - "github.com/hashicorp/boundary/api/groups" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/internal/types/resource" - "github.com/mitchellh/go-wordwrap" -) - -func memberSynopsisFunc(inFunc string) string { - var in string - switch { - case strings.HasPrefix(inFunc, "add"): - in = "Add users to" - case strings.HasPrefix(inFunc, "set"): - in = "Set the full contents of the users on" - case strings.HasPrefix(inFunc, "remove"): - in = "Remove users from" - } - return wordwrap.WrapString(fmt.Sprintf("%s a group", in), base.TermWidth) -} - -func addMembersHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary groups add-members [options] [args]", - "", - ` Adds members (users) to a group given its ID. The "user" flag can be specified multiple times. Example:`, - "", - ` $ boundary groups add-members -id g_1234567890 -user u_1234567890`, - "", - "", - }) -} - -func setMembersHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary groups set-members [options] [args]", - "", - ` Sets the complete set of members (users) on a group given its ID. The "user" flag can be specified multiple times. Example:`, - "", - ` $ boundary groups set-principals -id g_1234567890 -user u_anon -user u_1234567890`, - "", - "", - }) -} - -func removeMembersHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary groups remove-members [options] [args]", - "", - ` Removes members (users) from a group given its ID. The "user" flag can be specified multiple times. Example:`, - "", - ` $ boundary groups remove-members -id g_1234567890 -user u_1234567890`, - "", - "", - }) -} - -func populateFlags(c *Command, f *base.FlagSet, flagNames []string) { - common.PopulateCommonFlags(c.Command, f, resource.Group.String(), flagNames) - - for _, name := range flagNames { - switch name { - case "member": - f.StringSliceVar(&base.StringSliceVar{ - Name: "member", - Target: &c.flagMembers, - Usage: "The members (users) to add, remove, or set. May be specified multiple times.", - }) - } - } -} - -func generateGroupTableOutput(in *groups.Group) string { - nonAttributeMap := map[string]interface{}{ - "ID": in.Id, - "Version": in.Version, - "Created Time": in.CreatedTime.Local().Format(time.RFC1123), - "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), - } - - if in.Name != "" { - nonAttributeMap["Name"] = in.Name - } - if in.Description != "" { - nonAttributeMap["Description"] = in.Description - } - - maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) - - var groupMaps []map[string]interface{} - if len(in.Members) > 0 { - for _, member := range in.Members { - m := map[string]interface{}{ - "ID": member.Id, - "Scope ID": member.ScopeId, - } - groupMaps = append(groupMaps, m) - } - if l := len("Scope ID"); l > maxLength { - maxLength = l - } - } - - ret := []string{ - "", - "Group information:", - base.WrapMap(2, maxLength+2, nonAttributeMap), - "", - " Scope:", - base.ScopeInfoForOutput(in.Scope, maxLength), - } - - if len(in.AuthorizedActions) > 0 { - ret = append(ret, - "", - " Authorized Actions:", - base.WrapSlice(4, in.AuthorizedActions), - ) - } - - if len(in.Members) > 0 { - ret = append(ret, - "", - " Members:", - ) - for _, m := range groupMaps { - ret = append(ret, - base.WrapMap(4, maxLength, m), - "", - ) - } - } - - return base.WrapForHelpText(ret) -} diff --git a/internal/cmd/commands/groups/group.go b/internal/cmd/commands/groups/group.go deleted file mode 100644 index 66e12f8ba..000000000 --- a/internal/cmd/commands/groups/group.go +++ /dev/null @@ -1,302 +0,0 @@ -package groups - -import ( - "fmt" - "net/http" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/groups" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*Command)(nil) - _ cli.CommandAutocomplete = (*Command)(nil) -) - -type Command struct { - *base.Command - - Func string - - flagMembers []string -} - -func (c *Command) Synopsis() string { - switch c.Func { - case "add-members", "set-members", "remove-members": - return memberSynopsisFunc(c.Func) - default: - return common.SynopsisFunc(c.Func, "group") - } -} - -var helpMap = func() map[string]func() string { - ret := common.HelpMap("group") - ret["add-members"] = addMembersHelp - ret["set-members"] = setMembersHelp - ret["remove-members"] = removeMembersHelp - return ret -} - -var flagsMap = map[string][]string{ - "create": {"scope-id", "name", "description"}, - "update": {"id", "name", "description", "version"}, - "read": {"id"}, - "delete": {"id"}, - "list": {"scope-id", "recursive"}, - "add-members": {"id", "member", "version"}, - "set-members": {"id", "member", "version"}, - "remove-members": {"id", "member", "version"}, -} - -func (c *Command) Help() string { - hm := helpMap() - if c.Func == "" { - return hm["base"]() - } - return hm[c.Func]() + c.Flags().Help() -} - -func (c *Command) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - populateFlags(c, f, flagsMap[c.Func]) - - return set -} - -func (c *Command) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *Command) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *Command) Run(args []string) int { - if c.Func == "" { - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(flagsMap[c.Func], "scope-id") && c.FlagScopeId == "" { - c.UI.Error("Scope ID must be passed in via -scope-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []groups.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, groups.DefaultName()) - default: - opts = append(opts, groups.WithName(c.FlagName)) - } - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, groups.DefaultDescription()) - default: - opts = append(opts, groups.WithDescription(c.FlagDescription)) - } - switch c.FlagRecursive { - case true: - opts = append(opts, groups.WithRecursive(true)) - } - - members := c.flagMembers - switch c.Func { - case "add-members", "remove-members": - if len(c.flagMembers) == 0 { - c.UI.Error("No members supplied via -member") - return 1 - } - - case "set-members": - switch len(c.flagMembers) { - case 0: - c.UI.Error("No members supplied via -member") - return 1 - case 1: - if c.flagMembers[0] == "null" { - members = nil - } - } - } - - groupClient := groups.NewClient(client) - - // Perform check-and-set when needed - var version uint32 - switch c.Func { - case "create", "read", "delete", "list": - // These don't udpate so don't need the existing version - default: - switch c.FlagVersion { - case 0: - opts = append(opts, groups.WithAutomaticVersioning(true)) - default: - version = uint32(c.FlagVersion) - } - } - - existed := true - var result api.GenericResult - var listResult api.GenericListResult - - switch c.Func { - case "create": - result, err = groupClient.Create(c.Context, c.FlagScopeId, opts...) - case "update": - result, err = groupClient.Update(c.Context, c.FlagId, version, opts...) - case "read": - result, err = groupClient.Read(c.Context, c.FlagId, opts...) - case "delete": - _, err = groupClient.Delete(c.Context, c.FlagId, opts...) - if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { - existed = false - err = nil - } - case "list": - listResult, err = groupClient.List(c.Context, c.FlagScopeId, opts...) - case "add-members": - result, err = groupClient.AddMembers(c.Context, c.FlagId, version, members, opts...) - case "set-members": - result, err = groupClient.SetMembers(c.Context, c.FlagId, version, members, opts...) - case "remove-members": - result, err = groupClient.RemoveMembers(c.Context, c.FlagId, version, members, opts...) - } - - plural := "group" - if c.Func == "list" { - plural = "groups" - } - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - switch c.Func { - case "delete": - switch base.Format(c.UI) { - case "json": - c.UI.Output("null") - case "table": - output := "The delete operation completed successfully" - switch existed { - case true: - output += "." - default: - output += ", however the resource did not exist at the time." - } - c.UI.Output(output) - } - return 0 - - case "list": - listedGroups := listResult.GetItems().([]*groups.Group) - switch base.Format(c.UI) { - case "json": - if len(listedGroups) == 0 { - c.UI.Output("null") - return 0 - } - b, err := base.JsonFormatter{}.Format(listedGroups) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - - case "table": - if len(listedGroups) == 0 { - c.UI.Output("No groups found") - return 0 - } - var output []string - output = []string{ - "", - "Group information:", - } - for i, g := range listedGroups { - if i > 0 { - output = append(output, "") - } - if true { - output = append(output, - fmt.Sprintf(" ID: %s", g.Id), - ) - } - if c.FlagRecursive { - output = append(output, - fmt.Sprintf(" Scope ID: %s", g.Scope.Id), - ) - } - if true { - output = append(output, - fmt.Sprintf(" Version: %d", g.Version), - ) - } - if g.Name != "" { - output = append(output, - fmt.Sprintf(" Name: %s", g.Name), - ) - } - if g.Description != "" { - output = append(output, - fmt.Sprintf(" Description: %s", g.Description), - ) - } - if len(g.AuthorizedActions) > 0 { - output = append(output, - " Authorized Actions:", - base.WrapSlice(6, g.AuthorizedActions), - ) - } - } - c.UI.Output(base.WrapForHelpText(output)) - } - return 0 - } - - group := result.GetItem().(*groups.Group) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateGroupTableOutput(group)) - case "json": - b, err := base.JsonFormatter{}.Format(group) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/groupscmd/funcs.go b/internal/cmd/commands/groupscmd/funcs.go new file mode 100644 index 000000000..f25a753f3 --- /dev/null +++ b/internal/cmd/commands/groupscmd/funcs.go @@ -0,0 +1,254 @@ +package groupscmd + +import ( + "fmt" + "strings" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/groups" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/mitchellh/go-wordwrap" +) + +func init() { + extraActionsFlagsMapFunc = extraActionsFlagsMapFuncImpl + extraSynopsisFunc = extraSynopsisFuncImpl + extraFlagsFunc = extraFlagsFuncImpl + extraFlagsHandlingFunc = extraFlagsHandlingFuncImpl + executeExtraActions = executeExtraActionsImpl +} + +type extraCmdVars struct { + flagMembers []string +} + +func extraActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "add-members": {"id", "member", "version"}, + "remove-members": {"id", "member", "version"}, + "set-members": {"id", "member", "version"}, + } +} + +func extraSynopsisFuncImpl(c *Command) string { + switch c.Func { + case "add-members", "set-members", "remove-members": + var in string + switch { + case strings.HasPrefix(c.Func, "add"): + in = "Add members to" + case strings.HasPrefix(c.Func, "set"): + in = "Set the full contents of the users on" + case strings.HasPrefix(c.Func, "remove"): + in = "Remove users from" + } + return wordwrap.WrapString(fmt.Sprintf("%s a group", in), base.TermWidth) + + default: + return "" + } +} + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "": + return helpMap["base"]() + + case "add-members": + return base.WrapForHelpText([]string{ + "Usage: boundary groups add-members [options] [args]", + "", + ` Adds members (users) to a group given its ID. The "user" flag can be specified multiple times. Example:`, + "", + ` $ boundary groups add-members -id g_1234567890 -user u_1234567890`, + "", + "", + }) + + case "set-members": + return base.WrapForHelpText([]string{ + "Usage: boundary groups set-members [options] [args]", + "", + ` Sets the complete set of members (users) on a group given its ID. The "user" flag can be specified multiple times. Example:`, + "", + ` $ boundary groups set-principals -id g_1234567890 -user u_anon -user u_1234567890`, + "", + "", + }) + + case "remove-members": + return base.WrapForHelpText([]string{ + "Usage: boundary groups remove-members [options] [args]", + "", + ` Removes members (users) from a group given its ID. The "user" flag can be specified multiple times. Example:`, + "", + ` $ boundary groups remove-members -id g_1234567890 -user u_1234567890`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} + +func extraFlagsFuncImpl(c *Command, _ *base.FlagSets, f *base.FlagSet) { + for _, name := range flagsMap[c.Func] { + switch name { + case "member": + f.StringSliceVar(&base.StringSliceVar{ + Name: "member", + Target: &c.flagMembers, + Usage: "The members (users) to add, remove, or set. May be specified multiple times.", + }) + } + } +} + +func extraFlagsHandlingFuncImpl(c *Command, opts *[]groups.Option) int { + switch c.Func { + case "add-members", "remove-members": + if len(c.flagMembers) == 0 { + c.UI.Error("No members supplied via -member") + return 1 + } + + case "set-members": + switch len(c.flagMembers) { + case 0: + c.UI.Error("No members supplied via -member") + return 1 + case 1: + if c.flagMembers[0] == "null" { + c.flagMembers = nil + } + } + } + + return 0 +} + +func executeExtraActionsImpl(c *Command, origResult api.GenericResult, origError error, groupClient *groups.Client, version uint32, opts []groups.Option) (api.GenericResult, error) { + switch c.Func { + case "add-members": + return groupClient.AddMembers(c.Context, c.FlagId, version, c.flagMembers, opts...) + case "set-members": + return groupClient.SetMembers(c.Context, c.FlagId, version, c.flagMembers, opts...) + case "remove-members": + return groupClient.RemoveMembers(c.Context, c.FlagId, version, c.flagMembers, opts...) + } + return origResult, origError +} + +func (c *Command) printListTable(items []*groups.Group) string { + if len(items) == 0 { + return "No groups found" + } + var output []string + output = []string{ + "", + "Group information:", + } + for i, g := range items { + if i > 0 { + output = append(output, "") + } + if true { + output = append(output, + fmt.Sprintf(" ID: %s", g.Id), + ) + } + if c.FlagRecursive { + output = append(output, + fmt.Sprintf(" Scope ID: %s", g.Scope.Id), + ) + } + if true { + output = append(output, + fmt.Sprintf(" Version: %d", g.Version), + ) + } + if g.Name != "" { + output = append(output, + fmt.Sprintf(" Name: %s", g.Name), + ) + } + if g.Description != "" { + output = append(output, + fmt.Sprintf(" Description: %s", g.Description), + ) + } + if len(g.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, g.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(item *groups.Group) string { + nonAttributeMap := map[string]interface{}{ + "ID": item.Id, + "Version": item.Version, + "Created Time": item.CreatedTime.Local().Format(time.RFC1123), + "Updated Time": item.UpdatedTime.Local().Format(time.RFC1123), + } + + if item.Name != "" { + nonAttributeMap["Name"] = item.Name + } + if item.Description != "" { + nonAttributeMap["Description"] = item.Description + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) + + var groupMaps []map[string]interface{} + if len(item.Members) > 0 { + for _, member := range item.Members { + m := map[string]interface{}{ + "ID": member.Id, + "Scope ID": member.ScopeId, + } + groupMaps = append(groupMaps, m) + } + if l := len("Scope ID"); l > maxLength { + maxLength = l + } + } + + ret := []string{ + "", + "Group information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + "", + " Scope:", + base.ScopeInfoForOutput(item.Scope, maxLength), + } + + if len(item.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, item.AuthorizedActions), + ) + } + + if len(item.Members) > 0 { + ret = append(ret, + "", + " Members:", + ) + for _, m := range groupMaps { + ret = append(ret, + base.WrapMap(4, maxLength, m), + "", + ) + } + } + + return base.WrapForHelpText(ret) +} diff --git a/internal/cmd/commands/groupscmd/groups.gen.go b/internal/cmd/commands/groupscmd/groups.gen.go new file mode 100644 index 000000000..8a6fd24d7 --- /dev/null +++ b/internal/cmd/commands/groupscmd/groups.gen.go @@ -0,0 +1,362 @@ +// Code generated by "make api"; DO NOT EDIT. +package groupscmd + +import ( + "fmt" + "net/http" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/groups" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string + + extraCmdVars +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "group" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("group") + + switch c.Func { + + case "create": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "update": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "delete": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = c.extraHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "create": {"scope-id", "name", "description"}, + + "read": {"id"}, + + "update": {"id", "name", "description", "version"}, + + "delete": {"id"}, + + "list": {"scope-id", "recursive"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "group", flagsMap[c.Func]) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "group" + switch c.Func { + case "list": + c.plural = "groups" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []groups.Option + + if strutil.StrListContains(flagsMap[c.Func], "scope-id") { + switch c.Func { + + case "create": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + + case "list": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + groupsClient := groups.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, groups.DefaultName()) + default: + opts = append(opts, groups.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, groups.DefaultDescription()) + default: + opts = append(opts, groups.WithDescription(c.FlagDescription)) + } + + switch c.FlagRecursive { + case true: + opts = append(opts, groups.WithRecursive(true)) + } + + var version uint32 + + switch c.Func { + + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, groups.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "add-members": + switch c.FlagVersion { + case 0: + opts = append(opts, groups.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "remove-members": + switch c.FlagVersion { + case 0: + opts = append(opts, groups.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "set-members": + switch c.FlagVersion { + case 0: + opts = append(opts, groups.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + } + + if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + existed := true + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "create": + result, err = groupsClient.Create(c.Context, c.FlagScopeId, opts...) + + case "read": + result, err = groupsClient.Read(c.Context, c.FlagId, opts...) + + case "update": + result, err = groupsClient.Update(c.Context, c.FlagId, version, opts...) + + case "delete": + _, err = groupsClient.Delete(c.Context, c.FlagId, opts...) + if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { + existed = false + err = nil + } + + case "list": + listResult, err = groupsClient.List(c.Context, c.FlagScopeId, opts...) + + } + + result, err = executeExtraActions(c, result, err, groupsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + c.UI.Output(fmt.Sprintf("{ \"existed\": %t }", existed)) + + case "table": + output := "The delete operation completed successfully" + switch existed { + case true: + output += "." + default: + output += ", however the resource did not exist at the time." + } + c.UI.Output(output) + } + + return 0 + + case "list": + listedItems := listResult.GetItems().([]*groups.Group) + switch base.Format(c.UI) { + case "json": + switch { + + case len(listedItems) == 0: + c.UI.Output("null") + + default: + b, err := base.JsonFormatter{}.Format(listedItems) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + case "table": + c.UI.Output(c.printListTable(listedItems)) + } + + return 0 + + } + + item := result.GetItem().(*groups.Group) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *[]groups.Option) int { return 0 } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *groups.Client, _ uint32, _ []groups.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/hostcatalogs/funcs.go b/internal/cmd/commands/hostcatalogs/funcs.go deleted file mode 100644 index 6d1953b34..000000000 --- a/internal/cmd/commands/hostcatalogs/funcs.go +++ /dev/null @@ -1,77 +0,0 @@ -package hostcatalogs - -import ( - "fmt" - "sort" - "time" - - "github.com/hashicorp/boundary/api/hostcatalogs" - "github.com/hashicorp/boundary/internal/cmd/base" -) - -func generateHostCatalogTableOutput(in *hostcatalogs.HostCatalog) string { - nonAttributeMap := map[string]interface{}{ - "ID": in.Id, - "Version": in.Version, - "Type": in.Type, - "Created Time": in.CreatedTime.Local().Format(time.RFC1123), - "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), - } - - if in.Name != "" { - nonAttributeMap["Name"] = in.Name - } - if in.Description != "" { - nonAttributeMap["Description"] = in.Description - } - - maxLength := base.MaxAttributesLength(nonAttributeMap, in.Attributes, keySubstMap) - - ret := []string{ - "", - "Host Catalog information:", - base.WrapMap(2, maxLength+2, nonAttributeMap), - "", - " Scope:", - base.ScopeInfoForOutput(in.Scope, maxLength), - } - - if len(in.AuthorizedActions) > 0 { - ret = append(ret, - "", - " Authorized Actions:", - base.WrapSlice(4, in.AuthorizedActions), - ) - } - - if len(in.AuthorizedCollectionActions) > 0 { - keys := make([]string, 0, len(in.AuthorizedCollectionActions)) - for k := range in.AuthorizedCollectionActions { - keys = append(keys, k) - } - sort.Strings(keys) - ret = append(ret, "", - " Authorized Actions on Host Catalog's Collections:", - ) - for _, key := range keys { - ret = append(ret, - fmt.Sprintf(" %ss:", key), - base.WrapSlice(6, in.AuthorizedCollectionActions[key]), - ) - } - } - - if len(in.Attributes) > 0 { - ret = append(ret, - "", - " Attributes:", - base.WrapMap(4, maxLength, in.Attributes), - ) - } - - return base.WrapForHelpText(ret) -} - -var keySubstMap = map[string]string{ - "address": "Address", -} diff --git a/internal/cmd/commands/hostcatalogs/hostcatalog.go b/internal/cmd/commands/hostcatalogs/hostcatalog.go deleted file mode 100644 index 337bd5a98..000000000 --- a/internal/cmd/commands/hostcatalogs/hostcatalog.go +++ /dev/null @@ -1,284 +0,0 @@ -package hostcatalogs - -import ( - "fmt" - "net/http" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/hostcatalogs" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/internal/types/resource" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*Command)(nil) - _ cli.CommandAutocomplete = (*Command)(nil) -) - -type Command struct { - *base.Command - - Func string -} - -func (c *Command) Synopsis() string { - return common.SynopsisFunc(c.Func, "host catalog") -} - -var flagsMap = map[string][]string{ - "read": {"id"}, - "delete": {"id"}, - "list": {"scope-id", "recursive"}, -} - -func (c *Command) Help() string { - helpMap := common.HelpMap("host catalog") - var helpStr string - switch c.Func { - case "": - return base.WrapForHelpText([]string{ - "Usage: boundary host-catalogs [sub command] [options] [args]", - "", - " This command allows operations on Boundary host catalog resources. Example:", - "", - " Read a host catalog:", - "", - ` $ boundary host-catalogs read -id hcst_1234567890`, - "", - " Please see the host-catalogs subcommand help for detailed usage information.", - }) - case "create": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary host-catalogs create [type] [sub command] [options] [args]", - "", - " This command allows create operations on Boundary host catalog resources. Example:", - "", - " Create a static-type host catalog:", - "", - ` $ boundary host-catalogs create static -name prodops -description "For ProdOps usage"`, - "", - " Please see the typed subcommand help for detailed usage information.", - }) - case "update": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary host-catalogs update [type] [sub command] [options] [args]", - "", - " This command allows update operations on Boundary host catalog resources. Example:", - "", - " Update a static-type host catalog:", - "", - ` $ boundary host-catalogs update static -id hcst_1234567890 -name devops -description "For DevOps usage"`, - "", - " Please see the typed subcommand help for detailed usage information.", - }) - default: - helpStr = helpMap[c.Func]() - } - return helpStr + c.Flags().Help() -} - -func (c *Command) Flags() *base.FlagSets { - if len(flagsMap[c.Func]) == 0 { - return c.FlagSet(base.FlagSetNone) - } - - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - - f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, resource.HostCatalog.String(), flagsMap[c.Func]) - - return set -} - -func (c *Command) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *Command) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *Command) Run(args []string) int { - switch c.Func { - case "", "create", "update": - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(flagsMap[c.Func], "scope-id") && c.FlagScopeId == "" { - c.UI.Error("Scope ID must be passed in via -scope-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []hostcatalogs.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, hostcatalogs.DefaultName()) - default: - opts = append(opts, hostcatalogs.WithName(c.FlagName)) - } - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, hostcatalogs.DefaultDescription()) - default: - opts = append(opts, hostcatalogs.WithDescription(c.FlagDescription)) - } - switch c.FlagRecursive { - case true: - opts = append(opts, hostcatalogs.WithRecursive(true)) - } - - hostcatalogClient := hostcatalogs.NewClient(client) - - existed := true - var result api.GenericResult - var listResult api.GenericListResult - - switch c.Func { - case "read": - result, err = hostcatalogClient.Read(c.Context, c.FlagId, opts...) - case "delete": - _, err = hostcatalogClient.Delete(c.Context, c.FlagId, opts...) - if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { - existed = false - err = nil - } - case "list": - listResult, err = hostcatalogClient.List(c.Context, c.FlagScopeId, opts...) - } - - plural := "host catalog" - if c.Func == "list" { - plural = "host catalogs" - } - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - switch c.Func { - case "delete": - switch base.Format(c.UI) { - case "json": - c.UI.Output("null") - case "table": - output := "The delete operation completed successfully" - switch existed { - case true: - output += "." - default: - output += ", however the resource did not exist at the time." - } - c.UI.Output(output) - } - return 0 - - case "list": - listedCatalogs := listResult.GetItems().([]*hostcatalogs.HostCatalog) - switch base.Format(c.UI) { - case "json": - if len(listedCatalogs) == 0 { - c.UI.Output("null") - return 0 - } - b, err := base.JsonFormatter{}.Format(listedCatalogs) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - - case "table": - if len(listedCatalogs) == 0 { - c.UI.Output("No host catalogs found") - return 0 - } - var output []string - output = []string{ - "", - "Host Catalog information:", - } - for i, m := range listedCatalogs { - if i > 0 { - output = append(output, "") - } - if true { - output = append(output, - fmt.Sprintf(" ID: %s", m.Id), - ) - } - if c.FlagRecursive { - output = append(output, - fmt.Sprintf(" Scope ID: %s", m.Scope.Id), - ) - } - if true { - output = append(output, - fmt.Sprintf(" Version: %d", m.Version), - fmt.Sprintf(" Type: %s", m.Type), - ) - } - if m.Name != "" { - output = append(output, - fmt.Sprintf(" Name: %s", m.Name), - ) - } - if m.Description != "" { - output = append(output, - fmt.Sprintf(" Description: %s", m.Description), - ) - } - if len(m.AuthorizedActions) > 0 { - output = append(output, - " Authorized Actions:", - base.WrapSlice(6, m.AuthorizedActions), - ) - } - } - c.UI.Output(base.WrapForHelpText(output)) - } - return 0 - } - - catalog := result.GetItem().(*hostcatalogs.HostCatalog) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateHostCatalogTableOutput(catalog)) - case "json": - b, err := base.JsonFormatter{}.Format(catalog) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/hostcatalogs/static.go b/internal/cmd/commands/hostcatalogs/static.go deleted file mode 100644 index e5e1cc60f..000000000 --- a/internal/cmd/commands/hostcatalogs/static.go +++ /dev/null @@ -1,174 +0,0 @@ -package hostcatalogs - -import ( - "fmt" - "net/textproto" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/hostcatalogs" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*StaticCommand)(nil) - _ cli.CommandAutocomplete = (*StaticCommand)(nil) -) - -type StaticCommand struct { - *base.Command - - Func string -} - -func (c *StaticCommand) Synopsis() string { - return fmt.Sprintf("%s a static-type host catalog", textproto.CanonicalMIMEHeaderKey(c.Func)) -} - -var staticFlagsMap = map[string][]string{ - "create": {"scope-id", "name", "description"}, - "update": {"id", "name", "description", "version"}, -} - -func (c *StaticCommand) Help() string { - var info string - switch c.Func { - case "create": - info = base.WrapForHelpText([]string{ - "Usage: boundary host-catalogs create static [options] [args]", - "", - " Create a static-type host catalog. Example:", - "", - ` $ boundary host-catalogs create static -name prodops -description "Static host-catalog for ProdOps"`, - "", - "", - }) - - case "update": - info = base.WrapForHelpText([]string{ - "Usage: boundary host-catalogs update static [options] [args]", - "", - " Update a static-type host catalog given its ID. Example:", - "", - ` $ boundary host-catalogs update static -id hcst_1234567890 -name "devops" -description "Static host-catalog for DevOps"`, - "", - "", - }) - } - return info + c.Flags().Help() -} - -func (c *StaticCommand) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "static-type host catalog", staticFlagsMap[c.Func]) - - return set -} - -func (c *StaticCommand) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *StaticCommand) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *StaticCommand) Run(args []string) int { - if c.Func == "" { - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(staticFlagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(staticFlagsMap[c.Func], "scope-id") && c.FlagScopeId == "" { - c.UI.Error("Scope ID must be passed in via -scope-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []hostcatalogs.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, hostcatalogs.DefaultName()) - default: - opts = append(opts, hostcatalogs.WithName(c.FlagName)) - } - - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, hostcatalogs.DefaultDescription()) - default: - opts = append(opts, hostcatalogs.WithDescription(c.FlagDescription)) - } - - hostcatalogClient := hostcatalogs.NewClient(client) - - // Perform check-and-set when needed - var version uint32 - switch c.Func { - case "create": - // These don't update so don't need the existing version - default: - switch c.FlagVersion { - case 0: - opts = append(opts, hostcatalogs.WithAutomaticVersioning(true)) - default: - version = uint32(c.FlagVersion) - } - } - - var result api.GenericResult - - switch c.Func { - case "create": - result, err = hostcatalogClient.Create(c.Context, "static", c.FlagScopeId, opts...) - case "update": - result, err = hostcatalogClient.Update(c.Context, c.FlagId, version, opts...) - } - - plural := "static-type host-catalog" - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - catalog := result.GetItem().(*hostcatalogs.HostCatalog) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateHostCatalogTableOutput(catalog)) - case "json": - b, err := base.JsonFormatter{}.Format(catalog) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/hostcatalogscmd/funcs.go b/internal/cmd/commands/hostcatalogscmd/funcs.go new file mode 100644 index 000000000..15c2644fc --- /dev/null +++ b/internal/cmd/commands/hostcatalogscmd/funcs.go @@ -0,0 +1,173 @@ +package hostcatalogscmd + +import ( + "fmt" + "sort" + "time" + + "github.com/hashicorp/boundary/api/hostcatalogs" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "": + return base.WrapForHelpText([]string{ + "Usage: boundary host-catalogs [sub command] [options] [args]", + "", + " This command allows operations on Boundary host catalog resources. Example:", + "", + " Read a host catalog:", + "", + ` $ boundary host-catalogs read -id hcst_1234567890`, + "", + " Please see the host-catalogs subcommand help for detailed usage information.", + }) + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-catalogs create [type] [sub command] [options] [args]", + "", + " This command allows create operations on Boundary host catalog resources. Example:", + "", + " Create a static-type host catalog:", + "", + ` $ boundary host-catalogs create static -name prodops -description "For ProdOps usage"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-catalogs update [type] [sub command] [options] [args]", + "", + " This command allows update operations on Boundary host catalog resources. Example:", + "", + " Update a static-type host catalog:", + "", + ` $ boundary host-catalogs update static -id hcst_1234567890 -name devops -description "For DevOps usage"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + default: + helpStr = helpMap["base"]() + } + return helpStr + c.Flags().Help() +} + +func (c *Command) printListTable(items []*hostcatalogs.HostCatalog) string { + if len(items) == 0 { + return "No host catalogs found" + } + + var output []string + output = []string{ + "", + "Host Catalog information:", + } + for i, m := range items { + if i > 0 { + output = append(output, "") + } + if true { + output = append(output, + fmt.Sprintf(" ID: %s", m.Id), + ) + } + if c.FlagRecursive { + output = append(output, + fmt.Sprintf(" Scope ID: %s", m.Scope.Id), + ) + } + if true { + output = append(output, + fmt.Sprintf(" Version: %d", m.Version), + fmt.Sprintf(" Type: %s", m.Type), + ) + } + if m.Name != "" { + output = append(output, + fmt.Sprintf(" Name: %s", m.Name), + ) + } + if m.Description != "" { + output = append(output, + fmt.Sprintf(" Description: %s", m.Description), + ) + } + if len(m.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, m.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(in *hostcatalogs.HostCatalog) string { + nonAttributeMap := map[string]interface{}{ + "ID": in.Id, + "Version": in.Version, + "Type": in.Type, + "Created Time": in.CreatedTime.Local().Format(time.RFC1123), + "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), + } + + if in.Name != "" { + nonAttributeMap["Name"] = in.Name + } + if in.Description != "" { + nonAttributeMap["Description"] = in.Description + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, in.Attributes, keySubstMap) + + ret := []string{ + "", + "Host Catalog information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + "", + " Scope:", + base.ScopeInfoForOutput(in.Scope, maxLength), + } + + if len(in.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, in.AuthorizedActions), + ) + } + + if len(in.AuthorizedCollectionActions) > 0 { + keys := make([]string, 0, len(in.AuthorizedCollectionActions)) + for k := range in.AuthorizedCollectionActions { + keys = append(keys, k) + } + sort.Strings(keys) + ret = append(ret, "", + " Authorized Actions on Host Catalog's Collections:", + ) + for _, key := range keys { + ret = append(ret, + fmt.Sprintf(" %ss:", key), + base.WrapSlice(6, in.AuthorizedCollectionActions[key]), + ) + } + } + + if len(in.Attributes) > 0 { + ret = append(ret, + "", + " Attributes:", + base.WrapMap(4, maxLength, in.Attributes), + ) + } + + return base.WrapForHelpText(ret) +} + +var keySubstMap = map[string]string{ + "address": "Address", +} diff --git a/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go b/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go new file mode 100644 index 000000000..478b861f0 --- /dev/null +++ b/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go @@ -0,0 +1,288 @@ +// Code generated by "make api"; DO NOT EDIT. +package hostcatalogscmd + +import ( + "fmt" + "net/http" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/hostcatalogs" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "host catalog" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("host catalog") + + switch c.Func { + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "delete": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = c.extraHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "read": {"id"}, + + "delete": {"id"}, + + "list": {"scope-id", "recursive"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "host catalog", flagsMap[c.Func]) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + + case "create", "update": + return cli.RunResultHelp + + } + + c.plural = "host catalog" + switch c.Func { + case "list": + c.plural = "host catalogs" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []hostcatalogs.Option + + if strutil.StrListContains(flagsMap[c.Func], "scope-id") { + switch c.Func { + case "list": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + hostcatalogsClient := hostcatalogs.NewClient(client) + + switch c.FlagRecursive { + case true: + opts = append(opts, hostcatalogs.WithRecursive(true)) + } + + var version uint32 + + if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + existed := true + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "read": + result, err = hostcatalogsClient.Read(c.Context, c.FlagId, opts...) + + case "delete": + _, err = hostcatalogsClient.Delete(c.Context, c.FlagId, opts...) + if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { + existed = false + err = nil + } + + case "list": + listResult, err = hostcatalogsClient.List(c.Context, c.FlagScopeId, opts...) + + } + + result, err = executeExtraActions(c, result, err, hostcatalogsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + c.UI.Output(fmt.Sprintf("{ \"existed\": %t }", existed)) + + case "table": + output := "The delete operation completed successfully" + switch existed { + case true: + output += "." + default: + output += ", however the resource did not exist at the time." + } + c.UI.Output(output) + } + + return 0 + + case "list": + listedItems := listResult.GetItems().([]*hostcatalogs.HostCatalog) + switch base.Format(c.UI) { + case "json": + switch { + + case len(listedItems) == 0: + c.UI.Output("null") + + default: + b, err := base.JsonFormatter{}.Format(listedItems) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + case "table": + c.UI.Output(c.printListTable(listedItems)) + } + + return 0 + + } + + item := result.GetItem().(*hostcatalogs.HostCatalog) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *[]hostcatalogs.Option) int { return 0 } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *hostcatalogs.Client, _ uint32, _ []hostcatalogs.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/hostcatalogscmd/static_funcs.go b/internal/cmd/commands/hostcatalogscmd/static_funcs.go new file mode 100644 index 000000000..e0a571c1d --- /dev/null +++ b/internal/cmd/commands/hostcatalogscmd/static_funcs.go @@ -0,0 +1,33 @@ +package hostcatalogscmd + +import ( + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func (c *StaticCommand) extraStaticHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-catalogs create static [options] [args]", + "", + " Create a static-type host catalog. Example:", + "", + ` $ boundary host-catalogs create static -name prodops -description "Static host-catalog for ProdOps"`, + "", + "", + }) + + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-catalogs update static [options] [args]", + "", + " Update a static-type host catalog given its ID. Example:", + "", + ` $ boundary host-catalogs update static -id hcst_1234567890 -name "devops" -description "Static host-catalog for DevOps"`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} diff --git a/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go b/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go new file mode 100644 index 000000000..36802ea86 --- /dev/null +++ b/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go @@ -0,0 +1,241 @@ +// Code generated by "make api"; DO NOT EDIT. +package hostcatalogscmd + +import ( + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/hostcatalogs" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initStaticFlags() { + flagsOnce.Do(func() { + extraFlags := extraStaticActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsStaticMap[k] = append(flagsStaticMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*StaticCommand)(nil) + _ cli.CommandAutocomplete = (*StaticCommand)(nil) +) + +type StaticCommand struct { + *base.Command + + Func string + + plural string +} + +func (c *StaticCommand) AutocompleteArgs() complete.Predictor { + initStaticFlags() + return complete.PredictAnything +} + +func (c *StaticCommand) AutocompleteFlags() complete.Flags { + initStaticFlags() + return c.Flags().Completions() +} + +func (c *StaticCommand) Synopsis() string { + if extra := extraStaticSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "host catalog" + + synopsisStr = fmt.Sprintf("%s %s", "static-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *StaticCommand) Help() string { + initStaticFlags() + + var helpStr string + helpMap := common.HelpMap("host catalog") + + switch c.Func { + default: + + helpStr = c.extraStaticHelpFunc(helpMap) + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsStaticMap = map[string][]string{ + + "create": {"scope-id", "name", "description"}, + + "update": {"id", "name", "description", "version"}, +} + +func (c *StaticCommand) Flags() *base.FlagSets { + if len(flagsStaticMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "static-type host catalog", flagsStaticMap[c.Func]) + + extraStaticFlagsFunc(c, set, f) + + return set +} + +func (c *StaticCommand) Run(args []string) int { + initStaticFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "static-type host catalog" + switch c.Func { + case "list": + c.plural = "static-type host catalogs" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsStaticMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []hostcatalogs.Option + + if strutil.StrListContains(flagsStaticMap[c.Func], "scope-id") { + switch c.Func { + case "create": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + hostcatalogsClient := hostcatalogs.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, hostcatalogs.DefaultName()) + default: + opts = append(opts, hostcatalogs.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, hostcatalogs.DefaultDescription()) + default: + opts = append(opts, hostcatalogs.WithDescription(c.FlagDescription)) + } + + switch c.FlagRecursive { + case true: + opts = append(opts, hostcatalogs.WithRecursive(true)) + } + + var version uint32 + + switch c.Func { + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, hostcatalogs.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } + + if ret := extraStaticFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + var result api.GenericResult + + switch c.Func { + + case "create": + result, err = hostcatalogsClient.Create(c.Context, "static", c.FlagScopeId, opts...) + + case "update": + result, err = hostcatalogsClient.Update(c.Context, c.FlagId, version, opts...) + + } + + result, err = executeExtraStaticActions(c, result, err, hostcatalogsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomStaticActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + } + + item := result.GetItem().(*hostcatalogs.HostCatalog) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + extraStaticActionsFlagsMapFunc = func() map[string][]string { return nil } + extraStaticSynopsisFunc = func(*StaticCommand) string { return "" } + extraStaticFlagsFunc = func(*StaticCommand, *base.FlagSets, *base.FlagSet) {} + extraStaticFlagsHandlingFunc = func(*StaticCommand, *[]hostcatalogs.Option) int { return 0 } + executeExtraStaticActions = func(_ *StaticCommand, inResult api.GenericResult, inErr error, _ *hostcatalogs.Client, _ uint32, _ []hostcatalogs.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomStaticActionOutput = func(*StaticCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/hosts/funcs.go b/internal/cmd/commands/hosts/funcs.go deleted file mode 100644 index 40bae759c..000000000 --- a/internal/cmd/commands/hosts/funcs.go +++ /dev/null @@ -1,65 +0,0 @@ -package hosts - -import ( - "time" - - "github.com/hashicorp/boundary/api/hosts" - "github.com/hashicorp/boundary/internal/cmd/base" -) - -func generateHostTableOutput(in *hosts.Host) string { - nonAttributeMap := map[string]interface{}{ - "ID": in.Id, - "Version": in.Version, - "Type": in.Type, - "Created Time": in.CreatedTime.Local().Format(time.RFC1123), - "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), - "Host Catalog ID": in.HostCatalogId, - } - - if in.Name != "" { - nonAttributeMap["Name"] = in.Name - } - if in.Description != "" { - nonAttributeMap["Description"] = in.Description - } - - maxLength := base.MaxAttributesLength(nonAttributeMap, in.Attributes, keySubstMap) - - ret := []string{ - "", - "Host information:", - base.WrapMap(2, maxLength+2, nonAttributeMap), - "", - " Scope:", - base.ScopeInfoForOutput(in.Scope, maxLength), - } - - if len(in.AuthorizedActions) > 0 { - ret = append(ret, - "", - " Authorized Actions:", - base.WrapSlice(4, in.AuthorizedActions), - ) - } - - if len(in.HostSetIds) > 0 { - ret = append(ret, - "", - " Host Set IDs:", - base.WrapSlice(4, in.HostSetIds), - ) - } - - if len(in.Attributes) > 0 { - ret = append(ret, - "", - " Attributes:", - base.WrapMap(4, maxLength, in.Attributes), - ) - } - - return base.WrapForHelpText(ret) -} - -var keySubstMap = map[string]string{} diff --git a/internal/cmd/commands/hosts/host.go b/internal/cmd/commands/hosts/host.go deleted file mode 100644 index d7c2be14d..000000000 --- a/internal/cmd/commands/hosts/host.go +++ /dev/null @@ -1,271 +0,0 @@ -package hosts - -import ( - "fmt" - "net/http" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/hosts" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/internal/types/resource" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*Command)(nil) - _ cli.CommandAutocomplete = (*Command)(nil) -) - -type Command struct { - *base.Command - - Func string -} - -func (c *Command) Synopsis() string { - return common.SynopsisFunc(c.Func, "host") -} - -var flagsMap = map[string][]string{ - "read": {"id"}, - "delete": {"id"}, - "list": {"host-catalog-id"}, -} - -func (c *Command) Help() string { - helpMap := common.HelpMap("host") - var helpStr string - switch c.Func { - case "": - return base.WrapForHelpText([]string{ - "Usage: boundary hosts [sub command] [options] [args]", - "", - " This command allows operations on Boundary host resources. Example:", - "", - " Read a host:", - "", - ` $ boundary hosts read -id hst_1234567890`, - "", - " Please see the hosts subcommand help for detailed usage information.", - }) - case "create": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary hosts create [type] [sub command] [options] [args]", - "", - " This command allows create operations on Boundary host resources. Example:", - "", - " Create a static-type host:", - "", - ` $ boundary hosts create static -name prodops -description "For ProdOps usage"`, - "", - " Please see the typed subcommand help for detailed usage information.", - }) - case "update": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary hosts update [type] [sub command] [options] [args]", - "", - " This command allows update operations on Boundary host resources. Example:", - "", - " Update a static-type host:", - "", - ` $ boundary hosts update static -id hst_1234567890 -name devops -description "For DevOps usage"`, - "", - " Please see the typed subcommand help for detailed usage information.", - }) - default: - helpStr = helpMap[c.Func]() - } - return helpStr + c.Flags().Help() -} - -func (c *Command) Flags() *base.FlagSets { - if len(flagsMap[c.Func]) == 0 { - return c.FlagSet(base.FlagSetNone) - } - - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, resource.Host.String(), flagsMap[c.Func]) - - return set -} - -func (c *Command) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *Command) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *Command) Run(args []string) int { - switch c.Func { - case "", "create", "update": - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(flagsMap[c.Func], "host-catalog-id") && c.FlagHostCatalogId == "" { - c.UI.Error("Host Catalog ID must be passed in via -host-catalog-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []hosts.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, hosts.DefaultName()) - default: - opts = append(opts, hosts.WithName(c.FlagName)) - } - - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, hosts.DefaultDescription()) - default: - opts = append(opts, hosts.WithDescription(c.FlagDescription)) - } - - hostClient := hosts.NewClient(client) - - existed := true - var result api.GenericResult - var listResult api.GenericListResult - - switch c.Func { - case "read": - result, err = hostClient.Read(c.Context, c.FlagId, opts...) - case "delete": - _, err = hostClient.Delete(c.Context, c.FlagId, opts...) - if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { - existed = false - err = nil - } - case "list": - listResult, err = hostClient.List(c.Context, c.FlagHostCatalogId, opts...) - } - - plural := "host" - if c.Func == "list" { - plural = "hosts" - } - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - switch c.Func { - case "delete": - switch base.Format(c.UI) { - case "json": - c.UI.Output("null") - case "table": - output := "The delete operation completed successfully" - switch existed { - case true: - output += "." - default: - output += ", however the resource did not exist at the time." - } - c.UI.Output(output) - } - return 0 - - case "list": - listedHosts := listResult.GetItems().([]*hosts.Host) - switch base.Format(c.UI) { - case "json": - if len(listedHosts) == 0 { - c.UI.Output("null") - return 0 - } - b, err := base.JsonFormatter{}.Format(listedHosts) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - - case "table": - if len(listedHosts) == 0 { - c.UI.Output("No hosts found") - return 0 - } - var output []string - output = []string{ - "", - "Host information:", - } - for i, m := range listedHosts { - if i > 0 { - output = append(output, "") - } - if true { - output = append(output, - fmt.Sprintf(" ID: %s", m.Id), - fmt.Sprintf(" Version: %d", m.Version), - fmt.Sprintf(" Type: %s", m.Type), - ) - } - if m.Name != "" { - output = append(output, - fmt.Sprintf(" Name: %s", m.Name), - ) - } - if m.Description != "" { - output = append(output, - fmt.Sprintf(" Description: %s", m.Description), - ) - } - if len(m.AuthorizedActions) > 0 { - output = append(output, - " Authorized Actions:", - base.WrapSlice(6, m.AuthorizedActions), - ) - } - } - c.UI.Output(base.WrapForHelpText(output)) - } - return 0 - } - - host := result.GetItem().(*hosts.Host) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateHostTableOutput(host)) - case "json": - b, err := base.JsonFormatter{}.Format(host) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/hosts/static.go b/internal/cmd/commands/hosts/static.go deleted file mode 100644 index 6a8cd18e3..000000000 --- a/internal/cmd/commands/hosts/static.go +++ /dev/null @@ -1,201 +0,0 @@ -package hosts - -import ( - "fmt" - "net/textproto" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/hosts" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*StaticCommand)(nil) - _ cli.CommandAutocomplete = (*StaticCommand)(nil) -) - -type StaticCommand struct { - *base.Command - - Func string - - flagAddress string -} - -func (c *StaticCommand) Synopsis() string { - return fmt.Sprintf("%s a static-type host", textproto.CanonicalMIMEHeaderKey(c.Func)) -} - -var staticFlagsMap = map[string][]string{ - "create": {"host-catalog-id", "name", "description", "address"}, - "update": {"id", "name", "description", "version", "address"}, -} - -func (c *StaticCommand) Help() string { - var info string - switch c.Func { - case "create": - info = base.WrapForHelpText([]string{ - "Usage: boundary hosts static create [options] [args]", - "", - " Create a static-type host. Example:", - "", - ` $ boundary hosts static create -name prodops -description "Static host for ProdOps" -address "127.0.0.1"`, - "", - "", - }) - - case "update": - info = base.WrapForHelpText([]string{ - "Usage: boundary hosts update static [options] [args]", - "", - " Update a static-type host given its ID. Example:", - "", - ` $ boundary hosts update static -id hst_1234567890 -name "devops" -description "Static host for DevOps" -address "10.20.30.40"`, - "", - "", - }) - } - return info + c.Flags().Help() -} - -func (c *StaticCommand) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "static-type host", staticFlagsMap[c.Func]) - - f = set.NewFlagSet("Static Host Options") - - for _, name := range staticFlagsMap[c.Func] { - switch name { - case "address": - f.StringVar(&base.StringVar{ - Name: "address", - Target: &c.flagAddress, - Usage: "The address of the host", - }) - } - } - - return set -} - -func (c *StaticCommand) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *StaticCommand) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *StaticCommand) Run(args []string) int { - if c.Func == "" { - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(staticFlagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(staticFlagsMap[c.Func], "host-catalog-id") && c.FlagHostCatalogId == "" { - c.UI.Error("Host Catalog ID must be passed in via -host-catalog-id") - return 1 - } - if c.Func == "create" && c.flagAddress == "" { - c.UI.Error("Address must be passed in via -address") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []hosts.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, hosts.DefaultName()) - default: - opts = append(opts, hosts.WithName(c.FlagName)) - } - - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, hosts.DefaultDescription()) - default: - opts = append(opts, hosts.WithDescription(c.FlagDescription)) - } - - switch c.flagAddress { - case "": - case "null": - opts = append(opts, hosts.DefaultStaticHostAddress()) - default: - opts = append(opts, hosts.WithStaticHostAddress(c.flagAddress)) - } - - hostClient := hosts.NewClient(client) - - // Perform check-and-set when needed - var version uint32 - switch c.Func { - case "create": - // These don't update so don't need the existing version - default: - switch c.FlagVersion { - case 0: - opts = append(opts, hosts.WithAutomaticVersioning(true)) - default: - version = uint32(c.FlagVersion) - } - } - - var result api.GenericResult - - switch c.Func { - case "create": - result, err = hostClient.Create(c.Context, c.FlagHostCatalogId, opts...) - case "update": - result, err = hostClient.Update(c.Context, c.FlagId, version, opts...) - } - - plural := "static-type host" - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - host := result.GetItem().(*hosts.Host) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateHostTableOutput(host)) - case "json": - b, err := base.JsonFormatter{}.Format(host) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/hostscmd/funcs.go b/internal/cmd/commands/hostscmd/funcs.go new file mode 100644 index 000000000..313267c3a --- /dev/null +++ b/internal/cmd/commands/hostscmd/funcs.go @@ -0,0 +1,153 @@ +package hostscmd + +import ( + "fmt" + "time" + + "github.com/hashicorp/boundary/api/hosts" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "": + return base.WrapForHelpText([]string{ + "Usage: boundary hosts [sub command] [options] [args]", + "", + " This command allows operations on Boundary host resources. Example:", + "", + " Read a host:", + "", + ` $ boundary hosts read -id hst_1234567890`, + "", + " Please see the hosts subcommand help for detailed usage information.", + }) + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary hosts create [type] [sub command] [options] [args]", + "", + " This command allows create operations on Boundary host resources. Example:", + "", + " Create a static-type host:", + "", + ` $ boundary hosts create static -name prodops -description "For ProdOps usage"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary hosts update [type] [sub command] [options] [args]", + "", + " This command allows update operations on Boundary host resources. Example:", + "", + " Update a static-type host:", + "", + ` $ boundary hosts update static -id hst_1234567890 -name devops -description "For DevOps usage"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + default: + helpStr = helpMap[c.Func]() + } + return helpStr + c.Flags().Help() +} + +func (c *Command) printListTable(items []*hosts.Host) string { + if len(items) == 0 { + return "No host sets found" + } + + var output []string + output = []string{ + "", + "Host information:", + } + for i, item := range items { + if i > 0 { + output = append(output, "") + } + if true { + output = append(output, + fmt.Sprintf(" ID: %s", item.Id), + fmt.Sprintf(" Version: %d", item.Version), + fmt.Sprintf(" Type: %s", item.Type), + ) + } + if item.Name != "" { + output = append(output, + fmt.Sprintf(" Name: %s", item.Name), + ) + } + if item.Description != "" { + output = append(output, + fmt.Sprintf(" Description: %s", item.Description), + ) + } + if len(item.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, item.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(in *hosts.Host) string { + nonAttributeMap := map[string]interface{}{ + "ID": in.Id, + "Version": in.Version, + "Type": in.Type, + "Created Time": in.CreatedTime.Local().Format(time.RFC1123), + "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), + "Host Catalog ID": in.HostCatalogId, + } + + if in.Name != "" { + nonAttributeMap["Name"] = in.Name + } + if in.Description != "" { + nonAttributeMap["Description"] = in.Description + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, in.Attributes, keySubstMap) + + ret := []string{ + "", + "Host information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + "", + " Scope:", + base.ScopeInfoForOutput(in.Scope, maxLength), + } + + if len(in.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, in.AuthorizedActions), + ) + } + + if len(in.HostSetIds) > 0 { + ret = append(ret, + "", + " Host Set IDs:", + base.WrapSlice(4, in.HostSetIds), + ) + } + + if len(in.Attributes) > 0 { + ret = append(ret, + "", + " Attributes:", + base.WrapMap(4, maxLength, in.Attributes), + ) + } + + return base.WrapForHelpText(ret) +} + +var keySubstMap = map[string]string{} diff --git a/internal/cmd/commands/hostscmd/hosts.gen.go b/internal/cmd/commands/hostscmd/hosts.gen.go new file mode 100644 index 000000000..04270b7b5 --- /dev/null +++ b/internal/cmd/commands/hostscmd/hosts.gen.go @@ -0,0 +1,299 @@ +// Code generated by "make api"; DO NOT EDIT. +package hostscmd + +import ( + "fmt" + "net/http" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/hosts" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "host" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("host") + + switch c.Func { + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "delete": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = c.extraHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "read": {"id"}, + + "delete": {"id"}, + + "list": {"host-catalog-id"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "host", flagsMap[c.Func]) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + + case "create", "update": + return cli.RunResultHelp + + } + + c.plural = "host" + switch c.Func { + case "list": + c.plural = "hosts" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []hosts.Option + + if strutil.StrListContains(flagsMap[c.Func], "host-catalog-id") { + switch c.Func { + case "list": + if c.FlagHostCatalogId == "" { + c.UI.Error("HostCatalog ID must be passed in via -host-catalog-id or BOUNDARY_HOST_CATALOG_ID") + return 1 + } + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + hostsClient := hosts.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, hosts.DefaultName()) + default: + opts = append(opts, hosts.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, hosts.DefaultDescription()) + default: + opts = append(opts, hosts.WithDescription(c.FlagDescription)) + } + + var version uint32 + + if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + existed := true + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "read": + result, err = hostsClient.Read(c.Context, c.FlagId, opts...) + + case "delete": + _, err = hostsClient.Delete(c.Context, c.FlagId, opts...) + if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { + existed = false + err = nil + } + + case "list": + listResult, err = hostsClient.List(c.Context, c.FlagHostCatalogId, opts...) + + } + + result, err = executeExtraActions(c, result, err, hostsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + c.UI.Output(fmt.Sprintf("{ \"existed\": %t }", existed)) + + case "table": + output := "The delete operation completed successfully" + switch existed { + case true: + output += "." + default: + output += ", however the resource did not exist at the time." + } + c.UI.Output(output) + } + + return 0 + + case "list": + listedItems := listResult.GetItems().([]*hosts.Host) + switch base.Format(c.UI) { + case "json": + switch { + + case len(listedItems) == 0: + c.UI.Output("null") + + default: + b, err := base.JsonFormatter{}.Format(listedItems) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + case "table": + c.UI.Output(c.printListTable(listedItems)) + } + + return 0 + + } + + item := result.GetItem().(*hosts.Host) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *[]hosts.Option) int { return 0 } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *hosts.Client, _ uint32, _ []hosts.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/hostscmd/static_funcs.go b/internal/cmd/commands/hostscmd/static_funcs.go new file mode 100644 index 000000000..028a9d711 --- /dev/null +++ b/internal/cmd/commands/hostscmd/static_funcs.go @@ -0,0 +1,86 @@ +package hostscmd + +import ( + "github.com/hashicorp/boundary/api/hosts" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func init() { + extraStaticActionsFlagsMapFunc = extraStaticActionsFlagsMapFuncImpl + extraStaticFlagsFunc = extraStaticFlagsFuncImpl + extraStaticFlagsHandlingFunc = extraStaticFlagsHandlingFuncImpl +} + +type extraStaticCmdVars struct { + flagAddress string +} + +func extraStaticActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "create": {"address"}, + "update": {"address"}, + } +} + +func (c *StaticCommand) extraStaticHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary hosts static create [options] [args]", + "", + " Create a static-type host. Example:", + "", + ` $ boundary hosts static create -name prodops -description "Static host for ProdOps" -address "127.0.0.1"`, + "", + "", + }) + + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary hosts update static [options] [args]", + "", + " Update a static-type host given its ID. Example:", + "", + ` $ boundary hosts update static -id hst_1234567890 -name "devops" -description "Static host for DevOps" -address "10.20.30.40"`, + "", + "", + }) + default: + helpStr = helpMap[c.Func]() + } + + return helpStr + c.Flags().Help() +} + +func extraStaticFlagsFuncImpl(c *StaticCommand, set *base.FlagSets, _ *base.FlagSet) { + f := set.NewFlagSet("Static Host Options") + + for _, name := range flagsStaticMap[c.Func] { + switch name { + case "address": + f.StringVar(&base.StringVar{ + Name: "address", + Target: &c.flagAddress, + Usage: "The address of the host", + }) + } + } +} + +func extraStaticFlagsHandlingFuncImpl(c *StaticCommand, opts *[]hosts.Option) int { + if c.Func == "create" && c.flagAddress == "" { + c.UI.Error("Address must be provided via -address") + return 1 + } + + switch c.flagAddress { + case "": + case "null": + *opts = append(*opts, hosts.DefaultStaticHostAddress()) + default: + *opts = append(*opts, hosts.WithStaticHostAddress(c.flagAddress)) + } + + return 0 +} diff --git a/internal/cmd/commands/hostscmd/static_hosts.gen.go b/internal/cmd/commands/hostscmd/static_hosts.gen.go new file mode 100644 index 000000000..27d78a1f0 --- /dev/null +++ b/internal/cmd/commands/hostscmd/static_hosts.gen.go @@ -0,0 +1,238 @@ +// Code generated by "make api"; DO NOT EDIT. +package hostscmd + +import ( + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/hosts" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initStaticFlags() { + flagsOnce.Do(func() { + extraFlags := extraStaticActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsStaticMap[k] = append(flagsStaticMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*StaticCommand)(nil) + _ cli.CommandAutocomplete = (*StaticCommand)(nil) +) + +type StaticCommand struct { + *base.Command + + Func string + + plural string + + extraStaticCmdVars +} + +func (c *StaticCommand) AutocompleteArgs() complete.Predictor { + initStaticFlags() + return complete.PredictAnything +} + +func (c *StaticCommand) AutocompleteFlags() complete.Flags { + initStaticFlags() + return c.Flags().Completions() +} + +func (c *StaticCommand) Synopsis() string { + if extra := extraStaticSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "host" + + synopsisStr = fmt.Sprintf("%s %s", "static-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *StaticCommand) Help() string { + initStaticFlags() + + var helpStr string + helpMap := common.HelpMap("host") + + switch c.Func { + default: + + helpStr = c.extraStaticHelpFunc(helpMap) + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsStaticMap = map[string][]string{ + + "create": {"host-catalog-id", "name", "description"}, + + "update": {"id", "name", "description", "version"}, +} + +func (c *StaticCommand) Flags() *base.FlagSets { + if len(flagsStaticMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "static-type host", flagsStaticMap[c.Func]) + + extraStaticFlagsFunc(c, set, f) + + return set +} + +func (c *StaticCommand) Run(args []string) int { + initStaticFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "static-type host" + switch c.Func { + case "list": + c.plural = "static-type hosts" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsStaticMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []hosts.Option + + if strutil.StrListContains(flagsStaticMap[c.Func], "host-catalog-id") { + switch c.Func { + case "create": + if c.FlagHostCatalogId == "" { + c.UI.Error("HostCatalog ID must be passed in via -host-catalog-id or BOUNDARY_HOST_CATALOG_ID") + return 1 + } + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + hostsClient := hosts.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, hosts.DefaultName()) + default: + opts = append(opts, hosts.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, hosts.DefaultDescription()) + default: + opts = append(opts, hosts.WithDescription(c.FlagDescription)) + } + + var version uint32 + + switch c.Func { + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, hosts.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } + + if ret := extraStaticFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + var result api.GenericResult + + switch c.Func { + + case "create": + result, err = hostsClient.Create(c.Context, c.FlagHostCatalogId, opts...) + + case "update": + result, err = hostsClient.Update(c.Context, c.FlagId, version, opts...) + + } + + result, err = executeExtraStaticActions(c, result, err, hostsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomStaticActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + } + + item := result.GetItem().(*hosts.Host) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + extraStaticActionsFlagsMapFunc = func() map[string][]string { return nil } + extraStaticSynopsisFunc = func(*StaticCommand) string { return "" } + extraStaticFlagsFunc = func(*StaticCommand, *base.FlagSets, *base.FlagSet) {} + extraStaticFlagsHandlingFunc = func(*StaticCommand, *[]hosts.Option) int { return 0 } + executeExtraStaticActions = func(_ *StaticCommand, inResult api.GenericResult, inErr error, _ *hosts.Client, _ uint32, _ []hosts.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomStaticActionOutput = func(*StaticCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/hostsets/funcs.go b/internal/cmd/commands/hostsets/funcs.go deleted file mode 100644 index 4d9577a54..000000000 --- a/internal/cmd/commands/hostsets/funcs.go +++ /dev/null @@ -1,65 +0,0 @@ -package hostsets - -import ( - "time" - - "github.com/hashicorp/boundary/api/hostsets" - "github.com/hashicorp/boundary/internal/cmd/base" -) - -func generateHostSetTableOutput(in *hostsets.HostSet) string { - nonAttributeMap := map[string]interface{}{ - "ID": in.Id, - "Version": in.Version, - "Type": in.Type, - "Created Time": in.CreatedTime.Local().Format(time.RFC1123), - "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), - "Host Catalog ID": in.HostCatalogId, - } - - if in.Name != "" { - nonAttributeMap["Name"] = in.Name - } - if in.Description != "" { - nonAttributeMap["Description"] = in.Description - } - - maxLength := base.MaxAttributesLength(nonAttributeMap, in.Attributes, keySubstMap) - - ret := []string{ - "", - "Host Set information:", - base.WrapMap(2, maxLength+2, nonAttributeMap), - "", - " Scope:", - base.ScopeInfoForOutput(in.Scope, maxLength), - } - - if len(in.AuthorizedActions) > 0 { - ret = append(ret, - "", - " Authorized Actions:", - base.WrapSlice(4, in.AuthorizedActions), - ) - } - - if len(in.HostIds) > 0 { - ret = append(ret, - "", - " Host IDs:", - base.WrapSlice(4, in.HostIds), - ) - } - - if len(in.Attributes) > 0 { - ret = append(ret, - "", - " Attributes:", - base.WrapMap(4, maxLength, in.Attributes), - ) - } - - return base.WrapForHelpText(ret) -} - -var keySubstMap = map[string]string{} diff --git a/internal/cmd/commands/hostsets/hostset.go b/internal/cmd/commands/hostsets/hostset.go deleted file mode 100644 index 74cb249b5..000000000 --- a/internal/cmd/commands/hostsets/hostset.go +++ /dev/null @@ -1,366 +0,0 @@ -package hostsets - -import ( - "fmt" - "net/http" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/hostsets" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/internal/types/resource" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*Command)(nil) - _ cli.CommandAutocomplete = (*Command)(nil) -) - -type Command struct { - *base.Command - - Func string - - flagHosts []string -} - -func (c *Command) Synopsis() string { - switch c.Func { - case "add-hosts": - return "Add hosts to the specified host set" - case "remove-hosts": - return "Remove hosts from the specified host set" - case "set-hosts": - return "Set the full contents of the hosts on the specified host set" - default: - return common.SynopsisFunc(c.Func, "host set") - } -} - -var flagsMap = map[string][]string{ - "read": {"id"}, - "delete": {"id"}, - "list": {"host-catalog-id"}, - "add-hosts": {"id", "host", "version"}, - "set-hosts": {"id", "host", "version"}, - "remove-hosts": {"id", "host", "version"}, -} - -func (c *Command) Help() string { - helpMap := common.HelpMap("host set") - var helpStr string - switch c.Func { - case "": - return base.WrapForHelpText([]string{ - "Usage: boundary host-sets [sub command] [options] [args]", - "", - " This command allows operations on Boundary host set resources. Example:", - "", - " Read a host set:", - "", - ` $ boundary host-sets read -id hsst_1234567890`, - "", - " Please see the host-sets subcommand help for detailed usage information.", - }) - case "create": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary host-sets create [type] [sub command] [options] [args]", - "", - " This command allows create operations on Boundary host set resources. Example:", - "", - " Create a static-type host set:", - "", - ` $ boundary host-sets create static -name prodops -description "For ProdOps usage"`, - "", - " Please see the typed subcommand help for detailed usage information.", - }) - case "update": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary host-sets update [type] [sub command] [options] [args]", - "", - " This command allows update operations on Boundary host set resources. Example:", - "", - " Update a static-type host set:", - "", - ` $ boundary host-sets update static -id hsst_1234567890 -name devops -description "For DevOps usage"`, - "", - " Please see the typed subcommand help for detailed usage information.", - }) - case "add-hosts": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary host-sets add-hosts [sub command] [options] [args]", - "", - " This command allows adding hosts to host set resources, if the types match and the operation is allowed by the given host set type. Example:", - "", - " Add static-type hosts to a static-type host set:", - "", - ` $ boundary host-sets add-hosts -id hsst_1234567890 -host hst_1234567890 -host hst_0987654321`, - }) - case "remove-hosts": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary host-sets remove-hosts [sub command] [options] [args]", - "", - " This command allows removing hosts from host set resources, if the types match and the operation is allowed by the given host set type. Example:", - "", - " Remove static-type hosts from a static-type host set:", - "", - ` $ boundary host-sets remove-hosts -id hsst_1234567890 -host hst_0987654321`, - }) - case "set-hosts": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary host-sets set-hosts [sub command] [options] [args]", - "", - " This command allows setting the complete set of hosts on host set resources, if the types match and the operation is allowed by the given host set type. Example:", - "", - " Set the complete set of static-type hosts on a static-type host set:", - "", - ` $ boundary host-sets remove-hosts -id hsst_1234567890 -host hst_1234567890`, - }) - default: - helpStr = helpMap[c.Func]() - } - return helpStr + c.Flags().Help() -} - -func (c *Command) Flags() *base.FlagSets { - if len(flagsMap[c.Func]) == 0 { - return c.FlagSet(base.FlagSetNone) - } - - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, resource.HostSet.String(), flagsMap[c.Func]) - - for _, name := range flagsMap[c.Func] { - switch name { - case "host": - f.StringSliceVar(&base.StringSliceVar{ - Name: "host", - Target: &c.flagHosts, - Usage: "The hosts to add, remove, or set. May be specified multiple times.", - }) - } - } - - return set -} - -func (c *Command) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *Command) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *Command) Run(args []string) int { - switch c.Func { - case "", "create", "update": - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(flagsMap[c.Func], "host-catalog-id") && c.FlagHostCatalogId == "" { - c.UI.Error("Host Catalog ID must be passed in via -host-catalog-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []hostsets.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, hostsets.DefaultName()) - default: - opts = append(opts, hostsets.WithName(c.FlagName)) - } - - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, hostsets.DefaultDescription()) - default: - opts = append(opts, hostsets.WithDescription(c.FlagDescription)) - } - - hosts := c.flagHosts - switch c.Func { - case "add-hosts", "remove-hosts": - if len(c.flagHosts) == 0 { - c.UI.Error("No hosts supplied via -host") - return 1 - } - - case "set-hosts": - switch len(c.flagHosts) { - case 0: - c.UI.Error("No hosts supplied via -host") - return 1 - case 1: - if c.flagHosts[0] == "null" { - hosts = nil - } - } - } - - // Perform check-and-set when needed - var version uint32 - switch c.Func { - case "add-hosts", "remove-hosts", "set-hosts": - switch c.FlagVersion { - case 0: - opts = append(opts, hostsets.WithAutomaticVersioning(true)) - default: - version = uint32(c.FlagVersion) - } - default: - // The only other one that needs it is update, handled by the static - // file - } - - hostsetClient := hostsets.NewClient(client) - - existed := true - var result api.GenericResult - var listResult api.GenericListResult - - switch c.Func { - case "read": - result, err = hostsetClient.Read(c.Context, c.FlagId, opts...) - case "delete": - _, err = hostsetClient.Delete(c.Context, c.FlagId, opts...) - if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { - existed = false - err = nil - } - case "list": - listResult, err = hostsetClient.List(c.Context, c.FlagHostCatalogId, opts...) - case "add-hosts": - result, err = hostsetClient.AddHosts(c.Context, c.FlagId, version, hosts, opts...) - case "remove-hosts": - result, err = hostsetClient.RemoveHosts(c.Context, c.FlagId, version, hosts, opts...) - case "set-hosts": - result, err = hostsetClient.SetHosts(c.Context, c.FlagId, version, hosts, opts...) - } - - plural := "host set" - if c.Func == "list" { - plural = "host sets" - } - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - switch c.Func { - case "delete": - switch base.Format(c.UI) { - case "json": - c.UI.Output("null") - case "table": - output := "The delete operation completed successfully" - switch existed { - case true: - output += "." - default: - output += ", however the resource did not exist at the time." - } - c.UI.Output(output) - } - return 0 - - case "list": - listedSets := listResult.GetItems().([]*hostsets.HostSet) - switch base.Format(c.UI) { - case "json": - if len(listedSets) == 0 { - c.UI.Output("null") - return 0 - } - b, err := base.JsonFormatter{}.Format(listedSets) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - - case "table": - if len(listedSets) == 0 { - c.UI.Output("No host sets found") - return 0 - } - var output []string - output = []string{ - "", - "Host Set information:", - } - for i, m := range listedSets { - if i > 0 { - output = append(output, "") - } - if true { - output = append(output, - fmt.Sprintf(" ID: %s", m.Id), - fmt.Sprintf(" Version: %d", m.Version), - fmt.Sprintf(" Type: %s", m.Type), - ) - } - if m.Name != "" { - output = append(output, - fmt.Sprintf(" Name: %s", m.Name), - ) - } - if m.Description != "" { - output = append(output, - fmt.Sprintf(" Description: %s", m.Description), - ) - } - if len(m.AuthorizedActions) > 0 { - output = append(output, - " Authorized Actions:", - base.WrapSlice(6, m.AuthorizedActions), - ) - } - } - c.UI.Output(base.WrapForHelpText(output)) - } - return 0 - } - - set := result.GetItem().(*hostsets.HostSet) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateHostSetTableOutput(set)) - case "json": - b, err := base.JsonFormatter{}.Format(set) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/hostsets/static.go b/internal/cmd/commands/hostsets/static.go deleted file mode 100644 index 2cb88710c..000000000 --- a/internal/cmd/commands/hostsets/static.go +++ /dev/null @@ -1,174 +0,0 @@ -package hostsets - -import ( - "fmt" - "net/textproto" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/hostsets" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*StaticCommand)(nil) - _ cli.CommandAutocomplete = (*StaticCommand)(nil) -) - -type StaticCommand struct { - *base.Command - - Func string -} - -func (c *StaticCommand) Synopsis() string { - return fmt.Sprintf("%s a static-type host set", textproto.CanonicalMIMEHeaderKey(c.Func)) -} - -var staticFlagsMap = map[string][]string{ - "create": {"host-catalog-id", "name", "description"}, - "update": {"id", "name", "description", "version"}, -} - -func (c *StaticCommand) Help() string { - var info string - switch c.Func { - case "create": - info = base.WrapForHelpText([]string{ - "Usage: boundary host-sets create static [options] [args]", - "", - " Create a static-type host set. Example:", - "", - ` $ boundary host-sets create static -name prodops -description "Static host-set for ProdOps"`, - "", - "", - }) - - case "update": - info = base.WrapForHelpText([]string{ - "Usage: boundary host-sets update static [options] [args]", - "", - " Update a static-type host set given its ID. Example:", - "", - ` $ boundary host-sets update static -id hsst_1234567890 -name "devops" -description "Static host-set for DevOps"`, - "", - "", - }) - } - return info + c.Flags().Help() -} - -func (c *StaticCommand) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "static-type host set", staticFlagsMap[c.Func]) - - return set -} - -func (c *StaticCommand) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *StaticCommand) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *StaticCommand) Run(args []string) int { - if c.Func == "" { - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(staticFlagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(staticFlagsMap[c.Func], "host-catalog-id") && c.FlagHostCatalogId == "" { - c.UI.Error("Host Catalog ID must be passed in via -host-catalog-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []hostsets.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, hostsets.DefaultName()) - default: - opts = append(opts, hostsets.WithName(c.FlagName)) - } - - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, hostsets.DefaultDescription()) - default: - opts = append(opts, hostsets.WithDescription(c.FlagDescription)) - } - - hostsetClient := hostsets.NewClient(client) - - // Perform check-and-set when needed - var version uint32 - switch c.Func { - case "create": - // These don't update so don't need the existing version - default: - switch c.FlagVersion { - case 0: - opts = append(opts, hostsets.WithAutomaticVersioning(true)) - default: - version = uint32(c.FlagVersion) - } - } - - var result api.GenericResult - - switch c.Func { - case "create": - result, err = hostsetClient.Create(c.Context, c.FlagHostCatalogId, opts...) - case "update": - result, err = hostsetClient.Update(c.Context, c.FlagId, version, opts...) - } - - plural := "static-type host-set" - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - set := result.GetItem().(*hostsets.HostSet) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateHostSetTableOutput(set)) - case "json": - b, err := base.JsonFormatter{}.Format(set) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/hostsetscmd/funcs.go b/internal/cmd/commands/hostsetscmd/funcs.go new file mode 100644 index 000000000..39538659b --- /dev/null +++ b/internal/cmd/commands/hostsetscmd/funcs.go @@ -0,0 +1,272 @@ +package hostsetscmd + +import ( + "fmt" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/hostsets" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" +) + +func init() { + extraActionsFlagsMapFunc = extraActionsFlagsMapFuncImpl + extraSynopsisFunc = extraSynopsisFuncImpl + extraFlagsFunc = extraFlagsFuncImpl + extraFlagsHandlingFunc = extraFlagsHandlingFuncImpl + executeExtraActions = executeExtraActionsImpl +} + +type extraCmdVars struct { + flagHosts []string +} + +func extraActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "add-hosts": {"id", "host", "version"}, + "set-hosts": {"id", "host", "version"}, + "remove-hosts": {"id", "host", "version"}, + } +} + +func extraSynopsisFuncImpl(c *Command) string { + switch c.Func { + case "add-hosts": + return "Add hosts to the specified host set" + case "remove-hosts": + return "Remove hosts from the specified host set" + case "set-hosts": + return "Set the full contents of the hosts on the specified host set" + default: + return common.SynopsisFunc(c.Func, "host set") + } +} + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "": + return base.WrapForHelpText([]string{ + "Usage: boundary host-sets [sub command] [options] [args]", + "", + " This command allows operations on Boundary host set resources. Example:", + "", + " Read a host set:", + "", + ` $ boundary host-sets read -id hsst_1234567890`, + "", + " Please see the host-sets subcommand help for detailed usage information.", + }) + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-sets create [type] [sub command] [options] [args]", + "", + " This command allows create operations on Boundary host set resources. Example:", + "", + " Create a static-type host set:", + "", + ` $ boundary host-sets create static -name prodops -description "For ProdOps usage"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-sets update [type] [sub command] [options] [args]", + "", + " This command allows update operations on Boundary host set resources. Example:", + "", + " Update a static-type host set:", + "", + ` $ boundary host-sets update static -id hsst_1234567890 -name devops -description "For DevOps usage"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + case "add-hosts": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-sets add-hosts [sub command] [options] [args]", + "", + " This command allows adding hosts to host set resources, if the types match and the operation is allowed by the given host set type. Example:", + "", + " Add static-type hosts to a static-type host set:", + "", + ` $ boundary host-sets add-hosts -id hsst_1234567890 -host hst_1234567890 -host hst_0987654321`, + "", + "", + }) + case "remove-hosts": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-sets remove-hosts [sub command] [options] [args]", + "", + " This command allows removing hosts from host set resources, if the types match and the operation is allowed by the given host set type. Example:", + "", + " Remove static-type hosts from a static-type host set:", + "", + ` $ boundary host-sets remove-hosts -id hsst_1234567890 -host hst_0987654321`, + "", + "", + }) + case "set-hosts": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-sets set-hosts [sub command] [options] [args]", + "", + " This command allows setting the complete set of hosts on host set resources, if the types match and the operation is allowed by the given host set type. Example:", + "", + " Set the complete set of static-type hosts on a static-type host set:", + "", + ` $ boundary host-sets remove-hosts -id hsst_1234567890 -host hst_1234567890`, + "", + "", + }) + default: + helpStr = helpMap[c.Func]() + } + return helpStr + c.Flags().Help() +} + +func extraFlagsFuncImpl(c *Command, _ *base.FlagSets, f *base.FlagSet) { + for _, name := range flagsMap[c.Func] { + switch name { + case "host": + f.StringSliceVar(&base.StringSliceVar{ + Name: "host", + Target: &c.flagHosts, + Usage: "The hosts to add, remove, or set. May be specified multiple times.", + }) + } + } +} + +func extraFlagsHandlingFuncImpl(c *Command, opts *[]hostsets.Option) int { + switch c.Func { + case "add-hosts", "remove-hosts": + if len(c.flagHosts) == 0 { + c.UI.Error("No hosts supplied via -host") + return 1 + } + + case "set-hosts": + switch len(c.flagHosts) { + case 0: + c.UI.Error("No hosts supplied via -host") + return 1 + case 1: + if c.flagHosts[0] == "null" { + c.flagHosts = nil + } + } + } + + return 0 +} + +func executeExtraActionsImpl(c *Command, origResult api.GenericResult, origError error, hostsetClient *hostsets.Client, version uint32, opts []hostsets.Option) (api.GenericResult, error) { + switch c.Func { + case "add-hosts": + return hostsetClient.AddHosts(c.Context, c.FlagId, version, c.flagHosts, opts...) + case "remove-hosts": + return hostsetClient.RemoveHosts(c.Context, c.FlagId, version, c.flagHosts, opts...) + case "set-hosts": + return hostsetClient.SetHosts(c.Context, c.FlagId, version, c.flagHosts, opts...) + } + return origResult, origError +} + +func (c *Command) printListTable(items []*hostsets.HostSet) string { + if len(items) == 0 { + return "No host sets found" + } + + var output []string + output = []string{ + "", + "Host Set information:", + } + for i, m := range items { + if i > 0 { + output = append(output, "") + } + if true { + output = append(output, + fmt.Sprintf(" ID: %s", m.Id), + fmt.Sprintf(" Version: %d", m.Version), + fmt.Sprintf(" Type: %s", m.Type), + ) + } + if m.Name != "" { + output = append(output, + fmt.Sprintf(" Name: %s", m.Name), + ) + } + if m.Description != "" { + output = append(output, + fmt.Sprintf(" Description: %s", m.Description), + ) + } + if len(m.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, m.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(in *hostsets.HostSet) string { + nonAttributeMap := map[string]interface{}{ + "ID": in.Id, + "Version": in.Version, + "Type": in.Type, + "Created Time": in.CreatedTime.Local().Format(time.RFC1123), + "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), + "Host Catalog ID": in.HostCatalogId, + } + + if in.Name != "" { + nonAttributeMap["Name"] = in.Name + } + if in.Description != "" { + nonAttributeMap["Description"] = in.Description + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, in.Attributes, keySubstMap) + + ret := []string{ + "", + "Host Set information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + "", + " Scope:", + base.ScopeInfoForOutput(in.Scope, maxLength), + } + + if len(in.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, in.AuthorizedActions), + ) + } + + if len(in.HostIds) > 0 { + ret = append(ret, + "", + " Host IDs:", + base.WrapSlice(4, in.HostIds), + ) + } + + if len(in.Attributes) > 0 { + ret = append(ret, + "", + " Attributes:", + base.WrapMap(4, maxLength, in.Attributes), + ) + } + + return base.WrapForHelpText(ret) +} + +var keySubstMap = map[string]string{} diff --git a/internal/cmd/commands/hostsetscmd/hostsets.gen.go b/internal/cmd/commands/hostsetscmd/hostsets.gen.go new file mode 100644 index 000000000..5ca359be9 --- /dev/null +++ b/internal/cmd/commands/hostsetscmd/hostsets.gen.go @@ -0,0 +1,329 @@ +// Code generated by "make api"; DO NOT EDIT. +package hostsetscmd + +import ( + "fmt" + "net/http" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/hostsets" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string + + extraCmdVars +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "host set" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("host set") + + switch c.Func { + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "delete": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = c.extraHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "read": {"id"}, + + "delete": {"id"}, + + "list": {"host-catalog-id"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "host set", flagsMap[c.Func]) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + + case "create", "update": + return cli.RunResultHelp + + } + + c.plural = "host set" + switch c.Func { + case "list": + c.plural = "host sets" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []hostsets.Option + + if strutil.StrListContains(flagsMap[c.Func], "host-catalog-id") { + switch c.Func { + case "list": + if c.FlagHostCatalogId == "" { + c.UI.Error("HostCatalog ID must be passed in via -host-catalog-id or BOUNDARY_HOST_CATALOG_ID") + return 1 + } + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + hostsetsClient := hostsets.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, hostsets.DefaultName()) + default: + opts = append(opts, hostsets.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, hostsets.DefaultDescription()) + default: + opts = append(opts, hostsets.WithDescription(c.FlagDescription)) + } + + var version uint32 + + switch c.Func { + + case "add-hosts": + switch c.FlagVersion { + case 0: + opts = append(opts, hostsets.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "set-hosts": + switch c.FlagVersion { + case 0: + opts = append(opts, hostsets.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "remove-hosts": + switch c.FlagVersion { + case 0: + opts = append(opts, hostsets.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + } + + if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + existed := true + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "read": + result, err = hostsetsClient.Read(c.Context, c.FlagId, opts...) + + case "delete": + _, err = hostsetsClient.Delete(c.Context, c.FlagId, opts...) + if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { + existed = false + err = nil + } + + case "list": + listResult, err = hostsetsClient.List(c.Context, c.FlagHostCatalogId, opts...) + + } + + result, err = executeExtraActions(c, result, err, hostsetsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + c.UI.Output(fmt.Sprintf("{ \"existed\": %t }", existed)) + + case "table": + output := "The delete operation completed successfully" + switch existed { + case true: + output += "." + default: + output += ", however the resource did not exist at the time." + } + c.UI.Output(output) + } + + return 0 + + case "list": + listedItems := listResult.GetItems().([]*hostsets.HostSet) + switch base.Format(c.UI) { + case "json": + switch { + + case len(listedItems) == 0: + c.UI.Output("null") + + default: + b, err := base.JsonFormatter{}.Format(listedItems) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + case "table": + c.UI.Output(c.printListTable(listedItems)) + } + + return 0 + + } + + item := result.GetItem().(*hostsets.HostSet) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *[]hostsets.Option) int { return 0 } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *hostsets.Client, _ uint32, _ []hostsets.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/hostsetscmd/static_funcs.go b/internal/cmd/commands/hostsetscmd/static_funcs.go new file mode 100644 index 000000000..95c98a197 --- /dev/null +++ b/internal/cmd/commands/hostsetscmd/static_funcs.go @@ -0,0 +1,35 @@ +package hostsetscmd + +import ( + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func (c *StaticCommand) extraStaticHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-sets create static [options] [args]", + "", + " Create a static-type host set. Example:", + "", + ` $ boundary host-sets create static -name prodops -description "Static host-set for ProdOps"`, + "", + "", + }) + + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary host-sets update static [options] [args]", + "", + " Update a static-type host set given its ID. Example:", + "", + ` $ boundary host-sets update static -id hsst_1234567890 -name "devops" -description "Static host-set for DevOps"`, + "", + "", + }) + default: + helpStr = helpMap[c.Func]() + } + return helpStr + c.Flags().Help() +} diff --git a/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go b/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go new file mode 100644 index 000000000..7f8aaea20 --- /dev/null +++ b/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go @@ -0,0 +1,236 @@ +// Code generated by "make api"; DO NOT EDIT. +package hostsetscmd + +import ( + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/hostsets" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initStaticFlags() { + flagsOnce.Do(func() { + extraFlags := extraStaticActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsStaticMap[k] = append(flagsStaticMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*StaticCommand)(nil) + _ cli.CommandAutocomplete = (*StaticCommand)(nil) +) + +type StaticCommand struct { + *base.Command + + Func string + + plural string +} + +func (c *StaticCommand) AutocompleteArgs() complete.Predictor { + initStaticFlags() + return complete.PredictAnything +} + +func (c *StaticCommand) AutocompleteFlags() complete.Flags { + initStaticFlags() + return c.Flags().Completions() +} + +func (c *StaticCommand) Synopsis() string { + if extra := extraStaticSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "host set" + + synopsisStr = fmt.Sprintf("%s %s", "static-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *StaticCommand) Help() string { + initStaticFlags() + + var helpStr string + helpMap := common.HelpMap("host set") + + switch c.Func { + default: + + helpStr = c.extraStaticHelpFunc(helpMap) + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsStaticMap = map[string][]string{ + + "create": {"host-catalog-id", "name", "description"}, + + "update": {"id", "name", "description", "version"}, +} + +func (c *StaticCommand) Flags() *base.FlagSets { + if len(flagsStaticMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "static-type host set", flagsStaticMap[c.Func]) + + extraStaticFlagsFunc(c, set, f) + + return set +} + +func (c *StaticCommand) Run(args []string) int { + initStaticFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "static-type host set" + switch c.Func { + case "list": + c.plural = "static-type host sets" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsStaticMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []hostsets.Option + + if strutil.StrListContains(flagsStaticMap[c.Func], "host-catalog-id") { + switch c.Func { + case "create": + if c.FlagHostCatalogId == "" { + c.UI.Error("HostCatalog ID must be passed in via -host-catalog-id or BOUNDARY_HOST_CATALOG_ID") + return 1 + } + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + hostsetsClient := hostsets.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, hostsets.DefaultName()) + default: + opts = append(opts, hostsets.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, hostsets.DefaultDescription()) + default: + opts = append(opts, hostsets.WithDescription(c.FlagDescription)) + } + + var version uint32 + + switch c.Func { + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, hostsets.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } + + if ret := extraStaticFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + var result api.GenericResult + + switch c.Func { + + case "create": + result, err = hostsetsClient.Create(c.Context, c.FlagHostCatalogId, opts...) + + case "update": + result, err = hostsetsClient.Update(c.Context, c.FlagId, version, opts...) + + } + + result, err = executeExtraStaticActions(c, result, err, hostsetsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomStaticActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + } + + item := result.GetItem().(*hostsets.HostSet) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + extraStaticActionsFlagsMapFunc = func() map[string][]string { return nil } + extraStaticSynopsisFunc = func(*StaticCommand) string { return "" } + extraStaticFlagsFunc = func(*StaticCommand, *base.FlagSets, *base.FlagSet) {} + extraStaticFlagsHandlingFunc = func(*StaticCommand, *[]hostsets.Option) int { return 0 } + executeExtraStaticActions = func(_ *StaticCommand, inResult api.GenericResult, inErr error, _ *hostsets.Client, _ uint32, _ []hostsets.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomStaticActionOutput = func(*StaticCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/roles/funcs.go b/internal/cmd/commands/roles/funcs.go deleted file mode 100644 index dad1b623e..000000000 --- a/internal/cmd/commands/roles/funcs.go +++ /dev/null @@ -1,151 +0,0 @@ -package roles - -import ( - "fmt" - "strings" - "time" - - "github.com/hashicorp/boundary/api/roles" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/internal/types/resource" - "github.com/mitchellh/go-wordwrap" -) - -func principalsGrantsSynopsisFunc(inFunc string, principals bool) string { - var in string - switchStr := "principals (users, groups)" - if !principals { - switchStr = "grants" - } - switch { - case strings.HasPrefix(inFunc, "add"): - in = fmt.Sprintf("Add %s to", switchStr) - case strings.HasPrefix(inFunc, "set"): - in = fmt.Sprintf("Set the full contents of the %s on", switchStr) - case strings.HasPrefix(inFunc, "remove"): - in = fmt.Sprintf("Remove %s from", switchStr) - } - return wordwrap.WrapString(fmt.Sprintf("%s a role", in), base.TermWidth) -} - -func addPrincipalsHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary roles add-principals [options] [args]", - "", - ` Adds principals (users, groups) to a role given its ID. The "principal" flag can be specified multiple times. Example:`, - "", - ` $ boundary roles add-principals -id r_1234567890 -principal u_1234567890`, - }) -} - -func setPrincipalsHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary roles set-principals [options] [args]", - "", - ` Sets the complete set of principals (users, groups) on a role given its ID. The "principal" flag can be specified multiple times. Example:`, - "", - ` $ boundary roles set-principals -id r_1234567890 -principal u_anon -principal sg_1234567890`, - }) -} - -func removePrincipalsHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary roles remove-principals [options] [args]", - "", - ` Removes principals (users, groups) from a role given its ID. The "principal" flags can be specified multiple times. Example:`, - "", - ` $ boundary roles remove-principals -id r_1234567890 -principal sg_1234567890`, - }) -} - -func populateFlags(c *Command, f *base.FlagSet, flagNames []string) { - common.PopulateCommonFlags(c.Command, f, resource.Role.String(), flagNames) - - for _, name := range flagNames { - switch name { - case "grantscopeid": - f.StringVar(&base.StringVar{ - Name: "grant-scope-id", - Target: &c.flagGrantScopeId, - Usage: "The scope ID for grants set on the role", - }) - case "principal": - f.StringSliceVar(&base.StringSliceVar{ - Name: "principal", - Target: &c.flagPrincipals, - Usage: "The principals (users or groups) to add, remove, or set. May be specified multiple times.", - }) - case "grant": - f.StringSliceVar(&base.StringSliceVar{ - Name: "grant", - Target: &c.flagGrants, - Usage: "The grants to add, remove, or set. May be specified multiple times. Can be in compact string format or JSON (be sure to escape JSON properly).", - }) - } - } -} - -func generateRoleTableOutput(in *roles.Role) string { - nonAttributeMap := map[string]interface{}{ - "ID": in.Id, - "Version": in.Version, - "Created Time": in.CreatedTime.Local().Format(time.RFC1123), - "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), - } - - if in.Name != "" { - nonAttributeMap["Name"] = in.Name - } - if in.Description != "" { - nonAttributeMap["Description"] = in.Description - } - if in.GrantScopeId != "" { - nonAttributeMap["Grant Scope ID"] = in.GrantScopeId - } - - maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) - - ret := []string{ - "", - "Role information:", - base.WrapMap(2, maxLength+2, nonAttributeMap), - "", - " Scope:", - base.ScopeInfoForOutput(in.Scope, maxLength), - } - - if len(in.AuthorizedActions) > 0 { - ret = append(ret, - "", - " Authorized Actions:", - base.WrapSlice(4, in.AuthorizedActions), - ) - } - - if len(in.Principals) > 0 { - ret = append(ret, - "", - fmt.Sprintf(" Principals: %s", ""), - ) - } - for _, principal := range in.Principals { - ret = append(ret, - fmt.Sprintf(" ID: %s", principal.Id), - fmt.Sprintf(" Type: %s", principal.Type), - fmt.Sprintf(" Scope ID: %s", principal.ScopeId), - ) - } - if len(in.Grants) > 0 { - ret = append(ret, - "", - fmt.Sprintf(" Canonical Grants: %s", ""), - ) - } - for _, grant := range in.Grants { - ret = append(ret, - fmt.Sprintf(" %s", grant.Canonical), - ) - } - return base.WrapForHelpText(ret) -} diff --git a/internal/cmd/commands/roles/role.go b/internal/cmd/commands/roles/role.go deleted file mode 100644 index 280664491..000000000 --- a/internal/cmd/commands/roles/role.go +++ /dev/null @@ -1,356 +0,0 @@ -package roles - -import ( - "fmt" - "net/http" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/roles" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/internal/perms" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*Command)(nil) - _ cli.CommandAutocomplete = (*Command)(nil) -) - -type Command struct { - *base.Command - - Func string - - flagGrantScopeId string - flagPrincipals []string - flagGrants []string -} - -func (c *Command) Synopsis() string { - switch c.Func { - case "", "create", "update", "read", "delete", "list": - return common.SynopsisFunc(c.Func, "role") - case "add-principals", "set-principals", "remove-principals": - return principalsGrantsSynopsisFunc(c.Func, true) - case "add-grants", "set-grants", "remove-grants": - return principalsGrantsSynopsisFunc(c.Func, false) - } - return "" -} - -var helpMap = func() map[string]func() string { - ret := common.HelpMap("role") - ret["add-principals"] = addPrincipalsHelp - ret["set-principals"] = setPrincipalsHelp - ret["remove-principals"] = removePrincipalsHelp - ret["add-grants"] = addPrincipalsHelp - ret["set-grants"] = setPrincipalsHelp - ret["remove-grants"] = removePrincipalsHelp - return ret -} - -var flagsMap = map[string][]string{ - "create": {"scope-id", "name", "description", "grantscopeid"}, - "update": {"id", "name", "description", "grantscopeid", "version"}, - "read": {"id"}, - "delete": {"id"}, - "list": {"scope-id", "recursive"}, - "add-principals": {"id", "principal", "version"}, - "set-principals": {"id", "principal", "version"}, - "remove-principals": {"id", "principal", "version"}, - "add-grants": {"id", "grant", "version"}, - "set-grants": {"id", "grant", "version"}, - "remove-grants": {"id", "grant", "version"}, -} - -func (c *Command) Help() string { - hm := helpMap() - if c.Func == "" { - return hm["base"]() - } - return hm[c.Func]() + c.Flags().Help() -} - -func (c *Command) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - populateFlags(c, f, flagsMap[c.Func]) - - return set -} - -func (c *Command) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *Command) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *Command) Run(args []string) int { - if c.Func == "" { - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(flagsMap[c.Func], "scope-id") && c.FlagScopeId == "" { - c.UI.Error("Scope ID must be passed in via -scope-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []roles.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, roles.DefaultName()) - default: - opts = append(opts, roles.WithName(c.FlagName)) - } - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, roles.DefaultDescription()) - default: - opts = append(opts, roles.WithDescription(c.FlagDescription)) - } - switch c.FlagRecursive { - case true: - opts = append(opts, roles.WithRecursive(true)) - } - - switch c.flagGrantScopeId { - case "": - case "null": - opts = append(opts, roles.DefaultGrantScopeId()) - default: - opts = append(opts, roles.WithGrantScopeId(c.flagGrantScopeId)) - } - - principals := c.flagPrincipals - grants := c.flagGrants - switch c.Func { - case "add-principals", "remove-principals": - if len(c.flagPrincipals) == 0 { - c.UI.Error("No principals supplied via -principal") - return 1 - } - - case "add-grants", "remove-grants": - if len(c.flagGrants) == 0 { - c.UI.Error("No grants supplied via -grant") - return 1 - } - - case "set-principals": - switch len(c.flagPrincipals) { - case 0: - c.UI.Error("No principals supplied via -principal") - return 1 - case 1: - if c.flagPrincipals[0] == "null" { - principals = nil - } - } - - case "set-grants": - switch len(c.flagGrants) { - case 0: - c.UI.Error("No grants supplied via -grant") - return 1 - case 1: - if c.flagGrants[0] == "null" { - grants = nil - } - } - } - - if len(grants) > 0 { - for _, grant := range grants { - _, err := perms.Parse("global", grant) - if err != nil { - c.UI.Error(fmt.Errorf("Grant %q could not be parsed successfully: %w", grant, err).Error()) - return 1 - } - } - } - - roleClient := roles.NewClient(client) - - // Perform check-and-set when needed - var version uint32 - switch c.Func { - case "create", "read", "delete", "list": - // These don't udpate so don't need the existing version - default: - switch c.FlagVersion { - case 0: - opts = append(opts, roles.WithAutomaticVersioning(true)) - default: - version = uint32(c.FlagVersion) - } - } - - existed := true - var result api.GenericResult - var listResult api.GenericListResult - - switch c.Func { - case "create": - result, err = roleClient.Create(c.Context, c.FlagScopeId, opts...) - case "update": - result, err = roleClient.Update(c.Context, c.FlagId, version, opts...) - case "read": - result, err = roleClient.Read(c.Context, c.FlagId, opts...) - case "delete": - _, err = roleClient.Delete(c.Context, c.FlagId, opts...) - if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { - existed = false - err = nil - } - case "list": - listResult, err = roleClient.List(c.Context, c.FlagScopeId, opts...) - case "add-principals": - result, err = roleClient.AddPrincipals(c.Context, c.FlagId, version, principals, opts...) - case "set-principals": - result, err = roleClient.SetPrincipals(c.Context, c.FlagId, version, principals, opts...) - case "remove-principals": - result, err = roleClient.RemovePrincipals(c.Context, c.FlagId, version, principals, opts...) - case "add-grants": - result, err = roleClient.AddGrants(c.Context, c.FlagId, version, grants, opts...) - case "set-grants": - result, err = roleClient.SetGrants(c.Context, c.FlagId, version, grants, opts...) - case "remove-grants": - result, err = roleClient.RemoveGrants(c.Context, c.FlagId, version, grants, opts...) - } - - plural := "role" - if c.Func == "list" { - plural = "roles" - } - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - switch c.Func { - case "delete": - switch base.Format(c.UI) { - case "json": - c.UI.Output("null") - case "table": - output := "The delete operation completed successfully" - switch existed { - case true: - output += "." - default: - output += ", however the resource did not exist at the time." - } - c.UI.Output(output) - } - return 0 - - case "list": - listedRoles := listResult.GetItems().([]*roles.Role) - switch base.Format(c.UI) { - case "json": - if len(listedRoles) == 0 { - c.UI.Output("null") - return 0 - } - b, err := base.JsonFormatter{}.Format(listedRoles) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - - case "table": - if len(listedRoles) == 0 { - c.UI.Output("No roles found") - return 0 - } - var output []string - output = []string{ - "", - "Role information:", - } - for i, r := range listedRoles { - if i > 0 { - output = append(output, "") - } - if true { - output = append(output, - fmt.Sprintf(" ID: %s", r.Id), - ) - } - if c.FlagRecursive { - output = append(output, - fmt.Sprintf(" Scope ID: %s", r.Scope.Id), - ) - } - if true { - output = append(output, - fmt.Sprintf(" Version: %d", r.Version), - ) - } - if r.Name != "" { - output = append(output, - fmt.Sprintf(" Name: %s", r.Name), - ) - } - if r.Description != "" { - output = append(output, - fmt.Sprintf(" Description: %s", r.Description), - ) - } - if len(r.AuthorizedActions) > 0 { - output = append(output, - " Authorized Actions:", - base.WrapSlice(6, r.AuthorizedActions), - ) - } - } - c.UI.Output(base.WrapForHelpText(output)) - } - return 0 - } - - role := result.GetItem().(*roles.Role) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateRoleTableOutput(role)) - case "json": - b, err := base.JsonFormatter{}.Format(role) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/rolescmd/funcs.go b/internal/cmd/commands/rolescmd/funcs.go new file mode 100644 index 000000000..1589964fe --- /dev/null +++ b/internal/cmd/commands/rolescmd/funcs.go @@ -0,0 +1,359 @@ +package rolescmd + +import ( + "fmt" + "strings" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/roles" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/perms" + "github.com/mitchellh/go-wordwrap" +) + +func init() { + extraActionsFlagsMapFunc = extraActionsFlagsMapFuncImpl + extraSynopsisFunc = extraSynopsisFuncImpl + extraFlagsFunc = extraFlagsFuncImpl + extraFlagsHandlingFunc = extraFlagsHandlingFuncImpl + executeExtraActions = executeExtraActionsImpl +} + +type extraCmdVars struct { + flagGrantScopeId string + flagPrincipals []string + flagGrants []string +} + +func extraActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "create": {"grant-scope-id"}, + "update": {"grant-scope-id"}, + "add-principals": {"id", "principal", "version"}, + "set-principals": {"id", "principal", "version"}, + "remove-principals": {"id", "principal", "version"}, + "add-grants": {"id", "grant", "version"}, + "set-grants": {"id", "grant", "version"}, + "remove-grants": {"id", "grant", "version"}, + } +} + +func extraSynopsisFuncImpl(c *Command) string { + switch c.Func { + case "add-principals", "set-principals", "remove-principals": + return c.principalsGrantsSynopsisFunc(c.Func, true) + case "add-grants", "set-grants", "remove-grants": + return c.principalsGrantsSynopsisFunc(c.Func, false) + } + + return "" +} + +func (c *Command) principalsGrantsSynopsisFunc(inFunc string, principals bool) string { + var in string + switchStr := "principals (users, groups)" + if !principals { + switchStr = "grants" + } + switch { + case strings.HasPrefix(inFunc, "add"): + in = fmt.Sprintf("Add %s to", switchStr) + case strings.HasPrefix(inFunc, "set"): + in = fmt.Sprintf("Set the full contents of the %s on", switchStr) + case strings.HasPrefix(inFunc, "remove"): + in = fmt.Sprintf("Remove %s from", switchStr) + } + return wordwrap.WrapString(fmt.Sprintf("%s a role", in), base.TermWidth) +} + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "add-principals": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary roles add-principals [options] [args]", + "", + ` Adds principals (users, groups) to a role given its ID. The "principal" flag can be specified multiple times. Example:`, + "", + ` $ boundary roles add-principals -id r_1234567890 -principal u_1234567890`, + "", + "", + }) + + case "set-principals": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary roles set-principals [options] [args]", + "", + ` Sets the complete set of principals (users, groups) on a role given its ID. The "principal" flag can be specified multiple times. Example:`, + "", + ` $ boundary roles set-principals -id r_1234567890 -principal u_anon -principal sg_1234567890`, + "", + "", + }) + + case "remove-principals": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary roles remove-principals [options] [args]", + "", + ` Removes principals (users, groups) from a role given its ID. The "principal" flags can be specified multiple times. Example:`, + "", + ` $ boundary roles remove-principals -id r_1234567890 -principal sg_1234567890`, + "", + "", + }) + + case "add-grants": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary roles add-grants [options] [args]", + "", + ` Adds grants to a role given its ID. The "grant" flag can be specified multiple times. Example:`, + "", + ` $ boundary roles add-grants -id r_1234567890 -grant "id=*;type=*;actions=read"`, + "", + "", + }) + + case "set-grants": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary roles set-grants [options] [args]", + "", + ` Sets the complete set of grants on a role given its ID. The "grant" flag can be specified multiple times. Example:`, + "", + ` $ boundary roles set-grants -id r_1234567890 -grant "id=*;type=*;actions=read" -grant "id=*;type=*;actions=list"`, + "", + "", + }) + + case "remove-grants": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary roles remove-grants [options] [args]", + "", + ` Removes grants from a role given its ID. The "grant" flags can be specified multiple times. Example:`, + "", + ` $ boundary roles remove-grants -id r_1234567890 -grant "id=*;type=*;actions=read"`, + "", + "", + }) + + default: + helpStr = helpMap["base"]() + } + return helpStr + c.Flags().Help() +} + +func extraFlagsFuncImpl(c *Command, _ *base.FlagSets, f *base.FlagSet) { + for _, name := range flagsMap[c.Func] { + switch name { + case "grant-scope-id": + f.StringVar(&base.StringVar{ + Name: "grant-scope-id", + Target: &c.flagGrantScopeId, + Usage: "The scope ID for grants set on the role", + }) + case "principal": + f.StringSliceVar(&base.StringSliceVar{ + Name: "principal", + Target: &c.flagPrincipals, + Usage: "The principals (users or groups) to add, remove, or set. May be specified multiple times.", + }) + case "grant": + f.StringSliceVar(&base.StringSliceVar{ + Name: "grant", + Target: &c.flagGrants, + Usage: "The grants to add, remove, or set. May be specified multiple times. Can be in compact string format or JSON (be sure to escape JSON properly).", + }) + } + } +} + +func extraFlagsHandlingFuncImpl(c *Command, opts *[]roles.Option) int { + switch c.flagGrantScopeId { + case "": + case "null": + *opts = append(*opts, roles.DefaultGrantScopeId()) + default: + *opts = append(*opts, roles.WithGrantScopeId(c.flagGrantScopeId)) + } + + switch c.Func { + case "add-principals", "remove-principals": + if len(c.flagPrincipals) == 0 { + c.UI.Error("No principals supplied via -principal") + return 1 + } + + case "add-grants", "remove-grants": + if len(c.flagGrants) == 0 { + c.UI.Error("No grants supplied via -grant") + return 1 + } + + case "set-principals": + switch len(c.flagPrincipals) { + case 0: + c.UI.Error("No principals supplied via -principal") + return 1 + case 1: + if c.flagPrincipals[0] == "null" { + c.flagPrincipals = nil + } + } + + case "set-grants": + switch len(c.flagGrants) { + case 0: + c.UI.Error("No grants supplied via -grant") + return 1 + case 1: + if c.flagGrants[0] == "null" { + c.flagGrants = nil + } + } + } + + if len(c.flagGrants) > 0 { + for _, grant := range c.flagGrants { + _, err := perms.Parse("global", grant) + if err != nil { + c.UI.Error(fmt.Errorf("Grant %q could not be parsed successfully: %w", grant, err).Error()) + return 1 + } + } + } + + return 0 +} + +func executeExtraActionsImpl(c *Command, origResult api.GenericResult, origError error, roleClient *roles.Client, version uint32, opts []roles.Option) (api.GenericResult, error) { + switch c.Func { + case "add-principals": + return roleClient.AddPrincipals(c.Context, c.FlagId, version, c.flagPrincipals, opts...) + case "set-principals": + return roleClient.SetPrincipals(c.Context, c.FlagId, version, c.flagPrincipals, opts...) + case "remove-principals": + return roleClient.RemovePrincipals(c.Context, c.FlagId, version, c.flagPrincipals, opts...) + case "add-grants": + return roleClient.AddGrants(c.Context, c.FlagId, version, c.flagGrants, opts...) + case "set-grants": + return roleClient.SetGrants(c.Context, c.FlagId, version, c.flagGrants, opts...) + case "remove-grants": + return roleClient.RemoveGrants(c.Context, c.FlagId, version, c.flagGrants, opts...) + } + return origResult, origError +} + +func (c *Command) printListTable(items []*roles.Role) string { + if len(items) == 0 { + return "No roles found" + } + + var output []string + output = []string{ + "", + "Role information:", + } + for i, item := range items { + if i > 0 { + output = append(output, "") + } + if true { + output = append(output, + fmt.Sprintf(" ID: %s", item.Id), + ) + } + if c.FlagRecursive { + output = append(output, + fmt.Sprintf(" Scope ID: %s", item.Scope.Id), + ) + } + if true { + output = append(output, + fmt.Sprintf(" Version: %d", item.Version), + ) + } + if item.Name != "" { + output = append(output, + fmt.Sprintf(" Name: %s", item.Name), + ) + } + if item.Description != "" { + output = append(output, + fmt.Sprintf(" Description: %s", item.Description), + ) + } + if len(item.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, item.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(in *roles.Role) string { + nonAttributeMap := map[string]interface{}{ + "ID": in.Id, + "Version": in.Version, + "Created Time": in.CreatedTime.Local().Format(time.RFC1123), + "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), + } + + if in.Name != "" { + nonAttributeMap["Name"] = in.Name + } + if in.Description != "" { + nonAttributeMap["Description"] = in.Description + } + if in.GrantScopeId != "" { + nonAttributeMap["Grant Scope ID"] = in.GrantScopeId + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) + + ret := []string{ + "", + "Role information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + "", + " Scope:", + base.ScopeInfoForOutput(in.Scope, maxLength), + } + + if len(in.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, in.AuthorizedActions), + ) + } + + if len(in.Principals) > 0 { + ret = append(ret, + "", + fmt.Sprintf(" Principals: %s", ""), + ) + } + for _, principal := range in.Principals { + ret = append(ret, + fmt.Sprintf(" ID: %s", principal.Id), + fmt.Sprintf(" Type: %s", principal.Type), + fmt.Sprintf(" Scope ID: %s", principal.ScopeId), + ) + } + if len(in.Grants) > 0 { + ret = append(ret, + "", + fmt.Sprintf(" Canonical Grants: %s", ""), + ) + } + for _, grant := range in.Grants { + ret = append(ret, + fmt.Sprintf(" %s", grant.Canonical), + ) + } + + return base.WrapForHelpText(ret) +} diff --git a/internal/cmd/commands/rolescmd/roles.gen.go b/internal/cmd/commands/rolescmd/roles.gen.go new file mode 100644 index 000000000..a18b43afa --- /dev/null +++ b/internal/cmd/commands/rolescmd/roles.gen.go @@ -0,0 +1,386 @@ +// Code generated by "make api"; DO NOT EDIT. +package rolescmd + +import ( + "fmt" + "net/http" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/roles" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string + + extraCmdVars +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "role" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("role") + + switch c.Func { + + case "create": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "update": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "delete": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = c.extraHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "create": {"scope-id", "name", "description"}, + + "read": {"id"}, + + "update": {"id", "name", "description", "version"}, + + "delete": {"id"}, + + "list": {"scope-id", "recursive"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "role", flagsMap[c.Func]) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "role" + switch c.Func { + case "list": + c.plural = "roles" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []roles.Option + + if strutil.StrListContains(flagsMap[c.Func], "scope-id") { + switch c.Func { + + case "create": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + + case "list": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + rolesClient := roles.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, roles.DefaultName()) + default: + opts = append(opts, roles.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, roles.DefaultDescription()) + default: + opts = append(opts, roles.WithDescription(c.FlagDescription)) + } + + switch c.FlagRecursive { + case true: + opts = append(opts, roles.WithRecursive(true)) + } + + var version uint32 + + switch c.Func { + + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, roles.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "add-grants": + switch c.FlagVersion { + case 0: + opts = append(opts, roles.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "remove-grants": + switch c.FlagVersion { + case 0: + opts = append(opts, roles.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "set-grants": + switch c.FlagVersion { + case 0: + opts = append(opts, roles.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "add-principals": + switch c.FlagVersion { + case 0: + opts = append(opts, roles.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "remove-principals": + switch c.FlagVersion { + case 0: + opts = append(opts, roles.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "set-principals": + switch c.FlagVersion { + case 0: + opts = append(opts, roles.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + } + + if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + existed := true + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "create": + result, err = rolesClient.Create(c.Context, c.FlagScopeId, opts...) + + case "read": + result, err = rolesClient.Read(c.Context, c.FlagId, opts...) + + case "update": + result, err = rolesClient.Update(c.Context, c.FlagId, version, opts...) + + case "delete": + _, err = rolesClient.Delete(c.Context, c.FlagId, opts...) + if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { + existed = false + err = nil + } + + case "list": + listResult, err = rolesClient.List(c.Context, c.FlagScopeId, opts...) + + } + + result, err = executeExtraActions(c, result, err, rolesClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + c.UI.Output(fmt.Sprintf("{ \"existed\": %t }", existed)) + + case "table": + output := "The delete operation completed successfully" + switch existed { + case true: + output += "." + default: + output += ", however the resource did not exist at the time." + } + c.UI.Output(output) + } + + return 0 + + case "list": + listedItems := listResult.GetItems().([]*roles.Role) + switch base.Format(c.UI) { + case "json": + switch { + + case len(listedItems) == 0: + c.UI.Output("null") + + default: + b, err := base.JsonFormatter{}.Format(listedItems) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + case "table": + c.UI.Output(c.printListTable(listedItems)) + } + + return 0 + + } + + item := result.GetItem().(*roles.Role) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *[]roles.Option) int { return 0 } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *roles.Client, _ uint32, _ []roles.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/scopes/funcs.go b/internal/cmd/commands/scopes/funcs.go deleted file mode 100644 index ea41bcf53..000000000 --- a/internal/cmd/commands/scopes/funcs.go +++ /dev/null @@ -1,63 +0,0 @@ -package scopes - -import ( - "fmt" - "sort" - "time" - - "github.com/hashicorp/boundary/api/scopes" - "github.com/hashicorp/boundary/internal/cmd/base" -) - -func generateScopeTableOutput(in *scopes.Scope) string { - nonAttributeMap := map[string]interface{}{ - "ID": in.Id, - "Version": in.Version, - "Created Time": in.CreatedTime.Local().Format(time.RFC1123), - "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), - } - - if in.Name != "" { - nonAttributeMap["Name"] = in.Name - } - if in.Description != "" { - nonAttributeMap["Description"] = in.Description - } - - maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) - - ret := []string{ - "", - "Scope information:", - base.WrapMap(2, maxLength+2, nonAttributeMap), - "", - " Scope (parent):", - base.ScopeInfoForOutput(in.Scope, maxLength), - } - - if len(in.AuthorizedActions) > 0 { - ret = append(ret, - "", - " Authorized Actions:", - base.WrapSlice(4, in.AuthorizedActions), - "", - ) - } - - if len(in.AuthorizedCollectionActions) > 0 { - keys := make([]string, 0, len(in.AuthorizedCollectionActions)) - for k := range in.AuthorizedCollectionActions { - keys = append(keys, k) - } - sort.Strings(keys) - ret = append(ret, " Authorized Actions on Scope's Collections:") - for _, key := range keys { - ret = append(ret, - fmt.Sprintf(" %s:", key), - base.WrapSlice(6, in.AuthorizedCollectionActions[key]), - ) - } - } - - return base.WrapForHelpText(ret) -} diff --git a/internal/cmd/commands/scopes/scope.go b/internal/cmd/commands/scopes/scope.go deleted file mode 100644 index 93a5b9829..000000000 --- a/internal/cmd/commands/scopes/scope.go +++ /dev/null @@ -1,282 +0,0 @@ -package scopes - -import ( - "fmt" - "net/http" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/scopes" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/internal/types/resource" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*Command)(nil) - _ cli.CommandAutocomplete = (*Command)(nil) -) - -type Command struct { - *base.Command - - Func string - - flagSkipAdminRoleCreation bool - flagSkipDefaultRoleCreation bool -} - -func (c *Command) Synopsis() string { - return common.SynopsisFunc(c.Func, "scope") -} - -var flagsMap = map[string][]string{ - "create": {"scope-id", "name", "description", "skip-admin-role-creation", "skip-default-role-creation"}, - "update": {"id", "name", "description", "version"}, - "read": {"id"}, - "delete": {"id"}, - "list": {"scope-id", "recursive"}, -} - -func (c *Command) Help() string { - helpMap := common.HelpMap("scope") - if c.Func == "" { - return helpMap["base"]() - } - return helpMap[c.Func]() + c.Flags().Help() -} - -func (c *Command) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, resource.Scope.String(), flagsMap[c.Func]) - - if c.Func == "create" { - f.BoolVar(&base.BoolVar{ - Name: "skip-admin-role-creation", - Target: &c.flagSkipAdminRoleCreation, - Usage: "If set, a role granting the current user access to administer the newly-created scope will not automatically be created", - }) - f.BoolVar(&base.BoolVar{ - Name: "skip-default-role-creation", - Target: &c.flagSkipDefaultRoleCreation, - Usage: "If set, a role granting the anonymous user access to log into auth methods and a few other actions within the newly-created scope will not automatically be created", - }) - } - - return set -} - -func (c *Command) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *Command) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *Command) Run(args []string) int { - if c.Func == "" { - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(flagsMap[c.Func], "scope-id") && c.FlagScopeId == "" { - c.UI.Error("Scope ID must be passed in via -scope-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []scopes.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, scopes.DefaultName()) - default: - opts = append(opts, scopes.WithName(c.FlagName)) - } - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, scopes.DefaultDescription()) - default: - opts = append(opts, scopes.WithDescription(c.FlagDescription)) - } - switch c.FlagRecursive { - case true: - opts = append(opts, scopes.WithRecursive(true)) - } - - if c.flagSkipAdminRoleCreation { - opts = append(opts, scopes.WithSkipAdminRoleCreation(c.flagSkipAdminRoleCreation)) - } - if c.flagSkipDefaultRoleCreation { - opts = append(opts, scopes.WithSkipDefaultRoleCreation(c.flagSkipDefaultRoleCreation)) - } - - scopeClient := scopes.NewClient(client) - - // Perform check-and-set when needed - var version uint32 - switch c.Func { - case "create", "read", "delete", "list": - // These don't udpate so don't need the existing version - default: - switch c.FlagVersion { - case 0: - opts = append(opts, scopes.WithAutomaticVersioning(true)) - default: - version = uint32(c.FlagVersion) - } - } - - existed := true - var result api.GenericResult - var listResult api.GenericListResult - - switch c.Func { - case "create": - result, err = scopeClient.Create(c.Context, c.FlagScopeId, opts...) - case "update": - result, err = scopeClient.Update(c.Context, c.FlagId, version, opts...) - case "read": - result, err = scopeClient.Read(c.Context, c.FlagId, opts...) - case "delete": - _, err = scopeClient.Delete(c.Context, c.FlagId, opts...) - if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { - existed = false - err = nil - } - case "list": - listResult, err = scopeClient.List(c.Context, c.FlagScopeId, opts...) - } - - plural := "scope" - if c.Func == "list" { - plural = "scopes" - } - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - switch c.Func { - case "delete": - switch base.Format(c.UI) { - case "json": - c.UI.Output("null") - case "table": - output := "The delete operation completed successfully" - switch existed { - case true: - output += "." - default: - output += ", however the resource did not exist at the time." - } - c.UI.Output(output) - } - return 0 - - case "list": - listedScopes := listResult.GetItems().([]*scopes.Scope) - switch base.Format(c.UI) { - case "json": - if len(listedScopes) == 0 { - c.UI.Output("null") - return 0 - } - b, err := base.JsonFormatter{}.Format(listedScopes) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - - case "table": - if len(listedScopes) == 0 { - c.UI.Output("No child scopes found") - return 0 - } - var output []string - output = []string{ - "", - "Scope information:", - } - for i, s := range listedScopes { - if i > 0 { - output = append(output, "") - } - if true { - output = append(output, - fmt.Sprintf(" ID: %s", s.Id), - ) - } - if c.FlagRecursive { - output = append(output, - fmt.Sprintf(" Scope ID: %s", s.Scope.Id), - ) - } - if true { - output = append(output, - fmt.Sprintf(" Version: %d", s.Version), - ) - } - if s.Name != "" { - output = append(output, - fmt.Sprintf(" Name: %s", s.Name), - ) - } - if s.Description != "" { - output = append(output, - fmt.Sprintf(" Description: %s", s.Description), - ) - } - if len(s.AuthorizedActions) > 0 { - output = append(output, - " Authorized Actions:", - base.WrapSlice(6, s.AuthorizedActions), - ) - } - } - c.UI.Output(base.WrapForHelpText(output)) - } - return 0 - } - - scope := result.GetItem().(*scopes.Scope) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateScopeTableOutput(scope)) - case "json": - b, err := base.JsonFormatter{}.Format(scope) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/scopescmd/funcs.go b/internal/cmd/commands/scopescmd/funcs.go new file mode 100644 index 000000000..6e7c940d3 --- /dev/null +++ b/internal/cmd/commands/scopescmd/funcs.go @@ -0,0 +1,159 @@ +package scopescmd + +import ( + "fmt" + "sort" + "time" + + "github.com/hashicorp/boundary/api/scopes" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func init() { + extraActionsFlagsMapFunc = extraActionsFlagsMapFuncImpl + extraFlagsFunc = extraFlagsFuncImpl + extraFlagsHandlingFunc = extraFlagsHandlingFuncImpl +} + +func extraActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "create": {"skip-admin-role-creation", "skip-default-role-creation"}, + } +} + +type extraCmdVars struct { + flagSkipAdminRoleCreation bool + flagSkipDefaultRoleCreation bool +} + +func extraFlagsFuncImpl(c *Command, set *base.FlagSets, f *base.FlagSet) { + for _, name := range flagsMap[c.Func] { + switch name { + case "skip-admin-role-creation": + f.BoolVar(&base.BoolVar{ + Name: "skip-admin-role-creation", + Target: &c.flagSkipAdminRoleCreation, + Usage: "If set, a role granting the current user access to administer the newly-created scope will not automatically be created", + }) + case "skip-default-role-creation": + f.BoolVar(&base.BoolVar{ + Name: "skip-default-role-creation", + Target: &c.flagSkipDefaultRoleCreation, + Usage: "If set, a role granting the anonymous user access to log into auth methods and a few other actions within the newly-created scope will not automatically be created", + }) + } + } +} + +func extraFlagsHandlingFuncImpl(c *Command, opts *[]scopes.Option) int { + if c.flagSkipAdminRoleCreation { + *opts = append(*opts, scopes.WithSkipAdminRoleCreation(c.flagSkipAdminRoleCreation)) + } + if c.flagSkipDefaultRoleCreation { + *opts = append(*opts, scopes.WithSkipDefaultRoleCreation(c.flagSkipDefaultRoleCreation)) + } + + return 0 +} + +func (c *Command) printListTable(items []*scopes.Scope) string { + if len(items) == 0 { + return "No child scopes found" + } + var output []string + output = []string{ + "", + "Scope information:", + } + for i, item := range items { + if i > 0 { + output = append(output, "") + } + if true { + output = append(output, + fmt.Sprintf(" ID: %s", item.Id), + ) + } + if c.FlagRecursive { + output = append(output, + fmt.Sprintf(" Scope ID: %s", item.Scope.Id), + ) + } + if true { + output = append(output, + fmt.Sprintf(" Version: %d", item.Version), + ) + } + if item.Name != "" { + output = append(output, + fmt.Sprintf(" Name: %s", item.Name), + ) + } + if item.Description != "" { + output = append(output, + fmt.Sprintf(" Description: %s", item.Description), + ) + } + if len(item.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, item.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(in *scopes.Scope) string { + nonAttributeMap := map[string]interface{}{ + "ID": in.Id, + "Version": in.Version, + "Created Time": in.CreatedTime.Local().Format(time.RFC1123), + "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), + } + + if in.Name != "" { + nonAttributeMap["Name"] = in.Name + } + if in.Description != "" { + nonAttributeMap["Description"] = in.Description + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) + + ret := []string{ + "", + "Scope information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + "", + " Scope (parent):", + base.ScopeInfoForOutput(in.Scope, maxLength), + } + + if len(in.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, in.AuthorizedActions), + "", + ) + } + + if len(in.AuthorizedCollectionActions) > 0 { + keys := make([]string, 0, len(in.AuthorizedCollectionActions)) + for k := range in.AuthorizedCollectionActions { + keys = append(keys, k) + } + sort.Strings(keys) + ret = append(ret, " Authorized Actions on Scope's Collections:") + for _, key := range keys { + ret = append(ret, + fmt.Sprintf(" %s:", key), + base.WrapSlice(6, in.AuthorizedCollectionActions[key]), + ) + } + } + + return base.WrapForHelpText(ret) +} diff --git a/internal/cmd/commands/scopescmd/scopes.gen.go b/internal/cmd/commands/scopescmd/scopes.gen.go new file mode 100644 index 000000000..848533822 --- /dev/null +++ b/internal/cmd/commands/scopescmd/scopes.gen.go @@ -0,0 +1,336 @@ +// Code generated by "make api"; DO NOT EDIT. +package scopescmd + +import ( + "fmt" + "net/http" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/scopes" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string + + extraCmdVars +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "scope" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("scope") + + switch c.Func { + + case "create": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "update": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "delete": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = helpMap["base"]() + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "create": {"scope-id", "name", "description"}, + + "read": {"id"}, + + "update": {"id", "name", "description", "version"}, + + "delete": {"id"}, + + "list": {"scope-id", "recursive"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "scope", flagsMap[c.Func]) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "scope" + switch c.Func { + case "list": + c.plural = "scopes" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []scopes.Option + + if strutil.StrListContains(flagsMap[c.Func], "scope-id") { + switch c.Func { + + case "create": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + + case "list": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + scopesClient := scopes.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, scopes.DefaultName()) + default: + opts = append(opts, scopes.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, scopes.DefaultDescription()) + default: + opts = append(opts, scopes.WithDescription(c.FlagDescription)) + } + + switch c.FlagRecursive { + case true: + opts = append(opts, scopes.WithRecursive(true)) + } + + var version uint32 + + switch c.Func { + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, scopes.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } + + if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + existed := true + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "create": + result, err = scopesClient.Create(c.Context, c.FlagScopeId, opts...) + + case "read": + result, err = scopesClient.Read(c.Context, c.FlagId, opts...) + + case "update": + result, err = scopesClient.Update(c.Context, c.FlagId, version, opts...) + + case "delete": + _, err = scopesClient.Delete(c.Context, c.FlagId, opts...) + if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { + existed = false + err = nil + } + + case "list": + listResult, err = scopesClient.List(c.Context, c.FlagScopeId, opts...) + + } + + result, err = executeExtraActions(c, result, err, scopesClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + c.UI.Output(fmt.Sprintf("{ \"existed\": %t }", existed)) + + case "table": + output := "The delete operation completed successfully" + switch existed { + case true: + output += "." + default: + output += ", however the resource did not exist at the time." + } + c.UI.Output(output) + } + + return 0 + + case "list": + listedItems := listResult.GetItems().([]*scopes.Scope) + switch base.Format(c.UI) { + case "json": + switch { + + case len(listedItems) == 0: + c.UI.Output("null") + + default: + b, err := base.JsonFormatter{}.Format(listedItems) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + case "table": + c.UI.Output(c.printListTable(listedItems)) + } + + return 0 + + } + + item := result.GetItem().(*scopes.Scope) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *[]scopes.Option) int { return 0 } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *scopes.Client, _ uint32, _ []scopes.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/sessions/funcs.go b/internal/cmd/commands/sessions/funcs.go deleted file mode 100644 index 3d4aac507..000000000 --- a/internal/cmd/commands/sessions/funcs.go +++ /dev/null @@ -1,107 +0,0 @@ -package sessions - -import ( - "fmt" - "strings" - "time" - - "github.com/hashicorp/boundary/api/sessions" - "github.com/hashicorp/boundary/internal/cmd/base" -) - -func generateSessionTableOutput(in *sessions.Session) string { - nonAttributeMap := map[string]interface{}{ - "ID": in.Id, - "Target ID": in.TargetId, - "Created Time": in.CreatedTime.Local().Format(time.RFC1123), - "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), - "Expiration Time": in.ExpirationTime.Local().Format(time.RFC1123), - "Version": in.Version, - "Type": in.Type, - "Auth Token ID": in.AuthTokenId, - "User ID": in.UserId, - "Host Set ID": in.HostSetId, - "Host ID": in.HostId, - "Endpoint": in.Endpoint, - "Status": in.Status, - } - if len(strings.TrimSpace(in.TerminationReason)) > 0 { - nonAttributeMap["Termination Reason"] = in.TerminationReason - } - - maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) - - var statesMaps []map[string]interface{} - if len(in.States) > 0 { - for _, state := range in.States { - m := map[string]interface{}{ - "Status": state.Status, - "Start Time": state.StartTime.Local().Format(time.RFC1123), - } - if !state.EndTime.IsZero() { - m["End Time"] = state.EndTime.Local().Format(time.RFC1123) - } - statesMaps = append(statesMaps, m) - } - if l := len("Start Time"); l > maxLength { - maxLength = l - } - } - - var workerInfoMaps []map[string]interface{} - if len(in.WorkerInfo) > 0 { - for _, wi := range in.WorkerInfo { - m := map[string]interface{}{ - "Address": wi.Address, - } - workerInfoMaps = append(workerInfoMaps, m) - } - if l := len("Address"); l > maxLength { - maxLength = l - } - } - - ret := []string{ - "", - "Session information:", - base.WrapMap(2, maxLength+2, nonAttributeMap), - "", - " Scope:", - base.ScopeInfoForOutput(in.Scope, maxLength), - } - - if len(in.AuthorizedActions) > 0 { - ret = append(ret, - "", - " Authorized Actions:", - base.WrapSlice(4, in.AuthorizedActions), - ) - } - - if len(in.States) > 0 { - ret = append(ret, - "", - " States:", - ) - for _, m := range statesMaps { - ret = append(ret, - base.WrapMap(4, maxLength, m), - "", - ) - } - } - - if len(in.WorkerInfo) > 0 { - ret = append(ret, - fmt.Sprintf(" Worker Info: %s", ""), - ) - for _, m := range workerInfoMaps { - ret = append(ret, - base.WrapMap(4, maxLength, m), - "", - ) - } - } - - return base.WrapForHelpText(ret) -} diff --git a/internal/cmd/commands/sessions/session.go b/internal/cmd/commands/sessions/session.go deleted file mode 100644 index 37b45425e..000000000 --- a/internal/cmd/commands/sessions/session.go +++ /dev/null @@ -1,223 +0,0 @@ -package sessions - -import ( - "fmt" - "time" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/sessions" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/internal/types/resource" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*Command)(nil) - _ cli.CommandAutocomplete = (*Command)(nil) -) - -type Command struct { - *base.Command - - Func string -} - -func (c *Command) Synopsis() string { - return common.SynopsisFunc(c.Func, "session") -} - -var flagsMap = map[string][]string{ - "read": {"id"}, - "cancel": {"id"}, - "list": {"scope-id", "recursive"}, -} - -func (c *Command) Help() string { - helpMap := common.HelpMap("session") - var helpStr string - switch c.Func { - case "": - return base.WrapForHelpText([]string{ - "Usage: boundary sessions [sub command] [options] [args]", - "", - " This command allows operations on Boundary sessions.", - "", - " Read a session:", - "", - ` $ boundary sessions read -id s_1234567890`, - "", - " Please see the sessions subcommand help for detailed usage information.", - }) - case "cancel": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary sessions cancel [options] [args]", - "", - " Cancel the session specified by ID. Example:", - "", - ` $ boundary sessions cancel -id s_1234567890`, - "", - "", - }) - default: - helpStr = helpMap[c.Func]() - } - return helpStr + c.Flags().Help() -} - -func (c *Command) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, resource.Session.String(), flagsMap[c.Func]) - - return set -} - -func (c *Command) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *Command) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *Command) Run(args []string) int { - if c.Func == "" { - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(flagsMap[c.Func], "scope-id") && c.FlagScopeId == "" { - c.UI.Error("Scope ID must be passed in via -scope-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - sessionClient := sessions.NewClient(client) - - var opts []sessions.Option - - switch c.FlagRecursive { - case true: - opts = append(opts, sessions.WithRecursive(true)) - } - - var result api.GenericResult - var listResult api.GenericListResult - - switch c.Func { - case "read": - result, err = sessionClient.Read(c.Context, c.FlagId, opts...) - case "cancel": - result, err = sessionClient.Cancel(c.Context, c.FlagId, 0, append(opts, sessions.WithAutomaticVersioning(true))...) - case "list": - listResult, err = sessionClient.List(c.Context, c.FlagScopeId, opts...) - } - - plural := "session" - if c.Func == "list" { - plural = "sessions" - } - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - switch c.Func { - case "list": - listedSessions := listResult.GetItems().([]*sessions.Session) - switch base.Format(c.UI) { - case "json": - if len(listedSessions) == 0 { - c.UI.Output("null") - return 0 - } - b, err := base.JsonFormatter{}.Format(listedSessions) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - - case "table": - if len(listedSessions) == 0 { - c.UI.Output("No sessions found") - return 0 - } - var output []string - output = []string{ - "", - "Session information:", - } - for i, t := range listedSessions { - if i > 0 { - output = append(output, "") - } - if true { - output = append(output, - fmt.Sprintf(" ID: %s", t.Id), - ) - } - if c.FlagRecursive { - output = append(output, - fmt.Sprintf(" Scope ID: %s", t.Scope.Id), - ) - } - if true { - output = append(output, - fmt.Sprintf(" Status: %s", t.Status), - fmt.Sprintf(" Created Time: %s", t.CreatedTime.Local().Format(time.RFC1123)), - fmt.Sprintf(" Expiration Time: %s", t.ExpirationTime.Local().Format(time.RFC1123)), - fmt.Sprintf(" Updated Time: %s", t.UpdatedTime.Local().Format(time.RFC1123)), - fmt.Sprintf(" User ID: %s", t.UserId), - fmt.Sprintf(" Target ID: %s", t.TargetId), - ) - } - if len(t.AuthorizedActions) > 0 { - output = append(output, - " Authorized Actions:", - base.WrapSlice(6, t.AuthorizedActions), - ) - } - } - c.UI.Output(base.WrapForHelpText(output)) - } - return 0 - } - - sess := result.GetItem().(*sessions.Session) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateSessionTableOutput(sess)) - case "json": - b, err := base.JsonFormatter{}.Format(sess) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/sessionscmd/funcs.go b/internal/cmd/commands/sessionscmd/funcs.go new file mode 100644 index 000000000..68e31fd11 --- /dev/null +++ b/internal/cmd/commands/sessionscmd/funcs.go @@ -0,0 +1,205 @@ +package sessionscmd + +import ( + "fmt" + "strings" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/sessions" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func init() { + extraActionsFlagsMapFunc = extraActionsFlagsMapFuncImpl + executeExtraActions = executeExtraActionsImpl +} + +func extraActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "cancel": {"id"}, + } +} + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "": + return base.WrapForHelpText([]string{ + "Usage: boundary sessions [sub command] [options] [args]", + "", + " This command allows operations on Boundary sessions.", + "", + " Read a session:", + "", + ` $ boundary sessions read -id s_1234567890`, + "", + " Please see the sessions subcommand help for detailed usage information.", + }) + + case "cancel": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary sessions cancel [options] [args]", + "", + " Cancel the session specified by ID. If the session is already canceled, this command succeeds with no effect. Example:", + "", + ` $ boundary sessions cancel -id s_1234567890`, + "", + "", + }) + + default: + helpStr = helpMap["base"]() + } + + return helpStr + c.Flags().Help() +} + +func executeExtraActionsImpl(c *Command, origResult api.GenericResult, origError error, sessionClient *sessions.Client, version uint32, opts []sessions.Option) (api.GenericResult, error) { + switch c.Func { + case "cancel": + return sessionClient.Cancel(c.Context, c.FlagId, version, opts...) + } + return origResult, origError +} + +func (c *Command) printListTable(items []*sessions.Session) string { + if len(items) == 0 { + return "No sessions found" + } + var output []string + output = []string{ + "", + "Session information:", + } + for i, item := range items { + if i > 0 { + output = append(output, "") + } + if true { + output = append(output, + fmt.Sprintf(" ID: %s", item.Id), + ) + } + if c.FlagRecursive { + output = append(output, + fmt.Sprintf(" Scope ID: %s", item.Scope.Id), + ) + } + if true { + output = append(output, + fmt.Sprintf(" Status: %s", item.Status), + fmt.Sprintf(" Created Time: %s", item.CreatedTime.Local().Format(time.RFC1123)), + fmt.Sprintf(" Expiration Time: %s", item.ExpirationTime.Local().Format(time.RFC1123)), + fmt.Sprintf(" Updated Time: %s", item.UpdatedTime.Local().Format(time.RFC1123)), + fmt.Sprintf(" User ID: %s", item.UserId), + fmt.Sprintf(" Target ID: %s", item.TargetId), + ) + } + if len(item.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, item.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(in *sessions.Session) string { + nonAttributeMap := map[string]interface{}{ + "ID": in.Id, + "Target ID": in.TargetId, + "Created Time": in.CreatedTime.Local().Format(time.RFC1123), + "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), + "Expiration Time": in.ExpirationTime.Local().Format(time.RFC1123), + "Version": in.Version, + "Type": in.Type, + "Auth Token ID": in.AuthTokenId, + "User ID": in.UserId, + "Host Set ID": in.HostSetId, + "Host ID": in.HostId, + "Endpoint": in.Endpoint, + "Status": in.Status, + } + if len(strings.TrimSpace(in.TerminationReason)) > 0 { + nonAttributeMap["Termination Reason"] = in.TerminationReason + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) + + var statesMaps []map[string]interface{} + if len(in.States) > 0 { + for _, state := range in.States { + m := map[string]interface{}{ + "Status": state.Status, + "Start Time": state.StartTime.Local().Format(time.RFC1123), + } + if !state.EndTime.IsZero() { + m["End Time"] = state.EndTime.Local().Format(time.RFC1123) + } + statesMaps = append(statesMaps, m) + } + if l := len("Start Time"); l > maxLength { + maxLength = l + } + } + + var workerInfoMaps []map[string]interface{} + if len(in.WorkerInfo) > 0 { + for _, wi := range in.WorkerInfo { + m := map[string]interface{}{ + "Address": wi.Address, + } + workerInfoMaps = append(workerInfoMaps, m) + } + if l := len("Address"); l > maxLength { + maxLength = l + } + } + + ret := []string{ + "", + "Session information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + "", + " Scope:", + base.ScopeInfoForOutput(in.Scope, maxLength), + } + + if len(in.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, in.AuthorizedActions), + ) + } + + if len(in.States) > 0 { + ret = append(ret, + "", + " States:", + ) + for _, m := range statesMaps { + ret = append(ret, + base.WrapMap(4, maxLength, m), + "", + ) + } + } + + if len(in.WorkerInfo) > 0 { + ret = append(ret, + fmt.Sprintf(" Worker Info: %s", ""), + ) + for _, m := range workerInfoMaps { + ret = append(ret, + base.WrapMap(4, maxLength, m), + "", + ) + } + } + + return base.WrapForHelpText(ret) +} diff --git a/internal/cmd/commands/sessionscmd/sessions.gen.go b/internal/cmd/commands/sessionscmd/sessions.gen.go new file mode 100644 index 000000000..bbcaa55a3 --- /dev/null +++ b/internal/cmd/commands/sessionscmd/sessions.gen.go @@ -0,0 +1,259 @@ +// Code generated by "make api"; DO NOT EDIT. +package sessionscmd + +import ( + "fmt" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/sessions" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "session" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("session") + + switch c.Func { + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = c.extraHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "read": {"id"}, + + "list": {"scope-id", "recursive"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "session", flagsMap[c.Func]) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "session" + switch c.Func { + case "list": + c.plural = "sessions" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []sessions.Option + + if strutil.StrListContains(flagsMap[c.Func], "scope-id") { + switch c.Func { + case "list": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + sessionsClient := sessions.NewClient(client) + + switch c.FlagRecursive { + case true: + opts = append(opts, sessions.WithRecursive(true)) + } + + var version uint32 + + switch c.Func { + case "cancel": + switch c.FlagVersion { + case 0: + opts = append(opts, sessions.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } + + if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "read": + result, err = sessionsClient.Read(c.Context, c.FlagId, opts...) + + case "list": + listResult, err = sessionsClient.List(c.Context, c.FlagScopeId, opts...) + + } + + result, err = executeExtraActions(c, result, err, sessionsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + case "list": + listedItems := listResult.GetItems().([]*sessions.Session) + switch base.Format(c.UI) { + case "json": + switch { + + case len(listedItems) == 0: + c.UI.Output("null") + + default: + b, err := base.JsonFormatter{}.Format(listedItems) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + case "table": + c.UI.Output(c.printListTable(listedItems)) + } + + return 0 + } + + item := result.GetItem().(*sessions.Session) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *[]sessions.Option) int { return 0 } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *sessions.Client, _ uint32, _ []sessions.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/targets/funcs.go b/internal/cmd/commands/targets/funcs.go deleted file mode 100644 index 016e2e76b..000000000 --- a/internal/cmd/commands/targets/funcs.go +++ /dev/null @@ -1,169 +0,0 @@ -package targets - -import ( - "fmt" - "strings" - "time" - - "github.com/hashicorp/boundary/api/scopes" - "github.com/hashicorp/boundary/api/targets" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/mitchellh/go-wordwrap" -) - -func hostSetSynopsisFunc(inFunc string) string { - var in string - switch { - case strings.HasPrefix(inFunc, "add"): - in = "Add host sets to" - case strings.HasPrefix(inFunc, "set"): - in = "Set the full contents of the host sets on" - case strings.HasPrefix(inFunc, "remove"): - in = "Remove host sets from" - } - return wordwrap.WrapString(fmt.Sprintf("%s a target", in), base.TermWidth) -} - -func generateTargetTableOutput(in *targets.Target) string { - nonAttributeMap := map[string]interface{}{ - "ID": in.Id, - "Version": in.Version, - "Type": in.Type, - "Created Time": in.CreatedTime.Local().Format(time.RFC1123), - "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), - "Session Connection Limit": in.SessionConnectionLimit, - "Session Max Seconds": in.SessionMaxSeconds, - } - - if in.Name != "" { - nonAttributeMap["Name"] = in.Name - } - if in.Description != "" { - nonAttributeMap["Description"] = in.Description - } - if in.WorkerFilter != "" { - nonAttributeMap["Worker Filter"] = in.WorkerFilter - } - - maxLength := base.MaxAttributesLength(nonAttributeMap, in.Attributes, keySubstMap) - - var hostSetMaps []map[string]interface{} - if len(in.HostSets) > 0 { - for _, set := range in.HostSets { - m := map[string]interface{}{ - "ID": set.Id, - "Host Catalog ID": set.HostCatalogId, - } - hostSetMaps = append(hostSetMaps, m) - } - if l := len("Host Catalog ID"); l > maxLength { - maxLength = l - } - } - - ret := []string{ - "", - "Target information:", - base.WrapMap(2, maxLength+2, nonAttributeMap), - "", - " Scope:", - base.ScopeInfoForOutput(in.Scope, maxLength), - } - - if len(in.AuthorizedActions) > 0 { - ret = append(ret, - "", - " Authorized Actions:", - base.WrapSlice(4, in.AuthorizedActions), - ) - } - - if len(in.HostSets) > 0 { - ret = append(ret, - "", - " Host Sets:", - ) - for _, m := range hostSetMaps { - ret = append(ret, - base.WrapMap(4, maxLength, m), - "", - ) - } - } - - if len(in.Attributes) > 0 { - ret = append(ret, - " Attributes:", - base.WrapMap(4, maxLength, in.Attributes), - ) - } - - return base.WrapForHelpText(ret) -} - -func generateAuthorizationTableOutput(in *targets.SessionAuthorization) string { - var ret []string - - nonAttributeMap := map[string]interface{}{ - "Session ID": in.SessionId, - "Target ID": in.TargetId, - "Scope ID": in.Scope.Id, - "User ID": in.UserId, - "Host ID": in.HostId, - "Endpoint": in.Endpoint, - "Created Time": in.CreatedTime.Local().Format(time.RFC1123), - "Type": in.Type, - "Authorization Token": in.AuthorizationToken, - } - - maxLength := 0 - for k := range nonAttributeMap { - if len(k) > maxLength { - maxLength = len(k) - } - } - - ret = append(ret, "", "Target information:") - - ret = append(ret, - // We do +2 because there is another +2 offset for host sets below - base.WrapMap(2, maxLength+2, nonAttributeMap), - ) - - return base.WrapForHelpText(ret) -} - -var keySubstMap = map[string]string{ - "default_port": "Default Port", -} - -func exampleOutput() string { - in := &targets.Target{ - Id: "ttcp_1234567890", - ScopeId: "global", - Scope: &scopes.ScopeInfo{ - Id: "global", - }, - Name: "foo", - Description: "The bar of foos", - CreatedTime: time.Now().Add(-5 * time.Minute), - UpdatedTime: time.Now(), - Version: 3, - Type: "tcp", - HostSetIds: []string{"hsst_1234567890", "hsst_0987654321"}, - HostSets: []*targets.HostSet{ - { - Id: "hsst_1234567890", - HostCatalogId: "hcst_1234567890", - }, - { - Id: "hsst_0987654321", - HostCatalogId: "hcst_1234567890", - }, - }, - Attributes: map[string]interface{}{ - "default_port": 22, - }, - } - return generateTargetTableOutput(in) -} diff --git a/internal/cmd/commands/targets/target.go b/internal/cmd/commands/targets/target.go deleted file mode 100644 index ecfb82126..000000000 --- a/internal/cmd/commands/targets/target.go +++ /dev/null @@ -1,498 +0,0 @@ -package targets - -import ( - "fmt" - "net/http" - "os" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/targets" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/internal/types/resource" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*Command)(nil) - _ cli.CommandAutocomplete = (*Command)(nil) -) - -type Command struct { - *base.Command - - Func string - - flagHostSets []string - flagHostId string -} - -func (c *Command) Synopsis() string { - switch c.Func { - case "add-host-sets", "set-host-sets", "remove-host-sets": - return hostSetSynopsisFunc(c.Func) - case "authorize-session": - return "Request session authorization against the target" - default: - return common.SynopsisFunc(c.Func, "target") - } -} - -var flagsMap = map[string][]string{ - "authorize-session": {"id", "host-id"}, - "read": {"id"}, - "delete": {"id"}, - "list": {"scope-id", "recursive"}, - "add-host-sets": {"id", "host-set", "version"}, - "remove-host-sets": {"id", "host-set", "version"}, - "set-host-sets": {"id", "host-set", "version"}, -} - -func (c *Command) Help() string { - helpMap := common.HelpMap("target") - var helpStr string - switch c.Func { - case "": - return base.WrapForHelpText([]string{ - "Usage: boundary targets [sub command] [options] [args]", - "", - " This command allows operations on Boundary target resources. Example:", - "", - " Read a target:", - "", - ` $ boundary targets read -id ttcp_1234567890`, - "", - " Please see the targets subcommand help for detailed usage information.", - }) - case "create": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary targets create [type] [sub command] [options] [args]", - "", - " This command allows create operations on Boundary target resources. Example:", - "", - " Create a tcp-type target:", - "", - ` $ boundary targets create tcp -name prodops -description "For ProdOps usage"`, - "", - " Please see the typed subcommand help for detailed usage information.", - }) - case "update": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary targets update [type] [sub command] [options] [args]", - "", - " This command allows update operations on Boundary target resources. Example:", - "", - " Update a tcp-type target:", - "", - ` $ boundary targets update tcp -id ttcp_1234567890 -name devops -description "For DevOps usage"`, - "", - " Please see the typed subcommand help for detailed usage information.", - }) - case "add-host-sets": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary target add-host-sets [options] [args]", - "", - " This command allows adding host-set resources to target resources. Example:", - "", - " Add host-set resources to a tcp-type target:", - "", - ` $ boundary targets add-host-sets -id ttcp_1234567890 -host-set hsst_1234567890 -host-set hsst_0987654321`, - "", - "", - }) - case "remove-host-sets": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary target remove-host-sets [options] [args]", - "", - " This command allows removing host-set resources from target resources. Example:", - "", - " Remove host-set resources from a tcp-type target:", - "", - ` $ boundary targets add-host-sets -id ttcp_1234567890 -host hsst_1234567890 -host-set hsst_0987654321`, - "", - "", - }) - case "set-host-sets": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary target set-host-sets [options] [args]", - "", - " This command allows setting the complete set of host-set resources on a target resource. Example:", - "", - " Set host-set resources on a tcp-type target:", - "", - ` $ boundary targets set-host-sets -id ttcp_1234567890 -host-set hsst_1234567890`, - "", - "", - }) - case "authorize-session": - helpStr = base.WrapForHelpText([]string{ - "Usage: boundary target authorize-session [options] [args]", - "", - " This command allows fetching session authorization credentials against a target. Example:", - "", - " Request an authorized session using the target ID:", - "", - ` $ boundary targets authorize-session -id ttcp_1234567890`, - "", - " Request an authorized session using the scope ID and target name:", - "", - ` $ boundary targets authorize-session -scope-id o_1234567890 -name prod-ssh`, - "", - "", - }) - default: - helpStr = helpMap[c.Func]() - } - return helpStr + c.Flags().Help() -} - -func (c *Command) Flags() *base.FlagSets { - if len(flagsMap[c.Func]) == 0 { - return c.FlagSet(base.FlagSetNone) - } - - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, resource.Target.String(), flagsMap[c.Func]) - - for _, name := range flagsMap[c.Func] { - switch name { - case "host-set": - f.StringSliceVar(&base.StringSliceVar{ - Name: "host-set", - Target: &c.flagHostSets, - Usage: "The host-set resources to add, remove, or set. May be specified multiple times.", - }) - case "host-id": - f.StringVar(&base.StringVar{ - Name: "host-id", - Target: &c.flagHostId, - Usage: "The ID of a specific host to connect to out of the hosts from the target's host sets. If not specified, one is chosen at random.", - }) - } - } - - if c.Func == "authorize-session" { - flagsMap[c.Func] = append(flagsMap[c.Func], "name", "scope-id", "scope-name") - - // We put these here to change usage and change defaults (don't want - // them populated by default) - f.StringVar(&base.StringVar{ - Name: "name", - Target: &c.FlagName, - Usage: "Target name, if authorizing the session via scope parameters and target name.", - }) - - f.StringVar(&base.StringVar{ - Name: "scope-id", - Target: &c.FlagScopeId, - EnvVar: "BOUNDARY_SCOPE_ID", - Completion: complete.PredictAnything, - Usage: "Target scope ID, if authorizing the session via scope parameters and target name. Mutually exclusive with -scope-name.", - }) - - f.StringVar(&base.StringVar{ - Name: "scope-name", - Target: &c.FlagScopeName, - EnvVar: "BOUNDARY_SCOPE_NAME", - Completion: complete.PredictAnything, - Usage: "Target scope name, if authorizing the session via scope parameters and target name. Mutually exclusive with -scope-id.", - }) - } - - return set -} - -func (c *Command) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *Command) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *Command) Run(args []string) int { - if os.Getenv("BOUNDARY_EXAMPLE_CLI_OUTPUT") != "" { - c.UI.Output(exampleOutput()) - return 0 - } - - switch c.Func { - case "", "create", "update": - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - var opts []targets.Option - - if strutil.StrListContains(flagsMap[c.Func], "id") { - switch c.Func { - case "authorize-session": - if c.FlagId == "" && - (c.FlagName == "" || - (c.FlagScopeId == "" && c.FlagScopeName == "")) { - c.UI.Error("ID was not passed in, but no combination of name and scope ID/name was passed in either") - return 1 - } - if c.FlagId != "" && - (c.FlagName != "" || c.FlagScopeId != "" || c.FlagScopeName != "") { - c.UI.Error("Cannot specify a target ID and also other lookup parameters") - return 1 - } - default: - if c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - } - } - if strutil.StrListContains(flagsMap[c.Func], "scope-id") { - switch c.Func { - case "list": - if c.FlagScopeId == "" { - c.UI.Error("Scope ID must be passed in via -scope-id") - return 1 - } - default: - if c.FlagScopeId != "" { - opts = append(opts, targets.WithScopeId(c.FlagScopeId)) - } - } - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - switch c.FlagName { - case "": - case "null": - opts = append(opts, targets.DefaultName()) - default: - opts = append(opts, targets.WithName(c.FlagName)) - } - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, targets.DefaultDescription()) - default: - opts = append(opts, targets.WithDescription(c.FlagDescription)) - } - switch c.FlagRecursive { - case true: - opts = append(opts, targets.WithRecursive(true)) - } - - switch c.FlagScopeName { - case "": - default: - opts = append(opts, targets.WithScopeName(c.FlagScopeName)) - } - - hostSets := c.flagHostSets - switch c.Func { - case "add-host-sets", "remove-host-sets": - if len(c.flagHostSets) == 0 { - c.UI.Error("No host-sets supplied via -host-set") - return 1 - } - - case "set-host-sets": - switch len(c.flagHostSets) { - case 0: - c.UI.Error("No host-sets supplied via -host-set") - return 1 - case 1: - if c.flagHostSets[0] == "null" { - hostSets = nil - } - } - case "authorize-session": - if len(c.flagHostId) != 0 { - opts = append(opts, targets.WithHostId(c.flagHostId)) - } - } - - // Perform check-and-set when needed - var version uint32 - switch c.Func { - case "add-host-sets", "remove-host-sets", "set-host-sets": - switch c.FlagVersion { - case 0: - opts = append(opts, targets.WithAutomaticVersioning(true)) - default: - version = uint32(c.FlagVersion) - } - default: - // The only other one that needs it is update, handled by the static - // file - } - - targetClient := targets.NewClient(client) - - existed := true - var result api.GenericResult - var listResult api.GenericListResult - var sar *targets.SessionAuthorizationResult - - switch c.Func { - case "read": - result, err = targetClient.Read(c.Context, c.FlagId, opts...) - case "delete": - _, err = targetClient.Delete(c.Context, c.FlagId, opts...) - if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { - existed = false - err = nil - } - case "list": - listResult, err = targetClient.List(c.Context, c.FlagScopeId, opts...) - case "add-host-sets": - result, err = targetClient.AddHostSets(c.Context, c.FlagId, version, hostSets, opts...) - case "remove-host-sets": - result, err = targetClient.RemoveHostSets(c.Context, c.FlagId, version, hostSets, opts...) - case "set-host-sets": - result, err = targetClient.SetHostSets(c.Context, c.FlagId, version, hostSets, opts...) - case "authorize-session": - sar, err = targetClient.AuthorizeSession(c.Context, c.FlagId, opts...) - } - - plural := "target" - switch c.Func { - case "list": - plural = "targets" - case "authorize-session": - plural = "a session against target" - } - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - switch c.Func { - case "delete": - switch base.Format(c.UI) { - case "json": - c.UI.Output("null") - case "table": - output := "The delete operation completed successfully" - switch existed { - case true: - output += "." - default: - output += ", however the resource did not exist at the time." - } - c.UI.Output(output) - } - return 0 - - case "list": - listedTargets := listResult.GetItems().([]*targets.Target) - switch base.Format(c.UI) { - case "json": - if len(listedTargets) == 0 { - c.UI.Output("null") - return 0 - } - b, err := base.JsonFormatter{}.Format(listedTargets) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - - case "table": - if len(listedTargets) == 0 { - c.UI.Output("No targets found") - return 0 - } - var output []string - output = []string{ - "", - "Target information:", - } - for i, m := range listedTargets { - if i > 0 { - output = append(output, "") - } - if true { - output = append(output, - fmt.Sprintf(" ID: %s", m.Id), - ) - } - if c.FlagRecursive { - output = append(output, - fmt.Sprintf(" Scope ID: %s", m.Scope.Id), - ) - } - if true { - output = append(output, - fmt.Sprintf(" Version: %d", m.Version), - fmt.Sprintf(" Type: %s", m.Type), - ) - } - if m.Name != "" { - output = append(output, - fmt.Sprintf(" Name: %s", m.Name), - ) - } - if m.Description != "" { - output = append(output, - fmt.Sprintf(" Description: %s", m.Description), - ) - } - if len(m.AuthorizedActions) > 0 { - output = append(output, - " Authorized Actions:", - base.WrapSlice(6, m.AuthorizedActions), - ) - } - } - c.UI.Output(base.WrapForHelpText(output)) - } - return 0 - - case "authorize-session": - sa := sar.GetItem().(*targets.SessionAuthorization) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateAuthorizationTableOutput(sa)) - case "json": - b, err := base.JsonFormatter{}.Format(sa) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - return 0 - } - - target := result.GetItem().(*targets.Target) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateTargetTableOutput(target)) - case "json": - b, err := base.JsonFormatter{}.Format(target) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/targets/tcp.go b/internal/cmd/commands/targets/tcp.go deleted file mode 100644 index 6c8d0daba..000000000 --- a/internal/cmd/commands/targets/tcp.go +++ /dev/null @@ -1,268 +0,0 @@ -package targets - -import ( - "fmt" - "net/textproto" - "strconv" - "time" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/targets" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/hashicorp/go-bexpr" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*TcpCommand)(nil) - _ cli.CommandAutocomplete = (*TcpCommand)(nil) -) - -type TcpCommand struct { - *base.Command - - Func string - flagDefaultPort string - flagSessionMaxSeconds string - flagSessionConnectionLimit string - flagWorkerFilter string -} - -func (c *TcpCommand) Synopsis() string { - return fmt.Sprintf("%s a tcp-type target", textproto.CanonicalMIMEHeaderKey(c.Func)) -} - -var tcpFlagsMap = map[string][]string{ - "create": {"scope-id", "name", "description", "default-port", "session-max-seconds", "session-connection-limit", "worker-filter"}, - "update": {"id", "name", "description", "version", "default-port", "session-max-seconds", "session-connection-limit", "worker-filter"}, -} - -func (c *TcpCommand) Help() string { - var info string - switch c.Func { - case "create": - info = base.WrapForHelpText([]string{ - "Usage: boundary targets create tcp [options] [args]", - "", - " Create a tcp-type target. Example:", - "", - ` $ boundary targets create tcp -name prodops -description "Tcp target for ProdOps"`, - "", - "", - }) - - case "update": - info = base.WrapForHelpText([]string{ - "Usage: boundary targets tcp update [options] [args]", - "", - " Update a tcp-type target given its ID. Example:", - "", - ` $ boundary targets tcp update -id ttcp_1234567890 -name "devops" -description "Tcp target for DevOps"`, - "", - "", - }) - } - return info + c.Flags().Help() -} - -func (c *TcpCommand) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - common.PopulateCommonFlags(c.Command, f, "tcp-type target", tcpFlagsMap[c.Func]) - - for _, name := range tcpFlagsMap[c.Func] { - switch name { - case "default-port": - f.StringVar(&base.StringVar{ - Name: "default-port", - Target: &c.flagDefaultPort, - Usage: "The default port to set on the target.", - }) - case "session-max-seconds": - f.StringVar(&base.StringVar{ - Name: "session-max-seconds", - Target: &c.flagSessionMaxSeconds, - Usage: `The maximum lifetime of the session, including all connections. Can be specified as an integer number of seconds or a duration string.`, - }) - case "session-connection-limit": - f.StringVar(&base.StringVar{ - Name: "session-connection-limit", - Target: &c.flagSessionConnectionLimit, - Usage: "The maximum number of connections allowed for a session. -1 means unlimited.", - }) - case "worker-filter": - f.StringVar(&base.StringVar{ - Name: "worker-filter", - Target: &c.flagWorkerFilter, - Usage: "A boolean expression to filter which workers can handle sessions for this target.", - }) - } - } - - return set -} - -func (c *TcpCommand) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *TcpCommand) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *TcpCommand) Run(args []string) int { - if c.Func == "" { - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(tcpFlagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(tcpFlagsMap[c.Func], "scope-id") && c.FlagScopeId == "" { - c.UI.Error("Scope ID must be passed in via -scope-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []targets.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, targets.DefaultName()) - default: - opts = append(opts, targets.WithName(c.FlagName)) - } - - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, targets.DefaultDescription()) - default: - opts = append(opts, targets.WithDescription(c.FlagDescription)) - } - - switch c.flagDefaultPort { - case "": - case "null": - opts = append(opts, targets.DefaultTcpTargetDefaultPort()) - default: - port, err := strconv.ParseUint(c.flagDefaultPort, 10, 32) - if err != nil { - c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagDefaultPort, err)) - return 1 - } - opts = append(opts, targets.WithTcpTargetDefaultPort(uint32(port))) - } - - switch c.flagSessionMaxSeconds { - case "": - case "null": - opts = append(opts, targets.DefaultSessionMaxSeconds()) - default: - var final uint32 - dur, err := strconv.ParseUint(c.flagSessionMaxSeconds, 10, 32) - if err == nil { - final = uint32(dur) - } else { - dur, err := time.ParseDuration(c.flagSessionMaxSeconds) - if err != nil { - c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagSessionMaxSeconds, err)) - return 1 - } - final = uint32(dur.Seconds()) - } - opts = append(opts, targets.WithSessionMaxSeconds(final)) - } - - switch c.flagSessionConnectionLimit { - case "": - case "null": - opts = append(opts, targets.DefaultSessionConnectionLimit()) - default: - limit, err := strconv.ParseInt(c.flagSessionConnectionLimit, 10, 32) - if err != nil { - c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagSessionConnectionLimit, err)) - return 1 - } - opts = append(opts, targets.WithSessionConnectionLimit(int32(limit))) - } - - switch c.flagWorkerFilter { - case "": - case "null": - opts = append(opts, targets.DefaultWorkerFilter()) - default: - if _, err := bexpr.CreateEvaluator(c.flagWorkerFilter); err != nil { - c.UI.Error(fmt.Sprintf("Unable to successfully parse filter expression: %s", err)) - return 1 - } - opts = append(opts, targets.WithWorkerFilter(c.flagWorkerFilter)) - } - - targetClient := targets.NewClient(client) - - // Perform check-and-set when needed - var version uint32 - switch c.Func { - case "create": - // These don't update so don't need the existing version - default: - switch c.FlagVersion { - case 0: - opts = append(opts, targets.WithAutomaticVersioning(true)) - default: - version = uint32(c.FlagVersion) - } - } - - var result api.GenericResult - - switch c.Func { - case "create": - result, err = targetClient.Create(c.Context, "tcp", c.FlagScopeId, opts...) - case "update": - result, err = targetClient.Update(c.Context, c.FlagId, version, opts...) - } - - plural := "tcp-type target" - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - target := result.GetItem().(*targets.Target) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateTargetTableOutput(target)) - case "json": - b, err := base.JsonFormatter{}.Format(target) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/targetscmd/funcs.go b/internal/cmd/commands/targetscmd/funcs.go new file mode 100644 index 000000000..becf0848b --- /dev/null +++ b/internal/cmd/commands/targetscmd/funcs.go @@ -0,0 +1,492 @@ +package targetscmd + +import ( + "fmt" + "strings" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/scopes" + "github.com/hashicorp/boundary/api/targets" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/go-wordwrap" + "github.com/posener/complete" +) + +func init() { + extraActionsFlagsMapFunc = extraActionsFlagsMapFuncImpl + extraSynopsisFunc = extraSynopsisFuncImpl + extraFlagsFunc = extraFlagsFuncImpl + extraFlagsHandlingFunc = extraFlagsHandlingFuncImpl + executeExtraActions = executeExtraActionsImpl + printCustomActionOutput = printCustomActionOutputImpl +} + +type extraCmdVars struct { + flagHostSets []string + flagHostId string + sar *targets.SessionAuthorizationResult +} + +func extraActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "authorize-session": {"id", "host-id"}, + "add-host-sets": {"id", "host-set", "version"}, + "remove-host-sets": {"id", "host-set", "version"}, + "set-host-sets": {"id", "host-set", "version"}, + } +} + +func extraSynopsisFuncImpl(c *Command) string { + switch c.Func { + case "add-host-sets", "set-host-sets", "remove-host-sets": + var in string + switch { + case strings.HasPrefix(c.Func, "add"): + in = "Add host sets to" + case strings.HasPrefix(c.Func, "set"): + in = "Set the full contents of the host sets on" + case strings.HasPrefix(c.Func, "remove"): + in = "Remove host sets from" + } + return wordwrap.WrapString(fmt.Sprintf("%s a target", in), base.TermWidth) + + case "authorize-session": + return "Request session authorization against the target" + + default: + return "" + } +} + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "": + return base.WrapForHelpText([]string{ + "Usage: boundary targets [sub command] [options] [args]", + "", + " This command allows operations on Boundary target resources. Example:", + "", + " Read a target:", + "", + ` $ boundary targets read -id ttcp_1234567890`, + "", + " Please see the targets subcommand help for detailed usage information.", + }) + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary targets create [type] [sub command] [options] [args]", + "", + " This command allows create operations on Boundary target resources. Example:", + "", + " Create a tcp-type target:", + "", + ` $ boundary targets create tcp -name prodops -description "For ProdOps usage"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary targets update [type] [sub command] [options] [args]", + "", + " This command allows update operations on Boundary target resources. Example:", + "", + " Update a tcp-type target:", + "", + ` $ boundary targets update tcp -id ttcp_1234567890 -name devops -description "For DevOps usage"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + case "add-host-sets": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary target add-host-sets [options] [args]", + "", + " This command allows adding host-set resources to target resources. Example:", + "", + " Add host-set resources to a tcp-type target:", + "", + ` $ boundary targets add-host-sets -id ttcp_1234567890 -host-set hsst_1234567890 -host-set hsst_0987654321`, + "", + "", + }) + case "remove-host-sets": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary target remove-host-sets [options] [args]", + "", + " This command allows removing host-set resources from target resources. Example:", + "", + " Remove host-set resources from a tcp-type target:", + "", + ` $ boundary targets add-host-sets -id ttcp_1234567890 -host hsst_1234567890 -host-set hsst_0987654321`, + "", + "", + }) + case "set-host-sets": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary target set-host-sets [options] [args]", + "", + " This command allows setting the complete set of host-set resources on a target resource. Example:", + "", + " Set host-set resources on a tcp-type target:", + "", + ` $ boundary targets set-host-sets -id ttcp_1234567890 -host-set hsst_1234567890`, + "", + "", + }) + case "authorize-session": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary target authorize-session [options] [args]", + "", + " This command allows fetching session authorization credentials against a target. Example:", + "", + " Request an authorized session using the target ID:", + "", + ` $ boundary targets authorize-session -id ttcp_1234567890`, + "", + " Request an authorized session using the scope ID and target name:", + "", + ` $ boundary targets authorize-session -scope-id o_1234567890 -name prod-ssh`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} + +func extraFlagsFuncImpl(c *Command, _ *base.FlagSets, f *base.FlagSet) { + for _, name := range flagsMap[c.Func] { + switch name { + case "host-set": + f.StringSliceVar(&base.StringSliceVar{ + Name: "host-set", + Target: &c.flagHostSets, + Usage: "The host-set resources to add, remove, or set. May be specified multiple times.", + }) + case "host-id": + f.StringVar(&base.StringVar{ + Name: "host-id", + Target: &c.flagHostId, + Usage: "The ID of a specific host to connect to out of the hosts from the target's host sets. If not specified, one is chosen at random.", + }) + } + } + + if c.Func == "authorize-session" { + flagsMap[c.Func] = append(flagsMap[c.Func], "name", "scope-id", "scope-name") + + // We put these here to change usage and change defaults (don't want + // them populated by default). Otherwise the common flags function will + // populate these values, and they can't be changed after-the-fact. + f.StringVar(&base.StringVar{ + Name: "name", + Target: &c.FlagName, + Usage: "Target name, if authorizing the session via scope parameters and target name.", + }) + + f.StringVar(&base.StringVar{ + Name: "scope-id", + Target: &c.FlagScopeId, + EnvVar: "BOUNDARY_SCOPE_ID", + Completion: complete.PredictAnything, + Usage: "Target scope ID, if authorizing the session via scope parameters and target name. Mutually exclusive with -scope-name.", + }) + + f.StringVar(&base.StringVar{ + Name: "scope-name", + Target: &c.FlagScopeName, + EnvVar: "BOUNDARY_SCOPE_NAME", + Completion: complete.PredictAnything, + Usage: "Target scope name, if authorizing the session via scope parameters and target name. Mutually exclusive with -scope-id.", + }) + } +} + +func extraFlagsHandlingFuncImpl(c *Command, opts *[]targets.Option) int { + // This is custom logic because of the authorized-session handling. If we + // support all resources to be looked up by name + scope info we can + // eventually graduate this out to the main template. + if strutil.StrListContains(flagsMap[c.Func], "id") { + switch c.Func { + case "authorize-session": + if c.FlagId == "" && + (c.FlagName == "" || + (c.FlagScopeId == "" && c.FlagScopeName == "")) { + c.UI.Error("ID was not passed in, but no combination of name and scope ID/name was passed in either") + return 1 + } + if c.FlagId != "" && + (c.FlagName != "" || c.FlagScopeId != "" || c.FlagScopeName != "") { + c.UI.Error("Cannot specify a target ID and also other lookup parameters") + return 1 + } + default: + if c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + } + } + + if strutil.StrListContains(flagsMap[c.Func], "scope-id") && c.FlagScopeId != "" { + *opts = append(*opts, targets.WithScopeId(c.FlagScopeId)) + } + + switch c.Func { + case "add-host-sets", "remove-host-sets": + if len(c.flagHostSets) == 0 { + c.UI.Error("No host-sets supplied via -host-set") + return 1 + } + + case "set-host-sets": + switch len(c.flagHostSets) { + case 0: + c.UI.Error("No host-sets supplied via -host-set") + return 1 + case 1: + if c.flagHostSets[0] == "null" { + c.flagHostSets = nil + } + } + + case "authorize-session": + if len(c.flagHostId) != 0 { + *opts = append(*opts, targets.WithHostId(c.flagHostId)) + } + } + + return 0 +} + +func executeExtraActionsImpl(c *Command, origResult api.GenericResult, origError error, targetClient *targets.Client, version uint32, opts []targets.Option) (api.GenericResult, error) { + switch c.Func { + case "add-host-sets": + return targetClient.AddHostSets(c.Context, c.FlagId, version, c.flagHostSets, opts...) + case "remove-host-sets": + return targetClient.RemoveHostSets(c.Context, c.FlagId, version, c.flagHostSets, opts...) + case "set-host-sets": + return targetClient.SetHostSets(c.Context, c.FlagId, version, c.flagHostSets, opts...) + case "authorize-session": + var err error + c.plural = "a session against target" + c.sar, err = targetClient.AuthorizeSession(c.Context, c.FlagId, opts...) + return nil, err + } + return origResult, origError +} + +func (c *Command) printListTable(items []*targets.Target) string { + if len(items) == 0 { + return "No targets found" + } + var output []string + output = []string{ + "", + "Target information:", + } + for i, m := range items { + if i > 0 { + output = append(output, "") + } + if true { + output = append(output, + fmt.Sprintf(" ID: %s", m.Id), + ) + } + if c.FlagRecursive { + output = append(output, + fmt.Sprintf(" Scope ID: %s", m.Scope.Id), + ) + } + if true { + output = append(output, + fmt.Sprintf(" Version: %d", m.Version), + fmt.Sprintf(" Type: %s", m.Type), + ) + } + if m.Name != "" { + output = append(output, + fmt.Sprintf(" Name: %s", m.Name), + ) + } + if m.Description != "" { + output = append(output, + fmt.Sprintf(" Description: %s", m.Description), + ) + } + if len(m.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, m.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(item *targets.Target) string { + nonAttributeMap := map[string]interface{}{ + "ID": item.Id, + "Version": item.Version, + "Type": item.Type, + "Created Time": item.CreatedTime.Local().Format(time.RFC1123), + "Updated Time": item.UpdatedTime.Local().Format(time.RFC1123), + "Session Connection Limit": item.SessionConnectionLimit, + "Session Max Seconds": item.SessionMaxSeconds, + } + + if item.Name != "" { + nonAttributeMap["Name"] = item.Name + } + if item.Description != "" { + nonAttributeMap["Description"] = item.Description + } + if item.WorkerFilter != "" { + nonAttributeMap["Worker Filter"] = item.WorkerFilter + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap) + + var hostSetMaps []map[string]interface{} + if len(item.HostSets) > 0 { + for _, set := range item.HostSets { + m := map[string]interface{}{ + "ID": set.Id, + "Host Catalog ID": set.HostCatalogId, + } + hostSetMaps = append(hostSetMaps, m) + } + if l := len("Host Catalog ID"); l > maxLength { + maxLength = l + } + } + + ret := []string{ + "", + "Target information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + "", + " Scope:", + base.ScopeInfoForOutput(item.Scope, maxLength), + } + + if len(item.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, item.AuthorizedActions), + ) + } + + ret = append(ret, + "", + ) + + if len(item.HostSets) > 0 { + ret = append(ret, + " Host Sets:", + ) + for _, m := range hostSetMaps { + ret = append(ret, + base.WrapMap(4, maxLength, m), + "", + ) + } + } + + if len(item.Attributes) > 0 { + ret = append(ret, + " Attributes:", + base.WrapMap(4, maxLength, item.Attributes), + ) + } + + return base.WrapForHelpText(ret) +} + +func printCustomActionOutputImpl(c *Command) (bool, error) { + switch c.Func { + case "authorize-session": + item := c.sar.GetItem().(*targets.SessionAuthorization) + + switch base.Format(c.UI) { + case "table": + var ret []string + + nonAttributeMap := map[string]interface{}{ + "Session ID": item.SessionId, + "Target ID": item.TargetId, + "Scope ID": item.Scope.Id, + "User ID": item.UserId, + "Host ID": item.HostId, + "Endpoint": item.Endpoint, + "Created Time": item.CreatedTime.Local().Format(time.RFC1123), + "Type": item.Type, + "Authorization Token": item.AuthorizationToken, + } + + maxLength := 0 + for k := range nonAttributeMap { + if len(k) > maxLength { + maxLength = len(k) + } + } + + ret = append(ret, "", "Target information:") + ret = append(ret, + // We do +2 because there is another +2 offset for host sets below + base.WrapMap(2, maxLength+2, nonAttributeMap), + ) + c.UI.Output(base.WrapForHelpText(ret)) + return true, nil + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + return false, fmt.Errorf("Error formatting as JSON: %w", err) + } + c.UI.Output(string(b)) + return true, nil + } + } + + return false, nil +} + +var keySubstMap = map[string]string{ + "default_port": "Default Port", +} + +func exampleOutput() string { + in := &targets.Target{ + Id: "ttcp_1234567890", + ScopeId: "global", + Scope: &scopes.ScopeInfo{ + Id: "global", + }, + Name: "foo", + Description: "The bar of foos", + CreatedTime: time.Now().Add(-5 * time.Minute), + UpdatedTime: time.Now(), + Version: 3, + Type: "tcp", + HostSetIds: []string{"hsst_1234567890", "hsst_0987654321"}, + HostSets: []*targets.HostSet{ + { + Id: "hsst_1234567890", + HostCatalogId: "hcst_1234567890", + }, + { + Id: "hsst_0987654321", + HostCatalogId: "hcst_1234567890", + }, + }, + Attributes: map[string]interface{}{ + "default_port": 22, + }, + } + return printItemTable(in) +} diff --git a/internal/cmd/commands/targetscmd/targets.gen.go b/internal/cmd/commands/targetscmd/targets.gen.go new file mode 100644 index 000000000..59fb26dd9 --- /dev/null +++ b/internal/cmd/commands/targetscmd/targets.gen.go @@ -0,0 +1,335 @@ +// Code generated by "make api"; DO NOT EDIT. +package targetscmd + +import ( + "fmt" + "net/http" + "os" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/targets" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string + + extraCmdVars +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "target" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("target") + + switch c.Func { + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "delete": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = c.extraHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "read": {"id"}, + + "delete": {"id"}, + + "list": {"scope-id", "recursive"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "target", flagsMap[c.Func]) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + if os.Getenv("BOUNDARY_EXAMPLE_CLI_OUTPUT") != "" { + c.UI.Output(exampleOutput()) + return 0 + } + + switch c.Func { + case "": + return cli.RunResultHelp + + case "create", "update": + return cli.RunResultHelp + + } + + c.plural = "target" + switch c.Func { + case "list": + c.plural = "targets" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + var opts []targets.Option + + if strutil.StrListContains(flagsMap[c.Func], "scope-id") { + switch c.Func { + case "list": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + targetsClient := targets.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, targets.DefaultName()) + default: + opts = append(opts, targets.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, targets.DefaultDescription()) + default: + opts = append(opts, targets.WithDescription(c.FlagDescription)) + } + + switch c.FlagRecursive { + case true: + opts = append(opts, targets.WithRecursive(true)) + } + + var version uint32 + + switch c.Func { + + case "add-host-sets": + switch c.FlagVersion { + case 0: + opts = append(opts, targets.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "remove-host-sets": + switch c.FlagVersion { + case 0: + opts = append(opts, targets.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "set-host-sets": + switch c.FlagVersion { + case 0: + opts = append(opts, targets.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + } + + if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + existed := true + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "read": + result, err = targetsClient.Read(c.Context, c.FlagId, opts...) + + case "delete": + _, err = targetsClient.Delete(c.Context, c.FlagId, opts...) + if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { + existed = false + err = nil + } + + case "list": + listResult, err = targetsClient.List(c.Context, c.FlagScopeId, opts...) + + } + + result, err = executeExtraActions(c, result, err, targetsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + c.UI.Output(fmt.Sprintf("{ \"existed\": %t }", existed)) + + case "table": + output := "The delete operation completed successfully" + switch existed { + case true: + output += "." + default: + output += ", however the resource did not exist at the time." + } + c.UI.Output(output) + } + + return 0 + + case "list": + listedItems := listResult.GetItems().([]*targets.Target) + switch base.Format(c.UI) { + case "json": + switch { + + case len(listedItems) == 0: + c.UI.Output("null") + + default: + b, err := base.JsonFormatter{}.Format(listedItems) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + case "table": + c.UI.Output(c.printListTable(listedItems)) + } + + return 0 + + } + + item := result.GetItem().(*targets.Target) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *[]targets.Option) int { return 0 } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *targets.Client, _ uint32, _ []targets.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/targetscmd/tcp_funcs.go b/internal/cmd/commands/targetscmd/tcp_funcs.go new file mode 100644 index 000000000..cb9dc0c29 --- /dev/null +++ b/internal/cmd/commands/targetscmd/tcp_funcs.go @@ -0,0 +1,154 @@ +package targetscmd + +import ( + "fmt" + "strconv" + "time" + + "github.com/hashicorp/boundary/api/targets" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/go-bexpr" +) + +func init() { + extraTcpActionsFlagsMapFunc = extraTcpActionsFlagsMapFuncImpl + extraTcpFlagsFunc = extraTcpFlagsFuncImpl + extraTcpFlagsHandlingFunc = extraTcpFlagsHandlingFuncImpl +} + +func extraTcpActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "create": {"default-port", "session-max-seconds", "session-connection-limit", "worker-filter"}, + "update": {"default-port", "session-max-seconds", "session-connection-limit", "worker-filter"}, + } +} + +type extraTcpCmdVars struct { + flagDefaultPort string + flagSessionMaxSeconds string + flagSessionConnectionLimit string + flagWorkerFilter string +} + +func (c *TcpCommand) extraTcpHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary targets create tcp [options] [args]", + "", + " Create a tcp-type target. Example:", + "", + ` $ boundary targets create tcp -name prodops -description "Tcp target for ProdOps"`, + "", + "", + }) + + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary targets tcp update [options] [args]", + "", + " Update a tcp-type target given its ID. Example:", + "", + ` $ boundary targets tcp update -id ttcp_1234567890 -name "devops" -description "Tcp target for DevOps"`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} + +func extraTcpFlagsFuncImpl(c *TcpCommand, set *base.FlagSets, f *base.FlagSet) { + f = set.NewFlagSet("TCP Target Options") + + for _, name := range flagsTcpMap[c.Func] { + switch name { + case "default-port": + f.StringVar(&base.StringVar{ + Name: "default-port", + Target: &c.flagDefaultPort, + Usage: "The default port to set on the target.", + }) + case "session-max-seconds": + f.StringVar(&base.StringVar{ + Name: "session-max-seconds", + Target: &c.flagSessionMaxSeconds, + Usage: `The maximum lifetime of the session, including all connections. Can be specified as an integer number of seconds or a duration string.`, + }) + case "session-connection-limit": + f.StringVar(&base.StringVar{ + Name: "session-connection-limit", + Target: &c.flagSessionConnectionLimit, + Usage: "The maximum number of connections allowed for a session. -1 means unlimited.", + }) + case "worker-filter": + f.StringVar(&base.StringVar{ + Name: "worker-filter", + Target: &c.flagWorkerFilter, + Usage: "A boolean expression to filter which workers can handle sessions for this target.", + }) + } + } +} + +func extraTcpFlagsHandlingFuncImpl(c *TcpCommand, opts *[]targets.Option) int { + switch c.flagDefaultPort { + case "": + case "null": + *opts = append(*opts, targets.DefaultTcpTargetDefaultPort()) + default: + port, err := strconv.ParseUint(c.flagDefaultPort, 10, 32) + if err != nil { + c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagDefaultPort, err)) + return 1 + } + *opts = append(*opts, targets.WithTcpTargetDefaultPort(uint32(port))) + } + + switch c.flagSessionMaxSeconds { + case "": + case "null": + *opts = append(*opts, targets.DefaultSessionMaxSeconds()) + default: + var final uint32 + dur, err := strconv.ParseUint(c.flagSessionMaxSeconds, 10, 32) + if err == nil { + final = uint32(dur) + } else { + dur, err := time.ParseDuration(c.flagSessionMaxSeconds) + if err != nil { + c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagSessionMaxSeconds, err)) + return 1 + } + final = uint32(dur.Seconds()) + } + *opts = append(*opts, targets.WithSessionMaxSeconds(final)) + } + + switch c.flagSessionConnectionLimit { + case "": + case "null": + *opts = append(*opts, targets.DefaultSessionConnectionLimit()) + default: + limit, err := strconv.ParseInt(c.flagSessionConnectionLimit, 10, 32) + if err != nil { + c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagSessionConnectionLimit, err)) + return 1 + } + *opts = append(*opts, targets.WithSessionConnectionLimit(int32(limit))) + } + + switch c.flagWorkerFilter { + case "": + case "null": + *opts = append(*opts, targets.DefaultWorkerFilter()) + default: + if _, err := bexpr.CreateEvaluator(c.flagWorkerFilter); err != nil { + c.UI.Error(fmt.Sprintf("Unable to successfully parse filter expression: %s", err)) + return 1 + } + *opts = append(*opts, targets.WithWorkerFilter(c.flagWorkerFilter)) + } + + return 0 +} diff --git a/internal/cmd/commands/targetscmd/tcp_targets.gen.go b/internal/cmd/commands/targetscmd/tcp_targets.gen.go new file mode 100644 index 000000000..8fd241788 --- /dev/null +++ b/internal/cmd/commands/targetscmd/tcp_targets.gen.go @@ -0,0 +1,243 @@ +// Code generated by "make api"; DO NOT EDIT. +package targetscmd + +import ( + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/targets" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initTcpFlags() { + flagsOnce.Do(func() { + extraFlags := extraTcpActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsTcpMap[k] = append(flagsTcpMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*TcpCommand)(nil) + _ cli.CommandAutocomplete = (*TcpCommand)(nil) +) + +type TcpCommand struct { + *base.Command + + Func string + + plural string + + extraTcpCmdVars +} + +func (c *TcpCommand) AutocompleteArgs() complete.Predictor { + initTcpFlags() + return complete.PredictAnything +} + +func (c *TcpCommand) AutocompleteFlags() complete.Flags { + initTcpFlags() + return c.Flags().Completions() +} + +func (c *TcpCommand) Synopsis() string { + if extra := extraTcpSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "target" + + synopsisStr = fmt.Sprintf("%s %s", "tcp-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *TcpCommand) Help() string { + initTcpFlags() + + var helpStr string + helpMap := common.HelpMap("target") + + switch c.Func { + default: + + helpStr = c.extraTcpHelpFunc(helpMap) + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsTcpMap = map[string][]string{ + + "create": {"scope-id", "name", "description"}, + + "update": {"id", "name", "description", "version"}, +} + +func (c *TcpCommand) Flags() *base.FlagSets { + if len(flagsTcpMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "tcp-type target", flagsTcpMap[c.Func]) + + extraTcpFlagsFunc(c, set, f) + + return set +} + +func (c *TcpCommand) Run(args []string) int { + initTcpFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "tcp-type target" + switch c.Func { + case "list": + c.plural = "tcp-type targets" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsTcpMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []targets.Option + + if strutil.StrListContains(flagsTcpMap[c.Func], "scope-id") { + switch c.Func { + case "create": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + targetsClient := targets.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, targets.DefaultName()) + default: + opts = append(opts, targets.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, targets.DefaultDescription()) + default: + opts = append(opts, targets.WithDescription(c.FlagDescription)) + } + + switch c.FlagRecursive { + case true: + opts = append(opts, targets.WithRecursive(true)) + } + + var version uint32 + + switch c.Func { + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, targets.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } + + if ret := extraTcpFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + var result api.GenericResult + + switch c.Func { + + case "create": + result, err = targetsClient.Create(c.Context, "tcp", c.FlagScopeId, opts...) + + case "update": + result, err = targetsClient.Update(c.Context, c.FlagId, version, opts...) + + } + + result, err = executeExtraTcpActions(c, result, err, targetsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomTcpActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + } + + item := result.GetItem().(*targets.Target) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + extraTcpActionsFlagsMapFunc = func() map[string][]string { return nil } + extraTcpSynopsisFunc = func(*TcpCommand) string { return "" } + extraTcpFlagsFunc = func(*TcpCommand, *base.FlagSets, *base.FlagSet) {} + extraTcpFlagsHandlingFunc = func(*TcpCommand, *[]targets.Option) int { return 0 } + executeExtraTcpActions = func(_ *TcpCommand, inResult api.GenericResult, inErr error, _ *targets.Client, _ uint32, _ []targets.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomTcpActionOutput = func(*TcpCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/users/funcs.go b/internal/cmd/commands/users/funcs.go deleted file mode 100644 index 73257937d..000000000 --- a/internal/cmd/commands/users/funcs.go +++ /dev/null @@ -1,135 +0,0 @@ -package users - -import ( - "fmt" - "strings" - "time" - - "github.com/hashicorp/boundary/api/users" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/internal/types/resource" - "github.com/mitchellh/go-wordwrap" -) - -func accountSynopsisFunc(inFunc string) string { - var in string - switch { - case strings.HasPrefix(inFunc, "add"): - in = "Add accounts to" - case strings.HasPrefix(inFunc, "set"): - in = "Set the full contents of the accounts on" - case strings.HasPrefix(inFunc, "remove"): - in = "Remove accounts from" - } - return wordwrap.WrapString(fmt.Sprintf("%s a user within Boundary", in), base.TermWidth) -} - -func addAccountsHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary users add-accounts [options] [args]", - "", - ` Associates accounts to a user given its ID. The "account" flag can be specified multiple times. Example:`, - "", - ` $ boundary users add-accounts -id u_1234567890 -account a_1234567890`, - }) -} - -func setAccountsHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary users set-accounts [options] [args]", - "", - ` Sets the complete set of associated accounts on a user given its ID. The "account" flag can be specified multiple times. Example:`, - "", - ` $ boundary users set-principals -id u_1234567890 -account a_0987654321 -account a_1234567890`, - }) -} - -func removeAccountsHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary users remove-accounts [options] [args]", - "", - ` Disassociates accounts from a user given its ID. The "account" flag can be specified multiple times. Example:`, - "", - ` $ boundary users remove-accounts -id u_1234567890 -account a_1234567890`, - }) -} - -func populateFlags(c *Command, f *base.FlagSet, flagNames []string) { - common.PopulateCommonFlags(c.Command, f, resource.User.String(), flagNames) - - for _, name := range flagNames { - switch name { - case "account": - f.StringSliceVar(&base.StringSliceVar{ - Name: "account", - Target: &c.flagAccounts, - Usage: "The accounts to add, remove, or set. May be specified multiple times.", - }) - } - } -} - -func generateUserTableOutput(in *users.User) string { - nonAttributeMap := map[string]interface{}{ - "ID": in.Id, - "Version": in.Version, - "Created Time": in.CreatedTime.Local().Format(time.RFC1123), - "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), - } - - if in.Name != "" { - nonAttributeMap["Name"] = in.Name - } - if in.Description != "" { - nonAttributeMap["Description"] = in.Description - } - - maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) - - var userMaps []map[string]interface{} - if len(in.Accounts) > 0 { - for _, account := range in.Accounts { - a := map[string]interface{}{ - "ID": account.Id, - "Scope ID": account.ScopeId, - } - userMaps = append(userMaps, a) - } - if l := len("Scope ID"); l > maxLength { - maxLength = l - } - } - - ret := []string{ - "", - "User information:", - base.WrapMap(2, maxLength+2, nonAttributeMap), - "", - " Scope:", - base.ScopeInfoForOutput(in.Scope, maxLength), - } - - if len(in.AuthorizedActions) > 0 { - ret = append(ret, - "", - " Authorized Actions:", - base.WrapSlice(4, in.AuthorizedActions), - ) - } - - if len(in.Accounts) > 0 { - ret = append(ret, - "", - " Accounts:", - ) - for _, m := range userMaps { - ret = append(ret, - base.WrapMap(4, maxLength, m), - "", - ) - } - } - - return base.WrapForHelpText(ret) -} diff --git a/internal/cmd/commands/users/user.go b/internal/cmd/commands/users/user.go deleted file mode 100644 index 798c02e3a..000000000 --- a/internal/cmd/commands/users/user.go +++ /dev/null @@ -1,302 +0,0 @@ -package users - -import ( - "fmt" - "net/http" - - "github.com/hashicorp/boundary/api" - "github.com/hashicorp/boundary/api/users" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/common" - "github.com/hashicorp/boundary/sdk/strutil" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var ( - _ cli.Command = (*Command)(nil) - _ cli.CommandAutocomplete = (*Command)(nil) -) - -type Command struct { - *base.Command - - Func string - - flagAccounts []string -} - -func (c *Command) Synopsis() string { - switch c.Func { - case "add-accounts", "set-accounts", "remove-accounts": - return accountSynopsisFunc(c.Func) - default: - return common.SynopsisFunc(c.Func, "user") - } -} - -var helpMap = func() map[string]func() string { - ret := common.HelpMap("user") - ret["add-accounts"] = addAccountsHelp - ret["set-accounts"] = setAccountsHelp - ret["remove-accounts"] = removeAccountsHelp - return ret -} - -var flagsMap = map[string][]string{ - "create": {"scope-id", "name", "description"}, - "update": {"id", "name", "description", "version"}, - "read": {"id"}, - "delete": {"id"}, - "list": {"scope-id", "recursive"}, - "add-accounts": {"id", "account", "version"}, - "set-accounts": {"id", "account", "version"}, - "remove-accounts": {"id", "account", "version"}, -} - -func (c *Command) Help() string { - hm := helpMap() - if c.Func == "" { - return hm["base"]() - } - return hm[c.Func]() + c.Flags().Help() -} - -func (c *Command) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") - populateFlags(c, f, flagsMap[c.Func]) - - return set -} - -func (c *Command) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *Command) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *Command) Run(args []string) int { - if c.Func == "" { - return cli.RunResultHelp - } - - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { - c.UI.Error("ID is required but not passed in via -id") - return 1 - } - if strutil.StrListContains(flagsMap[c.Func], "scope-id") && c.FlagScopeId == "" { - c.UI.Error("Scope ID must be passed in via -scope-id") - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) - return 2 - } - - var opts []users.Option - - switch c.FlagName { - case "": - case "null": - opts = append(opts, users.DefaultName()) - default: - opts = append(opts, users.WithName(c.FlagName)) - } - switch c.FlagDescription { - case "": - case "null": - opts = append(opts, users.DefaultDescription()) - default: - opts = append(opts, users.WithDescription(c.FlagDescription)) - } - switch c.FlagRecursive { - case true: - opts = append(opts, users.WithRecursive(true)) - } - - accounts := c.flagAccounts - switch c.Func { - case "add-accounts", "remove-accounts": - if len(c.flagAccounts) == 0 { - c.UI.Error("No accounts supplied via -account") - return 1 - } - - case "set-accounts": - switch len(c.flagAccounts) { - case 0: - c.UI.Error("No accounts supplied via -account") - return 1 - case 1: - if c.flagAccounts[0] == "null" { - accounts = nil - } - } - } - - userClient := users.NewClient(client) - - // Perform check-and-set when needed - var version uint32 - switch c.Func { - case "create", "read", "delete", "list": - // These don't udpate so don't need the existing version - default: - switch c.FlagVersion { - case 0: - opts = append(opts, users.WithAutomaticVersioning(true)) - default: - version = uint32(c.FlagVersion) - } - } - - existed := true - var result api.GenericResult - var listResult api.GenericListResult - - switch c.Func { - case "create": - result, err = userClient.Create(c.Context, c.FlagScopeId, opts...) - case "update": - result, err = userClient.Update(c.Context, c.FlagId, version, opts...) - case "read": - result, err = userClient.Read(c.Context, c.FlagId, opts...) - case "delete": - _, err = userClient.Delete(c.Context, c.FlagId, opts...) - if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { - existed = false - err = nil - } - case "list": - listResult, err = userClient.List(c.Context, c.FlagScopeId, opts...) - case "add-accounts": - result, err = userClient.AddAccounts(c.Context, c.FlagId, version, accounts, opts...) - case "set-accounts": - result, err = userClient.SetAccounts(c.Context, c.FlagId, version, accounts, opts...) - case "remove-accounts": - result, err = userClient.RemoveAccounts(c.Context, c.FlagId, version, accounts, opts...) - } - - plural := "user" - if c.Func == "list" { - plural = "users" - } - if err != nil { - if apiErr := api.AsServerError(err); apiErr != nil { - c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, base.PrintApiError(apiErr))) - return 1 - } - c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) - return 2 - } - - switch c.Func { - case "delete": - switch base.Format(c.UI) { - case "json": - c.UI.Output("null") - case "table": - output := "The delete operation completed successfully" - switch existed { - case true: - output += "." - default: - output += ", however the resource did not exist at the time." - } - c.UI.Output(output) - } - return 0 - - case "list": - listedUsers := listResult.GetItems().([]*users.User) - switch base.Format(c.UI) { - case "json": - if len(listedUsers) == 0 { - c.UI.Output("null") - return 0 - } - b, err := base.JsonFormatter{}.Format(listedUsers) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - - case "table": - if len(listedUsers) == 0 { - c.UI.Output("No users found") - return 0 - } - var output []string - output = []string{ - "", - "User information:", - } - for i, u := range listedUsers { - if i > 0 { - output = append(output, "") - } - if true { - output = append(output, - fmt.Sprintf(" ID: %s", u.Id), - ) - } - if c.FlagRecursive { - output = append(output, - fmt.Sprintf(" Scope ID: %s", u.Scope.Id), - ) - } - if true { - output = append(output, - fmt.Sprintf(" Version: %d", u.Version), - ) - } - if u.Name != "" { - output = append(output, - fmt.Sprintf(" Name: %s", u.Name), - ) - } - if u.Description != "" { - output = append(output, - fmt.Sprintf(" Description: %s", u.Description), - ) - } - if len(u.AuthorizedActions) > 0 { - output = append(output, - " Authorized Actions:", - base.WrapSlice(6, u.AuthorizedActions), - ) - } - } - c.UI.Output(base.WrapForHelpText(output)) - } - return 0 - } - - user := result.GetItem().(*users.User) - switch base.Format(c.UI) { - case "table": - c.UI.Output(generateUserTableOutput(user)) - case "json": - b, err := base.JsonFormatter{}.Format(user) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) - } - - return 0 -} diff --git a/internal/cmd/commands/userscmd/funcs.go b/internal/cmd/commands/userscmd/funcs.go new file mode 100644 index 000000000..c0b6ef559 --- /dev/null +++ b/internal/cmd/commands/userscmd/funcs.go @@ -0,0 +1,255 @@ +package userscmd + +import ( + "fmt" + "strings" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/users" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/mitchellh/go-wordwrap" +) + +func init() { + extraActionsFlagsMapFunc = extraActionsFlagsMapFuncImpl + extraSynopsisFunc = extraSynopsisFuncImpl + extraFlagsFunc = extraFlagsFuncImpl + extraFlagsHandlingFunc = extraFlagsHandlingFuncImpl + executeExtraActions = executeExtraActionsImpl +} + +type extraCmdVars struct { + flagAccounts []string +} + +func extraActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "add-accounts": {"id", "account", "version"}, + "set-accounts": {"id", "account", "version"}, + "remove-accounts": {"id", "account", "version"}, + } +} + +func extraSynopsisFuncImpl(c *Command) string { + switch c.Func { + case "add-accounts", "set-accounts", "remove-accounts": + var in string + switch { + case strings.HasPrefix(c.Func, "add"): + in = "Add accounts to" + case strings.HasPrefix(c.Func, "set"): + in = "Set the full contents of the accounts on" + case strings.HasPrefix(c.Func, "remove"): + in = "Remove accounts from" + } + return wordwrap.WrapString(fmt.Sprintf("%s a user within Boundary", in), base.TermWidth) + } + + return "" +} + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "add-accounts": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary users add-accounts [options] [args]", + "", + ` Associates accounts to a user given its ID. The "account" flag can be specified multiple times. Example:`, + "", + ` $ boundary users add-accounts -id u_1234567890 -account a_1234567890`, + "", + "", + }) + + case "set-accounts": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary users set-accounts [options] [args]", + "", + ` Sets the complete set of associated accounts on a user given its ID. The "account" flag can be specified multiple times. Example:`, + "", + ` $ boundary users set-principals -id u_1234567890 -account a_0987654321 -account a_1234567890`, + "", + "", + }) + + case "remove-accounts": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary users remove-accounts [options] [args]", + "", + ` Disassociates accounts from a user given its ID. The "account" flag can be specified multiple times. Example:`, + "", + ` $ boundary users remove-accounts -id u_1234567890 -account a_1234567890`, + "", + "", + }) + + default: + helpStr = helpMap["base"]() + } + return helpStr + c.Flags().Help() +} + +func extraFlagsFuncImpl(c *Command, _ *base.FlagSets, f *base.FlagSet) { + for _, name := range flagsMap[c.Func] { + switch name { + case "account": + f.StringSliceVar(&base.StringSliceVar{ + Name: "account", + Target: &c.flagAccounts, + Usage: "The accounts to add, remove, or set. May be specified multiple times.", + }) + } + } +} + +func extraFlagsHandlingFuncImpl(c *Command, opts *[]users.Option) int { + switch c.Func { + case "add-accounts", "remove-accounts": + if len(c.flagAccounts) == 0 { + c.UI.Error("No accounts supplied via -account") + return 1 + } + + case "set-accounts": + switch len(c.flagAccounts) { + case 0: + c.UI.Error("No accounts supplied via -account") + return 1 + case 1: + if c.flagAccounts[0] == "null" { + c.flagAccounts = nil + } + } + } + + return 0 +} + +func executeExtraActionsImpl(c *Command, origResult api.GenericResult, origError error, userClient *users.Client, version uint32, opts []users.Option) (api.GenericResult, error) { + switch c.Func { + case "add-accounts": + return userClient.AddAccounts(c.Context, c.FlagId, version, c.flagAccounts, opts...) + case "set-accounts": + return userClient.SetAccounts(c.Context, c.FlagId, version, c.flagAccounts, opts...) + case "remove-accounts": + return userClient.RemoveAccounts(c.Context, c.FlagId, version, c.flagAccounts, opts...) + } + return origResult, origError +} + +func (c *Command) printListTable(items []*users.User) string { + if len(items) == 0 { + return "No users found" + } + + var output []string + output = []string{ + "", + "User information:", + } + + for i, u := range items { + if i > 0 { + output = append(output, "") + } + if true { + output = append(output, + fmt.Sprintf(" ID: %s", u.Id), + ) + } + if c.FlagRecursive { + output = append(output, + fmt.Sprintf(" Scope ID: %s", u.Scope.Id), + ) + } + if true { + output = append(output, + fmt.Sprintf(" Version: %d", u.Version), + ) + } + if u.Name != "" { + output = append(output, + fmt.Sprintf(" Name: %s", u.Name), + ) + } + if u.Description != "" { + output = append(output, + fmt.Sprintf(" Description: %s", u.Description), + ) + } + if len(u.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, u.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(in *users.User) string { + nonAttributeMap := map[string]interface{}{ + "ID": in.Id, + "Version": in.Version, + "Created Time": in.CreatedTime.Local().Format(time.RFC1123), + "Updated Time": in.UpdatedTime.Local().Format(time.RFC1123), + } + + if in.Name != "" { + nonAttributeMap["Name"] = in.Name + } + if in.Description != "" { + nonAttributeMap["Description"] = in.Description + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) + + var userMaps []map[string]interface{} + if len(in.Accounts) > 0 { + for _, account := range in.Accounts { + a := map[string]interface{}{ + "ID": account.Id, + "Scope ID": account.ScopeId, + } + userMaps = append(userMaps, a) + } + if l := len("Scope ID"); l > maxLength { + maxLength = l + } + } + + ret := []string{ + "", + "User information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + "", + " Scope:", + base.ScopeInfoForOutput(in.Scope, maxLength), + } + + if len(in.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, in.AuthorizedActions), + ) + } + + if len(in.Accounts) > 0 { + ret = append(ret, + "", + " Accounts:", + ) + for _, m := range userMaps { + ret = append(ret, + base.WrapMap(4, maxLength, m), + "", + ) + } + } + + return base.WrapForHelpText(ret) +} diff --git a/internal/cmd/commands/userscmd/users.gen.go b/internal/cmd/commands/userscmd/users.gen.go new file mode 100644 index 000000000..49905b955 --- /dev/null +++ b/internal/cmd/commands/userscmd/users.gen.go @@ -0,0 +1,362 @@ +// Code generated by "make api"; DO NOT EDIT. +package userscmd + +import ( + "fmt" + "net/http" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/users" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string + + extraCmdVars +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "user" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("user") + + switch c.Func { + + case "create": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "update": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "delete": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = c.extraHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "create": {"scope-id", "name", "description"}, + + "read": {"id"}, + + "update": {"id", "name", "description", "version"}, + + "delete": {"id"}, + + "list": {"scope-id", "recursive"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "user", flagsMap[c.Func]) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "user" + switch c.Func { + case "list": + c.plural = "users" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + + var opts []users.Option + + if strutil.StrListContains(flagsMap[c.Func], "scope-id") { + switch c.Func { + + case "create": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + + case "list": + if c.FlagScopeId == "" { + c.UI.Error("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID") + return 1 + } + + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + usersClient := users.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, users.DefaultName()) + default: + opts = append(opts, users.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, users.DefaultDescription()) + default: + opts = append(opts, users.WithDescription(c.FlagDescription)) + } + + switch c.FlagRecursive { + case true: + opts = append(opts, users.WithRecursive(true)) + } + + var version uint32 + + switch c.Func { + + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, users.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "add-accounts": + switch c.FlagVersion { + case 0: + opts = append(opts, users.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "remove-accounts": + switch c.FlagVersion { + case 0: + opts = append(opts, users.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "set-accounts": + switch c.FlagVersion { + case 0: + opts = append(opts, users.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + } + + if ret := extraFlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + existed := true + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "create": + result, err = usersClient.Create(c.Context, c.FlagScopeId, opts...) + + case "read": + result, err = usersClient.Read(c.Context, c.FlagId, opts...) + + case "update": + result, err = usersClient.Update(c.Context, c.FlagId, version, opts...) + + case "delete": + _, err = usersClient.Delete(c.Context, c.FlagId, opts...) + if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { + existed = false + err = nil + } + + case "list": + listResult, err = usersClient.List(c.Context, c.FlagScopeId, opts...) + + } + + result, err = executeExtraActions(c, result, err, usersClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + c.UI.Output(fmt.Sprintf("{ \"existed\": %t }", existed)) + + case "table": + output := "The delete operation completed successfully" + switch existed { + case true: + output += "." + default: + output += ", however the resource did not exist at the time." + } + c.UI.Output(output) + } + + return 0 + + case "list": + listedItems := listResult.GetItems().([]*users.User) + switch base.Format(c.UI) { + case "json": + switch { + + case len(listedItems) == 0: + c.UI.Output("null") + + default: + b, err := base.JsonFormatter{}.Format(listedItems) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + case "table": + c.UI.Output(c.printListTable(listedItems)) + } + + return 0 + + } + + item := result.GetItem().(*users.User) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *[]users.Option) int { return 0 } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *users.Client, _ uint32, _ []users.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/gencli/Makefile b/internal/cmd/gencli/Makefile new file mode 100644 index 000000000..85063a614 --- /dev/null +++ b/internal/cmd/gencli/Makefile @@ -0,0 +1,12 @@ +# Determine this makefile's path. +# Be sure to place this BEFORE `include` directives, if any. +THIS_FILE := $(lastword $(MAKEFILE_LIST)) + +export CLI_GEN_BASEPATH := ${GEN_BASEPATH}/internal/cmd/commands + +cli: + go run . + goimports -w ${CLI_GEN_BASEPATH} + gofumpt -w ${CLI_GEN_BASEPATH} + +.PHONY: cli diff --git a/internal/cmd/gencli/input.go b/internal/cmd/gencli/input.go new file mode 100644 index 000000000..181d350a2 --- /dev/null +++ b/internal/cmd/gencli/input.go @@ -0,0 +1,308 @@ +package main + +import ( + "github.com/hashicorp/boundary/internal/types/resource" +) + +type cmdInfo struct { + // The type of the resource, e.g. "target" + ResourceType string + + // The API package under the api path + Pkg string + + // Standard actions (with standard parameters) used by this resource + StdActions []string + + // HasExtraCommandVars controls whether to generate an embedded struct with + // extra command variables + HasExtraCommandVars bool + + // SkipNormalHelp indicates skipping the normal help case for when it needs + // to be only custom (mainly for subactions) + SkipNormalHelp bool + + // HasExtraHelpFunc controls whether to include a default statement chaining + // to an extra help function + HasExtraHelpFunc bool + + // HasExampleCliOutput controls whether to generate code to look for a CLI + // output env var and print + HasExampleCliOutput bool + + // IsAbstractType triggers some behavior specialized for abstract types, + // e.g. those that have subcommands for create/update + IsAbstractType bool + + // HasId controls whether to add ID emptiness checking. Note that some + // commands that allow name/scope id or name/scope name handling may skip + // this in favor of custom logic. + HasId bool + + // Container controls what to generate for a required container (scope ID, + // auth method ID, etc.) + Container string + + // HasName controls whether to add name options + HasName bool + + // HasDescription controls whether to add description options + HasDescription bool + + // HasScopeName controls whether to add scope name options + HasScopeName bool + + // VersionedActions controls which actions to add a case for version checking + VersionedActions []string + + // SubActionPrefix specifies the prefix to use when generating sub-action + // commands (e.g. "targets update tcp") + SubActionPrefix string + + // NeedsSubTypeInCreate controls whether the sub-type must be passed in as + // an argument to a create call. Targets need this, accounts do not, etc. + NeedsSubTypeInCreate bool +} + +var inputStructs = map[string][]*cmdInfo{ + "accounts": { + { + ResourceType: resource.Account.String(), + Pkg: "accounts", + StdActions: []string{"read", "delete", "list"}, + HasExtraCommandVars: true, + HasExtraHelpFunc: true, + IsAbstractType: true, + Container: "AuthMethod", + HasId: true, + HasName: true, + HasDescription: true, + VersionedActions: []string{"change-password", "set-password"}, + }, + { + ResourceType: resource.Account.String(), + Pkg: "accounts", + StdActions: []string{"create", "update"}, + SubActionPrefix: "password", + HasExtraCommandVars: true, + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + Container: "AuthMethod", + HasDescription: true, + VersionedActions: []string{"update"}, + }, + }, + "authmethods": { + { + ResourceType: resource.AuthMethod.String(), + Pkg: "authmethods", + StdActions: []string{"read", "delete", "list"}, + IsAbstractType: true, + HasExtraHelpFunc: true, + Container: "Scope", + HasId: true, + }, + { + ResourceType: resource.AuthMethod.String(), + Pkg: "authmethods", + StdActions: []string{"create", "update"}, + SubActionPrefix: "password", + HasExtraCommandVars: true, + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + HasDescription: true, + Container: "Scope", + VersionedActions: []string{"update"}, + NeedsSubTypeInCreate: true, + }, + }, + "authtokens": { + { + ResourceType: resource.AuthToken.String(), + Pkg: "authtokens", + StdActions: []string{"read", "delete", "list"}, + Container: "Scope", + HasId: true, + }, + }, + "groups": { + { + ResourceType: resource.Group.String(), + Pkg: "groups", + StdActions: []string{"create", "read", "update", "delete", "list"}, + HasExtraCommandVars: true, + HasExtraHelpFunc: true, + HasId: true, + Container: "Scope", + HasName: true, + HasDescription: true, + VersionedActions: []string{"update", "add-members", "remove-members", "set-members"}, + }, + }, + "hostcatalogs": { + { + ResourceType: resource.HostCatalog.String(), + Pkg: "hostcatalogs", + StdActions: []string{"read", "delete", "list"}, + IsAbstractType: true, + HasExtraHelpFunc: true, + Container: "Scope", + HasId: true, + }, + { + ResourceType: resource.HostCatalog.String(), + Pkg: "hostcatalogs", + StdActions: []string{"create", "update"}, + SubActionPrefix: "static", + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + HasDescription: true, + Container: "Scope", + VersionedActions: []string{"update"}, + NeedsSubTypeInCreate: true, + }, + }, + "hostsets": { + { + ResourceType: resource.HostSet.String(), + Pkg: "hostsets", + StdActions: []string{"read", "delete", "list"}, + HasExtraCommandVars: true, + HasExtraHelpFunc: true, + IsAbstractType: true, + Container: "HostCatalog", + HasId: true, + HasName: true, + HasDescription: true, + VersionedActions: []string{"add-hosts", "set-hosts", "remove-hosts"}, + }, + { + ResourceType: resource.HostSet.String(), + Pkg: "hostsets", + StdActions: []string{"create", "update"}, + SubActionPrefix: "static", + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + Container: "HostCatalog", + HasDescription: true, + VersionedActions: []string{"update"}, + }, + }, + "hosts": { + { + ResourceType: resource.Host.String(), + Pkg: "hosts", + StdActions: []string{"read", "delete", "list"}, + HasExtraHelpFunc: true, + IsAbstractType: true, + Container: "HostCatalog", + HasId: true, + HasName: true, + HasDescription: true, + }, + { + ResourceType: resource.Host.String(), + Pkg: "hosts", + StdActions: []string{"create", "update"}, + SubActionPrefix: "static", + HasExtraCommandVars: true, + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + Container: "HostCatalog", + HasDescription: true, + VersionedActions: []string{"update"}, + }, + }, + "roles": { + { + ResourceType: resource.Role.String(), + Pkg: "roles", + StdActions: []string{"create", "read", "update", "delete", "list"}, + HasExtraCommandVars: true, + HasExtraHelpFunc: true, + HasId: true, + Container: "Scope", + HasName: true, + HasDescription: true, + VersionedActions: []string{"update", "add-grants", "remove-grants", "set-grants", "add-principals", "remove-principals", "set-principals"}, + }, + }, + "scopes": { + { + ResourceType: resource.Scope.String(), + Pkg: "scopes", + StdActions: []string{"create", "read", "update", "delete", "list"}, + HasExtraCommandVars: true, + HasId: true, + Container: "Scope", + HasName: true, + HasDescription: true, + VersionedActions: []string{"update"}, + }, + }, + "sessions": { + { + ResourceType: resource.Session.String(), + Pkg: "sessions", + StdActions: []string{"read", "list"}, + Container: "Scope", + HasExtraHelpFunc: true, + HasId: true, + VersionedActions: []string{"cancel"}, + }, + }, + "targets": { + { + ResourceType: resource.Target.String(), + Pkg: "targets", + StdActions: []string{"read", "delete", "list"}, + HasExtraCommandVars: true, + HasExtraHelpFunc: true, + HasExampleCliOutput: true, + IsAbstractType: true, + HasName: true, + HasDescription: true, + Container: "Scope", + VersionedActions: []string{"add-host-sets", "remove-host-sets", "set-host-sets"}, + }, + { + ResourceType: resource.Target.String(), + Pkg: "targets", + StdActions: []string{"create", "update"}, + SubActionPrefix: "tcp", + HasExtraCommandVars: true, + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + Container: "Scope", + HasDescription: true, + VersionedActions: []string{"update"}, + NeedsSubTypeInCreate: true, + }, + }, + "users": { + { + ResourceType: resource.User.String(), + Pkg: "users", + StdActions: []string{"create", "read", "update", "delete", "list"}, + HasExtraCommandVars: true, + HasExtraHelpFunc: true, + HasId: true, + Container: "Scope", + HasName: true, + HasDescription: true, + VersionedActions: []string{"update", "add-accounts", "remove-accounts", "set-accounts"}, + }, + }, +} diff --git a/internal/cmd/gencli/main.go b/internal/cmd/gencli/main.go new file mode 100644 index 000000000..9166baa8a --- /dev/null +++ b/internal/cmd/gencli/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + fillTemplates() +} diff --git a/internal/cmd/gencli/templates.go b/internal/cmd/gencli/templates.go new file mode 100644 index 000000000..5ea6abd82 --- /dev/null +++ b/internal/cmd/gencli/templates.go @@ -0,0 +1,463 @@ +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "text/template" + + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/iancoleman/strcase" +) + +func fillTemplates() { + for pkg, structs := range inputStructs { + for _, data := range structs { + outBuf := new(bytes.Buffer) + + if err := cmdTemplate.Execute(outBuf, data); err != nil { + fmt.Printf("error executing struct template for resource %s: %v\n", pkg, err) + os.Exit(1) + } + + fName := pkg + if data.SubActionPrefix != "" { + fName = fmt.Sprintf("%s_%s", data.SubActionPrefix, fName) + } + outFile, err := filepath.Abs(fmt.Sprintf("%s/%scmd/%s.gen.go", os.Getenv("CLI_GEN_BASEPATH"), pkg, fName)) + if err != nil { + fmt.Printf("error opening file for package %s: %v\n", pkg, err) + os.Exit(1) + } + outDir := filepath.Dir(outFile) + if _, err := os.Stat(outDir); os.IsNotExist(err) { + _ = os.Mkdir(outDir, os.ModePerm) + } + if err := ioutil.WriteFile(outFile, outBuf.Bytes(), 0o644); err != nil { + fmt.Printf("error writing file %q: %v\n", outFile, err) + os.Exit(1) + } + } + } +} + +func camelCase(in string) string { + return strcase.ToCamel(in) +} + +func envCase(in string) string { + return strcase.ToScreamingSnake(in) +} + +func snakeCase(in string) string { + return strcase.ToSnake(in) +} + +func kebabCase(in string) string { + return strcase.ToKebab(in) +} + +func lowerSpaceCase(in string) string { + return strcase.ToDelimited(in, ' ') +} + +func hasAction(in []string, action string) bool { + return strutil.StrListContains(in, action) +} + +var cmdTemplate = template.Must(template.New("").Funcs( + template.FuncMap{ + "camelCase": camelCase, + "envCase": envCase, + "snakeCase": snakeCase, + "kebabCase": kebabCase, + "lowerSpaceCase": lowerSpaceCase, + "hasAction": hasAction, + }, +).Parse(`{{ $input := . }} +// Code generated by "make api"; DO NOT EDIT. +package {{ .Pkg }}cmd + +import ( + "fmt" + "net/http" + "os" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/{{ .Pkg }}" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/internal/types/resource" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func init{{ camelCase .SubActionPrefix }}Flags() { + flagsOnce.Do(func() { + extraFlags := extra{{ camelCase .SubActionPrefix }}ActionsFlagsMapFunc() + for k, v := range extraFlags { + flags{{ camelCase .SubActionPrefix }}Map[k] = append(flags{{ camelCase .SubActionPrefix }}Map[k], v...) + } + }) +} + +var ( + _ cli.Command = (*{{ camelCase .SubActionPrefix }}Command)(nil) + _ cli.CommandAutocomplete = (*{{ camelCase .SubActionPrefix }}Command)(nil) +) + +type {{ camelCase .SubActionPrefix }}Command struct { + *base.Command + + Func string + + plural string + {{ if .HasExtraCommandVars }} + extra{{ camelCase .SubActionPrefix }}CmdVars + {{ end }} +} + +func (c *{{ camelCase .SubActionPrefix }}Command) AutocompleteArgs() complete.Predictor { + init{{ camelCase .SubActionPrefix }}Flags() + return complete.PredictAnything +} + +func (c *{{ camelCase .SubActionPrefix }}Command) AutocompleteFlags() complete.Flags { + init{{ camelCase .SubActionPrefix }}Flags() + return c.Flags().Completions() +} + +func (c *{{ camelCase .SubActionPrefix }}Command) Synopsis() string { + if extra := extra{{ camelCase .SubActionPrefix }}SynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "{{ lowerSpaceCase .ResourceType }}" + {{ if .SubActionPrefix }} + synopsisStr = fmt.Sprintf("%s %s", "{{ .SubActionPrefix }}-type", synopsisStr) + {{ end }} + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *{{ camelCase .SubActionPrefix }}Command) Help() string { + init{{ camelCase .SubActionPrefix }}Flags() + + var helpStr string + helpMap := common.HelpMap("{{ lowerSpaceCase .ResourceType }}") + + switch c.Func { + {{ if not .SkipNormalHelp }} + {{ range $i, $action := .StdActions }} + case "{{ $action }}": + helpStr = helpMap[c.Func]() + c.Flags().Help() + {{ end }} + {{ end }} + default: + {{ if .HasExtraHelpFunc }} + helpStr = c.extra{{ camelCase .SubActionPrefix }}HelpFunc(helpMap) + {{ else }} + helpStr = helpMap["base"]() + {{ end }} + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flags{{ camelCase .SubActionPrefix }}Map = map[string][]string{ + {{ range $i, $action := .StdActions }} + {{ if eq $action "create" }} + "create": { "{{ kebabCase $input.Container }}-id", "name", "description" }, + {{ end }} + {{ if eq $action "read" }} + "read": {"id"}, + {{ end }} + {{ if eq $action "update" }} + "update": {"id", "name", "description" {{ if hasAction $input.VersionedActions "update" }}, "version" {{ end }} }, + {{ end }} + {{ if eq $action "delete" }} + "delete": {"id"}, + {{ end }} + {{ if eq $action "list" }} + "list": { "{{ kebabCase $input.Container }}-id" {{ if (eq $input.Container "Scope") }}, "recursive"{{ end }} }, + {{ end }} + {{ end }} +} + +func (c *{{ camelCase .SubActionPrefix }}Command) Flags() *base.FlagSets { + if len(flags{{ camelCase .SubActionPrefix }}Map[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "{{ if .SubActionPrefix }}{{ .SubActionPrefix }}-type {{ end }}{{ lowerSpaceCase .ResourceType }}", flags{{ camelCase .SubActionPrefix }}Map[c.Func]) + + extra{{ camelCase .SubActionPrefix }}FlagsFunc(c, set, f) + + return set +} + +func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int { + init{{ camelCase .SubActionPrefix }}Flags() + + {{ if .HasExampleCliOutput }} + if os.Getenv("BOUNDARY_EXAMPLE_CLI_OUTPUT") != "" { + c.UI.Output(exampleOutput()) + return 0 + } + {{ end }} + + switch c.Func { + case "": + return cli.RunResultHelp + {{ if .IsAbstractType }} + case "create", "update": + return cli.RunResultHelp + {{ end }} + } + + c.plural = "{{ if .SubActionPrefix }}{{ .SubActionPrefix }}-type {{ end }}{{ lowerSpaceCase .ResourceType }}" + switch c.Func { + case "list": + c.plural = "{{ if .SubActionPrefix }}{{ .SubActionPrefix }}-type {{ end }}{{ lowerSpaceCase .ResourceType }}s" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + {{ if .HasId }} + if strutil.StrListContains(flags{{ camelCase .SubActionPrefix }}Map[c.Func], "id") && c.FlagId == "" { + c.UI.Error("ID is required but not passed in via -id") + return 1 + } + {{ end }} + + var opts []{{ .Pkg }}.Option + + if strutil.StrListContains(flags{{ camelCase .SubActionPrefix }}Map[c.Func], "{{ kebabCase .Container }}-id") { + switch c.Func { + {{ if hasAction .StdActions "create" }} + case "create": + if c.Flag{{ .Container }}Id == "" { + c.UI.Error("{{ .Container }} ID must be passed in via -{{ kebabCase .Container }}-id or BOUNDARY_{{ envCase .Container }}_ID") + return 1 + } + {{ end }} + {{ if hasAction .StdActions "list" }} + case "list": + if c.Flag{{ .Container }}Id == "" { + c.UI.Error("{{ .Container }} ID must be passed in via -{{ kebabCase .Container }}-id or BOUNDARY_{{ envCase .Container }}_ID") + return 1 + } + {{ end }} + } + } + + client, err := c.Client() + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error())) + return 2 + } + {{ .Pkg }}Client := {{ .Pkg }}.NewClient(client) + + {{ if .HasName }} + switch c.FlagName { + case "": + case "null": + opts = append(opts, {{ .Pkg }}.DefaultName()) + default: + opts = append(opts, {{ .Pkg }}.WithName(c.FlagName)) + } + {{ end }} + + {{ if .HasDescription }} + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, {{ .Pkg }}.DefaultDescription()) + default: + opts = append(opts, {{ .Pkg }}.WithDescription(c.FlagDescription)) + } + {{ end }} + + {{ if (eq .Container "Scope") }} + switch c.FlagRecursive { + case true: + opts = append(opts, {{ .Pkg }}.WithRecursive(true)) + } + {{ end }} + + {{ if .HasScopeName }} + switch c.FlagScopeName { + case "": + default: + opts = append(opts, {{ .Pkg }}.WithScopeName(c.FlagScopeName)) + } + {{ end }} + + var version uint32 + {{ if .VersionedActions }} + switch c.Func { + {{ range $i, $action := .VersionedActions }} + case "{{ $action }}": + switch c.FlagVersion { + case 0: + opts = append(opts, {{ $input.Pkg }}.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + {{ end }} + } + {{ end }} + + if ret := extra{{ camelCase .SubActionPrefix }}FlagsHandlingFunc(c, &opts); ret != 0 { + return ret + } + + {{ if hasAction .StdActions "delete" }} + existed := true + {{ end }} + var result api.GenericResult + {{ if hasAction .StdActions "list" }} + var listResult api.GenericListResult + {{ end }} + + switch c.Func { + {{ range $i, $action := $input.StdActions }} + {{ if eq $action "create" }} + case "create": + result, err = {{ $input.Pkg }}Client.Create(c.Context, {{ if (and $input.SubActionPrefix $input.NeedsSubTypeInCreate) }}"{{ $input.SubActionPrefix }}",{{ end }} c.Flag{{ $input.Container }}Id, opts...) + {{ end }} + {{ if eq $action "read" }} + case "read": + result, err = {{ $input.Pkg }}Client.Read(c.Context, c.FlagId, opts...) + {{ end }} + {{ if eq $action "update" }} + case "update": + result, err = {{ $input.Pkg }}Client.Update(c.Context, c.FlagId, version, opts...) + {{ end }} + {{ if eq $action "delete" }} + case "delete": + _, err = {{ $input.Pkg}}Client.Delete(c.Context, c.FlagId, opts...) + if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound { + existed = false + err = nil + } + {{ end }} + {{ if eq $action "list" }} + case "list": + listResult, err = {{ $input.Pkg}}Client.List(c.Context, c.Flag{{ $input.Container }}Id, opts...) + {{ end }} + {{ end }} + } + + result, err = executeExtra{{ camelCase .SubActionPrefix }}Actions(c, result, err, {{ .Pkg }}Client, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, c.plural, base.PrintApiError(apiErr))) + return 1 + } + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return 2 + } + + output, err := printCustom{{ camelCase .SubActionPrefix }}ActionOutput(c) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if output { + return 0 + } + + switch c.Func { + {{ range $i, $action := .StdActions }} + {{ if eq $action "delete" }} + case "delete": + switch base.Format(c.UI) { + case "json": + c.UI.Output(fmt.Sprintf("{ \"existed\": %t }", existed)) + + case "table": + output := "The delete operation completed successfully" + switch existed { + case true: + output += "." + default: + output += ", however the resource did not exist at the time." + } + c.UI.Output(output) + } + + return 0 + {{ end }} + {{ if eq $action "list" }} + case "list": + listedItems := listResult.GetItems().([]*{{ $input.Pkg }}.{{ camelCase $input.ResourceType }}) + switch base.Format(c.UI) { + case "json": + switch { + + case len(listedItems) == 0: + c.UI.Output("null") + + default: + b, err := base.JsonFormatter{}.Format(listedItems) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + case "table": + c.UI.Output(c.printListTable(listedItems)) + } + + return 0 + {{ end }} + {{ end }} + } + + item := result.GetItem().(*{{ .Pkg }}.{{ camelCase .ResourceType }}) + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item)) + + case "json": + b, err := base.JsonFormatter{}.Format(item) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} + +var ( + {{ if not .SubActionPrefix }} + flagsOnce = new(sync.Once) + {{ end }} + extra{{ camelCase .SubActionPrefix }}ActionsFlagsMapFunc = func() map[string][]string { return nil } + extra{{ camelCase .SubActionPrefix }}SynopsisFunc = func(*{{ camelCase .SubActionPrefix }}Command) string { return "" } + extra{{ camelCase .SubActionPrefix }}FlagsFunc = func(*{{ camelCase .SubActionPrefix }}Command, *base.FlagSets, *base.FlagSet) {} + extra{{ camelCase .SubActionPrefix }}FlagsHandlingFunc = func(*{{ camelCase .SubActionPrefix }}Command, *[]{{ .Pkg }}.Option) int { return 0 } + executeExtra{{ camelCase .SubActionPrefix }}Actions = func(_ *{{ camelCase .SubActionPrefix }}Command, inResult api.GenericResult, inErr error, _ *{{ .Pkg }}.Client, _ uint32, _ []{{ .Pkg }}.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustom{{ camelCase .SubActionPrefix }}ActionOutput = func(*{{ camelCase .SubActionPrefix }}Command) (bool, error) { return false, nil } +) +`)) diff --git a/internal/tests/cli/boundary/sessions.bats b/internal/tests/cli/boundary/sessions.bats index 33d12d1bb..24e2282c1 100644 --- a/internal/tests/cli/boundary/sessions.bats +++ b/internal/tests/cli/boundary/sessions.bats @@ -74,8 +74,10 @@ load _helpers [ "$status" -eq 0 ] run read_session $id [ "$status" -eq 0 ] + echo $output run cancel_session $id [ "$status" -eq 0 ] + echo $output } @test "boundary/session: verify read and cancelation permissions on unpriv session" { @@ -101,4 +103,4 @@ load _helpers [ "$status" -eq 0 ] run cancel_session $id [ "$status" -eq 0 ] -} \ No newline at end of file +} diff --git a/website/content/docs/api-clients/desktop.mdx b/website/content/docs/api-clients/desktop.mdx index b9191f151..caa38375c 100644 --- a/website/content/docs/api-clients/desktop.mdx +++ b/website/content/docs/api-clients/desktop.mdx @@ -8,7 +8,7 @@ description: |- # Boundary Desktop --> Boundary Desktop is currently an Alpha release still undergoing final testing before its official release. Should you experience any bugs or lack of desired functionality, please let us know so that we can evaluate a fix. You can open an issue at https://github.com/hashicorp/boundary/issues/new/choose. Your help is greatly appreciated! +-> Boundary Desktop is currently an Alpha release still undergoing final testing before its official release. Should you experience any bugs or lack of desired functionality, please let us know so that we can evaluate a fix. You can open an issue at https://github.com/hashicorp/boundary/issues/new/choose. Your help is greatly appreciated! Boundary Desktop is a standalone application that provides a simple interface for browsing and connecting to targets on your local computer (macOS currently @@ -18,7 +18,7 @@ using your favorite tooling! ## Getting Started -> If you're running Boundary for the first time, [download the latest binary](https://www.boundaryproject.io/downloads) -and run it in `dev` mode locally so you can have a server to run against: +and run it in `dev` mode locally so you can have a server to run against: ```shell-session $ boundary dev @@ -29,34 +29,35 @@ $ boundary dev 1. Download the latest .dmg installer from our [releases page](https://releases.hashicorp.com/boundary-desktop) 1. Double-click the downloaded .dmg to run the installer 1. Drag and drop Boundary into the applications folder -![](/img/boundary-desktop-drag-to-install.png) + ![](/img/boundary-desktop-drag-to-install.png) ### Run Boundary Desktop 1. Open the Boundary Desktop application -![](/img/boundary-desktop-open.png) + ![](/img/boundary-desktop-open.png) 1. You'll be prompted for the Boundary server origin, this is the URL for the client -to connect to the Boundary API. If you are running a local `dev` mode server, this -URL will be `http://localhost:9200` -![](/img/boundary-desktop-origin.png) -1. You can now login to Boundary. We're using a `dev` mode server in this example with the -username `admin` and the password `password` -![](/img/boundary-desktop-login.png) + to connect to the Boundary API. If you are running a local `dev` mode server, this + URL will be `http://localhost:9200` + ![](/img/boundary-desktop-origin.png) +1. You can now login to Boundary. We're using a `dev` mode server in this example with the + username `admin` and the password `password` + ![](/img/boundary-desktop-login.png) 1. After logging in, you should see the targets your user is authorized to connect to. Since -we are using a `dev` mode server we see the default generated target for `127.0.0.1:22` -![](/img/boundary-desktop-landing.png) + we are using a `dev` mode server we see the default generated target for `127.0.0.1:22` + ![](/img/boundary-desktop-landing.png) ### Connect! + -> The rest of this example assumes you're running Boundary in `dev` mode. -1. Click on `connect` next to the default target. A pop-up window will display the local -address of the proxy and the ephemeral port for the session -![](/img/boundary-desktop-connect.png) +1. Click on `connect` next to the default target. A pop-up window will display the local + address of the proxy and the ephemeral port for the session + ![](/img/boundary-desktop-connect.png) 1. Navigate to the `Sessions` pane and you'll see this session is in `pending` state because we -haven't made a connection to it yet (but will!) -![](/img/boundary-desktop-pending.png) + haven't made a connection to it yet (but will!) + ![](/img/boundary-desktop-pending.png) --> The next step assumes you have a SSH server running that the default target will connect to. +-> The next step assumes you have a SSH server running that the default target will connect to. 1. On the CLI, `ssh` to the target using the local ephemeral port created in the previous step @@ -68,10 +69,11 @@ Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '[127.0.0.1]:49250' (ECDSA) to the list of known hosts. Password: Last login: Thu Feb 11 17:49:09 2021 -$ +$ ``` + 1. Navigate back to the sessions view and you'll see this session is now active -![](/img/boundary-desktop-active.png) + ![](/img/boundary-desktop-active.png) 1. Click `Cancel` to cancel the session and you'll see the status go to `cancelling` briefly, then `terminated` -![](/img/boundary-desktop-terminated.png) + ![](/img/boundary-desktop-terminated.png) 1. Navigate back to the CLI and you'll see your SSH session has closed diff --git a/website/content/docs/concepts/security/permissions.mdx b/website/content/docs/concepts/security/permissions.mdx index e902cb6d1..1e9dcb446 100644 --- a/website/content/docs/concepts/security/permissions.mdx +++ b/website/content/docs/concepts/security/permissions.mdx @@ -222,11 +222,11 @@ wildcard or templated grant strings.
  • Type
  • -
      -
    • - account -
    • -
    +
      +
    • + account +
    • +
@@ -234,15 +234,19 @@ wildcard or templated grant strings.
  • create: Create an account
  • -
      -
    • type=<type>;actions=create
    • -
    +
      +
    • + type=<type>;actions=create +
    • +
  • list: List accounts
  • -
      -
    • type=<type>;actions=list
    • -
    +
      +
    • + type=<type>;actions=list +
    • +
    @@ -253,23 +257,23 @@ wildcard or templated grant strings.
    • ID
    • -
        -
      • - <id> -
      • -
      +
        +
      • + <id> +
      • +
    • Pin
    • -
        -
      • - <auth-method-id> -
      • -
      +
        +
      • + <auth-method-id> +
      • +
    • Type
    • -
        -
      • - account -
      • -
      +
        +
      • + account +
      • +
    @@ -277,38 +281,62 @@ wildcard or templated grant strings.
  • read: Read an account
  • -
      -
    • id=<id>;actions=read
    • -
    • id=<pin>;type=<type>;actions=read
    • -
    +
      +
    • + id=<id>;actions=read +
    • +
    • + id=<pin>;type=<type>;actions=read +
    • +
  • update: Update an account
  • -
      -
    • id=<id>;actions=update
    • -
    • id=<pin>;type=<type>;actions=update
    • -
    +
      +
    • + id=<id>;actions=update +
    • +
    • + id=<pin>;type=<type>;actions=update +
    • +
  • delete: Delete an account
  • -
      -
    • id=<id>;actions=delete
    • -
    • id=<pin>;type=<type>;actions=delete
    • -
    +
      +
    • + id=<id>;actions=delete +
    • +
    • + id=<pin>;type=<type>;actions=delete +
    • +
  • - set-password: Set a password on an account, without requring the current password + set-password: Set a password on an account, without + requring the current password
  • -
      -
    • id=<id>;actions=set-password
    • -
    • id=<pin>;type=<type>;actions=set-password
    • -
    +
      +
    • + id=<id>;actions=set-password +
    • +
    • + id=<pin>;type=<type>;actions=set-password +
    • +
  • - change-password: Change a password on an account given the current password + change-password: Change a password on an account given + the current password
  • -
      -
    • id=<id>;actions=change-password
    • -
    • id=<pin>;type=<type>;actions=change-password
    • -
    +
      +
    • + id=<id>;actions=change-password +
    • +
    • + + id=<pin>;type=<type>;actions=change-password + +
    • +
    @@ -326,11 +354,11 @@ wildcard or templated grant strings.
    • Type
    • -
        -
      • - auth-method -
      • -
      +
        +
      • + auth-method +
      • +
    @@ -338,15 +366,19 @@ wildcard or templated grant strings.
  • create: Create an auth method
  • -
      -
    • type=<type>;actions=create
    • -
    +
      +
    • + type=<type>;actions=create +
    • +
  • list: List auth methods
  • -
      -
    • type=<type>;actions=list
    • -
    +
      +
    • + type=<type>;actions=list +
    • +
    @@ -357,17 +389,17 @@ wildcard or templated grant strings.
    • ID
    • -
        -
      • - <id> -
      • -
      +
        +
      • + <id> +
      • +
    • Type
    • -
        -
      • - auth-method -
      • -
      +
        +
      • + auth-method +
      • +
    @@ -375,27 +407,35 @@ wildcard or templated grant strings.
  • read: Read an auth method
  • -
      -
    • id=<id>;actions=read
    • -
    +
      +
    • + id=<id>;actions=read +
    • +
  • update: Update an auth method
  • -
      -
    • id=<id>;actions=update
    • -
    +
      +
    • + id=<id>;actions=update +
    • +
  • delete: Delete an auth method
  • -
      -
    • id=<id>;actions=delete
    • -
    +
      +
    • + id=<id>;actions=delete +
    • +
  • authenticate: Authenticate to an auth method
  • -
      -
    • id=<id>;actions=authenticate
    • -
    +
      +
    • + id=<id>;actions=authenticate +
    • +
    @@ -413,11 +453,11 @@ wildcard or templated grant strings.
    • Type
    • -
        -
      • - auth-token -
      • -
      +
        +
      • + auth-token +
      • +
    @@ -425,9 +465,11 @@ wildcard or templated grant strings.
  • list: List auth tokens
  • -
      -
    • type=<type>;actions=list
    • -
    +
      +
    • + type=<type>;actions=list +
    • +
    @@ -438,17 +480,17 @@ wildcard or templated grant strings.
    • ID
    • -
        -
      • - <id> -
      • -
      +
        +
      • + <id> +
      • +
    • Type
    • -
        -
      • - auth-token -
      • -
      +
        +
      • + auth-token +
      • +
    @@ -456,15 +498,19 @@ wildcard or templated grant strings.
  • read: Read an auth token
  • -
      -
    • id=<id>;actions=read
    • -
    +
      +
    • + id=<id>;actions=read +
    • +
  • delete: Delete an auth token
  • -
      -
    • id=<id>;actions=delete
    • -
    +
      +
    • + id=<id>;actions=delete +
    • +
    @@ -483,11 +529,11 @@ wildcard or templated grant strings.
    • Type
    • -
        -
      • - group -
      • -
      +
        +
      • + group +
      • +
    @@ -495,15 +541,19 @@ wildcard or templated grant strings.
  • create: Create a group
  • -
      -
    • type=<type>;actions=create
    • -
    +
      +
    • + type=<type>;actions=create +
    • +
  • list: List groups
  • -
      -
    • type=<type>;actions=list
    • -
    +
      +
    • + type=<type>;actions=list +
    • +
    @@ -514,17 +564,17 @@ wildcard or templated grant strings.
    • ID
    • -
        -
      • - <id> -
      • -
      +
        +
      • + <id> +
      • +
    • Type
    • -
        -
      • - group -
      • -
      +
        +
      • + group +
      • +
    @@ -532,39 +582,51 @@ wildcard or templated grant strings.
  • read: Read a group
  • -
      -
    • id=<id>;actions=read
    • -
    +
      +
    • + id=<id>;actions=read +
    • +
  • update: Update a group
  • -
      -
    • id=<id>;actions=update
    • -
    +
      +
    • + id=<id>;actions=update +
    • +
  • delete: Delete a group
  • -
      -
    • id=<id>;actions=delete
    • -
    +
      +
    • + id=<id>;actions=delete +
    • +
  • add-members: Add members to a group
  • -
      -
    • id=<id>;actions=add-members
    • -
    +
      +
    • + id=<id>;actions=add-members +
    • +
  • set-members: Set the full set of members on a group
  • -
      -
    • id=<id>;actions=set-members
    • -
    +
      +
    • + id=<id>;actions=set-members +
    • +
  • remove-members: Remove members from a group
  • -
      -
    • id=<id>;actions=remove-members
    • -
    +
      +
    • + id=<id>;actions=remove-members +
    • +
    @@ -581,11 +643,11 @@ wildcard or templated grant strings.
    • Type
    • -
        -
      • - host -
      • -
      +
        +
      • + host +
      • +
    @@ -593,15 +655,19 @@ wildcard or templated grant strings.
  • create: Create a host
  • -
      -
    • type=<type>;actions=create
    • -
    +
      +
    • + type=<type>;actions=create +
    • +
  • list: List hosts
  • -
      -
    • type=<type>;actions=list
    • -
    +
      +
    • + type=<type>;actions=list +
    • +
    @@ -612,23 +678,23 @@ wildcard or templated grant strings.
    • ID
    • -
        -
      • - <id> -
      • -
      +
        +
      • + <id> +
      • +
    • Pin
    • -
        -
      • - <host-catalog-id> -
      • -
      +
        +
      • + <host-catalog-id> +
      • +
    • Type
    • -
        -
      • - host -
      • -
      +
        +
      • + host +
      • +
    @@ -636,24 +702,36 @@ wildcard or templated grant strings.
  • read: Read a host
  • -
      -
    • id=<id>;actions=read
    • -
    • id=<pin>;type=<type>;actions=read
    • -
    +
      +
    • + id=<id>;actions=read +
    • +
    • + id=<pin>;type=<type>;actions=read +
    • +
  • update: Update a host
  • -
      -
    • id=<id>;actions=update
    • -
    • id=<pin>;type=<type>;actions=update
    • -
    +
      +
    • + id=<id>;actions=update +
    • +
    • + id=<pin>;type=<type>;actions=update +
    • +
  • delete: Delete a host
  • -
      -
    • id=<id>;actions=delete
    • -
    • id=<pin>;type=<type>;actions=delete
    • -
    +
      +
    • + id=<id>;actions=delete +
    • +
    • + id=<pin>;type=<type>;actions=delete +
    • +
    @@ -670,11 +748,11 @@ wildcard or templated grant strings.
    • Type
    • -
        -
      • - host-catalog -
      • -
      +
        +
      • + host-catalog +
      • +
    @@ -682,15 +760,19 @@ wildcard or templated grant strings.
  • create: Create a host catalog
  • -
      -
    • type=<type>;actions=create
    • -
    +
      +
    • + type=<type>;actions=create +
    • +
  • list: List host catalogs
  • -
      -
    • type=<type>;actions=list
    • -
    +
      +
    • + type=<type>;actions=list +
    • +
    @@ -701,17 +783,17 @@ wildcard or templated grant strings.
    • ID
    • -
        -
      • - <id> -
      • -
      +
        +
      • + <id> +
      • +
    • Type
    • -
        -
      • - host-catalog -
      • -
      +
        +
      • + host-catalog +
      • +
    @@ -719,21 +801,27 @@ wildcard or templated grant strings.
  • read: Read a host catalog
  • -
      -
    • id=<id>;actions=read
    • -
    +
      +
    • + id=<id>;actions=read +
    • +
  • update: Update a host catalog
  • -
      -
    • id=<id>;actions=update
    • -
    +
      +
    • + id=<id>;actions=update +
    • +
  • delete: Delete a host catalog
  • -
      -
    • id=<id>;actions=delete
    • -
    +
      +
    • + id=<id>;actions=delete +
    • +
    @@ -750,11 +838,11 @@ wildcard or templated grant strings.
    • Type
    • -
        -
      • - host-set -
      • -
      +
        +
      • + host-set +
      • +
    @@ -762,15 +850,19 @@ wildcard or templated grant strings.
  • create: Create a host set
  • -
      -
    • type=<type>;actions=create
    • -
    +
      +
    • + type=<type>;actions=create +
    • +
  • list: List host sets
  • -
      -
    • type=<type>;actions=list
    • -
    +
      +
    • + type=<type>;actions=list +
    • +
    @@ -781,23 +873,23 @@ wildcard or templated grant strings.
    • ID
    • -
        -
      • - <id> -
      • -
      +
        +
      • + <id> +
      • +
    • Pin
    • -
        -
      • - <host-catalog-id> -
      • -
      +
        +
      • + <host-catalog-id> +
      • +
    • Type
    • -
        -
      • - host-set -
      • -
      +
        +
      • + host-set +
      • +
    @@ -805,45 +897,69 @@ wildcard or templated grant strings.
  • read: Read a host set
  • -
      -
    • id=<id>;actions=read
    • -
    • id=<pin>;type=<type>;actions=read
    • -
    +
      +
    • + id=<id>;actions=read +
    • +
    • + id=<pin>;type=<type>;actions=read +
    • +
  • update: Update a host set
  • -
      -
    • id=<id>;actions=update
    • -
    • id=<pin>;type=<type>;actions=update
    • -
    +
      +
    • + id=<id>;actions=update +
    • +
    • + id=<pin>;type=<type>;actions=update +
    • +
  • delete: Delete a host set
  • -
      -
    • id=<id>;actions=delete
    • -
    • id=<pin>;type=<type>;actions=delete
    • -
    +
      +
    • + id=<id>;actions=delete +
    • +
    • + id=<pin>;type=<type>;actions=delete +
    • +
  • add-hosts: Add hosts to a host-set
  • -
      -
    • id=<id>;actions=add-hosts
    • -
    • id=<pin>;type=<type>;actions=add-hosts
    • -
    +
      +
    • + id=<id>;actions=add-hosts +
    • +
    • + id=<pin>;type=<type>;actions=add-hosts +
    • +
  • set-hosts: Set the full set of hosts on a host set
  • -
      -
    • id=<id>;actions=set-hosts
    • -
    • id=<pin>;type=<type>;actions=set-hosts
    • -
    +
      +
    • + id=<id>;actions=set-hosts +
    • +
    • + id=<pin>;type=<type>;actions=set-hosts +
    • +
  • remove-hosts: Remove hosts from a host set
  • -
      -
    • id=<id>;actions=remove-hosts
    • -
    • id=<pin>;type=<type>;actions=remove-hosts
    • -
    +
      +
    • + id=<id>;actions=remove-hosts +
    • +
    • + id=<pin>;type=<type>;actions=remove-hosts +
    • +
    @@ -862,11 +978,11 @@ wildcard or templated grant strings.
    • Type
    • -
        -
      • - role -
      • -
      +
        +
      • + role +
      • +
    @@ -874,15 +990,19 @@ wildcard or templated grant strings.
  • create: Create a role
  • -
      -
    • type=<type>;actions=create
    • -
    +
      +
    • + type=<type>;actions=create +
    • +
  • list: List roles
  • -
      -
    • type=<type>;actions=list
    • -
    +
      +
    • + type=<type>;actions=list +
    • +
    @@ -893,17 +1013,17 @@ wildcard or templated grant strings.
    • ID
    • -
        -
      • - <id> -
      • -
      +
        +
      • + <id> +
      • +
    • Type
    • -
        -
      • - role -
      • -
      +
        +
      • + role +
      • +
    @@ -911,57 +1031,76 @@ wildcard or templated grant strings.
  • read: Read a role
  • -
      -
    • id=<id>;actions=read
    • -
    +
      +
    • + id=<id>;actions=read +
    • +
  • update: Update a role
  • -
      -
    • id=<id>;actions=update
    • -
    +
      +
    • + id=<id>;actions=update +
    • +
  • delete: Delete a role
  • -
      -
    • id=<id>;actions=delete
    • -
    +
      +
    • + id=<id>;actions=delete +
    • +
  • add-principals: Add principals to a role
  • -
      -
    • id=<id>;actions=add-principals
    • -
    +
      +
    • + id=<id>;actions=add-principals +
    • +
  • - set-principals: Set the full set of principals on a role + set-principals: Set the full set of principals on a + role
  • -
      -
    • id=<id>;actions=set-principals
    • -
    +
      +
    • + id=<id>;actions=set-principals +
    • +
  • remove-principals: Remove principals from a role
  • -
      -
    • id=<id>;actions=remove-principals
    • -
    +
      +
    • + id=<id>;actions=remove-principals +
    • +
  • add-grants: Add grants to a role
  • -
      -
    • id=<id>;actions=add-grants
    • -
    +
      +
    • + id=<id>;actions=add-grants +
    • +
  • set-grants: Set the full set of grants on a role
  • -
      -
    • id=<id>;actions=set-grants
    • -
    +
      +
    • + id=<id>;actions=set-grants +
    • +
  • remove-grants: Remove grants from a role
  • -
      -
    • id=<id>;actions=remove-grants
    • -
    +
      +
    • + id=<id>;actions=remove-grants +
    • +
    @@ -979,11 +1118,11 @@ wildcard or templated grant strings.
    • Type
    • -
        -
      • - scope -
      • -
      +
        +
      • + scope +
      • +
    @@ -991,15 +1130,19 @@ wildcard or templated grant strings.
  • create: Create a scope
  • -
      -
    • type=<type>;actions=create
    • -
    +
      +
    • + type=<type>;actions=create +
    • +
  • list: List scopes
  • -
      -
    • type=<type>;actions=list
    • -
    +
      +
    • + type=<type>;actions=list +
    • +
    @@ -1010,17 +1153,17 @@ wildcard or templated grant strings.
    • ID
    • -
        -
      • - <id> -
      • -
      +
        +
      • + <id> +
      • +
    • Type
    • -
        -
      • - scope -
      • -
      +
        +
      • + scope +
      • +
    @@ -1028,21 +1171,27 @@ wildcard or templated grant strings.
  • read: Read a scope
  • -
      -
    • id=<id>;actions=read
    • -
    +
      +
    • + id=<id>;actions=read +
    • +
  • update: Update a scope
  • -
      -
    • id=<id>;actions=update
    • -
    +
      +
    • + id=<id>;actions=update +
    • +
  • delete: Delete a scope
  • -
      -
    • id=<id>;actions=delete
    • -
    +
      +
    • + id=<id>;actions=delete +
    • +
    @@ -1059,11 +1208,11 @@ wildcard or templated grant strings.
    • Type
    • -
        -
      • - session -
      • -
      +
        +
      • + session +
      • +
    @@ -1071,9 +1220,11 @@ wildcard or templated grant strings.
  • list: List sessions
  • -
      -
    • type=<type>;actions=list
    • -
    +
      +
    • + type=<type>;actions=list +
    • +
    @@ -1084,17 +1235,17 @@ wildcard or templated grant strings.
    • ID
    • -
        -
      • - <id> -
      • -
      +
        +
      • + <id> +
      • +
    • Type
    • -
        -
      • - session -
      • -
      +
        +
      • + session +
      • +
    @@ -1102,27 +1253,37 @@ wildcard or templated grant strings.
  • read: Read a session
  • -
      -
    • id=<id>;actions=read
    • -
    +
      +
    • + id=<id>;actions=read +
    • +
  • cancel: Cancel a session
  • -
      -
    • id=<id>;actions=cancel
    • -
    +
      +
    • + id=<id>;actions=cancel +
    • +
  • - read:self: Read a session, which must be associated with the calling user + read:self: Read a session, which must be associated + with the calling user
  • -
      -
    • id=*;type=session;actions=read:self
    • -
    +
      +
    • + id=*;type=session;actions=read:self +
    • +
  • - cancel:self: Cancel a session, which must be associated with the calling user + cancel:self: Cancel a session, which must be associated + with the calling user
  • -
      -
    • id=*;type=session;actions=cancel:self
    • -
    +
      +
    • + id=*;type=session;actions=cancel:self +
    • +
    @@ -1139,11 +1300,11 @@ wildcard or templated grant strings.
    • Type
    • -
        -
      • - target -
      • -
      +
        +
      • + target +
      • +
    @@ -1151,15 +1312,19 @@ wildcard or templated grant strings.
  • create: Create a target
  • -
      -
    • type=<type>;actions=create
    • -
    +
      +
    • + type=<type>;actions=create +
    • +
  • list: List targets
  • -
      -
    • type=<type>;actions=list
    • -
    +
      +
    • + type=<type>;actions=list +
    • +
    @@ -1170,17 +1335,17 @@ wildcard or templated grant strings.
    • ID
    • -
        -
      • - <id> -
      • -
      +
        +
      • + <id> +
      • +
    • Type
    • -
        -
      • - target -
      • -
      +
        +
      • + target +
      • +
    @@ -1188,45 +1353,60 @@ wildcard or templated grant strings.
  • read: Read a target
  • -
      -
    • id=<id>;actions=read
    • -
    +
      +
    • + id=<id>;actions=read +
    • +
  • update: Update a target
  • -
      -
    • id=<id>;actions=update
    • -
    +
      +
    • + id=<id>;actions=update +
    • +
  • delete: Delete a target
  • -
      -
    • id=<id>;actions=delete
    • -
    +
      +
    • + id=<id>;actions=delete +
    • +
  • add-host-sets: Add host sets to a target
  • -
      -
    • id=<id>;actions=add-host-sets
    • -
    +
      +
    • + id=<id>;actions=add-host-sets +
    • +
  • - set-host-sets: Set the full set of host sets on a target + set-host-sets: Set the full set of host sets on a + target
  • -
      -
    • id=<id>;actions=set-host-sets
    • -
    +
      +
    • + id=<id>;actions=set-host-sets +
    • +
  • remove-host-sets: Remove host sets from a target
  • -
      -
    • id=<id>;actions=remove-host-sets
    • -
    +
      +
    • + id=<id>;actions=remove-host-sets +
    • +
  • authorize-session: Authorize a session via the target
  • -
      -
    • id=<id>;actions=authorize-session
    • -
    +
      +
    • + id=<id>;actions=authorize-session +
    • +
    @@ -1244,11 +1424,11 @@ wildcard or templated grant strings.
    • Type
    • -
        -
      • - user -
      • -
      +
        +
      • + user +
      • +
    @@ -1256,15 +1436,19 @@ wildcard or templated grant strings.
  • create: Create a user
  • -
      -
    • type=<type>;actions=create
    • -
    +
      +
    • + type=<type>;actions=create +
    • +
  • list: List users
  • -
      -
    • type=<type>;actions=list
    • -
    +
      +
    • + type=<type>;actions=list +
    • +
    @@ -1275,17 +1459,17 @@ wildcard or templated grant strings.
    • ID
    • -
        -
      • - <id> -
      • -
      +
        +
      • + <id> +
      • +
    • Type
    • -
        -
      • - user -
      • -
      +
        +
      • + user +
      • +
    @@ -1293,39 +1477,51 @@ wildcard or templated grant strings.
  • read: Read a user
  • -
      -
    • id=<id>;actions=read
    • -
    +
      +
    • + id=<id>;actions=read +
    • +
  • update: Update a user
  • -
      -
    • id=<id>;actions=update
    • -
    +
      +
    • + id=<id>;actions=update +
    • +
  • delete: Delete a user
  • -
      -
    • id=<id>;actions=delete
    • -
    +
      +
    • + id=<id>;actions=delete +
    • +
  • add-accounts: Add accounts to a user
  • -
      -
    • id=<id>;actions=add-accounts
    • -
    +
      +
    • + id=<id>;actions=add-accounts +
    • +
  • set-accounts: Set the full set of accounts on a user
  • -
      -
    • id=<id>;actions=set-accounts
    • -
    +
      +
    • + id=<id>;actions=set-accounts +
    • +
  • remove-accounts: Remove accounts from a user
  • -
      -
    • id=<id>;actions=remove-accounts
    • -
    +
      +
    • + id=<id>;actions=remove-accounts +
    • +
    diff --git a/website/content/docs/configuration/worker.mdx b/website/content/docs/configuration/worker.mdx index cd36a6c2e..c3409868f 100644 --- a/website/content/docs/configuration/worker.mdx +++ b/website/content/docs/configuration/worker.mdx @@ -19,18 +19,18 @@ worker { ``` - `name` - Specifies a unique name of this worker within the Boundary worker -cluster. + cluster. - `description` - Specifies a friendly description of this worker. - `public_addr` - Specifies the public host or IP address (and optionally port) -at which the worker can be reached _by clients for proxying_. This defaults to -the address of the listener marked for `proxy` purpose. This is especially -useful for cloud environments that do not bind a publicly accessible IP to a NIC -on the host directly, such as an Amazon EIP. + at which the worker can be reached _by clients for proxying_. This defaults to + the address of the listener marked for `proxy` purpose. This is especially + useful for cloud environments that do not bind a publicly accessible IP to a NIC + on the host directly, such as an Amazon EIP. - `controllers` - A list of hosts/IP addresses and optionally ports for reaching -controllers. The port will default to :9201 if not specified. + controllers. The port will default to :9201 if not specified. - `tags` - A map of key-value pairs where values are an array of strings. Most commonly used for [filtering](/docs/concepts/filtering) targets a worker can proxy via [worker tags](/docs/concepts/filtering/worker-tags). @@ -39,7 +39,7 @@ controllers. The port will default to :9201 if not specified. Workers require a KMS block designated for `worker-auth`. This is the KMS configuration for authentication between the workers and controllers and must be present. Example (not safe for production!): -```hcl +```hcl kms "aead" { purpose = "worker-auth" aead_type = "aes-gcm" @@ -48,13 +48,13 @@ authentication between the workers and controllers and must be present. Example } ``` -This configuration must be the same for the worker-auth configuration for the controller if you're +This configuration must be the same for the worker-auth configuration for the controller if you're running the controller and worker as separate servers. And optionally, a KMS stanza for configuration encryption purpose: ```hcl -# Configuration encryption block: decrypts sensitive values in the +# Configuration encryption block: decrypts sensitive values in the # configuration file. See `boundary config [encrypt|decrypt] -h`. kms "aead" { purpose = "config"` @@ -64,7 +64,7 @@ kms "aead" { ``` Boundary supports many kinds of KMS integrations. For a complete guide to all available -KMS types, see our [KMS documentation](/docs/configuration/kms). +KMS types, see our [KMS documentation](/docs/configuration/kms). # Complete Configuration Example @@ -88,11 +88,11 @@ worker { ] public_addr = "myhost.mycompany.com" - + tags { type = ["prod", "webservers"] region = ["us-east-1"] - } + } } # must be same key as used on controller config diff --git a/website/content/docs/installing/no-gen-resources.mdx b/website/content/docs/installing/no-gen-resources.mdx index 45b1ffcb9..ef7e514e9 100644 --- a/website/content/docs/installing/no-gen-resources.mdx +++ b/website/content/docs/installing/no-gen-resources.mdx @@ -1,6 +1,6 @@ --- layout: docs -page_title: Running Boundary in Non-Dev Environments +page_title: Running Boundary in Non-Dev Environments sidebar_title: Non-Dev Environments description: |- How to install boundary in non-dev environments. @@ -10,11 +10,11 @@ description: |- What are generated resources? When you run `boundary dev` or `boundary database init`, Boundary automatically generates a number of resources to make getting started easier. Default scopes, auth methods, user, account, and -targets are just some of the resources Boundary will generate unless you tell it not to. +targets are just some of the resources Boundary will generate unless you tell it not to. -In a production or long-running environment, these resoures are not neccessary, but without them, managing -Boundary from scratch isn't straight forward. How do you create your first user and login to administer a Boundary -deployment that has no authentication methods, users, accounts, etc? This section describes how to get your freshly +In a production or long-running environment, these resoures are not neccessary, but without them, managing +Boundary from scratch isn't straight forward. How do you create your first user and login to administer a Boundary +deployment that has no authentication methods, users, accounts, etc? This section describes how to get your freshly deployed Boundary installation off the ground for non-dev environments. ## Recovery KMS Workflow @@ -102,7 +102,6 @@ EOT - ## Initialize the Database Before you can start Boundary, the database must be initialized. It's useful to look at the help output for the init command: @@ -178,8 +177,6 @@ resource "boundary_scope" "project" { - - ### Create an Auth Method Create an auth method in the organization scope. @@ -277,7 +274,6 @@ resource "boundary_user" "myuser" { - ### Create Roles to Manage Scopes The following describes the four baseline roles you'll need to create to manage resources within the org and project @@ -286,6 +282,7 @@ allows you to manage them independently and fully within Terraform or on the CLI them down more or have control over them in a declarative way. The following example creates 4 roles: + 1. To allow anonymouns (unauthenticated) users the ability to list scopes and auth methods in the global scope. 1. To allow anonymouns (unauthenticated) users the ability to list scopes and auth methods in the orgnaization scope. 1. To allow `myuser` user administration grants at the org scope. @@ -370,10 +367,10 @@ resource "boundary_role" "org_anon_listing" { principal_ids = ["u_anon"] } ``` + - #### Org admin role for myuser @@ -409,10 +406,10 @@ resource "boundary_role" "org_admin" { principal_ids = [boundary_user.myuser.id] } ``` + - #### Project admin for myuser @@ -452,7 +449,6 @@ resource "boundary_role" "project_admin" { - ### Login as Your New User ```bash