Add host-catalogs CLI command. (#312)

pull/313/head
Jeff Mitchell 6 years ago committed by GitHub
parent 17ecb6f2ce
commit 20aef738c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,6 +13,7 @@ import (
"github.com/hashicorp/boundary/internal/cmd/commands/controller"
"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/roles"
"github.com/hashicorp/boundary/internal/cmd/commands/scopes"
@ -208,6 +209,49 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
}, nil
},
"host-catalogs": func() (cli.Command, error) {
return &hostcatalogs.Command{
Command: base.NewCommand(ui),
}, nil
},
"host-catalogs read": func() (cli.Command, error) {
return &hostcatalogs.Command{
Command: base.NewCommand(ui),
Func: "read",
}, nil
},
"host-catalogs delete": func() (cli.Command, error) {
return &hostcatalogs.Command{
Command: base.NewCommand(ui),
Func: "delete",
}, nil
},
"host-catalogs list": func() (cli.Command, error) {
return &hostcatalogs.Command{
Command: base.NewCommand(ui),
Func: "list",
}, nil
},
"host-catalogs static": func() (cli.Command, error) {
return &hostcatalogs.Command{
Command: base.NewCommand(ui),
Func: "static",
}, nil
},
"host-catalogs static create": func() (cli.Command, error) {
return &hostcatalogs.StaticCommand{
Command: base.NewCommand(ui),
Func: "create",
}, nil
},
"host-catalogs static update": func() (cli.Command, error) {
return &hostcatalogs.StaticCommand{
Command: base.NewCommand(ui),
Func: "update",
}, nil
},
"hosts create": func() (cli.Command, error) {
return &hosts.CreateCommand{
Command: base.NewCommand(ui),

@ -72,7 +72,7 @@ func (c *Command) Flags() *base.FlagSets {
if len(flagsMap[c.Func]) > 0 {
f := set.NewFlagSet("Command Options")
common.PopulateCommonFlags(c.Command, f, resource.User.String(), flagsMap[c.Func])
common.PopulateCommonFlags(c.Command, f, resource.AuthMethod.String(), flagsMap[c.Func])
}
return set

@ -59,7 +59,7 @@ func generateAuthMethodTableOutput(in *authmethods.AuthMethod) string {
}
}
ret = append(ret, "", "User information:")
ret = append(ret, "", "Auth method information:")
ret = append(ret,
// We do +2 because there is another +2 offset for attributes below

@ -0,0 +1,70 @@
package hostcatalogs
import (
"fmt"
"time"
"github.com/hashicorp/boundary/api/hostcatalogs"
"github.com/hashicorp/boundary/internal/cmd/base"
)
func generateHostCatalogTableOutput(in *hostcatalogs.HostCatalog) string {
var ret []string
nonAttributeMap := map[string]interface{}{
"ID": in.Id,
"Version": in.Version,
"Type": in.Type,
"Created Time": in.CreatedTime.Local().Format(time.RFC3339),
"Updated Time": in.UpdatedTime.Local().Format(time.RFC3339),
}
if in.Name != "" {
nonAttributeMap["Name"] = in.Name
}
if in.Description != "" {
nonAttributeMap["Description"] = in.Description
}
maxLength := 0
for k := range nonAttributeMap {
if len(k) > maxLength {
maxLength = len(k)
}
}
if len(in.Attributes) > 0 {
for k, v := range in.Attributes {
if attributeMap[k] != "" {
in.Attributes[attributeMap[k]] = v
delete(in.Attributes, k)
}
}
for k := range in.Attributes {
if len(k) > maxLength {
maxLength = len(k)
}
}
}
ret = append(ret, "", "Host catalog information:")
ret = append(ret,
// We do +2 because there is another +2 offset for attributes below
base.WrapMap(2, maxLength+2, nonAttributeMap),
)
if len(in.Attributes) > 0 {
if true {
ret = append(ret,
fmt.Sprintf(" Attributes: %s", ""),
)
}
ret = append(ret,
base.WrapMap(4, maxLength, in.Attributes),
)
}
return base.WrapForHelpText(ret)
}
var attributeMap = map[string]string{}

@ -0,0 +1,240 @@
package hostcatalogs
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/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 {
if c.Func == "static" {
return "Manage static host catalogs within Boundary"
}
return common.SynopsisFunc(c.Func, "host-catalog")
}
var flagsMap = map[string][]string{
"read": {"id"},
"delete": {"id"},
}
func (c *Command) Help() string {
helpMap := common.HelpMap("host-catalog")
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 "static":
return base.WrapForHelpText([]string{
"Usage: boundary host-catalogs static [sub command] [options] [args]",
"",
" This command allows operations on Boundary static-type host-catalog resources. Example:",
"",
" Create a static-type host-catalog:",
"",
` $ boundary host-catalogs static create -name prodops -description "For ProdOps usage"`,
"",
" Please see the subcommand help for detailed usage information.",
})
default:
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.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 {
if c.Func == "" || c.Func == "static" {
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 []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)
var existed bool
var catalog *hostcatalogs.HostCatalog
var listedCatalogs []*hostcatalogs.HostCatalog
var apiErr *api.Error
switch c.Func {
case "read":
catalog, apiErr, err = hostcatalogClient.Read(c.Context, c.FlagId, opts...)
case "delete":
existed, apiErr, err = hostcatalogClient.Delete(c.Context, c.FlagId, opts...)
case "list":
listedCatalogs, apiErr, err = hostcatalogClient.List(c.Context, opts...)
}
plural := "host catalog"
if c.Func == "list" {
plural = "host catalogs"
}
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(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),
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),
)
}
}
c.UI.Output(base.WrapForHelpText(output))
}
return 0
}
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
}

