From 78e10864c474fb93b5eaf1a5937e76ba3551ee9e Mon Sep 17 00:00:00 2001 From: Todd Date: Fri, 2 Feb 2024 14:33:32 -0800 Subject: [PATCH] Add alias CLI CRUDL commands (#4353) * Add alias CLI CRUDL commands --- api/aliases/alias.gen.go | 13 +- .../authorize_session_arguments.gen.go | 9 + api/aliases/target_alias_attributes.gen.go | 40 +++ internal/api/genapi/input.go | 40 ++- internal/api/genapi/templates.go | 4 +- internal/cmd/commands.go | 42 +++ .../cmd/commands/aliasescmd/aliases.gen.go | 308 ++++++++++++++++++ internal/cmd/commands/aliasescmd/funcs.go | 194 +++++++++++ .../commands/aliasescmd/target_aliases.gen.go | 273 ++++++++++++++++ .../cmd/commands/aliasescmd/target_funcs.go | 148 +++++++++ internal/cmd/common/help.go | 1 + internal/cmd/gencli/input.go | 26 ++ .../handlers/aliases/alias_service.go | 14 +- .../handlers/aliases/alias_service_test.go | 32 ++ internal/gen/controller.swagger.json | 4 + .../api/resources/aliases/v1/alias.proto | 7 +- internal/tests/api/aliases/alias_test.go | 21 +- .../api/resources/aliases/alias.pb.go | 100 +++--- 18 files changed, 1218 insertions(+), 58 deletions(-) create mode 100644 api/aliases/authorize_session_arguments.gen.go create mode 100644 api/aliases/target_alias_attributes.gen.go create mode 100644 internal/cmd/commands/aliasescmd/aliases.gen.go create mode 100644 internal/cmd/commands/aliasescmd/funcs.go create mode 100644 internal/cmd/commands/aliasescmd/target_aliases.gen.go create mode 100644 internal/cmd/commands/aliasescmd/target_funcs.go diff --git a/api/aliases/alias.gen.go b/api/aliases/alias.gen.go index 8904ac12f..e5a4b2161 100644 --- a/api/aliases/alias.gen.go +++ b/api/aliases/alias.gen.go @@ -28,6 +28,7 @@ type Alias struct { Version uint32 `json:"version,omitempty"` Value string `json:"value,omitempty"` DestinationId string `json:"destination_id,omitempty"` + Type string `json:"type,omitempty"` Attributes map[string]interface{} `json:"attributes,omitempty"` AuthorizedActions []string `json:"authorized_actions,omitempty"` @@ -114,7 +115,7 @@ func (c *Client) ApiClient() *api.Client { return c.client } -func (c *Client) Create(ctx context.Context, scopeId string, opt ...Option) (*AliasCreateResult, error) { +func (c *Client) Create(ctx context.Context, resourceType string, alias string, scopeId string, opt ...Option) (*AliasCreateResult, error) { if scopeId == "" { return nil, fmt.Errorf("empty scopeId value passed into Create request") } @@ -124,6 +125,16 @@ func (c *Client) Create(ctx context.Context, scopeId string, opt ...Option) (*Al if c.client == nil { return nil, fmt.Errorf("nil client") } + if resourceType == "" { + return nil, fmt.Errorf("empty resourceType value passed into Create request") + } else { + opts.postMap["type"] = resourceType + } + if alias == "" { + return nil, fmt.Errorf("empty alias value passed into Create request") + } else { + opts.postMap["value"] = alias + } opts.postMap["scope_id"] = scopeId diff --git a/api/aliases/authorize_session_arguments.gen.go b/api/aliases/authorize_session_arguments.gen.go new file mode 100644 index 000000000..e9105f83a --- /dev/null +++ b/api/aliases/authorize_session_arguments.gen.go @@ -0,0 +1,9 @@ +// Code generated by "make api"; DO NOT EDIT. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package aliases + +type AuthorizeSessionArguments struct { + HostId string `json:"host_id,omitempty"` +} diff --git a/api/aliases/target_alias_attributes.gen.go b/api/aliases/target_alias_attributes.gen.go new file mode 100644 index 000000000..a2ba298fa --- /dev/null +++ b/api/aliases/target_alias_attributes.gen.go @@ -0,0 +1,40 @@ +// Code generated by "make api"; DO NOT EDIT. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package aliases + +import ( + "fmt" + + "github.com/mitchellh/mapstructure" +) + +type TargetAliasAttributes struct { + AuthorizeSessionArguments *AuthorizeSessionArguments `json:"authorize_session_arguments,omitempty"` +} + +func AttributesMapToTargetAliasAttributes(in map[string]interface{}) (*TargetAliasAttributes, error) { + if in == nil { + return nil, fmt.Errorf("nil input map") + } + var out TargetAliasAttributes + dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Result: &out, + TagName: "json", + }) + if err != nil { + return nil, fmt.Errorf("error creating mapstructure decoder: %w", err) + } + if err := dec.Decode(in); err != nil { + return nil, fmt.Errorf("error decoding: %w", err) + } + return &out, nil +} + +func (pt *Alias) GetTargetAliasAttributes() (*TargetAliasAttributes, error) { + if pt.Type != "target" { + return nil, fmt.Errorf("asked to fetch %s-type attributes but alias is of type %s", "target", pt.Type) + } + return AttributesMapToTargetAliasAttributes(pt.Attributes) +} diff --git a/internal/api/genapi/input.go b/internal/api/genapi/input.go index 5d9d0d389..0b466b86c 100644 --- a/internal/api/genapi/input.go +++ b/internal/api/genapi/input.go @@ -764,12 +764,50 @@ var inputStructs = []*structInfo{ }, // Alias related resources + { + inProto: &aliases.TargetAliasAttributes{}, + outFile: "aliases/target_alias_attributes.gen.go", + subtypeName: "target", + parentTypeName: "Alias", + templates: []*template.Template{ + mapstructureConversionTemplate, + }, + }, + { + inProto: &aliases.AuthorizeSessionArguments{}, + outFile: "aliases/authorize_session_arguments.gen.go", + skipOptions: true, + }, { inProto: &aliases.Alias{}, outFile: "aliases/alias.gen.go", templates: []*template.Template{ clientTemplate, - commonCreateTemplate, + template.Must(template.New("").Funcs( + template.FuncMap{ + "snakeCase": snakeCase, + "funcName": func() string { + return "Create" + }, + "apiAction": func() string { + return "" + }, + "extraRequiredParams": func() []requiredParam { + return []requiredParam{ + { + Name: "resourceType", + Typ: "string", + PostType: "type", + }, + { + Name: "alias", + Typ: "string", + PostType: "value", + }, + } + }, + }, + ).Parse(createTemplateStr)), readTemplate, updateTemplate, deleteTemplate, diff --git a/internal/api/genapi/templates.go b/internal/api/genapi/templates.go index e62a43553..6c2bbd7d6 100644 --- a/internal/api/genapi/templates.go +++ b/internal/api/genapi/templates.go @@ -470,8 +470,8 @@ func (c *Client) {{ funcName }} (ctx context.Context, {{ range extraRequiredPara if c.client == nil { return nil, fmt.Errorf("nil client") - } - {{ range extraRequiredParams }} if {{ .Name }} == "" { + }{{ range extraRequiredParams }} + if {{ .Name }} == "" { return nil, fmt.Errorf("empty {{ .Name }} value passed into {{ funcName }} request") } else { opts.postMap["{{ .PostType }}"] = {{ .Name }} diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index 04c7c9b93..9f48605b4 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -6,6 +6,7 @@ package cmd import ( "github.com/hashicorp/boundary/internal/cmd/base" "github.com/hashicorp/boundary/internal/cmd/commands/accountscmd" + "github.com/hashicorp/boundary/internal/cmd/commands/aliasescmd" "github.com/hashicorp/boundary/internal/cmd/commands/authenticate" "github.com/hashicorp/boundary/internal/cmd/commands/authmethodscmd" "github.com/hashicorp/boundary/internal/cmd/commands/authtokenscmd" @@ -157,6 +158,47 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Func: "update", }), + "aliases": func() (cli.Command, error) { + return &aliasescmd.Command{ + Command: base.NewCommand(ui, opts...), + }, nil + }, + "aliases read": clientCacheWrapper( + &aliasescmd.Command{ + Command: base.NewCommand(ui, opts...), + Func: "read", + }), + "aliases delete": clientCacheWrapper( + &aliasescmd.Command{ + Command: base.NewCommand(ui, opts...), + Func: "delete", + }), + "aliases list": clientCacheWrapper( + &aliasescmd.Command{ + Command: base.NewCommand(ui, opts...), + Func: "list", + }), + "aliases create": clientCacheWrapper( + &aliasescmd.Command{ + Command: base.NewCommand(ui, opts...), + Func: "create", + }), + "aliases update": clientCacheWrapper( + &aliasescmd.Command{ + Command: base.NewCommand(ui, opts...), + Func: "update", + }), + "aliases create target": clientCacheWrapper( + &aliasescmd.TargetCommand{ + Command: base.NewCommand(ui, opts...), + Func: "create", + }), + "aliases update target": clientCacheWrapper( + &aliasescmd.TargetCommand{ + Command: base.NewCommand(ui, opts...), + Func: "update", + }), + "auth-methods": func() (cli.Command, error) { return &authmethodscmd.Command{ Command: base.NewCommand(ui, opts...), diff --git a/internal/cmd/commands/aliasescmd/aliases.gen.go b/internal/cmd/commands/aliasescmd/aliases.gen.go new file mode 100644 index 000000000..3ed40f269 --- /dev/null +++ b/internal/cmd/commands/aliasescmd/aliases.gen.go @@ -0,0 +1,308 @@ +// Code generated by "make cli"; DO NOT EDIT. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package aliasescmd + +import ( + "errors" + "fmt" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/aliases" + "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 := "alias" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("alias") + + 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, "alias", 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 = "alias" + switch c.Func { + case "list": + c.plural = "aliases" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.PrintCliError(errors.New("ID is required but not passed in via -id")) + return base.CommandUserError + } + + var opts []aliases.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 + } + aliasesClient := aliases.NewClient(client) + + switch c.FlagRecursive { + case true: + opts = append(opts, aliases.WithRecursive(true)) + } + + if c.FlagFilter != "" { + opts = append(opts, aliases.WithFilter(c.FlagFilter)) + } + + var version uint32 + + if ok := extraFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var resp *api.Response + var item *aliases.Alias + + var items []*aliases.Alias + + var readResult *aliases.AliasReadResult + + var deleteResult *aliases.AliasDeleteResult + + var listResult *aliases.AliasListResult + + switch c.Func { + + case "read": + readResult, err = aliasesClient.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 = aliasesClient.Delete(c.Context, c.FlagId, opts...) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + resp = deleteResult.GetResponse() + + case "list": + listResult, err = aliasesClient.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, aliasesClient, 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, *[]aliases.Option) bool { return true } + executeExtraActions = func(_ *Command, inResp *api.Response, inItem *aliases.Alias, inItems []*aliases.Alias, inErr error, _ *aliases.Client, _ uint32, _ []aliases.Option) (*api.Response, *aliases.Alias, []*aliases.Alias, error) { + return inResp, inItem, inItems, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/aliasescmd/funcs.go b/internal/cmd/commands/aliasescmd/funcs.go new file mode 100644 index 000000000..b45c4ce11 --- /dev/null +++ b/internal/cmd/commands/aliasescmd/funcs.go @@ -0,0 +1,194 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package aliasescmd + +import ( + "fmt" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/aliases" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +type extraCmdVars struct { + flagMembers []string +} + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "": + return base.WrapForHelpText([]string{ + "Usage: boundary aliases [sub command] [options] [args]", + "", + " This command allows operations on Boundary alias resources. Example:", + "", + " Read an alias:", + "", + ` $ boundary aliases read -id alt_1234567890`, + "", + " Please see the aliases subcommand help for detailed usage information.", + }) + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary aliases create target [options] [args]", + "", + " Create an alias. Example:", + "", + ` $ boundary aliases create target -value prod-ops.example -name prodops -description "Target alias for ProdOps"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary aliases update target [options] [args]", + "", + " Update an alias. Example:", + "", + ` $ boundary aliases update target -id alt_1234567890 -name devops`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + + default: + helpStr = "" + } + return helpStr + c.Flags().Help() +} + +func (c *Command) printListTable(items []*aliases.Alias) string { + if len(items) == 0 { + return "No aliases found" + } + var output []string + output = []string{ + "", + "Alias 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 item.Value != "" { + output = append(output, + fmt.Sprintf(" Value: %s", item.Value), + ) + } + if item.DestinationId != "" { + output = append(output, + fmt.Sprintf(" Destination ID: %s", item.DestinationId), + ) + } + if len(item.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, item.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(item *aliases.Alias, resp *api.Response) string { + nonAttributeMap := map[string]any{} + if item.Id != "" { + nonAttributeMap["ID"] = item.Id + } + 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) + } + if item.Name != "" { + nonAttributeMap["Name"] = item.Name + } + if item.Description != "" { + nonAttributeMap["Description"] = item.Description + } + if item.Value != "" { + nonAttributeMap["Value"] = item.Value + } + if item.DestinationId != "" { + nonAttributeMap["Destination ID"] = item.DestinationId + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, nil, nil) + + ret := []string{ + "", + "Alias 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), + ) + } + + if len(item.Attributes) > 0 { + ret = append(ret, + "", + " Attributes:", + // TODO: This looks kind of ugly but it is used across boundary. + // Revisit the formatting of attributes that have nested maps. + base.WrapMap(4, maxLength+2, item.Attributes), + ) + } + + return base.WrapForHelpText(ret) +} diff --git a/internal/cmd/commands/aliasescmd/target_aliases.gen.go b/internal/cmd/commands/aliasescmd/target_aliases.gen.go new file mode 100644 index 000000000..c67a9ae81 --- /dev/null +++ b/internal/cmd/commands/aliasescmd/target_aliases.gen.go @@ -0,0 +1,273 @@ +// Code generated by "make cli"; DO NOT EDIT. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package aliasescmd + +import ( + "errors" + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/aliases" + "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 initTargetFlags() { + flagsOnce.Do(func() { + extraFlags := extraTargetActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsTargetMap[k] = append(flagsTargetMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*TargetCommand)(nil) + _ cli.CommandAutocomplete = (*TargetCommand)(nil) +) + +type TargetCommand struct { + *base.Command + + Func string + + plural string + + extraTargetCmdVars +} + +func (c *TargetCommand) AutocompleteArgs() complete.Predictor { + initTargetFlags() + return complete.PredictAnything +} + +func (c *TargetCommand) AutocompleteFlags() complete.Flags { + initTargetFlags() + return c.Flags().Completions() +} + +func (c *TargetCommand) Synopsis() string { + if extra := extraTargetSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "alias" + + synopsisStr = fmt.Sprintf("%s %s", "target-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *TargetCommand) Help() string { + initTargetFlags() + + var helpStr string + helpMap := common.HelpMap("alias") + + switch c.Func { + + default: + + helpStr = c.extraTargetHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsTargetMap = map[string][]string{ + + "create": {"scope-id", "name", "description"}, + + "update": {"id", "name", "description", "version"}, +} + +func (c *TargetCommand) Flags() *base.FlagSets { + if len(flagsTargetMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "target-type alias", flagsTargetMap, c.Func) + + extraTargetFlagsFunc(c, set, f) + + return set +} + +func (c *TargetCommand) Run(args []string) int { + initTargetFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + + } + + c.plural = "target-type alias" + switch c.Func { + case "list": + c.plural = "target-type aliases" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsTargetMap[c.Func], "id") && c.FlagId == "" { + c.PrintCliError(errors.New("ID is required but not passed in via -id")) + return base.CommandUserError + } + + var opts []aliases.Option + + if strutil.StrListContains(flagsTargetMap[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 + } + aliasesClient := aliases.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, aliases.DefaultName()) + default: + opts = append(opts, aliases.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, aliases.DefaultDescription()) + default: + opts = append(opts, aliases.WithDescription(c.FlagDescription)) + } + + switch c.FlagRecursive { + case true: + opts = append(opts, aliases.WithRecursive(true)) + } + + if c.FlagFilter != "" { + opts = append(opts, aliases.WithFilter(c.FlagFilter)) + } + + var version uint32 + + switch c.Func { + + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, aliases.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + } + + if ok := extraTargetFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var resp *api.Response + var item *aliases.Alias + + var updateResult *aliases.AliasUpdateResult + + switch c.Func { + + case "update": + updateResult, err = aliasesClient.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 = executeExtraTargetActions(c, resp, item, err, aliasesClient, version, opts) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + + output, err := printCustomTargetActionOutput(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 *TargetCommand) 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 ( + extraTargetActionsFlagsMapFunc = func() map[string][]string { return nil } + extraTargetSynopsisFunc = func(*TargetCommand) string { return "" } + extraTargetFlagsFunc = func(*TargetCommand, *base.FlagSets, *base.FlagSet) {} + extraTargetFlagsHandlingFunc = func(*TargetCommand, *base.FlagSets, *[]aliases.Option) bool { return true } + executeExtraTargetActions = func(_ *TargetCommand, inResp *api.Response, inItem *aliases.Alias, inErr error, _ *aliases.Client, _ uint32, _ []aliases.Option) (*api.Response, *aliases.Alias, error) { + return inResp, inItem, inErr + } + printCustomTargetActionOutput = func(*TargetCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/aliasescmd/target_funcs.go b/internal/cmd/commands/aliasescmd/target_funcs.go new file mode 100644 index 000000000..961c3cce1 --- /dev/null +++ b/internal/cmd/commands/aliasescmd/target_funcs.go @@ -0,0 +1,148 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package aliasescmd + +import ( + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/aliases" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func init() { + extraTargetActionsFlagsMapFunc = extraTargetActionsFlagsMapFuncImpl + extraTargetFlagsFunc = extraTargetFlagsFuncImpl + extraTargetFlagsHandlingFunc = extraTargetFlagHandlingFuncImpl + executeExtraTargetActions = executeExtraTargetActionsImpl +} + +type extraTargetCmdVars struct { + flagValue string + flagDestinationId string + flagAuthorizeSessionHostId string +} + +func extraTargetActionsFlagsMapFuncImpl() map[string][]string { + return map[string][]string{ + "create": {"value", "destination-id", "authorize-session-host-id"}, + "update": {"value", "destination-id", "authorize-session-host-id"}, + } +} + +func (c *TargetCommand) extraTargetHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "": + return base.WrapForHelpText([]string{ + "Usage: boundary aliases [sub command] [options] [args]", + "", + " This command allows operations on Boundary alias resources. Example:", + "", + " Read an alias:", + "", + ` $ boundary aliases read -id alt_1234567890`, + "", + " Please see the aliases subcommand help for detailed usage information.", + }) + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary aliases create target [options] [args]", + "", + " Create a target-type alias. Example:", + "", + ` $ boundary aliases create target -value prod-ops.example -name prodops -description "Target alias for ProdOps"`, + "", + "", + }) + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary aliases update target [options] [args]", + "", + " Update an alias. Example:", + "", + ` $ boundary aliases update target -id alt_1234567890 -name devops`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} + +func extraTargetFlagsFuncImpl(c *TargetCommand, set *base.FlagSets, f *base.FlagSet) { + for _, name := range flagsTargetMap[c.Func] { + switch name { + case "value": + f.StringVar(&base.StringVar{ + Name: "value", + Target: &c.flagValue, + Usage: "The value of the alias", + }) + case "destination-id": + f.StringVar(&base.StringVar{ + Name: "destination-id", + Target: &c.flagDestinationId, + Usage: "The target id that the alias points to.", + }) + } + } + + f = set.NewFlagSet("Target Alias Options") + for _, name := range flagsTargetMap[c.Func] { + switch name { + case "authorize-session-host-id": + f.StringVar(&base.StringVar{ + Name: "authorize-session-host-id", + Target: &c.flagAuthorizeSessionHostId, + Usage: "The host id to pass in when authorizing a session with this alias.", + }) + } + } +} + +func extraTargetFlagHandlingFuncImpl(c *TargetCommand, _ *base.FlagSets, opts *[]aliases.Option) bool { + var attributes map[string]any + addAttribute := func(name string, value any) { + if attributes == nil { + attributes = make(map[string]any) + } + attributes[name] = value + } + + switch c.flagDestinationId { + case "": + case "null": + *opts = append(*opts, aliases.DefaultDestinationId()) + default: + *opts = append(*opts, aliases.WithDestinationId(c.flagDestinationId)) + } + + switch c.flagAuthorizeSessionHostId { + case "": + case "null": + addAttribute("authorize_session_arguments", nil) + default: + asa := aliases.AuthorizeSessionArguments{HostId: c.flagAuthorizeSessionHostId} + addAttribute("authorize_session_arguments", asa) + } + + if attributes != nil { + *opts = append(*opts, aliases.WithAttributes(attributes)) + } + + return true +} + +func executeExtraTargetActionsImpl(c *TargetCommand, inResp *api.Response, inItem *aliases.Alias, inErr error, aliasesClient *aliases.Client, version uint32, opts []aliases.Option) (*api.Response, *aliases.Alias, error) { + switch c.Func { + case "create": + // we specify our own create action here because we have more required + // fields in the sdk than is specified in the templates for all the other + // resources. + createResult, err := aliasesClient.Create(c.Context, "target", c.flagValue, c.FlagScopeId, opts...) + if err != nil { + return nil, nil, err + } + return createResult.GetResponse(), createResult.GetItem(), nil + } + return inResp, inItem, inErr +} diff --git a/internal/cmd/common/help.go b/internal/cmd/common/help.go index 33e5b33f0..fd042b0c5 100644 --- a/internal/cmd/common/help.go +++ b/internal/cmd/common/help.go @@ -47,6 +47,7 @@ func HelpMap(resType string) map[string]func() string { resource.SessionRecording.String(): "sr", resource.StorageBucket.String(): "sb", resource.Policy.String(): "p", + resource.Alias.String(): "alt", } return map[string]func() string{ "base": func() string { diff --git a/internal/cmd/gencli/input.go b/internal/cmd/gencli/input.go index 2c9f5913c..376906af6 100644 --- a/internal/cmd/gencli/input.go +++ b/internal/cmd/gencli/input.go @@ -150,6 +150,32 @@ var inputStructs = map[string][]*cmdInfo{ VersionedActions: []string{"update"}, }, }, + "aliases": { + { + ResourceType: resource.Alias.String(), + Pkg: "aliases", + StdActions: []string{"read", "delete", "list"}, + HasExtraHelpFunc: true, + Container: "Scope", + HasId: true, + }, + { + ResourceType: resource.Alias.String(), + Pkg: "aliases", + StdActions: []string{"create", "update"}, + SubActionPrefix: "target", + HasExtraCommandVars: true, + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + HasDescription: true, + Container: "Scope", + VersionedActions: []string{"update"}, + NeedsSubtypeInCreate: true, + SkipClientCallActions: []string{"create"}, + }, + }, "authmethods": { { ResourceType: resource.AuthMethod.String(), diff --git a/internal/daemon/controller/handlers/aliases/alias_service.go b/internal/daemon/controller/handlers/aliases/alias_service.go index 6d0c99380..f05374546 100644 --- a/internal/daemon/controller/handlers/aliases/alias_service.go +++ b/internal/daemon/controller/handlers/aliases/alias_service.go @@ -6,6 +6,7 @@ package aliases import ( "context" "fmt" + "strings" "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/alias/target" @@ -49,6 +50,8 @@ var ( ) ) +const aliasTypeTarget = "target" + func init() { var err error if maskManager, err = handlers.NewMaskManager( @@ -522,6 +525,9 @@ func toProto(ctx context.Context, in *target.Alias, opt ...handlers.Option) (*pb if outputFields.Has(globals.ScopeIdField) { out.ScopeId = in.GetScopeId() } + if outputFields.Has(globals.TypeField) { + out.Type = aliasTypeTarget + } if outputFields.Has(globals.NameField) && in.GetName() != "" { out.Name = wrapperspb.String(in.GetName()) } @@ -576,8 +582,14 @@ func validateCreateRequest(req *pbs.CreateAliasRequest) error { if scope.Global.String() != req.GetItem().GetScopeId() { badFields["scope_id"] = "This field is missing or improperly formatted." } + if req.GetItem().GetValue() == "" { + badFields[globals.ValueField] = "This field is required." + } + if !strings.EqualFold(req.GetItem().GetType(), aliasTypeTarget) { + badFields[globals.TypeField] = "This field is required. Current supported values are 'target'." + } if req.GetItem().GetTargetAliasAttributes().GetAuthorizeSessionArguments().GetHostId() != "" && req.GetItem().GetDestinationId().GetValue() == "" { - badFields["destination_id"] = "This field is required when 'attributes.authorize_sesion_arguments.host_id' is specified." + badFields[globals.DestinationIdField] = "This field is required when 'attributes.authorize_sesion_arguments.host_id' is specified." } return badFields }) diff --git a/internal/daemon/controller/handlers/aliases/alias_service_test.go b/internal/daemon/controller/handlers/aliases/alias_service_test.go index a38958042..4409402fc 100644 --- a/internal/daemon/controller/handlers/aliases/alias_service_test.go +++ b/internal/daemon/controller/handlers/aliases/alias_service_test.go @@ -68,6 +68,7 @@ func TestGet(t *testing.T) { globalScopeInfo := &scopes.ScopeInfo{Id: scope.Global.String(), Type: scope.Global.String(), Name: "global", Description: "Global Scope"} wantAlias := &pb.Alias{ + Type: "target", Id: og.GetPublicId(), ScopeId: "global", Scope: globalScopeInfo, @@ -166,6 +167,7 @@ func TestList(t *testing.T) { for i := 0; i < 10; i++ { gg := target.TestAlias(t, rw, fmt.Sprintf("test%d.alias", i)) wantGlobalAliass = append(wantGlobalAliass, &pb.Alias{ + Type: "target", Id: gg.GetPublicId(), ScopeId: gg.GetScopeId(), Scope: &scopes.ScopeInfo{Id: scope.Global.String(), Type: scope.Global.String(), Name: "global", Description: "Global Scope"}, @@ -261,6 +263,7 @@ func TestList(t *testing.T) { func aliasToProto(u *target.Alias, si *scopes.ScopeInfo, authorizedActions []string) *pb.Alias { pu := &pb.Alias{ + Type: "target", Id: u.GetPublicId(), Value: u.GetValue(), ScopeId: u.GetScopeId(), @@ -737,12 +740,14 @@ func TestCreate(t *testing.T) { { name: "Create a valid Alias", req: &pbs.CreateAliasRequest{Item: &pb.Alias{ + Type: "target", ScopeId: scope.Global.String(), Value: "valid.alias", }}, res: &pbs.CreateAliasResponse{ Uri: fmt.Sprintf("aliases/%s_", globals.TargetAliasPrefix), Item: &pb.Alias{ + Type: "target", ScopeId: scope.Global.String(), Scope: globalScopeInfo, Value: "valid.alias", @@ -754,6 +759,7 @@ func TestCreate(t *testing.T) { { name: "Alias to existing target", req: &pbs.CreateAliasRequest{Item: &pb.Alias{ + Type: "target", ScopeId: scope.Global.String(), Value: "target-assigned.valid.alias", DestinationId: wrapperspb.String(tar.GetPublicId()), @@ -761,6 +767,7 @@ func TestCreate(t *testing.T) { res: &pbs.CreateAliasResponse{ Uri: fmt.Sprintf("aliases/%s_", globals.TargetAliasPrefix), Item: &pb.Alias{ + Type: "target", ScopeId: scope.Global.String(), Scope: globalScopeInfo, Value: "target-assigned.valid.alias", @@ -770,9 +777,19 @@ func TestCreate(t *testing.T) { }, }, }, + { + name: "Omitting the alias type", + req: &pbs.CreateAliasRequest{Item: &pb.Alias{ + ScopeId: scope.Global.String(), + Value: "target-assigned.valid.alias", + DestinationId: wrapperspb.String(tar.GetPublicId()), + }}, + errContains: `This field is required`, + }, { name: "host id with no destination target", req: &pbs.CreateAliasRequest{Item: &pb.Alias{ + Type: "target", ScopeId: scope.Global.String(), Value: "host-id.no-destination.alias", Attrs: &pb.Alias_TargetAliasAttributes{ @@ -788,6 +805,7 @@ func TestCreate(t *testing.T) { { name: "Alias to non existing target", req: &pbs.CreateAliasRequest{Item: &pb.Alias{ + Type: "target", ScopeId: scope.Global.String(), Value: "unknowntarget.alias", DestinationId: wrapperspb.String("ttcp_1234567890"), @@ -797,6 +815,7 @@ func TestCreate(t *testing.T) { { name: "Duplicate Alias Value", req: &pbs.CreateAliasRequest{Item: &pb.Alias{ + Type: "target", ScopeId: scope.Global.String(), Value: al.GetValue(), }}, @@ -805,6 +824,7 @@ func TestCreate(t *testing.T) { { name: "Duplicate Alias Name", req: &pbs.CreateAliasRequest{Item: &pb.Alias{ + Type: "target", ScopeId: scope.Global.String(), Value: "duplicate.alias.name", Name: wrapperspb.String(al.GetName()), @@ -814,6 +834,7 @@ func TestCreate(t *testing.T) { { name: "must be in global scope", req: &pbs.CreateAliasRequest{Item: &pb.Alias{ + Type: "target", ScopeId: p.GetPublicId(), Value: "must.be.in.global.scope", }}, @@ -822,6 +843,7 @@ func TestCreate(t *testing.T) { { name: "Can't specify Id", req: &pbs.CreateAliasRequest{Item: &pb.Alias{ + Type: "target", ScopeId: scope.Global.String(), Id: globals.TargetAliasPrefix + "_notallowed", }}, @@ -831,6 +853,7 @@ func TestCreate(t *testing.T) { { name: "Can't specify Created Time", req: &pbs.CreateAliasRequest{Item: &pb.Alias{ + Type: "target", ScopeId: scope.Global.String(), CreatedTime: timestamppb.Now(), }}, @@ -840,6 +863,7 @@ func TestCreate(t *testing.T) { { name: "Can't specify Update Time", req: &pbs.CreateAliasRequest{Item: &pb.Alias{ + Type: "target", ScopeId: scope.Global.String(), UpdatedTime: timestamppb.Now(), }}, @@ -942,6 +966,7 @@ func TestUpdate(t *testing.T) { }, res: &pbs.UpdateAliasResponse{ Item: &pb.Alias{ + Type: "target", Id: og.GetPublicId(), ScopeId: og.GetScopeId(), Scope: globalScopeInfo, @@ -968,6 +993,7 @@ func TestUpdate(t *testing.T) { }, res: &pbs.UpdateAliasResponse{ Item: &pb.Alias{ + Type: "target", Id: og.GetPublicId(), ScopeId: og.GetScopeId(), Scope: globalScopeInfo, @@ -1024,6 +1050,7 @@ func TestUpdate(t *testing.T) { }, res: &pbs.UpdateAliasResponse{ Item: &pb.Alias{ + Type: "target", Id: og.GetPublicId(), ScopeId: og.GetScopeId(), Scope: globalScopeInfo, @@ -1049,6 +1076,7 @@ func TestUpdate(t *testing.T) { }, res: &pbs.UpdateAliasResponse{ Item: &pb.Alias{ + Type: "target", Id: og.GetPublicId(), Name: wrapperspb.String("updated"), ScopeId: og.GetScopeId(), @@ -1075,6 +1103,7 @@ func TestUpdate(t *testing.T) { }, res: &pbs.UpdateAliasResponse{ Item: &pb.Alias{ + Type: "target", Id: og.GetPublicId(), ScopeId: og.GetScopeId(), Scope: globalScopeInfo, @@ -1114,6 +1143,7 @@ func TestUpdate(t *testing.T) { }, res: &pbs.UpdateAliasResponse{ Item: &pb.Alias{ + Type: "target", Id: og.GetPublicId(), ScopeId: og.GetScopeId(), Scope: globalScopeInfo, @@ -1139,6 +1169,7 @@ func TestUpdate(t *testing.T) { }, res: &pbs.UpdateAliasResponse{ Item: &pb.Alias{ + Type: "target", Id: og.GetPublicId(), ScopeId: og.GetScopeId(), Scope: globalScopeInfo, @@ -1163,6 +1194,7 @@ func TestUpdate(t *testing.T) { }, res: &pbs.UpdateAliasResponse{ Item: &pb.Alias{ + Type: "target", Id: og.GetPublicId(), ScopeId: og.GetScopeId(), Scope: globalScopeInfo, diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index b89337d47..77a545481 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -4856,6 +4856,10 @@ "type": "string", "description": "destination_id is the id of the resource that this Alias points to." }, + "type": { + "type": "string", + "description": "type is the type of the alias." + }, "attributes": { "type": "object", "description": "The attributes that are applicable for the specific Alias type." diff --git a/internal/proto/controller/api/resources/aliases/v1/alias.proto b/internal/proto/controller/api/resources/aliases/v1/alias.proto index 9025f0c82..21ea5360c 100644 --- a/internal/proto/controller/api/resources/aliases/v1/alias.proto +++ b/internal/proto/controller/api/resources/aliases/v1/alias.proto @@ -73,13 +73,16 @@ message Alias { } ]; // @gotags: `class:"public" eventstream:"observation"` + // type is the type of the alias. + string type = 110; // @gotags: `class:"public" eventstream:"observation"` + oneof attrs { // The attributes that are applicable for the specific Alias type. - google.protobuf.Struct attributes = 110 [ + google.protobuf.Struct attributes = 120 [ (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.subtype) = "default" ]; - TargetAliasAttributes target_alias_attributes = 101 [ + TargetAliasAttributes target_alias_attributes = 121 [ (google.api.field_visibility).restriction = "INTERNAL", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.subtype) = "target" diff --git a/internal/tests/api/aliases/alias_test.go b/internal/tests/api/aliases/alias_test.go index 231740669..edaab46ee 100644 --- a/internal/tests/api/aliases/alias_test.go +++ b/internal/tests/api/aliases/alias_test.go @@ -36,7 +36,7 @@ func TestList(t *testing.T) { expected := lr.Items assert.Len(expected, 0) - cr, err := aliasClient.Create(tc.Context(), scopeId, aliases.WithValue("alias0")) + cr, err := aliasClient.Create(tc.Context(), "target", "alias0", scopeId) require.NoError(err) require.NotNil(cr) expected = append(expected, cr.Item) @@ -46,7 +46,7 @@ func TestList(t *testing.T) { assert.ElementsMatch(comparableSlice(expected[:1]), comparableSlice(ulResult.Items)) for i := 1; i < 10; i++ { - newAcctResult, err := aliasClient.Create(tc.Context(), scopeId, aliases.WithValue(fmt.Sprintf("alias%d", i))) + newAcctResult, err := aliasClient.Create(tc.Context(), "target", fmt.Sprintf("alias%d", i), scopeId) require.NoError(err) expected = append(expected, newAcctResult.Item) } @@ -101,7 +101,7 @@ func TestCrud(t *testing.T) { assert.EqualValues(wantedVersion, u.Version) } - u, err := aliasClient.Create(tc.Context(), scopeId, aliases.WithValue("alias.value")) + u, err := aliasClient.Create(tc.Context(), "target", "alias.value", scopeId) checkAlias("create", u.Item, err, "alias.value", 1) u, err = aliasClient.Read(tc.Context(), u.Item.Id) @@ -126,14 +126,23 @@ func TestErrors(t *testing.T) { client.SetToken(token.Token) aliasClient := aliases.NewClient(client) - u, err := aliasClient.Create(tc.Context(), scopeId, aliases.WithValue("first")) + u, err := aliasClient.Create(tc.Context(), "target", "first", scopeId) require.NoError(err) assert.NotNil(u) + // Creating an alias of an unknown type + _, err = aliasClient.Create(tc.Context(), "unknown", "first", scopeId) + require.Error(err) + apiErr := api.AsServerError(err) + require.NotNil(apiErr) + assert.EqualValues(http.StatusBadRequest, apiErr.Response().StatusCode()) + require.Len(apiErr.Details.RequestFields, 1) + assert.Equal(apiErr.Details.RequestFields[0].Name, "type") + // A malformed id is processed as the id and not a different path to the api. _, err = aliasClient.Read(tc.Context(), fmt.Sprintf("%s/../", u.Item.Id)) require.Error(err) - apiErr := api.AsServerError(err) + apiErr = api.AsServerError(err) require.NotNil(apiErr) assert.EqualValues(http.StatusBadRequest, apiErr.Response().StatusCode()) require.Len(apiErr.Details.RequestFields, 1) @@ -147,7 +156,7 @@ func TestErrors(t *testing.T) { assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode()) // Create another resource with the same name. - _, err = aliasClient.Create(tc.Context(), scopeId, aliases.WithValue("first")) + _, err = aliasClient.Create(tc.Context(), "target", "first", scopeId) require.Error(err) apiErr = api.AsServerError(err) require.NotNil(apiErr) diff --git a/sdk/pbs/controller/api/resources/aliases/alias.pb.go b/sdk/pbs/controller/api/resources/aliases/alias.pb.go index 5d14b16f1..49a8fdef1 100644 --- a/sdk/pbs/controller/api/resources/aliases/alias.pb.go +++ b/sdk/pbs/controller/api/resources/aliases/alias.pb.go @@ -57,6 +57,8 @@ type Alias struct { Value string `protobuf:"bytes,90,opt,name=value,proto3" json:"value,omitempty" class:"public"` // @gotags: `class:"public"` // destination_id is the id of the resource that this Alias points to. DestinationId *wrapperspb.StringValue `protobuf:"bytes,100,opt,name=destination_id,proto3" json:"destination_id,omitempty" class:"public" eventstream:"observation"` // @gotags: `class:"public" eventstream:"observation"` + // type is the type of the alias. + Type string `protobuf:"bytes,110,opt,name=type,proto3" json:"type,omitempty" class:"public" eventstream:"observation"` // @gotags: `class:"public" eventstream:"observation"` // Types that are assignable to Attrs: // // *Alias_Attributes @@ -168,6 +170,13 @@ func (x *Alias) GetDestinationId() *wrapperspb.StringValue { return nil } +func (x *Alias) GetType() string { + if x != nil { + return x.Type + } + return "" +} + func (m *Alias) GetAttrs() isAlias_Attrs { if m != nil { return m.Attrs @@ -202,11 +211,11 @@ type isAlias_Attrs interface { type Alias_Attributes struct { // The attributes that are applicable for the specific Alias type. - Attributes *structpb.Struct `protobuf:"bytes,110,opt,name=attributes,proto3,oneof"` + Attributes *structpb.Struct `protobuf:"bytes,120,opt,name=attributes,proto3,oneof"` } type Alias_TargetAliasAttributes struct { - TargetAliasAttributes *TargetAliasAttributes `protobuf:"bytes,101,opt,name=target_alias_attributes,json=targetAliasAttributes,proto3,oneof"` + TargetAliasAttributes *TargetAliasAttributes `protobuf:"bytes,121,opt,name=target_alias_attributes,json=targetAliasAttributes,proto3,oneof"` } func (*Alias_Attributes) isAlias_Attrs() {} @@ -335,8 +344,8 @@ var file_controller_api_resources_aliases_v1_alias_proto_rawDesc = []byte{ 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, - 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf8, - 0x06, 0x0a, 0x05, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, + 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8c, + 0x07, 0x0a, 0x05, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x1e, 0x20, @@ -373,48 +382,49 @@ var file_controller_api_resources_aliases_v1_alias_proto_rawDesc = []byte{ 0x6c, 0x75, 0x65, 0x42, 0x27, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1f, 0x0a, 0x0e, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x12, 0x0d, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x52, 0x0e, 0x64, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x12, 0x4a, 0x0a, 0x0a, - 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x0f, 0xa0, 0xda, 0x29, 0x01, 0x9a, - 0xe3, 0x29, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x94, 0x01, 0x0a, 0x17, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x65, 0x73, 0x18, 0x65, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x41, 0x74, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x42, 0x1e, 0xa0, 0xda, 0x29, 0x01, 0x9a, 0xe3, 0x29, 0x06, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0xfa, 0xd2, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x49, 0x4e, - 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x48, 0x00, 0x52, 0x15, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x41, 0x6c, 0x69, 0x61, 0x73, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, - 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x42, 0x07, 0x0a, 0x05, 0x61, 0x74, 0x74, 0x72, 0x73, 0x22, 0x9a, 0x01, 0x0a, 0x15, 0x54, 0x61, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x12, 0x4a, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x78, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x0f, 0xa0, + 0xda, 0x29, 0x01, 0x9a, 0xe3, 0x29, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x48, 0x00, + 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x94, 0x01, 0x0a, + 0x17, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x79, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, + 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x42, 0x1e, 0xa0, 0xda, 0x29, 0x01, + 0x9a, 0xe3, 0x29, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0xfa, 0xd2, 0xe4, 0x93, 0x02, 0x0a, + 0x12, 0x08, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x48, 0x00, 0x52, 0x15, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x73, 0x12, 0x80, 0x01, 0x0a, 0x1b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, - 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x2e, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x1b, 0x61, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x72, 0x67, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x7a, 0x0a, 0x19, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x5d, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x64, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x40, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x38, 0x0a, 0x2e, - 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x72, 0x67, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x06, - 0x48, 0x6f, 0x73, 0x74, 0x49, 0x64, 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x42, 0x50, 0x5a, 0x4e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x2f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x3b, 0x61, 0x6c, 0x69, - 0x61, 0x73, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x61, 0x74, 0x74, 0x72, 0x73, 0x22, 0x9a, 0x01, + 0x0a, 0x15, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x41, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x80, 0x01, 0x0a, 0x1b, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x72, + 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x1b, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x5f, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x7a, 0x0a, 0x19, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x41, 0x72, + 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x5d, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x42, 0x40, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, + 0x29, 0x38, 0x0a, 0x2e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x5f, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x5f, + 0x69, 0x64, 0x12, 0x06, 0x48, 0x6f, 0x73, 0x74, 0x49, 0x64, 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x69, + 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x50, 0x5a, 0x4e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, + 0x3b, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var (