From efaf58b568e94d1e38789b918a807aa3d2115bbc Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 13 Aug 2020 20:59:32 -0400 Subject: [PATCH] Add users CLI command and do some cleanup (#269) --- internal/cmd/commands.go | 37 +++ .../cmd/commands/authtokens/authtokens.go | 12 +- internal/cmd/commands/groups/funcs.go | 18 +- internal/cmd/commands/groups/group.go | 20 +- internal/cmd/commands/roles/funcs.go | 2 +- internal/cmd/commands/roles/role.go | 21 +- internal/cmd/commands/scopes/funcs.go | 1 + internal/cmd/commands/scopes/scope.go | 19 +- internal/cmd/commands/users/funcs.go | 37 +++ internal/cmd/commands/users/user.go | 232 ++++++++++++++++++ internal/cmd/common/help.go | 66 +++-- 11 files changed, 399 insertions(+), 66 deletions(-) create mode 100644 internal/cmd/commands/users/funcs.go create mode 100644 internal/cmd/commands/users/user.go diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index cae956f21..ba1403352 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/boundary/internal/cmd/commands/hosts" "github.com/hashicorp/boundary/internal/cmd/commands/roles" "github.com/hashicorp/boundary/internal/cmd/commands/scopes" + "github.com/hashicorp/boundary/internal/cmd/commands/users" "github.com/hashicorp/boundary/internal/cmd/commands/worker" "github.com/mitchellh/cli" @@ -275,6 +276,42 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Func: "list", }, nil }, + + "users": func() (cli.Command, error) { + return &users.Command{ + Command: base.NewCommand(ui), + }, nil + }, + "users create": func() (cli.Command, error) { + return &users.Command{ + Command: base.NewCommand(ui), + Func: "create", + }, nil + }, + "users read": func() (cli.Command, error) { + return &users.Command{ + Command: base.NewCommand(ui), + Func: "read", + }, nil + }, + "users update": func() (cli.Command, error) { + return &users.Command{ + Command: base.NewCommand(ui), + Func: "update", + }, nil + }, + "users delete": func() (cli.Command, error) { + return &users.Command{ + Command: base.NewCommand(ui), + Func: "delete", + }, nil + }, + "users list": func() (cli.Command, error) { + return &users.Command{ + Command: base.NewCommand(ui), + Func: "list", + }, nil + }, } } diff --git a/internal/cmd/commands/authtokens/authtokens.go b/internal/cmd/commands/authtokens/authtokens.go index cff704653..f40f6cee5 100644 --- a/internal/cmd/commands/authtokens/authtokens.go +++ b/internal/cmd/commands/authtokens/authtokens.go @@ -34,7 +34,7 @@ var flagsMap = map[string][]string{ } func (c *Command) Help() string { - helpMap := common.HelpMap(resource.AuthToken) + helpMap := common.HelpMap("auth-token") if c.Func == "" { return helpMap["base"]() } @@ -44,9 +44,11 @@ func (c *Command) Help() string { func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") + if len(flagsMap[c.Func]) > 0 { + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, resource.AuthToken, flagsMap[c.Func]) + } - common.PopulateCommonFlags(c.Command, f, resource.AuthToken, flagsMap[c.Func]) return set } @@ -136,7 +138,7 @@ func (c *Command) Run(args []string) int { } b, err := base.JsonFormatter{}.Format(listedTokens) if err != nil { - c.UI.Error(fmt.Errorf("Error formatting to JSON: %w", err).Error()) + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) return 1 } c.UI.Output(string(b)) @@ -176,7 +178,7 @@ func (c *Command) Run(args []string) int { case "json": b, err := base.JsonFormatter{}.Format(token) if err != nil { - c.UI.Error(fmt.Errorf("Error formatting to JSON: %w", err).Error()) + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) return 1 } c.UI.Output(string(b)) diff --git a/internal/cmd/commands/groups/funcs.go b/internal/cmd/commands/groups/funcs.go index 7ecb72ab8..cc083f13b 100644 --- a/internal/cmd/commands/groups/funcs.go +++ b/internal/cmd/commands/groups/funcs.go @@ -76,31 +76,31 @@ func generateGroupTableOutput(group *groups.Group) string { output = []string{ "", "Group information:", - fmt.Sprintf(" ID: %s", group.Id), - fmt.Sprintf(" Created At: %s", group.CreatedTime.Local().Format(time.RFC3339)), - fmt.Sprintf(" Updated At: %s", group.UpdatedTime.Local().Format(time.RFC3339)), - fmt.Sprintf(" Version: %d", group.Version), + fmt.Sprintf(" ID: %s", group.Id), + fmt.Sprintf(" Version: %d", group.Version), + fmt.Sprintf(" Created At: %s", group.CreatedTime.Local().Format(time.RFC3339)), + fmt.Sprintf(" Updated At: %s", group.UpdatedTime.Local().Format(time.RFC3339)), } } if group.Name != "" { output = append(output, - fmt.Sprintf(" Name: %s", group.Name), + fmt.Sprintf(" Name: %s", group.Name), ) } if group.Description != "" { output = append(output, - fmt.Sprintf(" Description: %s", group.Description), + fmt.Sprintf(" Description: %s", group.Description), ) } if len(group.Members) > 0 { output = append(output, - fmt.Sprintf(" Members: %s", ""), + fmt.Sprintf(" Members: %s", ""), ) } for _, member := range group.Members { output = append(output, - fmt.Sprintf(" ID: %s", member.Id), - fmt.Sprintf(" Scope ID: %s", member.ScopeId), + fmt.Sprintf(" ID: %s", member.Id), + fmt.Sprintf(" Scope ID: %s", member.ScopeId), ) } return base.WrapForHelpText(output) diff --git a/internal/cmd/commands/groups/group.go b/internal/cmd/commands/groups/group.go index 4e2931918..446c69749 100644 --- a/internal/cmd/commands/groups/group.go +++ b/internal/cmd/commands/groups/group.go @@ -7,7 +7,6 @@ import ( "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/hashicorp/vault/sdk/helper/strutil" "github.com/kr/pretty" "github.com/mitchellh/cli" @@ -36,7 +35,7 @@ func (c *Command) Synopsis() string { } var helpMap = func() map[string]func() string { - ret := common.HelpMap(resource.Group) + ret := common.HelpMap("group") ret["add-members"] = addMembersHelp ret["set-members"] = setMembersHelp ret["remove-members"] = removeMembersHelp @@ -64,9 +63,11 @@ func (c *Command) Help() string { func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") + if len(flagsMap[c.Func]) > 0 { + f := set.NewFlagSet("Command Options") + populateFlags(c, f, flagsMap[c.Func]) + } - populateFlags(c, f, flagsMap[c.Func]) return set } @@ -219,7 +220,7 @@ func (c *Command) Run(args []string) int { } b, err := base.JsonFormatter{}.Format(listedGroups) if err != nil { - c.UI.Error(fmt.Errorf("Error formatting to JSON: %w", err).Error()) + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) return 1 } c.UI.Output(string(b)) @@ -240,17 +241,18 @@ func (c *Command) Run(args []string) int { } if true { output = append(output, - fmt.Sprintf(" ID: %s", g.Id), + fmt.Sprintf(" ID: %s", g.Id), + fmt.Sprintf(" Version: %d", g.Version), ) } if g.Name != "" { output = append(output, - fmt.Sprintf(" Name: %s", g.Name), + fmt.Sprintf(" Name: %s", g.Name), ) } if g.Description != "" { output = append(output, - fmt.Sprintf(" Description: %s", g.Description), + fmt.Sprintf(" Description: %s", g.Description), ) } } @@ -265,7 +267,7 @@ func (c *Command) Run(args []string) int { case "json": b, err := base.JsonFormatter{}.Format(group) if err != nil { - c.UI.Error(fmt.Errorf("Error formatting to JSON: %w", err).Error()) + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) return 1 } c.UI.Output(string(b)) diff --git a/internal/cmd/commands/roles/funcs.go b/internal/cmd/commands/roles/funcs.go index d1273919a..484db5872 100644 --- a/internal/cmd/commands/roles/funcs.go +++ b/internal/cmd/commands/roles/funcs.go @@ -93,9 +93,9 @@ func generateRoleTableOutput(role *roles.Role) string { "", "Role information:", fmt.Sprintf(" ID: %s", role.Id), + fmt.Sprintf(" Version: %d", role.Version), fmt.Sprintf(" Created At: %s", role.CreatedTime.Local().Format(time.RFC3339)), fmt.Sprintf(" Updated At: %s", role.UpdatedTime.Local().Format(time.RFC3339)), - fmt.Sprintf(" Version: %d", role.Version), } } if role.Name != "" { diff --git a/internal/cmd/commands/roles/role.go b/internal/cmd/commands/roles/role.go index 9ea4825b5..7cea501a8 100644 --- a/internal/cmd/commands/roles/role.go +++ b/internal/cmd/commands/roles/role.go @@ -8,7 +8,6 @@ import ( "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" @@ -41,7 +40,7 @@ func (c *Command) Synopsis() string { } var helpMap = func() map[string]func() string { - ret := common.HelpMap(resource.Role) + ret := common.HelpMap("role") ret["add-principals"] = addPrincipalsHelp ret["set-principals"] = setPrincipalsHelp ret["remove-principals"] = removePrincipalsHelp @@ -75,9 +74,10 @@ func (c *Command) Help() string { 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]) + if len(flagsMap[c.Func]) > 0 { + f := set.NewFlagSet("Command Options") + populateFlags(c, f, flagsMap[c.Func]) + } return set } @@ -273,7 +273,7 @@ func (c *Command) Run(args []string) int { } b, err := base.JsonFormatter{}.Format(listedRoles) if err != nil { - c.UI.Error(fmt.Errorf("Error formatting to JSON: %w", err).Error()) + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) return 1 } c.UI.Output(string(b)) @@ -294,17 +294,18 @@ func (c *Command) Run(args []string) int { } if true { output = append(output, - fmt.Sprintf(" ID: %s", r.Id), + fmt.Sprintf(" ID: %s", r.Id), + fmt.Sprintf(" Version: %d", r.Version), ) } if r.Name != "" { output = append(output, - fmt.Sprintf(" Name: %s", r.Name), + fmt.Sprintf(" Name: %s", r.Name), ) } if r.Description != "" { output = append(output, - fmt.Sprintf(" Description: %s", r.Description), + fmt.Sprintf(" Description: %s", r.Description), ) } } @@ -319,7 +320,7 @@ func (c *Command) Run(args []string) int { case "json": b, err := base.JsonFormatter{}.Format(role) if err != nil { - c.UI.Error(fmt.Errorf("Error formatting to JSON: %w", err).Error()) + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) return 1 } c.UI.Output(string(b)) diff --git a/internal/cmd/commands/scopes/funcs.go b/internal/cmd/commands/scopes/funcs.go index db4e6a280..a8ddeaba5 100644 --- a/internal/cmd/commands/scopes/funcs.go +++ b/internal/cmd/commands/scopes/funcs.go @@ -16,6 +16,7 @@ func generateScopeTableOutput(in *scopes.Scope) string { "", "Scope information:", fmt.Sprintf(" ID: %s", in.Id), + fmt.Sprintf(" Version: %d", in.Version), fmt.Sprintf(" Created Time: %s", in.CreatedTime.Local().Format(time.RFC3339)), fmt.Sprintf(" Updated Time: %s", in.UpdatedTime.Local().Format(time.RFC3339)), }..., diff --git a/internal/cmd/commands/scopes/scope.go b/internal/cmd/commands/scopes/scope.go index 3d3bae95c..4e03cbd2a 100644 --- a/internal/cmd/commands/scopes/scope.go +++ b/internal/cmd/commands/scopes/scope.go @@ -35,7 +35,7 @@ var flagsMap = map[string][]string{ } func (c *Command) Help() string { - helpMap := common.HelpMap(resource.Scope) + helpMap := common.HelpMap("scope") if c.Func == "" { return helpMap["base"]() } @@ -45,9 +45,11 @@ func (c *Command) Help() string { func (c *Command) Flags() *base.FlagSets { set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) - f := set.NewFlagSet("Command Options") + if len(flagsMap[c.Func]) > 0 { + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, resource.Scope, flagsMap[c.Func]) + } - common.PopulateCommonFlags(c.Command, f, resource.Scope, flagsMap[c.Func]) return set } @@ -173,7 +175,7 @@ func (c *Command) Run(args []string) int { } b, err := base.JsonFormatter{}.Format(listedScopes) if err != nil { - c.UI.Error(fmt.Errorf("Error formatting to JSON: %w", err).Error()) + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) return 1 } c.UI.Output(string(b)) @@ -194,17 +196,18 @@ func (c *Command) Run(args []string) int { } if true { output = append(output, - fmt.Sprintf(" ID: %s", s.Id), + fmt.Sprintf(" ID: %s", s.Id), + fmt.Sprintf(" Version: %d", s.Version), ) } if s.Name != "" { output = append(output, - fmt.Sprintf(" Name: %s", s.Name), + fmt.Sprintf(" Name: %s", s.Name), ) } if s.Description != "" { output = append(output, - fmt.Sprintf(" Description: %s", s.Description), + fmt.Sprintf(" Description: %s", s.Description), ) } } @@ -219,7 +222,7 @@ func (c *Command) Run(args []string) int { case "json": b, err := base.JsonFormatter{}.Format(scope) if err != nil { - c.UI.Error(fmt.Errorf("Error formatting to JSON: %w", err).Error()) + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) return 1 } c.UI.Output(string(b)) diff --git a/internal/cmd/commands/users/funcs.go b/internal/cmd/commands/users/funcs.go new file mode 100644 index 000000000..3b4e4e8d8 --- /dev/null +++ b/internal/cmd/commands/users/funcs.go @@ -0,0 +1,37 @@ +package users + +import ( + "fmt" + "time" + + "github.com/hashicorp/boundary/api/users" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func generateUserTableOutput(in *users.User) string { + var ret []string + // This if true is here to line up columns for easy editing + if true { + ret = append(ret, []string{ + "", + "User information:", + fmt.Sprintf(" ID: %s", in.Id), + fmt.Sprintf(" Version: %d", in.Version), + fmt.Sprintf(" Created Time: %s", in.CreatedTime.Local().Format(time.RFC3339)), + fmt.Sprintf(" Updated Time: %s", in.UpdatedTime.Local().Format(time.RFC3339)), + }..., + ) + } + if in.Name != "" { + ret = append(ret, + fmt.Sprintf(" Name: %s", in.Name), + ) + } + if in.Description != "" { + ret = append(ret, + fmt.Sprintf(" Description: %s", in.Description), + ) + } + + return base.WrapForHelpText(ret) +} diff --git a/internal/cmd/commands/users/user.go b/internal/cmd/commands/users/user.go new file mode 100644 index 000000000..32ffdcae2 --- /dev/null +++ b/internal/cmd/commands/users/user.go @@ -0,0 +1,232 @@ +package users + +import ( + "fmt" + + "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/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, "user") +} + +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("user") + 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) + + if len(flagsMap[c.Func]) > 0 { + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, resource.User, 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 []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)) + } + + userClient := users.NewUsersClient(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()) + default: + version = uint32(c.FlagVersion) + } + } + + var existed bool + var user *users.User + var listedUsers []*users.User + var apiErr *api.Error + + switch c.Func { + case "create": + user, apiErr, err = userClient.Create(c.Context, opts...) + case "update": + user, apiErr, err = userClient.Update(c.Context, c.FlagId, version, opts...) + case "read": + user, apiErr, err = userClient.Read(c.Context, c.FlagId, opts...) + case "delete": + existed, apiErr, err = userClient.Delete(c.Context, c.FlagId, opts...) + case "list": + listedUsers, apiErr, err = userClient.List(c.Context, opts...) + } + + plural := "user" + if c.Func == "list" { + plural = "users" + } + 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(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), + 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), + ) + } + } + c.UI.Output(base.WrapForHelpText(output)) + } + return 0 + } + + 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/common/help.go b/internal/cmd/common/help.go index 741824742..b6858a4ca 100644 --- a/internal/cmd/common/help.go +++ b/internal/cmd/common/help.go @@ -10,75 +10,84 @@ import ( "github.com/mitchellh/go-wordwrap" ) -func SynopsisFunc(inFunc, resourceType string) string { +func SynopsisFunc(inFunc, resType string) string { if inFunc == "" { - return wordwrap.WrapString(fmt.Sprintf("Manage Boundary %ss", resourceType), base.TermWidth) + return wordwrap.WrapString(fmt.Sprintf("Manage Boundary %ss", resType), base.TermWidth) } - return wordwrap.WrapString(fmt.Sprintf("%s a %s within Boundary", textproto.CanonicalMIMEHeaderKey(inFunc), resourceType), base.TermWidth) + articleType := resType + switch resType[0] { + case 'a', 'e', 'i', 'o': + articleType = fmt.Sprintf("an %s", articleType) + default: + articleType = fmt.Sprintf("a %s", articleType) + } + return wordwrap.WrapString(fmt.Sprintf("%s %s within Boundary", textproto.CanonicalMIMEHeaderKey(inFunc), articleType), base.TermWidth) } -func HelpMap(resourceType resource.Type) map[string]func() string { +func HelpMap(resType string) map[string]func() string { prefixMap := map[string]string{ - resource.Scope.String(): "o", - resource.Role.String(): "r", - resource.Group.String(): "g", - resource.User.String(): "u", + resource.Scope.String(): "o", + resource.AuthToken.String(): "at", + resource.AuthMethod.String(): "am", + 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", + " This command allows operations on Boundary {{type}} resources. Examples", "", - " Create a {{type}}:", + " Create {{articletype}}:", "", ` $ boundary {{type}}s create -name prodops -description "For ProdOps usage"`, "", " Please see the {{type}}s subcommand help for detailed usage information.", - }, resourceType, prefixMap)) + }, resType, prefixMap)) }, "create": func() string { return base.WrapForHelpText(subType([]string{ "Usage: boundary {{type}}s create [options] [args]", "", - " Create a {{type}}. Example:", + " Create {{articletype}}. Example:", "", ` $ boundary {{type}}s create -name prodops -description "{{uppertype}} for ProdOps"`, "", "", - }, resourceType, prefixMap)) + }, resType, prefixMap)) }, "update": func() string { return base.WrapForHelpText(subType([]string{ "Usage: boundary {{type}}s update [options] [args]", "", - " Update a {{type}} given its ID. Example:", + " Update {{articletype}} given its ID. Example:", "", ` $ boundary {{type}}s update -id {{prefix}}_1234567890 -name "devops" -description "{{uppertype}} for DevOps"`, - }, resourceType, prefixMap)) + }, resType, prefixMap)) }, "read": func() string { return base.WrapForHelpText(subType([]string{ "Usage: boundary {{type}}s read [options] [args]", "", - " Read a {{type}} given its ID. Example:", + " Read {{articletype}} given its ID. Example:", "", ` $ boundary {{type}}s read -id {{prefix}}_1234567890`, - }, resourceType, prefixMap)) + }, resType, prefixMap)) }, "delete": func() string { return base.WrapForHelpText(subType([]string{ "Usage: boundary {{type}}s delete [options] [args]", "", - " Delete a {{type}} given its ID. Example:", + " Delete {{articletype}} given its ID. Example:", "", ` $ boundary {{type}}s delete -id {{prefix}}_1234567890`, - }, resourceType, prefixMap)) + }, resType, prefixMap)) }, "list": func() string { @@ -88,20 +97,29 @@ func HelpMap(resourceType resource.Type) map[string]func() string { " List {{type}}s within an enclosing scope or resource. Example:", "", ` $ boundary {{type}}s list`, - }, resourceType, prefixMap)) + }, resType, prefixMap)) }, } } -func subType(in []string, resType resource.Type, prefixMap map[string]string) []string { +func subType(in []string, resType string, prefixMap map[string]string) []string { + articleType := resType + switch resType[0] { + case 'a', 'e', 'i', 'o': + articleType = fmt.Sprintf("an %s", articleType) + default: + articleType = fmt.Sprintf("a %s", articleType) + } 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) + strings.Replace( + v, "{{type}}", resType, -1), + "{{uppertype}}", textproto.CanonicalMIMEHeaderKey(resType), -1), + "{{prefix}}", prefixMap[resType], -1), + "{{articletype}}", articleType, -1) } return in }