@ -0,0 +1,172 @@
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/vault/sdk/helper/strutil"
"github.com/kr/pretty"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
var _ cli.Command = (*StaticCommand)(nil)
var _ 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 within Boundary", textproto.CanonicalMIMEHeaderKey(c.Func))
}
var staticFlagsMap = map[string][]string{
"create": {"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 static create [options] [args]",
"",
" Create a static-type host-catalog. Example:",
"",
` $ boundary host-catalogs static create -name prodops -description "Static host-catalog for ProdOps"`,
"",
"",
})
case "update":
info = base.WrapForHelpText([]string{
"Usage: boundary host-catalogs static update [options] [args]",
"",
" Update a static-type host-catalog given its ID. Example:",
"",
` $ boundary host-catalogs static update -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)
if len(staticFlagsMap[c.Func]) > 0 {
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
}
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())
default:
version = uint32(c.FlagVersion)
}
}
var catalog *hostcatalogs.HostCatalog
var apiErr *api.Error
switch c.Func {
case "create":
catalog, apiErr, err = hostcatalogClient.Create(c.Context, "static", opts...)
case "update":
catalog, apiErr, err = hostcatalogClient.Update(c.Context, c.FlagId, version, opts...)
}
plural := "static-type host-catalog"
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 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
}

@ -26,12 +26,15 @@ func SynopsisFunc(inFunc, resType string) string {
func HelpMap(resType string) map[string]func() string {
prefixMap := map[string]string{
resource.Scope.String(): "o",
resource.AuthToken.String(): "at",
resource.AuthMethod.String(): "am",
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",
resource.HostCatalog.String(): "hc",
resource.HostSet.String(): "hs",
resource.Host.String(): "h",
}
return map[string]func() string{
"base": func() string {

@ -243,7 +243,7 @@ func (s Service) updateInRepo(ctx context.Context, authMethodId, scopeId, id str
dbMask := maskManager.Translate(mask)
if len(dbMask) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
repo, err := s.repoFn()
if err != nil {

@ -223,7 +223,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st
u.PublicId = id
dbMask := maskManager.Translate(mask)
if len(dbMask) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
repo, err := s.repoFn()
if err != nil {

@ -237,7 +237,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st
g.PublicId = id
dbMask := maskManager.Translate(mask)
if len(dbMask) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
repo, err := s.repoFn()
if err != nil {

@ -204,7 +204,7 @@ func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []str
h.PublicId = id
dbMask := maskManager.Translate(mask)
if len(dbMask) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
repo, err := s.staticRepoFn()
if err != nil {

@ -235,7 +235,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string
h.PublicId = id
dbMask := maskManager.Translate(mask)
if len(dbMask) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
repo, err := s.staticRepoFn()
if err != nil {

@ -212,7 +212,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId, id string
h.PublicId = id
dbMask := maskManager.Translate(mask)
if len(dbMask) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
repo, err := s.staticRepoFn()
if err != nil {

@ -301,7 +301,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st
u.PublicId = id
dbMask := maskManager.Translate(mask)
if len(dbMask) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
repo, err := s.repoFn()
if err != nil {

@ -224,7 +224,7 @@ func (s Service) updateInRepo(ctx context.Context, parentScope *scopes.ScopeInfo
iamScope.PublicId = scopeId
dbMask := maskManager.Translate(mask)
if len(dbMask) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
repo, err := s.repo()
if err != nil {

@ -189,7 +189,7 @@ func (s Service) updateInRepo(ctx context.Context, orgId, id string, mask []stri
u.PublicId = id
dbMask := maskManager.Translate(mask)
if len(dbMask) == 0 {
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."})
return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
repo, err := s.repoFn()
if err != nil {

@ -47,7 +47,7 @@ func TestMultiControllerMultiWorkerConnections(t *testing.T) {
// expecting it we'll see an out-of-date entry
return true
}
assert.WithinDuration(time.Now(), v.(time.Time), 7*time.Second)
assert.WithinDuration(time.Now(), v.(time.Time), 30*time.Second)
delete(workerMap, k.(string))
return true
})

Loading…
Cancel
Save