From b75a6fc5e5835239297c981692d40c52703619b7 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 13 Aug 2020 11:20:22 -0400 Subject: [PATCH] Update scopes CLI command in the model of the roles command (#262) * Update scopes CLI command in the model of the roles command * Pull help functions to helper methods --- internal/cmd/base/base.go | 5 + internal/cmd/commands.go | 24 +- .../cmd/commands/controller/controller.go | 2 +- internal/cmd/commands/dev/dev.go | 2 +- internal/cmd/commands/roles/funcs.go | 106 +------- internal/cmd/commands/roles/role.go | 70 +++--- internal/cmd/commands/scopes/create.go | 108 --------- internal/cmd/commands/scopes/funcs.go | 2 +- internal/cmd/commands/scopes/read.go | 101 -------- internal/cmd/commands/scopes/scope.go | 229 ++++++++++++++++++ internal/cmd/common/flags.go | 39 +++ internal/cmd/common/help.go | 107 ++++++++ 12 files changed, 442 insertions(+), 353 deletions(-) delete mode 100644 internal/cmd/commands/scopes/create.go delete mode 100644 internal/cmd/commands/scopes/read.go create mode 100644 internal/cmd/commands/scopes/scope.go create mode 100644 internal/cmd/common/flags.go create mode 100644 internal/cmd/common/help.go diff --git a/internal/cmd/base/base.go b/internal/cmd/base/base.go index 73d263e5a..7df8a7815 100644 --- a/internal/cmd/base/base.go +++ b/internal/cmd/base/base.go @@ -61,6 +61,11 @@ type Command struct { FlagTokenName string flagOutputCurlString bool + FlagId string + FlagName string + FlagDescription string + FlagVersion int + client *api.Client } diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index 176a212f0..0a491f4c0 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -59,13 +59,33 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { }, nil }, "scopes create": func() (cli.Command, error) { - return &scopes.CreateScopeCommand{ + return &scopes.Command{ Command: base.NewCommand(ui), + Func: "create", }, nil }, "scopes read": func() (cli.Command, error) { - return &scopes.ReadScopeCommand{ + return &scopes.Command{ Command: base.NewCommand(ui), + Func: "read", + }, nil + }, + "scopes update": func() (cli.Command, error) { + return &scopes.Command{ + Command: base.NewCommand(ui), + Func: "update", + }, nil + }, + "scopes delete": func() (cli.Command, error) { + return &scopes.Command{ + Command: base.NewCommand(ui), + Func: "delete", + }, nil + }, + "scopes list": func() (cli.Command, error) { + return &scopes.Command{ + Command: base.NewCommand(ui), + Func: "list", }, nil }, "config": func() (cli.Command, error) { diff --git a/internal/cmd/commands/controller/controller.go b/internal/cmd/commands/controller/controller.go index 18cd83719..8f57b9bd8 100644 --- a/internal/cmd/commands/controller/controller.go +++ b/internal/cmd/commands/controller/controller.go @@ -372,7 +372,7 @@ func (c *Command) ParseFlagsAndConfig(args []string) int { c.UI.Error(fmt.Sprintf("Invalid dev auth method ID, must start with %q", prefix)) return 1 } - if len(c.flagDevAuthMethodId) != 13 { + if len(c.flagDevAuthMethodId) != 15 { c.UI.Error(fmt.Sprintf("Invalid dev auth method ID, must be 10 base62 characters after %q", prefix)) return 1 } diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index e316b0b94..458feb90b 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -180,7 +180,7 @@ func (c *Command) Run(args []string) int { c.UI.Error(fmt.Sprintf("Invalid dev auth method ID, must start with %q", prefix)) return 1 } - if len(c.flagDevAuthMethodId) != 13 { + if len(c.flagDevAuthMethodId) != 15 { c.UI.Error(fmt.Sprintf("Invalid dev auth method ID, must be 10 base62 characters after %q", prefix)) return 1 } diff --git a/internal/cmd/commands/roles/funcs.go b/internal/cmd/commands/roles/funcs.go index 0f90c2865..3cb0e5b1c 100644 --- a/internal/cmd/commands/roles/funcs.go +++ b/internal/cmd/commands/roles/funcs.go @@ -2,22 +2,16 @@ package roles import ( "fmt" - "net/textproto" "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 synopsisFunc(inFunc string) string { - if inFunc == "" { - return wordwrap.WrapString("Manage Boundary roles", base.TermWidth) - } - return wordwrap.WrapString(fmt.Sprintf("%s a role within Boundary", textproto.CanonicalMIMEHeaderKey(inFunc)), base.TermWidth) -} - func principalsGrantsSynopsisFunc(inFunc string, principals bool) string { var in string switchStr := "principals (users, groups)" @@ -35,76 +29,6 @@ func principalsGrantsSynopsisFunc(inFunc string, principals bool) string { return wordwrap.WrapString(fmt.Sprintf("%s a role within Boundary", in), base.TermWidth) } -func baseHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary roles [sub command] [options] [args]", - "", - " This command allows operations on Boundary roles. Examples:", - "", - " Create a role:", - "", - ` $ boundary roles create -name foo -description "For ProdOps usage"`, - "", - " Add a grant to a role:", - "", - ` $ boundary roles add-grants -id r_1234567890 -grant "type=host-catalog;actions=create,delete"`, - "", - " Please see the role subcommand help for detailed usage information.", - }) -} - -func createHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary roles create [options] [args]", - "", - " Create a role. Example:", - "", - ` $ boundary roles create -name ops -description "Role for ops grants"`, - "", - "", - }) -} - -func updateHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary roles update [options] [args]", - "", - " Update a role given its ID. Example:", - "", - ` $ boundary roles update -id r_1234567890 -description "Development host grants"`, - }) -} - -func readHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary roles read [options] [args]", - "", - " Read a role given its ID. Example:", - "", - ` $ boundary roles read -id r_1234567890`, - }) -} - -func deleteHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary roles delete [options] [args]", - "", - " Delete a role given its ID. Example:", - "", - ` $ boundary roles delete -id r_1234567890`, - }) -} - -func listHelp() string { - return base.WrapForHelpText([]string{ - "Usage: boundary roles list [options] [args]", - "", - " List roles within a scope. Example:", - "", - ` $ boundary roles list -org o_1234567890`, - }) -} - func addPrincipalsHelp() string { return base.WrapForHelpText([]string{ "Usage: boundary roles add-principals [options] [args]", @@ -136,26 +60,10 @@ func removePrincipalsHelp() string { } func populateFlags(c *Command, f *base.FlagSet, flagNames []string) { + common.PopulateCommonFlags(c.Command, f, resource.Role, flagNames) + for _, name := range flagNames { switch name { - case "id": - f.StringVar(&base.StringVar{ - Name: "id", - Target: &c.flagId, - Usage: "ID of the role to operate on", - }) - case "name": - f.StringVar(&base.StringVar{ - Name: "name", - Target: &c.flagName, - Usage: "Name to set on the role", - }) - case "description": - f.StringVar(&base.StringVar{ - Name: "description", - Target: &c.flagDescription, - Usage: "Description to set on the role", - }) case "grantscopeid": f.StringVar(&base.StringVar{ Name: "grant-scope-id", @@ -174,12 +82,6 @@ func populateFlags(c *Command, f *base.FlagSet, flagNames []string) { 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).", }) - case "version": - f.IntVar(&base.IntVar{ - Name: "version", - Target: &c.flagVersion, - Usage: "The version of the resource against which to perform an update operation. If not specified, the command will perform a check-and-set automatically.", - }) } } } diff --git a/internal/cmd/commands/roles/role.go b/internal/cmd/commands/roles/role.go index 8076f215c..2d80dbc63 100644 --- a/internal/cmd/commands/roles/role.go +++ b/internal/cmd/commands/roles/role.go @@ -6,7 +6,9 @@ import ( "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/internal/types/resource" "github.com/hashicorp/vault/sdk/helper/strutil" "github.com/kr/pretty" "github.com/mitchellh/cli" @@ -21,39 +23,32 @@ type Command struct { Func string - flagId string - flagName string - flagDescription string flagGrantScopeId string flagPrincipals []string flagGrants []string - flagVersion int } func (c *Command) Synopsis() string { switch c.Func { - case "create", "update", "read", "delete", "list": - return synopsisFunc(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 "Manage Boundary roles" + return "" } -var helpMap = map[string]func() string{ - "create": createHelp, - "update": updateHelp, - "read": readHelp, - "delete": deleteHelp, - "list": listHelp, - "add-principals": addPrincipalsHelp, - "set-principals": setPrincipalsHelp, - "remove-principals": removePrincipalsHelp, - "add-grants": addPrincipalsHelp, - "set-grants": setPrincipalsHelp, - "remove-grants": removePrincipalsHelp, +var helpMap = func() map[string]func() string { + ret := common.HelpMap(resource.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{ @@ -70,10 +65,11 @@ var flagsMap = map[string][]string{ } func (c *Command) Help() string { + hm := helpMap() if c.Func == "" { - return baseHelp() + return hm["base"]() } - return helpMap[c.Func]() + "\n\n" + c.Flags().Help() + return hm[c.Func]() + "\n\n" + c.Flags().Help() } func (c *Command) Flags() *base.FlagSets { @@ -105,7 +101,7 @@ func (c *Command) Run(args []string) int { return 1 } - if strutil.StrListContains(flagsMap[c.Func], "id") && c.flagId == "" { + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { c.UI.Error("ID is required but not passed in via -id") return 1 } @@ -118,19 +114,19 @@ func (c *Command) Run(args []string) int { var opts []roles.Option - switch c.flagName { + switch c.FlagName { case "": case "null": opts = append(opts, roles.DefaultName()) default: - opts = append(opts, roles.WithName(c.flagName)) + opts = append(opts, roles.WithName(c.FlagName)) } - switch c.flagDescription { + switch c.FlagDescription { case "": case "null": opts = append(opts, roles.DefaultDescription()) default: - opts = append(opts, roles.WithDescription(c.flagDescription)) + opts = append(opts, roles.WithDescription(c.FlagDescription)) } switch c.flagGrantScopeId { case "": @@ -200,11 +196,11 @@ func (c *Command) Run(args []string) int { case "create", "read", "delete", "list": // These don't udpate so don't need the existing version default: - switch c.flagVersion { + switch c.FlagVersion { case 0: opts = append(opts, roles.WithAutomaticVersioning()) default: - version = uint32(c.flagVersion) + version = uint32(c.FlagVersion) } } @@ -217,25 +213,25 @@ func (c *Command) Run(args []string) int { case "create": role, apiErr, err = roleClient.Create(c.Context, opts...) case "update": - role, apiErr, err = roleClient.Update(c.Context, c.flagId, version, opts...) + role, apiErr, err = roleClient.Update(c.Context, c.FlagId, version, opts...) case "read": - role, apiErr, err = roleClient.Read(c.Context, c.flagId, opts...) + role, apiErr, err = roleClient.Read(c.Context, c.FlagId, opts...) case "delete": - existed, apiErr, err = roleClient.Delete(c.Context, c.flagId, opts...) + existed, apiErr, err = roleClient.Delete(c.Context, c.FlagId, opts...) case "list": listedRoles, apiErr, err = roleClient.List(c.Context, opts...) case "add-principals": - role, apiErr, err = roleClient.AddPrincipals(c.Context, c.flagId, version, principals, opts...) + role, apiErr, err = roleClient.AddPrincipals(c.Context, c.FlagId, version, principals, opts...) case "set-principals": - role, apiErr, err = roleClient.SetPrincipals(c.Context, c.flagId, version, principals, opts...) + role, apiErr, err = roleClient.SetPrincipals(c.Context, c.FlagId, version, principals, opts...) case "remove-principals": - role, apiErr, err = roleClient.RemovePrincipals(c.Context, c.flagId, version, principals, opts...) + role, apiErr, err = roleClient.RemovePrincipals(c.Context, c.FlagId, version, principals, opts...) case "add-grants": - role, apiErr, err = roleClient.AddGrants(c.Context, c.flagId, version, grants, opts...) + role, apiErr, err = roleClient.AddGrants(c.Context, c.FlagId, version, grants, opts...) case "set-grants": - role, apiErr, err = roleClient.SetGrants(c.Context, c.flagId, version, grants, opts...) + role, apiErr, err = roleClient.SetGrants(c.Context, c.FlagId, version, grants, opts...) case "remove-grants": - role, apiErr, err = roleClient.RemoveGrants(c.Context, c.flagId, version, grants, opts...) + role, apiErr, err = roleClient.RemoveGrants(c.Context, c.FlagId, version, grants, opts...) } plural := "role" diff --git a/internal/cmd/commands/scopes/create.go b/internal/cmd/commands/scopes/create.go deleted file mode 100644 index af9289122..000000000 --- a/internal/cmd/commands/scopes/create.go +++ /dev/null @@ -1,108 +0,0 @@ -package scopes - -import ( - "fmt" - "strings" - - "github.com/hashicorp/boundary/api/scopes" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/kr/pretty" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var _ cli.Command = (*CreateScopeCommand)(nil) -var _ cli.CommandAutocomplete = (*CreateScopeCommand)(nil) - -type CreateScopeCommand struct { - *base.Command - - flagName string - flagDescription string -} - -func (c *CreateScopeCommand) Synopsis() string { - return "Creates a scope within a parent scope" -} - -func (c *CreateScopeCommand) Help() string { - helpText := ` -Usage: boundary scopes create - - Creates a new scope within the scope specified by the ID from the - "scope" parameter or the associated environment variable. - - Example: - - $ boundary scopes create -scope= -name= - -` + c.Flags().Help() - - return strings.TrimSpace(helpText) -} - -func (c *CreateScopeCommand) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - - f := set.NewFlagSet("Command Options") - - f.StringVar(&base.StringVar{ - Name: "name", - Target: &c.flagName, - Completion: complete.PredictAnything, - Usage: "An optional name assigned to the project for display purposes", - }) - - f.StringVar(&base.StringVar{ - Name: "description", - Target: &c.flagDescription, - Completion: complete.PredictNothing, - Usage: "An optional description assigned to the project for display purposes", - }) - - return set -} - -func (c *CreateScopeCommand) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *CreateScopeCommand) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *CreateScopeCommand) Run(args []string) int { - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(err.Error()) - return 2 - } - - scp, apiErr, err := scopes.NewScopesClient(client).Create(c.Context, - client.ScopeId(), - scopes.WithName(c.flagName), - scopes.WithDescription(c.flagDescription)) - - switch { - case err != nil: - c.UI.Error(fmt.Errorf("error creating project: %w", err).Error()) - return 2 - case apiErr != nil: - c.UI.Error(pretty.Sprint(apiErr)) - return 2 - } - - switch base.Format(c.UI) { - case "table": - c.UI.Output(printScope(scp)) - } - - return 0 -} diff --git a/internal/cmd/commands/scopes/funcs.go b/internal/cmd/commands/scopes/funcs.go index 7a7e6ede5..db4e6a280 100644 --- a/internal/cmd/commands/scopes/funcs.go +++ b/internal/cmd/commands/scopes/funcs.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/boundary/internal/cmd/base" ) -func printScope(in *scopes.Scope) string { +func generateScopeTableOutput(in *scopes.Scope) string { var ret []string // This if true is here to line up columns for easy editing if true { diff --git a/internal/cmd/commands/scopes/read.go b/internal/cmd/commands/scopes/read.go deleted file mode 100644 index 71427cb87..000000000 --- a/internal/cmd/commands/scopes/read.go +++ /dev/null @@ -1,101 +0,0 @@ -package scopes - -import ( - "fmt" - "strings" - - "github.com/hashicorp/boundary/api/scopes" - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/kr/pretty" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var _ cli.Command = (*ReadScopeCommand)(nil) -var _ cli.CommandAutocomplete = (*ReadScopeCommand)(nil) - -type ReadScopeCommand struct { - *base.Command - - flagId string -} - -func (c *ReadScopeCommand) Synopsis() string { - return "Reads a scope's data" -} - -func (c *ReadScopeCommand) Help() string { - helpText := ` -Usage: boundary scopes read - - Returns information about a scope specified by the ID. The request will take place within the scope of the caller's authentication token. - - Example: - - $ boundary scopes read -id= - -` + c.Flags().Help() - - return strings.TrimSpace(helpText) -} - -func (c *ReadScopeCommand) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - - f := set.NewFlagSet("Command Options") - - f.StringVar(&base.StringVar{ - Name: "id", - Target: &c.flagId, - Completion: complete.PredictNothing, - Usage: "The ID of the scope to read", - }) - - return set -} - -func (c *ReadScopeCommand) AutocompleteArgs() complete.Predictor { - return complete.PredictAnything -} - -func (c *ReadScopeCommand) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *ReadScopeCommand) Run(args []string) int { - f := c.Flags() - - if err := f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - client, err := c.Client() - if err != nil { - c.UI.Error(err.Error()) - return 2 - } - - if len(c.flagId) == 0 { - c.UI.Error("No scope ID supplied via -id") - return 1 - } - - scp, apiErr, err := scopes.NewScopesClient(client).Read(c.Context, c.flagId) - - switch { - case err != nil: - c.UI.Error(fmt.Errorf("error reading project: %w", err).Error()) - return 2 - case apiErr != nil: - c.UI.Error(pretty.Sprint(apiErr)) - return 2 - } - - switch base.Format(c.UI) { - case "table": - c.UI.Output(printScope(scp)) - } - - return 0 -} diff --git a/internal/cmd/commands/scopes/scope.go b/internal/cmd/commands/scopes/scope.go new file mode 100644 index 000000000..3d3bae95c --- /dev/null +++ b/internal/cmd/commands/scopes/scope.go @@ -0,0 +1,229 @@ +package scopes + +import ( + "fmt" + + "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/vault/sdk/helper/strutil" + "github.com/kr/pretty" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +var _ cli.Command = (*Command)(nil) +var _ cli.CommandAutocomplete = (*Command)(nil) + +type Command struct { + *base.Command + + Func string +} + +func (c *Command) Synopsis() string { + return common.SynopsisFunc(c.Func, "scope") +} + +var flagsMap = map[string][]string{ + "create": {"name", "description"}, + "update": {"id", "name", "description", "version"}, + "read": {"id"}, + "delete": {"id"}, +} + +func (c *Command) Help() string { + helpMap := common.HelpMap(resource.Scope) + if c.Func == "" { + return helpMap["base"]() + } + return helpMap[c.Func]() + "\n\n" + 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, 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 + } + + 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)) + } + + scopeClient := scopes.NewScopesClient(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()) + default: + version = uint32(c.FlagVersion) + } + } + + var existed bool + var scope *scopes.Scope + var listedScopes []*scopes.Scope + var apiErr *api.Error + + switch c.Func { + case "create": + scope, apiErr, err = scopeClient.Create(c.Context, client.ScopeId(), opts...) + case "update": + scope, apiErr, err = scopeClient.Update(c.Context, c.FlagId, version, opts...) + case "read": + scope, apiErr, err = scopeClient.Read(c.Context, c.FlagId, opts...) + case "delete": + existed, apiErr, err = scopeClient.Delete(c.Context, c.FlagId, opts...) + case "list": + listedScopes, apiErr, err = scopeClient.List(c.Context, client.ScopeId(), opts...) + } + + plural := "scope" + if c.Func == "list" { + plural = "scopes" + } + if err != nil { + c.UI.Error(fmt.Sprintf("Error trying to %s %s: %s", c.Func, plural, err.Error())) + return 2 + } + if apiErr != nil { + c.UI.Error(fmt.Sprintf("Error from controller when performing %s on %s: %s", c.Func, plural, pretty.Sprint(apiErr))) + return 1 + } + + 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": + 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 to JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + + case "table": + if len(listedScopes) == 0 { + c.UI.Output("No 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 s.Name != "" { + output = append(output, + fmt.Sprintf(" Name: %s", s.Name), + ) + } + if s.Description != "" { + output = append(output, + fmt.Sprintf(" Description: %s", s.Description), + ) + } + } + c.UI.Output(base.WrapForHelpText(output)) + } + return 0 + } + + 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 to JSON: %w", err).Error()) + return 1 + } + c.UI.Output(string(b)) + } + + return 0 +} diff --git a/internal/cmd/common/flags.go b/internal/cmd/common/flags.go new file mode 100644 index 000000000..ef47a516b --- /dev/null +++ b/internal/cmd/common/flags.go @@ -0,0 +1,39 @@ +package common + +import ( + "fmt" + + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/types/resource" +) + +func PopulateCommonFlags(c *base.Command, f *base.FlagSet, resourceType resource.Type, flagNames []string) { + for _, name := range flagNames { + switch name { + case "id": + f.StringVar(&base.StringVar{ + Name: "id", + Target: &c.FlagId, + Usage: fmt.Sprintf("ID of the %s on which to operate", resourceType.String()), + }) + case "name": + f.StringVar(&base.StringVar{ + Name: "name", + Target: &c.FlagName, + Usage: fmt.Sprintf("Name to set on the %s", resourceType.String()), + }) + case "description": + f.StringVar(&base.StringVar{ + Name: "description", + Target: &c.FlagDescription, + Usage: fmt.Sprintf("Description to set on the %s", resourceType.String()), + }) + case "version": + f.IntVar(&base.IntVar{ + Name: "version", + Target: &c.FlagVersion, + Usage: fmt.Sprintf("The version of the %s against which to perform an update operation. If not specified, the command will perform a check-and-set automatically.", resourceType.String()), + }) + } + } +} diff --git a/internal/cmd/common/help.go b/internal/cmd/common/help.go new file mode 100644 index 000000000..741824742 --- /dev/null +++ b/internal/cmd/common/help.go @@ -0,0 +1,107 @@ +package common + +import ( + "fmt" + "net/textproto" + "strings" + + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/types/resource" + "github.com/mitchellh/go-wordwrap" +) + +func SynopsisFunc(inFunc, resourceType string) string { + if inFunc == "" { + return wordwrap.WrapString(fmt.Sprintf("Manage Boundary %ss", resourceType), base.TermWidth) + } + return wordwrap.WrapString(fmt.Sprintf("%s a %s within Boundary", textproto.CanonicalMIMEHeaderKey(inFunc), resourceType), base.TermWidth) +} + +func HelpMap(resourceType resource.Type) map[string]func() string { + prefixMap := map[string]string{ + resource.Scope.String(): "o", + resource.Role.String(): "r", + resource.Group.String(): "g", + resource.User.String(): "u", + } + return map[string]func() string{ + "base": func() string { + return base.WrapForHelpText(subType([]string{ + "Usage: boundary {{type}}s [sub command] [options] [args]", + "", + " This command allows operations on Boundary {{type}}s. Examples", + "", + " Create a {{type}}:", + "", + ` $ boundary {{type}}s create -name prodops -description "For ProdOps usage"`, + "", + " Please see the {{type}}s subcommand help for detailed usage information.", + }, resourceType, prefixMap)) + }, + + "create": func() string { + return base.WrapForHelpText(subType([]string{ + "Usage: boundary {{type}}s create [options] [args]", + "", + " Create a {{type}}. Example:", + "", + ` $ boundary {{type}}s create -name prodops -description "{{uppertype}} for ProdOps"`, + "", + "", + }, resourceType, prefixMap)) + }, + + "update": func() string { + return base.WrapForHelpText(subType([]string{ + "Usage: boundary {{type}}s update [options] [args]", + "", + " Update a {{type}} given its ID. Example:", + "", + ` $ boundary {{type}}s update -id {{prefix}}_1234567890 -name "devops" -description "{{uppertype}} for DevOps"`, + }, resourceType, prefixMap)) + }, + + "read": func() string { + return base.WrapForHelpText(subType([]string{ + "Usage: boundary {{type}}s read [options] [args]", + "", + " Read a {{type}} given its ID. Example:", + "", + ` $ boundary {{type}}s read -id {{prefix}}_1234567890`, + }, resourceType, prefixMap)) + }, + + "delete": func() string { + return base.WrapForHelpText(subType([]string{ + "Usage: boundary {{type}}s delete [options] [args]", + "", + " Delete a {{type}} given its ID. Example:", + "", + ` $ boundary {{type}}s delete -id {{prefix}}_1234567890`, + }, resourceType, prefixMap)) + }, + + "list": func() string { + return base.WrapForHelpText(subType([]string{ + "Usage: boundary {{type}}s list [options] [args]", + "", + " List {{type}}s within an enclosing scope or resource. Example:", + "", + ` $ boundary {{type}}s list`, + }, resourceType, prefixMap)) + }, + } +} + +func subType(in []string, resType resource.Type, prefixMap map[string]string) []string { + for i, v := range in { + in[i] = + strings.Replace( + strings.Replace( + strings.Replace( + v, "{{type}}", resType.String(), -1), + "{{uppertype}}", textproto.CanonicalMIMEHeaderKey(resType.String()), -1), + "{{prefix}}", prefixMap[resType.String()], -1) + } + return in +}