diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index 99ec4e17d..6681958ef 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -23,6 +23,7 @@ import ( "github.com/hashicorp/boundary/internal/cmd/commands/hostsetscmd" "github.com/hashicorp/boundary/internal/cmd/commands/logout" "github.com/hashicorp/boundary/internal/cmd/commands/managedgroupscmd" + "github.com/hashicorp/boundary/internal/cmd/commands/policiescmd" "github.com/hashicorp/boundary/internal/cmd/commands/rolescmd" "github.com/hashicorp/boundary/internal/cmd/commands/scopescmd" "github.com/hashicorp/boundary/internal/cmd/commands/server" @@ -779,6 +780,54 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Func: "update", }), + "policies": func() (cli.Command, error) { + return &policiescmd.Command{ + Command: base.NewCommand(ui, opts...), + }, nil + }, + "policies read": func() (cli.Command, error) { + return &policiescmd.Command{ + Command: base.NewCommand(ui, opts...), + Func: "read", + }, nil + }, + "policies delete": func() (cli.Command, error) { + return &policiescmd.Command{ + Command: base.NewCommand(ui, opts...), + Func: "delete", + }, nil + }, + "policies list": func() (cli.Command, error) { + return &policiescmd.Command{ + Command: base.NewCommand(ui, opts...), + Func: "list", + }, nil + }, + "policies create": func() (cli.Command, error) { + return &policiescmd.Command{ + Command: base.NewCommand(ui, opts...), + Func: "create", + }, nil + }, + "policies create storage": func() (cli.Command, error) { + return &policiescmd.StorageCommand{ + Command: base.NewCommand(ui, opts...), + Func: "create", + }, nil + }, + "policies update": func() (cli.Command, error) { + return &policiescmd.Command{ + Command: base.NewCommand(ui, opts...), + Func: "update", + }, nil + }, + "policies update storage": func() (cli.Command, error) { + return &policiescmd.StorageCommand{ + Command: base.NewCommand(ui, opts...), + Func: "update", + }, nil + }, + "read": func() (cli.Command, error) { return &genericcmd.Command{ Command: base.NewCommand(ui, opts...), diff --git a/internal/cmd/commands/policiescmd/funcs.go b/internal/cmd/commands/policiescmd/funcs.go new file mode 100644 index 000000000..1b1642c20 --- /dev/null +++ b/internal/cmd/commands/policiescmd/funcs.go @@ -0,0 +1,224 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package policiescmd + +import ( + "fmt" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/policies" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +var keySubstMap = map[string]string{ + "retain_for": "Storage Retention", + "delete_after": "Storage Deletion", + "days": "Days", + "overridable": "Overridable", +} + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "": + return base.WrapForHelpText([]string{ + "Usage: boundary policies [sub command] [options] [args]", + "", + " This command allows operations on Boundary policy resources. Example:", + "", + " Read a policy:", + "", + ` $ boundary policies read -id pst_1234567890`, + "", + " Please see the subcommand help for detailed usage information.", + }) + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary policies create [type] [sub command] [options] [args]", + "", + " This command allows create operations on Boundary policy resources. Example:", + "", + " Create a storage-type policy:", + "", + ` $ boundary policies create storage -name prod -description "Prod Storage Policy" -retain-for-days 10 -delete-after-days 20`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary policies update [type] [sub command] [options] [args]", + "", + " This command allows update operations on Boundary policy resources. Example:", + "", + " Update a storage-type policy:", + "", + ` $ boundary policies update storage -id pst_1234567890 -name dev -description "Dev Storage Policy" -retain-for-days 1 -delete-after-days 2`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + } + + return helpStr + c.Flags().Help() +} + +func (c *Command) printListTable(items []*policies.Policy) string { + if len(items) == 0 { + return "No policies found" + } + var output []string + output = []string{ + "", + "Policy information:", + } + for i, item := range items { + if i > 0 { + output = append(output, "") + } + if item.Id != "" { + output = append(output, + fmt.Sprintf(" ID: %s", item.Id), + ) + } else { + output = append(output, + fmt.Sprintf(" ID: %s", "(not available)"), + ) + } + if c.FlagRecursive && item.ScopeId != "" { + output = append(output, + fmt.Sprintf(" Scope ID: %s", item.ScopeId), + ) + } + if item.Version > 0 { + output = append(output, + fmt.Sprintf(" Version: %d", item.Version), + ) + } + if item.Type != "" { + output = append(output, + 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(item *policies.Policy, resp *api.Response) string { + nonAttributeMap := map[string]any{} + if item.Id != "" { + nonAttributeMap["ID"] = item.Id + } + if item.Name != "" { + nonAttributeMap["Name"] = item.Name + } + if item.Description != "" { + nonAttributeMap["Description"] = item.Description + } + if item.Version != 0 { + nonAttributeMap["Version"] = item.Version + } + if item.Type != "" { + nonAttributeMap["Type"] = item.Type + } + if !item.CreatedTime.IsZero() { + nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123) + } + if !item.UpdatedTime.IsZero() { + nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123) + } + + // MaxAttributesLength replaces attribute map keys with their human-readable + // names. In the case of policies, some attributes might in themselves be + // objects, so we search inside the attributes and re-run. + maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap) + for _, v := range item.Attributes { + if m, ok := v.(map[string]any); ok { + ml := base.MaxAttributesLength(nonAttributeMap, m, keySubstMap) + if ml > maxLength { + maxLength = ml + } + } + } + + ret := []string{ + "", + "Policy information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + } + + if item.Scope != nil { + ret = append(ret, + "", + " Scope:", + base.ScopeInfoForOutput(item.Scope, maxLength), + ) + } + + if len(item.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, item.AuthorizedActions), + ) + } + + ret = append(ret, + "", + ) + + switch item.Type { + // For storage-type policies, we have optional objects with optional values + // inside the attributes map, so we have to look in the attributes to print. + case "storage": + if len(item.Attributes) > 0 { + ret = append(ret, " Attributes:") + // MaxAttributesLength substitutes the JSON keys in the map for the + // named ones, so look in the key substring map for them instead of + // using the JSON name. + retainFor, ok := item.Attributes[keySubstMap["retain_for"]] + if ok && len(retainFor.(map[string]any)) > 0 { + ret = append(ret, + fmt.Sprintf(" %s:", keySubstMap["retain_for"]), + base.WrapMap(6, maxLength, retainFor.(map[string]any)), + "", + ) + } + deleteAfter, ok := item.Attributes[keySubstMap["delete_after"]] + if ok && len(deleteAfter.(map[string]any)) > 0 { + ret = append(ret, + fmt.Sprintf(" %s:", keySubstMap["delete_after"]), + base.WrapMap(6, maxLength, deleteAfter.(map[string]any)), + "", + ) + } + } + + default: + if len(item.Attributes) > 0 { + ret = append(ret, + " Attributes:", + base.WrapMap(4, maxLength, item.Attributes), + ) + } + } + + return base.WrapForHelpText(ret) +} diff --git a/internal/cmd/commands/policiescmd/policies.gen.go b/internal/cmd/commands/policiescmd/policies.gen.go new file mode 100644 index 000000000..6c646728f --- /dev/null +++ b/internal/cmd/commands/policiescmd/policies.gen.go @@ -0,0 +1,319 @@ +// Code generated by "make cli"; DO NOT EDIT. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package policiescmd + +import ( + "errors" + "fmt" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/policies" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/go-secure-stdlib/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 := "policy" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("policy") + + 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", "filter", "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, "policy", 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": + return cli.RunResultHelp + + case "update": + return cli.RunResultHelp + + } + + c.plural = "policy" + switch c.Func { + case "list": + c.plural = "policies" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + var opts []policies.Option + + if strutil.StrListContains(flagsMap[c.Func], "scope-id") { + switch c.Func { + + case "list": + if c.FlagScopeId == "" { + c.PrintCliError(errors.New("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID")) + return base.CommandUserError + } + + } + } + + client, err := c.Client() + if c.WrapperCleanupFunc != nil { + defer func() { + if err := c.WrapperCleanupFunc(); err != nil { + c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err)) + } + }() + } + if err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %w", err)) + return base.CommandCliError + } + policiesClient := policies.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, policies.DefaultName()) + default: + opts = append(opts, policies.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, policies.DefaultDescription()) + default: + opts = append(opts, policies.WithDescription(c.FlagDescription)) + } + + switch c.FlagRecursive { + case true: + opts = append(opts, policies.WithRecursive(true)) + } + + if c.FlagFilter != "" { + opts = append(opts, policies.WithFilter(c.FlagFilter)) + } + + var version uint32 + + if ok := extraFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var resp *api.Response + var item *policies.Policy + + var items []*policies.Policy + + var readResult *policies.PolicyReadResult + + var deleteResult *policies.PolicyDeleteResult + + var listResult *policies.PolicyListResult + + switch c.Func { + + case "read": + readResult, err = policiesClient.Read(c.Context, c.FlagId, opts...) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + resp = readResult.GetResponse() + item = readResult.GetItem() + + case "delete": + deleteResult, err = policiesClient.Delete(c.Context, c.FlagId, opts...) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + resp = deleteResult.GetResponse() + + case "list": + listResult, err = policiesClient.List(c.Context, c.FlagScopeId, opts...) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + resp = listResult.GetResponse() + items = listResult.GetItems() + + } + + resp, item, items, err = executeExtraActions(c, resp, item, items, err, policiesClient, version, opts) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + if output { + return base.CommandSuccess + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + if ok := c.PrintJsonItem(resp); !ok { + return base.CommandCliError + } + + case "table": + c.UI.Output("The delete operation completed successfully.") + } + + return base.CommandSuccess + + case "list": + switch base.Format(c.UI) { + case "json": + if ok := c.PrintJsonItems(resp); !ok { + return base.CommandCliError + } + + case "table": + c.UI.Output(c.printListTable(items)) + } + + return base.CommandSuccess + + } + + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item, resp)) + + case "json": + if ok := c.PrintJsonItem(resp); !ok { + return base.CommandCliError + } + } + + return base.CommandSuccess +} + +func (c *Command) checkFuncError(err error) int { + if err == nil { + return 0 + } + if apiErr := api.AsServerError(err); apiErr != nil { + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + return base.CommandApiError + } + c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return base.CommandCliError +} + +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, *base.FlagSets, *[]policies.Option) bool { return true } + executeExtraActions = func(_ *Command, inResp *api.Response, inItem *policies.Policy, inItems []*policies.Policy, inErr error, _ *policies.Client, _ uint32, _ []policies.Option) (*api.Response, *policies.Policy, []*policies.Policy, error) { + return inResp, inItem, inItems, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/policiescmd/storage_funcs.go b/internal/cmd/commands/policiescmd/storage_funcs.go new file mode 100644 index 000000000..ce84aef05 --- /dev/null +++ b/internal/cmd/commands/policiescmd/storage_funcs.go @@ -0,0 +1,168 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package policiescmd + +import ( + "fmt" + "strconv" + + "github.com/hashicorp/boundary/api/policies" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func init() { + extraStorageActionsFlagsMapFunc = extraStorageActionsFlagsMapFuncImpl + extraStorageFlagsFunc = extraStorageFlagsFuncImpl + extraStorageFlagsHandlingFunc = extraStorageFlagsHandlingFuncImpl +} + +type extraStorageCmdVars struct { + flagRetainForDays string + flagRetainForOverridable string + flagDeleteAfterDays string + flagDeleteAfterOverridable string + + flagRetainFor map[string]string + flagDeleteAfter map[string]string +} + +func (c *StorageCommand) extraStorageHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary policies create storage [options] [args]", + "", + " Create a storage-type policy. Example:", + "", + ` $ boundary policies create storage -name prod -description "Prod Storage Policy" -retain-for-days 800 -retain-for-overridable false -delete-after-days 900 -delete-after-overridable false`, + "", + "", + }) + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary policies update storage [options] [args]", + "", + " Update a storage-type policy given its id. Example:", + "", + ` $ boundary policies update storage -id pst_1234567890 -name dev -description "Dev Storage Policy" -retain-for-days 10 -retain-for-overridable null -delete-after-days 20 -delete-after-overridable true`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} + +func extraStorageActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "create": {"retain-for-days", "retain-for-overridable", "delete-after-days", "delete-after-overridable"}, + "update": {"retain-for-days", "retain-for-overridable", "delete-after-days", "delete-after-overridable"}, + } +} + +func extraStorageFlagsFuncImpl(c *StorageCommand, set *base.FlagSets, f *base.FlagSet) { + fs := set.NewFlagSet("Storage Policy Options") + + for _, name := range flagsStorageMap[c.Func] { + switch name { + case "retain-for-days": + fs.StringVar(&base.StringVar{ + Name: "retain-for-days", + Target: &c.flagRetainForDays, + Usage: "Number of days a session recording should be retained for.", + }) + case "retain-for-overridable": + fs.StringVar(&base.StringVar{ + Name: "retain-for-overridable", + Target: &c.flagRetainForOverridable, + Usage: "Allow/Disallow this policy's retention period to be overridden by downstream storage policies (true or false).", + }) + case "delete-after-days": + fs.StringVar(&base.StringVar{ + Name: "delete-after-days", + Target: &c.flagDeleteAfterDays, + Usage: "Number of days after which a session recording will be deleted.", + }) + case "delete-after-overridable": + fs.StringVar(&base.StringVar{ + Name: "delete-after-overridable", + Target: &c.flagDeleteAfterOverridable, + Usage: "Allow/Disallow this policy's deletion period to be overridden by downstream Policies (true or false)", + }) + } + } +} + +func extraStorageFlagsHandlingFuncImpl(c *StorageCommand, _ *base.FlagSets, opts *[]policies.Option) bool { + rf := map[string]any{} + da := map[string]any{} + + var withRetainFor, withDeleteAfter bool + switch c.flagRetainForDays { + case "": + case "null": + rf["days"] = nil + withRetainFor = true + default: + days, err := strconv.ParseInt(c.flagRetainForDays, 10, 64) + if err != nil { + c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagRetainForDays, err)) + return false + } + rf["days"] = days + withRetainFor = true + } + switch c.flagRetainForOverridable { + case "": + case "null": + rf["overridable"] = nil + withRetainFor = true + default: + overridable, err := strconv.ParseBool(c.flagRetainForOverridable) + if err != nil { + c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagRetainForOverridable, err)) + return false + } + rf["overridable"] = overridable + withRetainFor = true + } + + switch c.flagDeleteAfterDays { + case "": + case "null": + da["days"] = nil + withDeleteAfter = true + default: + days, err := strconv.ParseInt(c.flagDeleteAfterDays, 10, 64) + if err != nil { + c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagDeleteAfterDays, err)) + return false + } + da["days"] = days + withDeleteAfter = true + } + switch c.flagDeleteAfterOverridable { + case "": + case "null": + da["overridable"] = nil + withDeleteAfter = true + default: + overridable, err := strconv.ParseBool(c.flagDeleteAfterOverridable) + if err != nil { + c.UI.Error(fmt.Sprintf("Error parsing %q: %s", c.flagDeleteAfterOverridable, err)) + return false + } + da["overridable"] = overridable + withDeleteAfter = true + } + + if withRetainFor { + *opts = append(*opts, policies.WithStoragePolicyRetainFor(rf)) + } + if withDeleteAfter { + *opts = append(*opts, policies.WithStoragePolicyDeleteAfter(da)) + } + + return true +} diff --git a/internal/cmd/commands/policiescmd/storage_policies.gen.go b/internal/cmd/commands/policiescmd/storage_policies.gen.go new file mode 100644 index 000000000..eb0e14f5f --- /dev/null +++ b/internal/cmd/commands/policiescmd/storage_policies.gen.go @@ -0,0 +1,283 @@ +// Code generated by "make cli"; DO NOT EDIT. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package policiescmd + +import ( + "errors" + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/policies" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/go-secure-stdlib/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initStorageFlags() { + flagsOnce.Do(func() { + extraFlags := extraStorageActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsStorageMap[k] = append(flagsStorageMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*StorageCommand)(nil) + _ cli.CommandAutocomplete = (*StorageCommand)(nil) +) + +type StorageCommand struct { + *base.Command + + Func string + + plural string + + extraStorageCmdVars +} + +func (c *StorageCommand) AutocompleteArgs() complete.Predictor { + initStorageFlags() + return complete.PredictAnything +} + +func (c *StorageCommand) AutocompleteFlags() complete.Flags { + initStorageFlags() + return c.Flags().Completions() +} + +func (c *StorageCommand) Synopsis() string { + if extra := extraStorageSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "policy" + + synopsisStr = fmt.Sprintf("%s %s", "storage-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *StorageCommand) Help() string { + initStorageFlags() + + var helpStr string + helpMap := common.HelpMap("policy") + + switch c.Func { + + default: + + helpStr = c.extraStorageHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsStorageMap = map[string][]string{ + + "create": {"scope-id", "name", "description"}, + + "update": {"id", "name", "description", "version"}, +} + +func (c *StorageCommand) Flags() *base.FlagSets { + if len(flagsStorageMap[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, "storage-type policy", flagsStorageMap, c.Func) + + extraStorageFlagsFunc(c, set, f) + + return set +} + +func (c *StorageCommand) Run(args []string) int { + initStorageFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + + } + + c.plural = "storage-type policy" + switch c.Func { + case "list": + c.plural = "storage-type policies" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsStorageMap[c.Func], "id") && c.FlagId == "" { + c.PrintCliError(errors.New("ID is required but not passed in via -id")) + return base.CommandUserError + } + + var opts []policies.Option + + if strutil.StrListContains(flagsStorageMap[c.Func], "scope-id") { + switch c.Func { + + case "create": + if c.FlagScopeId == "" { + c.PrintCliError(errors.New("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID")) + return base.CommandUserError + } + + } + } + + client, err := c.Client() + if c.WrapperCleanupFunc != nil { + defer func() { + if err := c.WrapperCleanupFunc(); err != nil { + c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err)) + } + }() + } + if err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %w", err)) + return base.CommandCliError + } + policiesClient := policies.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, policies.DefaultName()) + default: + opts = append(opts, policies.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, policies.DefaultDescription()) + default: + opts = append(opts, policies.WithDescription(c.FlagDescription)) + } + + switch c.FlagRecursive { + case true: + opts = append(opts, policies.WithRecursive(true)) + } + + if c.FlagFilter != "" { + opts = append(opts, policies.WithFilter(c.FlagFilter)) + } + + var version uint32 + + switch c.Func { + + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, policies.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + } + + if ok := extraStorageFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var resp *api.Response + var item *policies.Policy + + var createResult *policies.PolicyCreateResult + + var updateResult *policies.PolicyUpdateResult + + switch c.Func { + + case "create": + createResult, err = policiesClient.Create(c.Context, "storage", c.FlagScopeId, opts...) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + resp = createResult.GetResponse() + item = createResult.GetItem() + + case "update": + updateResult, err = policiesClient.Update(c.Context, c.FlagId, version, opts...) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + resp = updateResult.GetResponse() + item = updateResult.GetItem() + + } + + resp, item, err = executeExtraStorageActions(c, resp, item, err, policiesClient, version, opts) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + + output, err := printCustomStorageActionOutput(c) + if err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + if output { + return base.CommandSuccess + } + + switch c.Func { + + } + + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item, resp)) + + case "json": + if ok := c.PrintJsonItem(resp); !ok { + return base.CommandCliError + } + } + + return base.CommandSuccess +} + +func (c *StorageCommand) checkFuncError(err error) int { + if err == nil { + return 0 + } + if apiErr := api.AsServerError(err); apiErr != nil { + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + return base.CommandApiError + } + c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return base.CommandCliError +} + +var ( + extraStorageActionsFlagsMapFunc = func() map[string][]string { return nil } + extraStorageSynopsisFunc = func(*StorageCommand) string { return "" } + extraStorageFlagsFunc = func(*StorageCommand, *base.FlagSets, *base.FlagSet) {} + extraStorageFlagsHandlingFunc = func(*StorageCommand, *base.FlagSets, *[]policies.Option) bool { return true } + executeExtraStorageActions = func(_ *StorageCommand, inResp *api.Response, inItem *policies.Policy, inErr error, _ *policies.Client, _ uint32, _ []policies.Option) (*api.Response, *policies.Policy, error) { + return inResp, inItem, inErr + } + printCustomStorageActionOutput = func(*StorageCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/common/help.go b/internal/cmd/common/help.go index bc3a170a6..261ab749a 100644 --- a/internal/cmd/common/help.go +++ b/internal/cmd/common/help.go @@ -44,6 +44,7 @@ func HelpMap(resType string) map[string]func() string { resource.Worker.String(): "w", resource.SessionRecording.String(): "sr", resource.StorageBucket.String(): "sb", + resource.Policy.String(): "p", } return map[string]func() string{ "base": func() string { diff --git a/internal/cmd/gencli/input.go b/internal/cmd/gencli/input.go index 801c9080a..a92337e3c 100644 --- a/internal/cmd/gencli/input.go +++ b/internal/cmd/gencli/input.go @@ -530,6 +530,32 @@ var inputStructs = map[string][]*cmdInfo{ VersionedActions: []string{"update"}, }, }, + "policies": { + { + ResourceType: resource.Policy.String(), + Pkg: "policies", + StdActions: []string{"read", "delete", "list"}, + HasExtraHelpFunc: true, + HasName: true, + HasDescription: true, + Container: "Scope", + }, + { + ResourceType: resource.Policy.String(), + Pkg: "policies", + StdActions: []string{"create", "update"}, + SubActionPrefix: "storage", + HasExtraCommandVars: true, + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + Container: "Scope", + HasDescription: true, + VersionedActions: []string{"update"}, + NeedsSubtypeInCreate: true, + }, + }, "roles": { { ResourceType: resource.Role.String(),