Add preferred endpoints logic to API/handler/DB and some CLI bits (#1529)
This commit is contained in:
1 parent
f9c620a5d5
commit
01d4383c1b
46 files changed
+1737
-231
No files matched your search
@@ -13,18 +13,19 @@ import (
|
||||
)
|
||||
|
||||
type HostSet struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
HostCatalogId string `json:"host_catalog_id,omitempty"`
|
||||
Scope *scopes.ScopeInfo `json:"scope,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
CreatedTime time.Time `json:"created_time,omitempty"`
|
||||
UpdatedTime time.Time `json:"updated_time,omitempty"`
|
||||
Version uint32 `json:"version,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
HostIds []string `json:"host_ids,omitempty"`
|
||||
Attributes map[string]interface{} `json:"attributes,omitempty"`
|
||||
AuthorizedActions []string `json:"authorized_actions,omitempty"`
|
||||
Id string `json:"id,omitempty"`
|
||||
HostCatalogId string `json:"host_catalog_id,omitempty"`
|
||||
Scope *scopes.ScopeInfo `json:"scope,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
CreatedTime time.Time `json:"created_time,omitempty"`
|
||||
UpdatedTime time.Time `json:"updated_time,omitempty"`
|
||||
Version uint32 `json:"version,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
HostIds []string `json:"host_ids,omitempty"`
|
||||
PreferredEndpoints []string `json:"preferred_endpoints,omitempty"`
|
||||
Attributes map[string]interface{} `json:"attributes,omitempty"`
|
||||
AuthorizedActions []string `json:"authorized_actions,omitempty"`
|
||||
|
||||
response *api.Response
|
||||
}
|
||||
|
||||
@@ -97,3 +97,15 @@ func DefaultName() Option {
|
||||
o.postMap["name"] = nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithPreferredEndpoints(inPreferredEndpoints []string) Option {
|
||||
return func(o *options) {
|
||||
o.postMap["preferred_endpoints"] = inPreferredEndpoints
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultPreferredEndpoints() Option {
|
||||
return func(o *options) {
|
||||
o.postMap["preferred_endpoints"] = nil
|
||||
}
|
||||
}
|
||||
@@ -58,4 +58,5 @@ const (
|
||||
ApplicationCredentialLibrariesField = "application_credential_libraries"
|
||||
ApplicationCredentialSourceIdsField = "application_credential_source_ids"
|
||||
ApplicationCredentialSourcesField = "application_credential_sources"
|
||||
PreferredEndpointsField = "preferred_endpoints"
|
||||
)
|
||||
@@ -560,6 +560,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||
Func: "create",
|
||||
}, nil
|
||||
},
|
||||
"host-sets create plugin": func() (cli.Command, error) {
|
||||
return &hostsetscmd.PluginCommand{
|
||||
Command: base.NewCommand(ui),
|
||||
Func: "create",
|
||||
}, nil
|
||||
},
|
||||
"host-sets update": func() (cli.Command, error) {
|
||||
return &hostsetscmd.Command{
|
||||
Command: base.NewCommand(ui),
|
||||
@@ -572,6 +578,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||
Func: "update",
|
||||
}, nil
|
||||
},
|
||||
"host-sets update plugin": func() (cli.Command, error) {
|
||||
return &hostsetscmd.PluginCommand{
|
||||
Command: base.NewCommand(ui),
|
||||
Func: "update",
|
||||
}, nil
|
||||
},
|
||||
"host-sets add-hosts": func() (cli.Command, error) {
|
||||
return &hostsetscmd.Command{
|
||||
Command: base.NewCommand(ui),
|
||||
|
||||
@@ -253,6 +253,9 @@ func printItemTable(result api.GenericResult) string {
|
||||
if item.HostCatalogId != "" {
|
||||
nonAttributeMap["Host Catalog ID"] = item.HostCatalogId
|
||||
}
|
||||
if item.PreferredEndpoints != nil {
|
||||
nonAttributeMap["Preferred Endpoints"] = item.PreferredEndpoints
|
||||
}
|
||||
|
||||
maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap)
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package hostsetscmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/api/hostsets"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
"github.com/hashicorp/boundary/internal/libs/endpoint"
|
||||
)
|
||||
|
||||
func init() {
|
||||
extraPluginActionsFlagsMapFunc = extraPluginActionsFlagsMapFuncImpl
|
||||
extraPluginFlagsFunc = extraPluginFlagsFuncImpl
|
||||
extraPluginFlagsHandlingFunc = extraPluginFlagsHandlingFuncImpl
|
||||
}
|
||||
|
||||
type extraPluginCmdVars struct {
|
||||
flagPreferredEndpoints []string
|
||||
}
|
||||
|
||||
func extraPluginActionsFlagsMapFuncImpl() map[string][]string {
|
||||
return map[string][]string{
|
||||
"create": {"preferred-endpoint"},
|
||||
"update": {"preferred-endpoint"},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PluginCommand) extraPluginHelpFunc(helpMap map[string]func() string) string {
|
||||
var helpStr string
|
||||
switch c.Func {
|
||||
case "create":
|
||||
helpStr = base.WrapForHelpText([]string{
|
||||
"Usage: boundary host-sets create plugin [options] [args]",
|
||||
"",
|
||||
" Create a host set of a type provided by a plugin. Example:",
|
||||
"",
|
||||
` $ boundary host-sets create plugin -plugin-id plg_1234567890 -name prodops -description "Plugin host-set for ProdOps"`,
|
||||
"",
|
||||
"",
|
||||
})
|
||||
|
||||
case "update":
|
||||
helpStr = base.WrapForHelpText([]string{
|
||||
"Usage: boundary host-sets update plugin [options] [args]",
|
||||
"",
|
||||
" Update a host set of a type provided by a plugin given its ID. Example:",
|
||||
"",
|
||||
` $ boundary host-sets update plugin -id hsplg_1234567890 -name "devops" -description "Plugin host-set for DevOps"`,
|
||||
"",
|
||||
"",
|
||||
})
|
||||
default:
|
||||
helpStr = helpMap[c.Func]()
|
||||
}
|
||||
return helpStr + c.Flags().Help()
|
||||
}
|
||||
|
||||
func extraPluginFlagsFuncImpl(c *PluginCommand, set *base.FlagSets, f *base.FlagSet) {
|
||||
fs := set.NewFlagSet("Plugin Host-Set Options")
|
||||
|
||||
for _, name := range flagsPluginMap[c.Func] {
|
||||
switch name {
|
||||
case "preferred-endpoint":
|
||||
fs.StringSliceVar(&base.StringSliceVar{
|
||||
Name: "preferred-endpoint",
|
||||
Target: &c.flagPreferredEndpoints,
|
||||
Usage: `An endpoint preference, specified by "cidr:<valid IPv4/6 CIDR>" ` +
|
||||
`or "dns:<globbed name>", specifying which IP address or DNS name out ` +
|
||||
`of a host's available possibilities should be preferred. May be specified ` +
|
||||
`multiple times, which will build up an in-order set of preferences. ` +
|
||||
`If no preferences are specified, a value will be chosen _at random_ from ` +
|
||||
`all available values. May not be valid for all plugin types.`,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func extraPluginFlagsHandlingFuncImpl(c *PluginCommand, _ *base.FlagSets, opts *[]hostsets.Option) bool {
|
||||
switch len(c.flagPreferredEndpoints) {
|
||||
case 0:
|
||||
case 1:
|
||||
if c.flagPreferredEndpoints[0] == "null" {
|
||||
*opts = append(*opts, hostsets.DefaultPreferredEndpoints())
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
if _, err := endpoint.NewPreferencer(c.Context, endpoint.WithPreferenceOrder(c.flagPreferredEndpoints)); err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Unable to successfully validate preferred endpoints: %s", err))
|
||||
return false
|
||||
}
|
||||
*opts = append(*opts, hostsets.WithPreferredEndpoints(c.flagPreferredEndpoints))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
// Code generated by "make cli"; DO NOT EDIT.
|
||||
package hostsetscmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/hostsets"
|
||||
"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 initPluginFlags() {
|
||||
flagsOnce.Do(func() {
|
||||
extraFlags := extraPluginActionsFlagsMapFunc()
|
||||
for k, v := range extraFlags {
|
||||
flagsPluginMap[k] = append(flagsPluginMap[k], v...)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
_ cli.Command = (*PluginCommand)(nil)
|
||||
_ cli.CommandAutocomplete = (*PluginCommand)(nil)
|
||||
)
|
||||
|
||||
type PluginCommand struct {
|
||||
*base.Command
|
||||
|
||||
Func string
|
||||
|
||||
plural string
|
||||
|
||||
extraPluginCmdVars
|
||||
}
|
||||
|
||||
func (c *PluginCommand) AutocompleteArgs() complete.Predictor {
|
||||
initPluginFlags()
|
||||
return complete.PredictAnything
|
||||
}
|
||||
|
||||
func (c *PluginCommand) AutocompleteFlags() complete.Flags {
|
||||
initPluginFlags()
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *PluginCommand) Synopsis() string {
|
||||
if extra := extraPluginSynopsisFunc(c); extra != "" {
|
||||
return extra
|
||||
}
|
||||
|
||||
synopsisStr := "host set"
|
||||
|
||||
synopsisStr = fmt.Sprintf("%s %s", "plugin-type", synopsisStr)
|
||||
|
||||
return common.SynopsisFunc(c.Func, synopsisStr)
|
||||
}
|
||||
|
||||
func (c *PluginCommand) Help() string {
|
||||
initPluginFlags()
|
||||
|
||||
var helpStr string
|
||||
helpMap := common.HelpMap("host set")
|
||||
|
||||
switch c.Func {
|
||||
default:
|
||||
|
||||
helpStr = c.extraPluginHelpFunc(helpMap)
|
||||
}
|
||||
|
||||
// Keep linter from complaining if we don't actually generate code using it
|
||||
_ = helpMap
|
||||
return helpStr
|
||||
}
|
||||
|
||||
var flagsPluginMap = map[string][]string{
|
||||
|
||||
"create": {"host-catalog-id", "name", "description"},
|
||||
|
||||
"update": {"id", "name", "description", "version"},
|
||||
}
|
||||
|
||||
func (c *PluginCommand) Flags() *base.FlagSets {
|
||||
if len(flagsPluginMap[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, "plugin-type host set", flagsPluginMap, c.Func)
|
||||
|
||||
extraPluginFlagsFunc(c, set, f)
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *PluginCommand) Run(args []string) int {
|
||||
initPluginFlags()
|
||||
|
||||
switch c.Func {
|
||||
case "":
|
||||
return cli.RunResultHelp
|
||||
}
|
||||
|
||||
c.plural = "plugin-type host set"
|
||||
switch c.Func {
|
||||
case "list":
|
||||
c.plural = "plugin-type host sets"
|
||||
}
|
||||
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.PrintCliError(err)
|
||||
return base.CommandUserError
|
||||
}
|
||||
|
||||
if strutil.StrListContains(flagsPluginMap[c.Func], "id") && c.FlagId == "" {
|
||||
c.PrintCliError(errors.New("ID is required but not passed in via -id"))
|
||||
return base.CommandUserError
|
||||
}
|
||||
|
||||
var opts []hostsets.Option
|
||||
|
||||
if strutil.StrListContains(flagsPluginMap[c.Func], "host-catalog-id") {
|
||||
switch c.Func {
|
||||
case "create":
|
||||
if c.FlagHostCatalogId == "" {
|
||||
c.PrintCliError(errors.New("HostCatalog ID must be passed in via -host-catalog-id or BOUNDARY_HOST_CATALOG_ID"))
|
||||
return base.CommandUserError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.PrintCliError(fmt.Errorf("Error creating API client: %s", err.Error()))
|
||||
return base.CommandCliError
|
||||
}
|
||||
hostsetsClient := hostsets.NewClient(client)
|
||||
|
||||
switch c.FlagName {
|
||||
case "":
|
||||
case "null":
|
||||
opts = append(opts, hostsets.DefaultName())
|
||||
default:
|
||||
opts = append(opts, hostsets.WithName(c.FlagName))
|
||||
}
|
||||
|
||||
switch c.FlagDescription {
|
||||
case "":
|
||||
case "null":
|
||||
opts = append(opts, hostsets.DefaultDescription())
|
||||
default:
|
||||
opts = append(opts, hostsets.WithDescription(c.FlagDescription))
|
||||
}
|
||||
|
||||
if c.FlagFilter != "" {
|
||||
opts = append(opts, hostsets.WithFilter(c.FlagFilter))
|
||||
}
|
||||
|
||||
var version uint32
|
||||
|
||||
switch c.Func {
|
||||
case "update":
|
||||
switch c.FlagVersion {
|
||||
case 0:
|
||||
opts = append(opts, hostsets.WithAutomaticVersioning(true))
|
||||
default:
|
||||
version = uint32(c.FlagVersion)
|
||||
}
|
||||
}
|
||||
|
||||
if ok := extraPluginFlagsHandlingFunc(c, f, &opts); !ok {
|
||||
return base.CommandUserError
|
||||
}
|
||||
|
||||
var result api.GenericResult
|
||||
|
||||
switch c.Func {
|
||||
|
||||
case "create":
|
||||
result, err = hostsetsClient.Create(c.Context, c.FlagHostCatalogId, opts...)
|
||||
|
||||
case "update":
|
||||
result, err = hostsetsClient.Update(c.Context, c.FlagId, version, opts...)
|
||||
|
||||
}
|
||||
|
||||
result, err = executeExtraPluginActions(c, result, err, hostsetsClient, version, opts)
|
||||
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil {
|
||||
var opts []base.Option
|
||||
|
||||
c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...)
|
||||
return base.CommandApiError
|
||||
}
|
||||
c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error()))
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
output, err := printCustomPluginActionOutput(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(result))
|
||||
|
||||
case "json":
|
||||
if ok := c.PrintJsonItem(result); !ok {
|
||||
return base.CommandCliError
|
||||
}
|
||||
}
|
||||
|
||||
return base.CommandSuccess
|
||||
}
|
||||
|
||||
var (
|
||||
extraPluginActionsFlagsMapFunc = func() map[string][]string { return nil }
|
||||
extraPluginSynopsisFunc = func(*PluginCommand) string { return "" }
|
||||
extraPluginFlagsFunc = func(*PluginCommand, *base.FlagSets, *base.FlagSet) {}
|
||||
extraPluginFlagsHandlingFunc = func(*PluginCommand, *base.FlagSets, *[]hostsets.Option) bool { return true }
|
||||
executeExtraPluginActions = func(_ *PluginCommand, inResult api.GenericResult, inErr error, _ *hostsets.Client, _ uint32, _ []hostsets.Option) (api.GenericResult, error) {
|
||||
return inResult, inErr
|
||||
}
|
||||
printCustomPluginActionOutput = func(*PluginCommand) (bool, error) { return false, nil }
|
||||
)
|
||||
@@ -282,6 +282,20 @@ var inputStructs = map[string][]*cmdInfo{
|
||||
HasDescription: true,
|
||||
VersionedActions: []string{"update"},
|
||||
},
|
||||
{
|
||||
ResourceType: resource.HostSet.String(),
|
||||
Pkg: "hostsets",
|
||||
StdActions: []string{"create", "update"},
|
||||
SubActionPrefix: "plugin",
|
||||
HasExtraCommandVars: true,
|
||||
SkipNormalHelp: true,
|
||||
HasExtraHelpFunc: true,
|
||||
HasId: true,
|
||||
HasName: true,
|
||||
Container: "HostCatalog",
|
||||
HasDescription: true,
|
||||
VersionedActions: []string{"update"},
|
||||
},
|
||||
},
|
||||
"hosts": {
|
||||
{
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
begin;
|
||||
|
||||
create table host_set_preferred_endpoint (
|
||||
create_time wt_timestamp,
|
||||
host_set_id wt_public_id not null
|
||||
constraint host_set_fkey
|
||||
references host_set(public_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
priority int not null
|
||||
constraint priority_must_be_greater_than_zero
|
||||
check(priority > 0),
|
||||
condition text not null
|
||||
constraint condition_must_not_be_too_short
|
||||
check(length(trim(condition)) > 4) -- minimum is 'dns:*'
|
||||
constraint condition_must_not_be_too_long
|
||||
check(length(trim(condition)) < 255)
|
||||
constraint condition_has_valid_prefix
|
||||
check(
|
||||
left(trim(condition), 4) = 'dns:'
|
||||
or
|
||||
left(trim(condition), 5) = 'cidr:'
|
||||
)
|
||||
constraint condition_does_not_contain_invalid_chars
|
||||
check(
|
||||
position('|' in trim(condition)) = 0
|
||||
and
|
||||
position('=' in trim(condition)) = 0
|
||||
),
|
||||
primary key(host_set_id, priority),
|
||||
constraint host_set_preferred_endpoint_host_set_id_condition_uq
|
||||
unique(host_set_id, condition)
|
||||
);
|
||||
|
||||
-- host_set_immutable_preferred_endpoint() ensures that endpoint conditions
|
||||
-- assigned to host sets are immutable.
|
||||
create function
|
||||
host_set_immutable_preferred_endpoint()
|
||||
returns trigger
|
||||
as $$
|
||||
begin
|
||||
raise exception 'preferred endpoints are immutable';
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create trigger immutable_preferred_endpoint
|
||||
before update on host_set_preferred_endpoint
|
||||
for each row execute procedure host_set_immutable_preferred_endpoint();
|
||||
|
||||
-- host_plugin_host_set_with_value_obj is useful for reading a plugin host set with its
|
||||
-- associated value objects (preferred endpoints) as columns with delimited
|
||||
-- values. The delimiter depends on the value objects (e.g. if they need
|
||||
-- ordering).
|
||||
create view host_plugin_host_set_with_value_obj as
|
||||
select
|
||||
hs.public_id,
|
||||
hs.catalog_id,
|
||||
hs.name,
|
||||
hs.description,
|
||||
hs.create_time,
|
||||
hs.update_time,
|
||||
hs.version,
|
||||
hs.attributes,
|
||||
-- the string_agg(..) column will be null if there are no associated value objects
|
||||
string_agg(distinct concat_ws('=', hspe.priority, hspe.condition), '|') as preferred_endpoints
|
||||
from
|
||||
host_plugin_set hs
|
||||
left outer join host_set_preferred_endpoint hspe on hs.public_id = hspe.host_set_id
|
||||
group by hs.public_id;
|
||||
comment on view host_plugin_host_set_with_value_obj is
|
||||
'host plugin host set with its associated value objects';
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,166 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/host"
|
||||
"github.com/hashicorp/boundary/internal/host/static"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_PreferredEndpointTable(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
rootWrapper := db.TestWrapper(t)
|
||||
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, rootWrapper))
|
||||
hc := static.TestCatalogs(t, conn, proj.PublicId, 1)[0]
|
||||
hs := static.TestSets(t, conn, hc.PublicId, 1)[0]
|
||||
|
||||
type testCondition struct {
|
||||
condition string
|
||||
priority uint32
|
||||
}
|
||||
insertTests := []struct {
|
||||
testName string
|
||||
hostSetId string
|
||||
conditions []testCondition
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
testName: "invalid host set id",
|
||||
hostSetId: "hsst_1234567890",
|
||||
conditions: []testCondition{
|
||||
{
|
||||
priority: 1,
|
||||
condition: "dns:abcd",
|
||||
},
|
||||
{
|
||||
priority: 2,
|
||||
condition: "cidr:1.2.3.4",
|
||||
},
|
||||
},
|
||||
wantErrContains: "host_set_fkey",
|
||||
},
|
||||
{
|
||||
testName: "invalid condition prefix",
|
||||
hostSetId: hs.PublicId,
|
||||
conditions: []testCondition{
|
||||
{
|
||||
priority: 1,
|
||||
condition: "dsn:abcd",
|
||||
},
|
||||
},
|
||||
wantErrContains: "condition_has_valid_prefix",
|
||||
},
|
||||
{
|
||||
testName: "invalid condition length",
|
||||
hostSetId: hs.PublicId,
|
||||
conditions: []testCondition{
|
||||
{
|
||||
priority: 1,
|
||||
condition: "dns:",
|
||||
},
|
||||
},
|
||||
wantErrContains: "condition_must_not_be_too_short",
|
||||
},
|
||||
{
|
||||
testName: "invalid priority",
|
||||
hostSetId: hs.PublicId,
|
||||
conditions: []testCondition{
|
||||
{
|
||||
priority: 0,
|
||||
condition: "dns:abcd",
|
||||
},
|
||||
},
|
||||
wantErrContains: "priority must not be empty",
|
||||
},
|
||||
{
|
||||
testName: "duplicate priority",
|
||||
hostSetId: hs.PublicId,
|
||||
conditions: []testCondition{
|
||||
{
|
||||
priority: 1,
|
||||
condition: "dns:abcd",
|
||||
},
|
||||
{
|
||||
priority: 1,
|
||||
condition: "cidr:1.2.3.4",
|
||||
},
|
||||
},
|
||||
wantErrContains: "host_set_preferred_endpoint_pkey",
|
||||
},
|
||||
{
|
||||
testName: "invalid char 1",
|
||||
hostSetId: hs.PublicId,
|
||||
conditions: []testCondition{
|
||||
{
|
||||
priority: 1,
|
||||
condition: "dns:ab|cd",
|
||||
},
|
||||
{
|
||||
priority: 1,
|
||||
condition: "cidr:1.2.3.4",
|
||||
},
|
||||
},
|
||||
wantErrContains: "condition_does_not_contain_invalid_chars",
|
||||
},
|
||||
{
|
||||
testName: "invalid char 2",
|
||||
hostSetId: hs.PublicId,
|
||||
conditions: []testCondition{
|
||||
{
|
||||
priority: 1,
|
||||
condition: "dns:abcd",
|
||||
},
|
||||
{
|
||||
priority: 1,
|
||||
condition: "cidr:1.2=.3.4",
|
||||
},
|
||||
},
|
||||
wantErrContains: "condition_does_not_contain_invalid_chars",
|
||||
},
|
||||
{
|
||||
testName: "valid",
|
||||
hostSetId: hs.PublicId,
|
||||
conditions: []testCondition{
|
||||
{
|
||||
priority: 1,
|
||||
condition: "dns:abcd",
|
||||
},
|
||||
{
|
||||
priority: 2,
|
||||
condition: "cidr:1.2.3.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range insertTests {
|
||||
t.Run("insert: "+tt.testName, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
_, err := rw.Exec(ctx, "delete from host_set_preferred_endpoint where host_set_id = ?", []interface{}{tt.hostSetId})
|
||||
require.NoError(err)
|
||||
|
||||
// Add items to insert
|
||||
var items []interface{}
|
||||
for _, cond := range tt.conditions {
|
||||
ep := host.AllocPreferredEndpoint()
|
||||
ep.HostSetId = tt.hostSetId
|
||||
ep.Condition = cond.condition
|
||||
ep.Priority = cond.priority
|
||||
items = append(items, ep)
|
||||
}
|
||||
err = rw.CreateItems(ctx, items)
|
||||
if tt.wantErrContains != "" {
|
||||
require.Error(err)
|
||||
assert.Contains(err.Error(), tt.wantErrContains)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ package schema
|
||||
|
||||
func init() {
|
||||
migrationStates["postgres"] = migrationState{
|
||||
binarySchemaVersion: 17004,
|
||||
binarySchemaVersion: 17005,
|
||||
upMigrations: map[int][]byte{
|
||||
1: []byte(`
|
||||
create domain wt_public_id as text
|
||||
@@ -7851,6 +7851,77 @@ alter table wh_session_connection_accumulating_fact
|
||||
('host_plugin_catalog', 1),
|
||||
('host_plugin_catalog_secret', 1),
|
||||
('host_plugin_set', 1);
|
||||
`),
|
||||
17005: []byte(`
|
||||
create table host_set_preferred_endpoint (
|
||||
create_time wt_timestamp,
|
||||
host_set_id wt_public_id not null
|
||||
constraint host_set_fkey
|
||||
references host_set(public_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
priority int not null
|
||||
constraint priority_must_be_greater_than_zero
|
||||
check(priority > 0),
|
||||
condition text not null
|
||||
constraint condition_must_not_be_empty
|
||||
check(length(trim(condition)) > 4) -- minimum is 'dns:*'
|
||||
constraint condition_must_not_be_too_long
|
||||
check(length(trim(condition)) < 255)
|
||||
constraint condition_has_valid_prefix
|
||||
check(
|
||||
left(trim(condition), 4) = 'dns:'
|
||||
or
|
||||
left(trim(condition), 5) = 'cidr:'
|
||||
)
|
||||
constraint condition_does_not_contain_invalid_chars
|
||||
check(
|
||||
position('|' in trim(condition)) = 0
|
||||
and
|
||||
position('=' in trim(condition)) = 0
|
||||
),
|
||||
primary key(host_set_id, priority),
|
||||
constraint host_set_preferred_endpoint_host_set_id_condition_uq
|
||||
unique(host_set_id, condition)
|
||||
);
|
||||
|
||||
-- host_set_immutable_preferred_endpoint() ensures that endpoint conditions
|
||||
-- assigned to host sets are immutable.
|
||||
create function
|
||||
host_set_immutable_preferred_endpoint()
|
||||
returns trigger
|
||||
as $$
|
||||
begin
|
||||
raise exception 'preferred endpoints are immutable';
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create trigger immutable_preferred_endpoint
|
||||
before update on host_set_preferred_endpoint
|
||||
for each row execute procedure host_set_immutable_preferred_endpoint();
|
||||
|
||||
-- host_plugin_host_set_with_value_obj is useful for reading a plugin host set with its
|
||||
-- associated value objects (preferred endpoints) as columns with delimited
|
||||
-- values. The delimiter depends on the value objects (e.g. if they need
|
||||
-- ordering).
|
||||
create view host_plugin_host_set_with_value_obj as
|
||||
select
|
||||
hs.public_id,
|
||||
hs.catalog_id,
|
||||
hs.name,
|
||||
hs.description,
|
||||
hs.create_time,
|
||||
hs.update_time,
|
||||
hs.version,
|
||||
hs.attributes,
|
||||
-- the string_agg(..) column will be null if there are no associated value objects
|
||||
string_agg(distinct concat_ws('=', hspe.priority, hspe.condition), '|') as preferred_endpoints
|
||||
from
|
||||
host_plugin_set hs
|
||||
left outer join host_set_preferred_endpoint hspe on hs.public_id = hspe.host_set_id
|
||||
group by hs.public_id;
|
||||
comment on view host_plugin_host_set_with_value_obj is
|
||||
'host plugin host set with its associated value objects';
|
||||
`),
|
||||
2001: []byte(`
|
||||
-- log_migration entries represent logs generated during migrations
|
||||
|
||||
@@ -3863,6 +3863,13 @@
|
||||
"description": "Output only. A list of Hosts in this Host Set.",
|
||||
"readOnly": true
|
||||
},
|
||||
"preferred_endpoints": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "An ordered list of endpoint preferences used to choose from among\nmultiple possible endpoints for a host."
|
||||
},
|
||||
"attributes": {
|
||||
"type": "object",
|
||||
"description": "The attributes that are applicable for the specific Host Set type."
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package host
|
||||
|
||||
// GetOpts - iterate the inbound Options and return a struct
|
||||
func GetOpts(opt ...Option) (options, error) {
|
||||
opts := getDefaultOptions()
|
||||
for _, o := range opt {
|
||||
if err := o(&opts); err != nil {
|
||||
return opts, err
|
||||
}
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// Option - how Options are passed as arguments.
|
||||
type Option func(*options) error
|
||||
|
||||
// options = how options are represented
|
||||
type options struct {
|
||||
WithLimit int
|
||||
WithOrderByCreateTime bool
|
||||
Ascending bool
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
return options{}
|
||||
}
|
||||
|
||||
// WithLimit provides an option to provide a limit. Intentionally allowing
|
||||
// negative integers. If WithLimit < 0, then unlimited results are
|
||||
// returned. If WithLimit == 0, then default limits are used for results.
|
||||
func WithLimit(l int) Option {
|
||||
return func(o *options) error {
|
||||
o.WithLimit = l
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithOrderByCreateTime provides an option to specify ordering by the
|
||||
// CreateTime field.
|
||||
func WithOrderByCreateTime(ascending bool) Option {
|
||||
return func(o *options) error {
|
||||
o.WithOrderByCreateTime = true
|
||||
o.Ascending = ascending
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_GetOpts(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("WithLimit", func(t *testing.T) {
|
||||
opts, err := GetOpts(WithLimit(1))
|
||||
require.NoError(t, err)
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.WithLimit = 1
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
t.Run("WithOrderByCreateTime-desc", func(t *testing.T) {
|
||||
opts, err := GetOpts(WithOrderByCreateTime(false))
|
||||
require.NoError(t, err)
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.WithOrderByCreateTime = true
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
t.Run("WithOrderByCreateTime-asc", func(t *testing.T) {
|
||||
opts, err := GetOpts(WithOrderByCreateTime(true))
|
||||
require.NoError(t, err)
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.WithOrderByCreateTime = true
|
||||
testOpts.Ascending = true
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func (c *HostCatalogSecret) SetTableName(n string) {
|
||||
func (c *HostCatalogSecret) encrypt(ctx context.Context, cipher wrapping.Wrapper) error {
|
||||
const op = "plugin.(HostCatalogSecret).encrypt"
|
||||
if len(c.Secret) == 0 {
|
||||
errors.New(ctx, errors.InvalidParameter, op, "no attributes defined")
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "no attributes defined")
|
||||
}
|
||||
if err := structwrapping.WrapStruct(ctx, cipher, c.HostCatalogSecret, nil); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt))
|
||||
|
||||
@@ -122,6 +122,7 @@ func TestHostCatalogSecret_Custom_Queries(t *testing.T) {
|
||||
plg := host.TestPlugin(t, conn, "test", "prefix")
|
||||
cat := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId())
|
||||
databaseWrapper, err := kkms.GetWrapper(ctx, prj.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
hcs, err := newHostCatalogSecret(ctx, cat.GetPublicId(), map[string]interface{}{"foo": "bar"})
|
||||
require.NoError(t, err)
|
||||
@@ -131,6 +132,7 @@ func TestHostCatalogSecret_Custom_Queries(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
found, err := newHostCatalogSecret(ctx, cat.GetPublicId(), nil)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, rw.LookupWhere(ctx, found, "catalog_id=?", found.GetCatalogId()))
|
||||
require.NoError(t, found.decrypt(ctx, databaseWrapper))
|
||||
require.NoError(t, hcs.decrypt(ctx, databaseWrapper))
|
||||
|
||||
@@ -186,7 +186,7 @@ func TestHostCatalog_Create_DuplicateNames(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
got.PublicId, err = newHostCatalogId(ctx, plg.GetIdPrefix())
|
||||
require.NoError(t, err)
|
||||
w.Create(ctx, got)
|
||||
require.NoError(t, w.Create(ctx, got))
|
||||
|
||||
// Can't create another resource with the same name in the same scope
|
||||
got.PublicId, err = newHostCatalogId(ctx, plg.GetIdPrefix())
|
||||
|
||||
@@ -16,17 +16,18 @@ type HostSet struct {
|
||||
tableName string `gorm:"-"`
|
||||
}
|
||||
|
||||
// NewHostSet creates a new in memory HostSet assigned to catalogId.
|
||||
// Attributes, name, and description are the only valid options. All other
|
||||
// options are ignored.
|
||||
// NewHostSet creates a new in memory HostSet assigned to catalogId. Attributes,
|
||||
// name, description, and preferred endpoints are the only valid options. All
|
||||
// other options are ignored.
|
||||
func NewHostSet(ctx context.Context, catalogId string, opt ...Option) (*HostSet, error) {
|
||||
const op = "plugin.NewHostSet"
|
||||
opts := getOpts(opt...)
|
||||
set := &HostSet{
|
||||
HostSet: &store.HostSet{
|
||||
CatalogId: catalogId,
|
||||
Name: opts.withName,
|
||||
Description: opts.withDescription,
|
||||
CatalogId: catalogId,
|
||||
Name: opts.withName,
|
||||
Description: opts.withDescription,
|
||||
PreferredEndpoints: opts.withPreferredEndpoints,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -37,6 +38,7 @@ func NewHostSet(ctx context.Context, catalogId string, opt ...Option) (*HostSet,
|
||||
}
|
||||
set.Attributes = attrs
|
||||
}
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,22 @@ func TestHostSet_Create(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-with-preferred-endpoints",
|
||||
args: args{
|
||||
catalogId: cat.GetPublicId(),
|
||||
opts: []Option{
|
||||
WithPreferredEndpoints([]string{"cidr:1.2.3.4"}),
|
||||
},
|
||||
},
|
||||
want: &HostSet{
|
||||
HostSet: &store.HostSet{
|
||||
CatalogId: cat.GetPublicId(),
|
||||
PreferredEndpoints: []string{"cidr:1.2.3.4"},
|
||||
Attributes: []byte("{}"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/host/plugin/store"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/plugin/host"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -94,12 +95,13 @@ func TestPluginSet_ImmutableFields(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
w := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
|
||||
ts := timestamp.Timestamp{Timestamp: ×tamppb.Timestamp{Seconds: 0, Nanos: 0}}
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
plg := host.TestPlugin(t, conn, "test", "prefix")
|
||||
cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
|
||||
new := TestSet(t, conn, cat.GetPublicId())
|
||||
new := TestSet(t, conn, kmsCache, cat)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -125,7 +127,7 @@ func TestPluginSet_ImmutableFields(t *testing.T) {
|
||||
fieldMask: []string{"CreateTime"},
|
||||
},
|
||||
{
|
||||
name: "plugin_host_catalog_id",
|
||||
name: "host_plugin_catalog_id",
|
||||
update: func() *HostSet {
|
||||
c := new.testCloneHostSet()
|
||||
c.CatalogId = "stc_01234567890"
|
||||
|
||||
@@ -14,11 +14,11 @@ type Option func(*options)
|
||||
|
||||
// options = how options are represented
|
||||
type options struct {
|
||||
withName string
|
||||
withDescription string
|
||||
withLimit int
|
||||
withAttributes map[string]interface{}
|
||||
withSecrets map[string]interface{}
|
||||
withName string
|
||||
withDescription string
|
||||
withAttributes map[string]interface{}
|
||||
withSecrets map[string]interface{}
|
||||
withPreferredEndpoints []string
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
@@ -57,11 +57,9 @@ func WithSecrets(secrets map[string]interface{}) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithLimit provides an option to provide a limit. Intentionally allowing
|
||||
// negative integers. If WithLimit < 0, then unlimited results are
|
||||
// returned. If WithLimit == 0, then default limits are used for results.
|
||||
func WithLimit(l int) Option {
|
||||
// WithPreferredEndpoints provides an optional preferred endpoints field.
|
||||
func WithPreferredEndpoints(with []string) Option {
|
||||
return func(o *options) {
|
||||
o.withLimit = l
|
||||
o.withPreferredEndpoints = with
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,10 @@ func Test_GetOpts(t *testing.T) {
|
||||
testOpts.withDescription = "test desc"
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
t.Run("WithLimit", func(t *testing.T) {
|
||||
opts := getOpts(WithLimit(5))
|
||||
t.Run("WithPreferredEndpoints", func(t *testing.T) {
|
||||
opts := getOpts(WithPreferredEndpoints([]string{"foo"}))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withLimit = 5
|
||||
assert.Equal(t, opts, testOpts)
|
||||
testOpts.withPreferredEndpoints = []string{"foo"}
|
||||
assert.EqualValues(t, opts, testOpts)
|
||||
})
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package plugin
|
||||
import (
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/host"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
)
|
||||
|
||||
@@ -24,7 +25,7 @@ type Repository struct {
|
||||
// only be used for one transaction and it is not safe for concurrent go
|
||||
// routines to access it. WithLimit option is used as a repo wide default
|
||||
// limit applied to all ListX methods.
|
||||
func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) {
|
||||
func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...host.Option) (*Repository, error) {
|
||||
const op = "plugin.NewRepository"
|
||||
switch {
|
||||
case r == nil:
|
||||
@@ -35,16 +36,19 @@ func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repo
|
||||
return nil, errors.NewDeprecated(errors.InvalidParameter, op, "kms")
|
||||
}
|
||||
|
||||
opts := getOpts(opt...)
|
||||
if opts.withLimit == 0 {
|
||||
opts, err := host.GetOpts(opt...)
|
||||
if err != nil {
|
||||
return nil, errors.WrapDeprecated(err, op)
|
||||
}
|
||||
if opts.WithLimit == 0 {
|
||||
// zero signals the boundary defaults should be used.
|
||||
opts.withLimit = db.DefaultLimit
|
||||
opts.WithLimit = db.DefaultLimit
|
||||
}
|
||||
|
||||
return &Repository{
|
||||
reader: r,
|
||||
writer: w,
|
||||
kms: kms,
|
||||
defaultLimit: opts.withLimit,
|
||||
defaultLimit: opts.WithLimit,
|
||||
}, nil
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/host"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
hostplg "github.com/hashicorp/boundary/internal/plugin/host"
|
||||
@@ -75,7 +76,9 @@ func (r *Repository) CreateCatalog(ctx context.Context, c *HostCatalog, _ ...Opt
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
hcSecret.encrypt(ctx, dbWrapper)
|
||||
if err := hcSecret.encrypt(ctx, dbWrapper); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
}
|
||||
|
||||
var newHostCatalog *HostCatalog
|
||||
@@ -146,20 +149,22 @@ func (r *Repository) LookupCatalog(ctx context.Context, id string, _ ...Option)
|
||||
}
|
||||
|
||||
// ListCatalogs returns a slice of HostCatalogs for the scope IDs. WithLimit is the only option supported.
|
||||
func (r *Repository) ListCatalogs(ctx context.Context, scopeIds []string, opt ...Option) ([]*HostCatalog, error) {
|
||||
func (r *Repository) ListCatalogs(ctx context.Context, scopeIds []string, opt ...host.Option) ([]*HostCatalog, error) {
|
||||
const op = "plugin.(Repository).ListCatalogs"
|
||||
if len(scopeIds) == 0 {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no scope id")
|
||||
}
|
||||
opts := getOpts(opt...)
|
||||
opts, err := host.GetOpts(opt...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
limit := r.defaultLimit
|
||||
if opts.withLimit != 0 {
|
||||
if opts.WithLimit != 0 {
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = opts.withLimit
|
||||
limit = opts.WithLimit
|
||||
}
|
||||
var hostCatalogs []*HostCatalog
|
||||
err := r.reader.SearchWhere(ctx, &hostCatalogs, "scope_id in (?)", []interface{}{scopeIds}, db.WithLimit(limit))
|
||||
if err != nil {
|
||||
if err := r.reader.SearchWhere(ctx, &hostCatalogs, "scope_id in (?)", []interface{}{scopeIds}, db.WithLimit(limit)); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return hostCatalogs, nil
|
||||
|
||||
@@ -204,7 +204,7 @@ func TestRepository_CreateCatalog(t *testing.T) {
|
||||
|
||||
dbWrapper, err := kmsCache.GetWrapper(ctx, got.GetScopeId(), kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
cSecret.decrypt(ctx, dbWrapper)
|
||||
require.NoError(t, cSecret.decrypt(ctx, dbWrapper))
|
||||
assert.Equal(string(tt.wantSecret), string(cSecret.Secret))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,9 +3,14 @@ package plugin
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/host"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
hostplg "github.com/hashicorp/boundary/internal/plugin/host"
|
||||
@@ -65,14 +70,51 @@ func (r *Repository) CreateSet(ctx context.Context, scopeId string, s *HostSet,
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
||||
}
|
||||
|
||||
var newHostSet *HostSet
|
||||
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, w db.Writer) error {
|
||||
newHostSet = s.clone()
|
||||
err := w.Create(ctx, newHostSet, db.WithOplog(oplogWrapper, s.oplog(oplog.OpType_OP_TYPE_CREATE)))
|
||||
var preferredEndpoints []interface{}
|
||||
if s.PreferredEndpoints != nil {
|
||||
preferredEndpoints = make([]interface{}, 0, len(s.PreferredEndpoints))
|
||||
for i, e := range s.PreferredEndpoints {
|
||||
obj, err := host.NewPreferredEndpoint(ctx, s.PublicId, uint32(i+1), e)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
preferredEndpoints = append(preferredEndpoints, obj)
|
||||
}
|
||||
}
|
||||
|
||||
var returnedHostSet *HostSet
|
||||
_, err = r.writer.DoTx(
|
||||
ctx,
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(_ db.Reader, w db.Writer) error {
|
||||
msgs := make([]*oplog.Message, 0, len(preferredEndpoints)+2)
|
||||
ticket, err := w.GetTicket(s)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
|
||||
}
|
||||
|
||||
returnedHostSet = s.clone()
|
||||
|
||||
var hsOplogMsg oplog.Message
|
||||
if err := w.Create(ctx, returnedHostSet, db.NewOplogMsg(&hsOplogMsg)); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
msgs = append(msgs, &hsOplogMsg)
|
||||
|
||||
if len(preferredEndpoints) > 0 {
|
||||
peOplogMsgs := make([]*oplog.Message, 0, len(preferredEndpoints))
|
||||
if err := w.CreateItems(ctx, preferredEndpoints, db.NewOplogMsgs(&peOplogMsgs)); err != nil {
|
||||
return err
|
||||
}
|
||||
msgs = append(msgs, peOplogMsgs...)
|
||||
}
|
||||
|
||||
metadata := s.oplog(oplog.OpType_OP_TYPE_CREATE)
|
||||
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
@@ -83,49 +125,172 @@ func (r *Repository) CreateSet(ctx context.Context, scopeId string, s *HostSet,
|
||||
}
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in catalog: %s", s.CatalogId)))
|
||||
}
|
||||
return newHostSet, nil
|
||||
return returnedHostSet, nil
|
||||
}
|
||||
|
||||
// LookupSet will look up a host set in the repository and return the host
|
||||
// set. If the host set is not found, it will return nil, nil.
|
||||
// All options are ignored.
|
||||
func (r *Repository) LookupSet(ctx context.Context, publicId string, _ ...Option) (*HostSet, error) {
|
||||
func (r *Repository) LookupSet(ctx context.Context, publicId string, opt ...host.Option) (*HostSet, error) {
|
||||
const op = "plugin.(Repository).LookupSet"
|
||||
if publicId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id")
|
||||
}
|
||||
|
||||
s := allocHostSet()
|
||||
s.PublicId = publicId
|
||||
|
||||
err := r.reader.LookupByPublicId(ctx, s)
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, nil
|
||||
}
|
||||
sets, err := r.getSets(ctx, publicId, "", opt...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in %s", s.PublicId)))
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
switch {
|
||||
case len(sets) == 0:
|
||||
return nil, nil // not an error to return no rows for a "lookup"
|
||||
case len(sets) > 1:
|
||||
return nil, errors.New(ctx, errors.NotSpecificIntegrity, op, fmt.Sprintf("%s matched more than 1 ", publicId))
|
||||
default:
|
||||
return sets[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
// ListSets returns a slice of HostSets for the catalogId. WithLimit is the
|
||||
// only option supported.
|
||||
func (r *Repository) ListSets(ctx context.Context, catalogId string, opt ...Option) ([]*HostSet, error) {
|
||||
func (r *Repository) ListSets(ctx context.Context, catalogId string, opt ...host.Option) ([]*HostSet, error) {
|
||||
const op = "plugin.(Repository).ListSets"
|
||||
if catalogId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no catalog id")
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing catalog id")
|
||||
}
|
||||
opts := getOpts(opt...)
|
||||
limit := r.defaultLimit
|
||||
if opts.withLimit != 0 {
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = opts.withLimit
|
||||
}
|
||||
var sets []*HostSet
|
||||
err := r.reader.SearchWhere(ctx, &sets, "catalog_id = ?", []interface{}{catalogId}, db.WithLimit(limit))
|
||||
|
||||
sets, err := r.getSets(ctx, "", catalogId, opt...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return sets, nil
|
||||
}
|
||||
|
||||
func (r *Repository) getSets(ctx context.Context, publicId string, catalogId string, opt ...host.Option) ([]*HostSet, error) {
|
||||
const op = "plugin.(Repository).getSets"
|
||||
const aggregateDelimiter = "|"
|
||||
const priorityDelimiter = "="
|
||||
|
||||
if publicId == "" && catalogId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing search criteria: both host set id and catalog id are empty")
|
||||
}
|
||||
if publicId != "" && catalogId != "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "searching for both a host set id and a catalog id is not supported")
|
||||
}
|
||||
|
||||
opts, err := host.GetOpts(opt...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
limit := r.defaultLimit
|
||||
if opts.WithLimit != 0 {
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = opts.WithLimit
|
||||
}
|
||||
|
||||
args := make([]interface{}, 0, 1)
|
||||
var where string
|
||||
|
||||
switch {
|
||||
case publicId != "":
|
||||
where, args = "public_id = ?", append(args, publicId)
|
||||
default:
|
||||
where, args = "catalog_id = ?", append(args, catalogId)
|
||||
}
|
||||
|
||||
dbArgs := []db.Option{db.WithLimit(limit)}
|
||||
|
||||
if opts.WithOrderByCreateTime {
|
||||
if opts.Ascending {
|
||||
dbArgs = append(dbArgs, db.WithOrder("create_time asc"))
|
||||
} else {
|
||||
dbArgs = append(dbArgs, db.WithOrder("create_time"))
|
||||
}
|
||||
}
|
||||
|
||||
var aggHostSets []*hostSetAgg
|
||||
if err := r.reader.SearchWhere(ctx, &aggHostSets, where, args, dbArgs...); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in %s", publicId)))
|
||||
}
|
||||
|
||||
if len(aggHostSets) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sets := make([]*HostSet, 0, len(aggHostSets))
|
||||
for _, agg := range aggHostSets {
|
||||
hs := allocHostSet()
|
||||
hs.PublicId = agg.PublicId
|
||||
hs.CatalogId = agg.CatalogId
|
||||
hs.Name = agg.Name
|
||||
hs.Description = agg.Description
|
||||
hs.CreateTime = agg.CreateTime
|
||||
hs.UpdateTime = agg.UpdateTime
|
||||
hs.Version = agg.Version
|
||||
hs.Attributes = agg.Attributes
|
||||
if agg.PreferredEndpoints != "" {
|
||||
eps := strings.Split(agg.PreferredEndpoints, aggregateDelimiter)
|
||||
if len(eps) > 0 {
|
||||
// We want to protect against someone messing with the DB
|
||||
// and not panic, so we do a bit of a dance here
|
||||
var sortErr error
|
||||
sort.Slice(eps, func(i, j int) bool {
|
||||
epi := strings.Split(eps[i], priorityDelimiter)
|
||||
if len(epi) != 2 {
|
||||
sortErr = errors.New(ctx, errors.NotSpecificIntegrity, op, fmt.Sprintf("preferred endpoint %s had unexpected fields", eps[i]))
|
||||
return false
|
||||
}
|
||||
epj := strings.Split(eps[j], priorityDelimiter)
|
||||
if len(epj) != 2 {
|
||||
sortErr = errors.New(ctx, errors.NotSpecificIntegrity, op, fmt.Sprintf("preferred endpoint %s had unexpected fields", eps[j]))
|
||||
return false
|
||||
}
|
||||
indexi, err := strconv.Atoi(epi[0])
|
||||
if err != nil {
|
||||
sortErr = errors.Wrap(ctx, err, op)
|
||||
return false
|
||||
}
|
||||
indexj, err := strconv.Atoi(epj[0])
|
||||
if err != nil {
|
||||
sortErr = errors.Wrap(ctx, err, op)
|
||||
return false
|
||||
}
|
||||
return indexi < indexj
|
||||
})
|
||||
if sortErr != nil {
|
||||
return nil, sortErr
|
||||
}
|
||||
for i, ep := range eps {
|
||||
// At this point they're in the correct order, but we still
|
||||
// have to strip off the priority
|
||||
eps[i] = strings.Split(ep, priorityDelimiter)[1]
|
||||
}
|
||||
|
||||
hs.PreferredEndpoints = eps
|
||||
}
|
||||
}
|
||||
sets = append(sets, hs)
|
||||
}
|
||||
|
||||
return sets, nil
|
||||
}
|
||||
|
||||
// hostSetAgg is a view that aggregates the host set's value objects in to
|
||||
// string fields delimited with the aggregateDelimiter of "|"
|
||||
type hostSetAgg struct {
|
||||
PublicId string `gorm:"primary_key"`
|
||||
CatalogId string
|
||||
PluginId string
|
||||
Name string
|
||||
Description string
|
||||
CreateTime *timestamp.Timestamp
|
||||
UpdateTime *timestamp.Timestamp
|
||||
Version uint32
|
||||
Attributes []byte
|
||||
PreferredEndpoints string
|
||||
}
|
||||
|
||||
// TableName returns the table name for gorm
|
||||
func (agg *hostSetAgg) TableName() string { return "host_plugin_host_set_with_value_obj" }
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/host"
|
||||
"github.com/hashicorp/boundary/internal/host/plugin/store"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
@@ -60,8 +61,8 @@ func TestRepository_CreateSet(t *testing.T) {
|
||||
name: "invalid-public-id-set",
|
||||
in: &HostSet{
|
||||
HostSet: &store.HostSet{
|
||||
CatalogId: catalog.PublicId,
|
||||
PublicId: "abcd_OOOOOOOOOO",
|
||||
CatalogId: catalog.PublicId,
|
||||
PublicId: "abcd_OOOOOOOOOO",
|
||||
Attributes: attrs,
|
||||
},
|
||||
},
|
||||
@@ -80,30 +81,47 @@ func TestRepository_CreateSet(t *testing.T) {
|
||||
name: "valid-no-options",
|
||||
in: &HostSet{
|
||||
HostSet: &store.HostSet{
|
||||
CatalogId: catalog.PublicId,
|
||||
CatalogId: catalog.PublicId,
|
||||
Attributes: attrs,
|
||||
},
|
||||
},
|
||||
want: &HostSet{
|
||||
HostSet: &store.HostSet{
|
||||
CatalogId: catalog.PublicId,
|
||||
CatalogId: catalog.PublicId,
|
||||
Attributes: attrs,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-preferred-endpoints",
|
||||
in: &HostSet{
|
||||
HostSet: &store.HostSet{
|
||||
CatalogId: catalog.PublicId,
|
||||
Attributes: attrs,
|
||||
PreferredEndpoints: []string{"cidr:1.2.3.4/32", "dns:a.b.c"},
|
||||
},
|
||||
},
|
||||
want: &HostSet{
|
||||
HostSet: &store.HostSet{
|
||||
CatalogId: catalog.PublicId,
|
||||
Attributes: attrs,
|
||||
PreferredEndpoints: []string{"cidr:1.2.3.4/32", "dns:a.b.c"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-with-name",
|
||||
in: &HostSet{
|
||||
HostSet: &store.HostSet{
|
||||
CatalogId: catalog.PublicId,
|
||||
Name: "test-name-repo",
|
||||
CatalogId: catalog.PublicId,
|
||||
Name: "test-name-repo",
|
||||
Attributes: attrs,
|
||||
},
|
||||
},
|
||||
want: &HostSet{
|
||||
HostSet: &store.HostSet{
|
||||
CatalogId: catalog.PublicId,
|
||||
Name: "test-name-repo",
|
||||
CatalogId: catalog.PublicId,
|
||||
Name: "test-name-repo",
|
||||
Attributes: attrs,
|
||||
},
|
||||
},
|
||||
@@ -114,14 +132,14 @@ func TestRepository_CreateSet(t *testing.T) {
|
||||
HostSet: &store.HostSet{
|
||||
CatalogId: catalog.PublicId,
|
||||
Description: ("test-description-repo"),
|
||||
Attributes: attrs,
|
||||
Attributes: attrs,
|
||||
},
|
||||
},
|
||||
want: &HostSet{
|
||||
HostSet: &store.HostSet{
|
||||
CatalogId: catalog.PublicId,
|
||||
Description: ("test-description-repo"),
|
||||
Attributes: attrs,
|
||||
Attributes: attrs,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -163,8 +181,8 @@ func TestRepository_CreateSet(t *testing.T) {
|
||||
|
||||
in := &HostSet{
|
||||
HostSet: &store.HostSet{
|
||||
CatalogId: catalog.PublicId,
|
||||
Name: "test-name-repo",
|
||||
CatalogId: catalog.PublicId,
|
||||
Name: "test-name-repo",
|
||||
Attributes: []byte("{}"),
|
||||
},
|
||||
}
|
||||
@@ -195,7 +213,7 @@ func TestRepository_CreateSet(t *testing.T) {
|
||||
|
||||
in := &HostSet{
|
||||
HostSet: &store.HostSet{
|
||||
Name: "test-name-repo",
|
||||
Name: "test-name-repo",
|
||||
Attributes: []byte("{}"),
|
||||
},
|
||||
}
|
||||
@@ -234,7 +252,7 @@ func TestRepository_LookupSet(t *testing.T) {
|
||||
plg := hostplg.TestPlugin(t, conn, "lookup", "lookup")
|
||||
|
||||
catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
|
||||
hostSet := TestSet(t, conn, catalog.PublicId)
|
||||
hostSet := TestSet(t, conn, kms, catalog)
|
||||
hostSetId, err := newHostSetId(ctx, plg.GetIdPrefix())
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -273,7 +291,9 @@ func TestRepository_LookupSet(t *testing.T) {
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.EqualValues(tt.want, got)
|
||||
if tt.want != nil {
|
||||
assert.Empty(cmp.Diff(got, tt.want, protocmp.Transform()), "LookupSet(%q) got response %q, wanted %q", tt.in, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -291,15 +311,15 @@ func TestRepository_ListSets(t *testing.T) {
|
||||
catalogB := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
|
||||
|
||||
hostSets := []*HostSet{
|
||||
TestSet(t, conn, catalogA.PublicId),
|
||||
TestSet(t, conn, catalogA.PublicId),
|
||||
TestSet(t, conn, catalogA.PublicId),
|
||||
TestSet(t, conn, kms, catalogA),
|
||||
TestSet(t, conn, kms, catalogA),
|
||||
TestSet(t, conn, kms, catalogA),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
opts []Option
|
||||
opts []host.Option
|
||||
want []*HostSet
|
||||
wantIsErr errors.Code
|
||||
}{
|
||||
@@ -310,7 +330,7 @@ func TestRepository_ListSets(t *testing.T) {
|
||||
{
|
||||
name: "Catalog-with-no-host-sets",
|
||||
in: catalogB.PublicId,
|
||||
want: []*HostSet{},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "Catalog-with-host-sets",
|
||||
@@ -355,13 +375,13 @@ func TestRepository_ListSets_Limits(t *testing.T) {
|
||||
count := 10
|
||||
var hostSets []*HostSet
|
||||
for i := 0; i < count; i++ {
|
||||
hostSets = append(hostSets, TestSet(t, conn, catalog.PublicId))
|
||||
hostSets = append(hostSets, TestSet(t, conn, kms, catalog))
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
repoOpts []Option
|
||||
listOpts []Option
|
||||
repoOpts []host.Option
|
||||
listOpts []host.Option
|
||||
wantLen int
|
||||
}{
|
||||
{
|
||||
@@ -370,34 +390,34 @@ func TestRepository_ListSets_Limits(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "With repo limit",
|
||||
repoOpts: []Option{WithLimit(3)},
|
||||
repoOpts: []host.Option{host.WithLimit(3)},
|
||||
wantLen: 3,
|
||||
},
|
||||
{
|
||||
name: "With negative repo limit",
|
||||
repoOpts: []Option{WithLimit(-1)},
|
||||
repoOpts: []host.Option{host.WithLimit(-1)},
|
||||
wantLen: count,
|
||||
},
|
||||
{
|
||||
name: "With List limit",
|
||||
listOpts: []Option{WithLimit(3)},
|
||||
listOpts: []host.Option{host.WithLimit(3)},
|
||||
wantLen: 3,
|
||||
},
|
||||
{
|
||||
name: "With negative List limit",
|
||||
listOpts: []Option{WithLimit(-1)},
|
||||
listOpts: []host.Option{host.WithLimit(-1)},
|
||||
wantLen: count,
|
||||
},
|
||||
{
|
||||
name: "With repo smaller than list limit",
|
||||
repoOpts: []Option{WithLimit(2)},
|
||||
listOpts: []Option{WithLimit(6)},
|
||||
repoOpts: []host.Option{host.WithLimit(2)},
|
||||
listOpts: []host.Option{host.WithLimit(6)},
|
||||
wantLen: 6,
|
||||
},
|
||||
{
|
||||
name: "With repo larger than list limit",
|
||||
repoOpts: []Option{WithLimit(6)},
|
||||
listOpts: []Option{WithLimit(2)},
|
||||
repoOpts: []host.Option{host.WithLimit(6)},
|
||||
listOpts: []host.Option{host.WithLimit(2)},
|
||||
wantLen: 2,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/host"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
)
|
||||
|
||||
@@ -21,7 +22,7 @@ func TestRepository_New(t *testing.T) {
|
||||
r db.Reader
|
||||
w db.Writer
|
||||
kms *kms.Kms
|
||||
opts []Option
|
||||
opts []host.Option
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
@@ -50,7 +51,7 @@ func TestRepository_New(t *testing.T) {
|
||||
r: rw,
|
||||
w: rw,
|
||||
kms: kmsCache,
|
||||
opts: []Option{WithLimit(5)},
|
||||
opts: []host.Option{host.WithLimit(5)},
|
||||
},
|
||||
want: &Repository{
|
||||
reader: rw,
|
||||
|
||||
@@ -183,9 +183,12 @@ type HostSet struct {
|
||||
// version allows optimistic locking of the resource
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Version uint32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"`
|
||||
// attributes is a jsonb formatted field.
|
||||
// attributes is a byte field containing marshaled JSON data
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Attributes []byte `protobuf:"bytes,8,opt,name=attributes,proto3" json:"attributes,omitempty" gorm:"default:null"`
|
||||
// preferred_endpoints stores string preference values
|
||||
// @inject_tag: `gorm:"-"`
|
||||
PreferredEndpoints []string `protobuf:"bytes,9,rep,name=preferred_endpoints,json=preferredEndpoints,proto3" json:"preferred_endpoints,omitempty" gorm:"-"`
|
||||
}
|
||||
|
||||
func (x *HostSet) Reset() {
|
||||
@@ -276,6 +279,13 @@ func (x *HostSet) GetAttributes() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *HostSet) GetPreferredEndpoints() []string {
|
||||
if x != nil {
|
||||
return x.PreferredEndpoints
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type HostCatalogSecret struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -419,7 +429,7 @@ var file_controller_storage_host_plugin_store_v1_host_proto_rawDesc = []byte{
|
||||
0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d,
|
||||
0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74,
|
||||
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x61,
|
||||
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x22, 0x81, 0x03, 0x0a, 0x07, 0x48, 0x6f,
|
||||
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x22, 0xb2, 0x03, 0x0a, 0x07, 0x48, 0x6f,
|
||||
0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
|
||||
0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d,
|
||||
@@ -443,30 +453,33 @@ var file_controller_storage_host_plugin_store_v1_host_proto_rawDesc = []byte{
|
||||
0x67, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a,
|
||||
0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x22, 0x98, 0x02,
|
||||
0x0a, 0x11, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x63,
|
||||
0x72, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67,
|
||||
0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
|
||||
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d,
|
||||
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12,
|
||||
0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
|
||||
0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||
0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06,
|
||||
0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x65,
|
||||
0x63, 0x72, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65,
|
||||
0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65,
|
||||
0x74, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x42, 0x40, 0x5a, 0x3e, 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, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
||||
0x61, 0x6c, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x73,
|
||||
0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
0x0c, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a,
|
||||
0x13, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f,
|
||||
0x69, 0x6e, 0x74, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x70, 0x72, 0x65, 0x66,
|
||||
0x65, 0x72, 0x72, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0x98,
|
||||
0x02, 0x0a, 0x11, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65,
|
||||
0x63, 0x72, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f,
|
||||
0x67, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69,
|
||||
0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
|
||||
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
|
||||
0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65,
|
||||
0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
|
||||
0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73,
|
||||
0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
|
||||
0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a,
|
||||
0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73,
|
||||
0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72,
|
||||
0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72,
|
||||
0x65, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x42, 0x40, 0x5a, 0x3e, 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, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x6e, 0x61, 0x6c, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f,
|
||||
0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/plugin/host"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -39,29 +40,29 @@ func TestCatalog(t *testing.T, conn *gorm.DB, scopeId, pluginId string, opt ...O
|
||||
// TestSet creates a plugin host sets in the provided DB
|
||||
// with the provided catalog id. The catalog must have been created
|
||||
// previously. The test will fail if any errors are encountered.
|
||||
func TestSet(t *testing.T, conn *gorm.DB, catalogId string, opt ...Option) *HostSet {
|
||||
func TestSet(t *testing.T, conn *gorm.DB, kmsCache *kms.Kms, hc *HostCatalog, opt ...Option) *HostSet {
|
||||
t.Helper()
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
w := db.New(conn)
|
||||
rw := db.New(conn)
|
||||
|
||||
assert := assert.New(t)
|
||||
set, err := NewHostSet(ctx, catalogId, opt...)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(set)
|
||||
repo, err := NewRepository(rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
|
||||
cg := allocHostCatalog()
|
||||
cg.PublicId = catalogId
|
||||
require.NoError(t, w.LookupByPublicId(ctx, cg))
|
||||
set, err := NewHostSet(ctx, hc.PublicId, opt...)
|
||||
require.NoError(err)
|
||||
require.NotNil(set)
|
||||
|
||||
plg := host.NewPlugin("", "")
|
||||
plg.PublicId = cg.GetPluginId()
|
||||
require.NoError(t, w.LookupByPublicId(ctx, plg))
|
||||
plg.PublicId = hc.GetPluginId()
|
||||
require.NoError(rw.LookupByPublicId(ctx, plg))
|
||||
|
||||
id, err := newHostSetId(ctx, plg.GetIdPrefix())
|
||||
assert.NoError(err)
|
||||
assert.NotEmpty(id)
|
||||
set.PublicId = id
|
||||
require.NoError(err)
|
||||
require.NotEmpty(id)
|
||||
|
||||
set, err = repo.CreateSet(ctx, hc.ScopeId, set, opt...)
|
||||
require.NoError(err)
|
||||
|
||||
require.NoError(t, w.Create(ctx, set))
|
||||
return set
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/plugin/host"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -35,6 +36,7 @@ func Test_TestSet(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
require.NotNil(prj)
|
||||
assert.NotEmpty(prj.GetPublicId())
|
||||
@@ -44,7 +46,7 @@ func Test_TestSet(t *testing.T) {
|
||||
assert.NotEmpty(plg.GetPublicId())
|
||||
|
||||
c := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId())
|
||||
set := TestSet(t, conn, c.GetPublicId(), WithName("foo"), WithDescription("bar"))
|
||||
set := TestSet(t, conn, kmsCache, c, WithName("foo"), WithDescription("bar"))
|
||||
assert.NotEmpty(set.GetPublicId())
|
||||
assert.True(strings.HasPrefix(set.GetPublicId(), fmt.Sprintf("%s_%s", HostSetPrefix, "prefix")))
|
||||
assert.Equal("foo", set.GetName())
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/host/store"
|
||||
"github.com/hashicorp/boundary/internal/libs/endpoint"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// defaultPreferredEndpointTableName defines the default table name for a
|
||||
// PreferredEndpoint
|
||||
const defaultPreferredEndpointTableName = "host_set_preferred_endpoint"
|
||||
|
||||
// PreferredEndpoint defines an endpoint condition for a host set.
|
||||
// PreferredEndpoints are value objects of an HostSet, therefore there's no need
|
||||
// for oplog metadata, since only the HostSet will have metadata because it's
|
||||
// the root aggregate.
|
||||
type PreferredEndpoint struct {
|
||||
*store.PreferredEndpoint
|
||||
tableName string
|
||||
}
|
||||
|
||||
// NewPreferredEndpoint creates a new in memory preferred endpoint assigned to a
|
||||
// HostSet. It supports no options.
|
||||
func NewPreferredEndpoint(ctx context.Context, hostSetId string, priority uint32, condition string) (*PreferredEndpoint, error) {
|
||||
const op = "host.NewPreferredEndpoint"
|
||||
|
||||
pe := &PreferredEndpoint{
|
||||
PreferredEndpoint: &store.PreferredEndpoint{
|
||||
HostSetId: hostSetId,
|
||||
Priority: priority,
|
||||
Condition: condition,
|
||||
},
|
||||
}
|
||||
if err := pe.validate(ctx, op); err != nil {
|
||||
return nil, err // intentionally not wrapped
|
||||
}
|
||||
return pe, nil
|
||||
}
|
||||
|
||||
// validate the PreferredEndpoint. On success, it will return nil.
|
||||
func (pe *PreferredEndpoint) validate(ctx context.Context, caller errors.Op) error {
|
||||
if pe.HostSetId == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, caller, "missing host set id")
|
||||
}
|
||||
if pe.Priority < 1 {
|
||||
return errors.New(ctx, errors.InvalidParameter, caller, "invalid priority value")
|
||||
}
|
||||
if pe.Condition == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, caller, "missing condition")
|
||||
}
|
||||
if _, err := endpoint.NewPreferencer(ctx, endpoint.WithPreferenceOrder([]string{pe.Condition})); err != nil {
|
||||
return errors.Wrap(ctx, err, caller)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllocPreferredEndpoint makes an empty one in-memory
|
||||
func AllocPreferredEndpoint() PreferredEndpoint {
|
||||
return PreferredEndpoint{
|
||||
PreferredEndpoint: &store.PreferredEndpoint{},
|
||||
}
|
||||
}
|
||||
|
||||
// Clone a PreferredEndpoint
|
||||
func (c *PreferredEndpoint) Clone() *PreferredEndpoint {
|
||||
cp := proto.Clone(c.PreferredEndpoint)
|
||||
return &PreferredEndpoint{
|
||||
PreferredEndpoint: cp.(*store.PreferredEndpoint),
|
||||
}
|
||||
}
|
||||
|
||||
// TableName returns the table name.
|
||||
func (c *PreferredEndpoint) TableName() string {
|
||||
if c.tableName != "" {
|
||||
return c.tableName
|
||||
}
|
||||
return defaultPreferredEndpointTableName
|
||||
}
|
||||
|
||||
// SetTableName sets the table name.
|
||||
func (c *PreferredEndpoint) SetTableName(n string) {
|
||||
c.tableName = n
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package host_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/host"
|
||||
"github.com/hashicorp/boundary/internal/host/plugin"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
hostplg "github.com/hashicorp/boundary/internal/plugin/host"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPreferredEndpoint_Create(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.TODO()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
rw := db.New(conn)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
plg := hostplg.TestPlugin(t, conn, "create", "create")
|
||||
catalog := plugin.TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
|
||||
set := plugin.TestSet(t, conn, kmsCache, catalog)
|
||||
|
||||
type args struct {
|
||||
hostSetId string
|
||||
priority uint32
|
||||
condition string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *host.PreferredEndpoint
|
||||
wantErr bool
|
||||
wantIsErr errors.Code
|
||||
create bool
|
||||
wantCreateErr bool
|
||||
wantCreateIsErr errors.Code
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
args: args{
|
||||
hostSetId: set.PublicId,
|
||||
priority: 1,
|
||||
condition: "cidr:1.2.3.4",
|
||||
},
|
||||
create: true,
|
||||
want: func() *host.PreferredEndpoint {
|
||||
want := host.AllocPreferredEndpoint()
|
||||
want.HostSetId = set.PublicId
|
||||
want.Condition = "cidr:1.2.3.4"
|
||||
want.Priority = 1
|
||||
return &want
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "dup", // must follow "valid" test. Url must be be unique for an OidcMethodId
|
||||
args: args{
|
||||
hostSetId: set.PublicId,
|
||||
priority: 1,
|
||||
condition: "cidr:1.2.3.4",
|
||||
},
|
||||
create: true,
|
||||
want: func() *host.PreferredEndpoint {
|
||||
want := host.AllocPreferredEndpoint()
|
||||
want.HostSetId = set.PublicId
|
||||
want.Condition = "cidr:1.2.3.4"
|
||||
want.Priority = 1
|
||||
return &want
|
||||
}(),
|
||||
wantCreateErr: true,
|
||||
wantCreateIsErr: errors.NotUnique,
|
||||
},
|
||||
{
|
||||
name: "bad-condition",
|
||||
args: args{
|
||||
hostSetId: set.PublicId,
|
||||
priority: 1,
|
||||
condition: "foobar:1.2.3.4",
|
||||
},
|
||||
wantErr: true,
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "empty-host-set",
|
||||
args: args{
|
||||
hostSetId: "",
|
||||
},
|
||||
wantErr: true,
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "invalid-priority",
|
||||
args: args{
|
||||
hostSetId: set.PublicId,
|
||||
priority: 0,
|
||||
},
|
||||
wantErr: true,
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "empty-condition",
|
||||
args: args{
|
||||
hostSetId: set.PublicId,
|
||||
priority: 1,
|
||||
},
|
||||
wantErr: true,
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
got, err := host.NewPreferredEndpoint(ctx, tt.args.hostSetId, tt.args.priority, tt.args.condition)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tt.want, got)
|
||||
if tt.create {
|
||||
ctx := context.Background()
|
||||
err = rw.Create(ctx, got)
|
||||
if tt.wantCreateErr {
|
||||
assert.Error(err)
|
||||
assert.True(errors.Match(errors.T(tt.wantCreateIsErr), err))
|
||||
return
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
}
|
||||
found := host.AllocPreferredEndpoint()
|
||||
require.NoError(rw.LookupWhere(ctx, &found, "host_set_id = ? and priority = ?", tt.args.hostSetId, tt.args.priority))
|
||||
assert.Equal(got, &found)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreferredEndpoint_Delete(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.TODO()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
rw := db.New(conn)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
plg := hostplg.TestPlugin(t, conn, "create", "create")
|
||||
catalog := plugin.TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
|
||||
set := plugin.TestSet(t, conn, kmsCache, catalog)
|
||||
|
||||
peFunc := func(priority uint32, condition string) *host.PreferredEndpoint {
|
||||
ep, err := host.NewPreferredEndpoint(ctx, set.PublicId, priority, condition)
|
||||
require.NoError(t, err)
|
||||
return ep
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
pes []*host.PreferredEndpoint
|
||||
name string
|
||||
overrides func(*host.PreferredEndpoint)
|
||||
wantErr bool
|
||||
wantRowsDeleted int
|
||||
}{
|
||||
{
|
||||
name: "valid-single",
|
||||
pes: []*host.PreferredEndpoint{peFunc(1, "cidr:1.2.3.4")},
|
||||
wantErr: false,
|
||||
wantRowsDeleted: 1,
|
||||
},
|
||||
{
|
||||
name: "bad-host-set-id",
|
||||
pes: []*host.PreferredEndpoint{peFunc(1, "cidr:1.2.3.4")},
|
||||
overrides: func(pe *host.PreferredEndpoint) { pe.HostSetId = "bad-id" },
|
||||
wantErr: false,
|
||||
wantRowsDeleted: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
var eput *host.PreferredEndpoint
|
||||
for _, pe := range tt.pes {
|
||||
cloned := pe.Clone()
|
||||
if eput == nil {
|
||||
eput = cloned
|
||||
}
|
||||
require.NoError(rw.Create(ctx, &cloned))
|
||||
}
|
||||
if tt.overrides != nil {
|
||||
tt.overrides(eput)
|
||||
}
|
||||
|
||||
deletedRows, err := rw.Delete(ctx, &eput, db.WithWhere("host_set_id = ?", eput.HostSetId))
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(err)
|
||||
if tt.wantRowsDeleted == 0 {
|
||||
assert.Equal(tt.wantRowsDeleted, deletedRows)
|
||||
return
|
||||
}
|
||||
require.Equal(tt.wantRowsDeleted, deletedRows)
|
||||
found := host.AllocPreferredEndpoint()
|
||||
err = rw.LookupWhere(ctx, &found, "host_set_id = ?", set.PublicId)
|
||||
assert.True(errors.IsNotFoundError(err))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -199,6 +199,75 @@ func (x *Set) GetCatalogId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type PreferredEndpoint struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The ID of the host set this belongs to.
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
HostSetId string `protobuf:"bytes,1,opt,name=host_set_id,json=hostSetId,proto3" json:"host_set_id,omitempty" gorm:"primary_key"`
|
||||
// The priority order, zero-indexed
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
Priority uint32 `protobuf:"varint,2,opt,name=priority,proto3" json:"priority,omitempty" gorm:"primary_key"`
|
||||
// The string text of the preference condition
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
Condition string `protobuf:"bytes,3,opt,name=condition,proto3" json:"condition,omitempty" gorm:"not_null"`
|
||||
}
|
||||
|
||||
func (x *PreferredEndpoint) Reset() {
|
||||
*x = PreferredEndpoint{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_storage_host_store_v1_host_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *PreferredEndpoint) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PreferredEndpoint) ProtoMessage() {}
|
||||
|
||||
func (x *PreferredEndpoint) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_storage_host_store_v1_host_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PreferredEndpoint.ProtoReflect.Descriptor instead.
|
||||
func (*PreferredEndpoint) Descriptor() ([]byte, []int) {
|
||||
return file_controller_storage_host_store_v1_host_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *PreferredEndpoint) GetHostSetId() string {
|
||||
if x != nil {
|
||||
return x.HostSetId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PreferredEndpoint) GetPriority() uint32 {
|
||||
if x != nil {
|
||||
return x.Priority
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *PreferredEndpoint) GetCondition() string {
|
||||
if x != nil {
|
||||
return x.Condition
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_controller_storage_host_store_v1_host_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_storage_host_store_v1_host_proto_rawDesc = []byte{
|
||||
@@ -219,11 +288,18 @@ var file_controller_storage_host_store_v1_host_proto_rawDesc = []byte{
|
||||
0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61,
|
||||
0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
|
||||
0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x42, 0x39, 0x5a, 0x37, 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, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x6e, 0x61, 0x6c, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73,
|
||||
0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x22, 0x6d, 0x0a, 0x11, 0x50, 0x72, 0x65,
|
||||
0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1e,
|
||||
0x0a, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1a,
|
||||
0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d,
|
||||
0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f,
|
||||
0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63,
|
||||
0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x39, 0x5a, 0x37, 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, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
||||
0x61, 0x6c, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74,
|
||||
0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -238,11 +314,12 @@ func file_controller_storage_host_store_v1_host_proto_rawDescGZIP() []byte {
|
||||
return file_controller_storage_host_store_v1_host_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_controller_storage_host_store_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_controller_storage_host_store_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_controller_storage_host_store_v1_host_proto_goTypes = []interface{}{
|
||||
(*Catalog)(nil), // 0: controller.storage.host.store.v1.Catalog
|
||||
(*Host)(nil), // 1: controller.storage.host.store.v1.Host
|
||||
(*Set)(nil), // 2: controller.storage.host.store.v1.Set
|
||||
(*Catalog)(nil), // 0: controller.storage.host.store.v1.Catalog
|
||||
(*Host)(nil), // 1: controller.storage.host.store.v1.Host
|
||||
(*Set)(nil), // 2: controller.storage.host.store.v1.Set
|
||||
(*PreferredEndpoint)(nil), // 3: controller.storage.host.store.v1.PreferredEndpoint
|
||||
}
|
||||
var file_controller_storage_host_store_v1_host_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
@@ -294,6 +371,18 @@ func file_controller_storage_host_store_v1_host_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_storage_host_store_v1_host_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*PreferredEndpoint); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
@@ -301,7 +390,7 @@ func file_controller_storage_host_store_v1_host_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_controller_storage_host_store_v1_host_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 3,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@ func NewPreferencer(ctx context.Context, opt ...Option) (*preferencer, error) {
|
||||
const op = "endpoint.NewPreferencer"
|
||||
opts, err := getOpts(opt...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter))
|
||||
}
|
||||
pref := &preferencer{
|
||||
matchers: opts.withMatchers,
|
||||
|
||||
@@ -43,6 +43,10 @@ message HostSet {
|
||||
// Output only. A list of Hosts in this Host Set.
|
||||
repeated string host_ids = 100 [json_name="host_ids"];
|
||||
|
||||
// An ordered list of endpoint preferences used to choose from among
|
||||
// multiple possible endpoints for a host.
|
||||
repeated string preferred_endpoints = 101 [json_name="preferred_endpoints", (custom_options.v1.generate_sdk_option) = true];
|
||||
|
||||
// The attributes that are applicable for the specific Host Set type.
|
||||
google.protobuf.Struct attributes = 110;
|
||||
|
||||
|
||||
@@ -78,9 +78,13 @@ message HostSet {
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
uint32 version = 7;
|
||||
|
||||
// attributes is a jsonb formatted field.
|
||||
// attributes is a byte field containing marshaled JSON data
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
bytes attributes = 8;
|
||||
|
||||
// preferred_endpoints stores string preference values
|
||||
// @inject_tag: `gorm:"-"`
|
||||
repeated string preferred_endpoints = 9;
|
||||
}
|
||||
|
||||
message HostCatalogSecret {
|
||||
@@ -109,4 +113,4 @@ message HostCatalogSecret {
|
||||
// It must be set.
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
string key_id = 6;
|
||||
}
|
||||
}
|
||||
@@ -33,3 +33,17 @@ message Set {
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
string catalog_id = 2;
|
||||
}
|
||||
|
||||
message PreferredEndpoint {
|
||||
// The ID of the host set this belongs to.
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
string host_set_id = 1;
|
||||
|
||||
// The priority order, zero-indexed
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
uint32 priority = 2;
|
||||
|
||||
// The string text of the preference condition
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
string condition = 3;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/cmd/config"
|
||||
"github.com/hashicorp/boundary/internal/credential/vault"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/host/plugin"
|
||||
hostplugin "github.com/hashicorp/boundary/internal/host/plugin"
|
||||
"github.com/hashicorp/boundary/internal/host/static"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
@@ -135,8 +135,8 @@ func New(ctx context.Context, conf *Config) (*Controller, error) {
|
||||
c.StaticHostRepoFn = func() (*static.Repository, error) {
|
||||
return static.NewRepository(dbase, dbase, c.kms)
|
||||
}
|
||||
c.PluginHostRepoFn = func() (*plugin.Repository, error) {
|
||||
return plugin.NewRepository(dbase, dbase, c.kms)
|
||||
c.PluginHostRepoFn = func() (*hostplugin.Repository, error) {
|
||||
return hostplugin.NewRepository(dbase, dbase, c.kms)
|
||||
}
|
||||
c.HostPluginRepoFn = func() (*host.Repository, error) {
|
||||
return host.NewRepository(dbase, dbase, c.kms)
|
||||
|
||||
@@ -67,7 +67,7 @@ type Service struct {
|
||||
|
||||
staticRepoFn common.StaticRepoFactory
|
||||
pluginHostRepoFn common.PluginHostRepoFactory
|
||||
pluginRepoFn common.HostPluginRepoFactory
|
||||
pluginRepoFn common.HostPluginRepoFactory
|
||||
iamRepoFn common.IamRepoFactory
|
||||
}
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ func TestList(t *testing.T) {
|
||||
var testPluginCatalogs []*pb.HostCatalog
|
||||
name := "test"
|
||||
plg := host.TestPlugin(t, conn, name, name)
|
||||
for i := 0 ; i < 3; i++ {
|
||||
for i := 0; i < 3; i++ {
|
||||
hc := plugin.TestCatalog(t, conn, pWithCatalogs.GetPublicId(), plg.GetPublicId())
|
||||
cat := &pb.HostCatalog{
|
||||
Id: hc.GetPublicId(),
|
||||
@@ -306,7 +306,7 @@ func TestList(t *testing.T) {
|
||||
|
||||
name = "different"
|
||||
plg = host.TestPlugin(t, conn, name, name)
|
||||
for i := 0 ; i < 3; i++ {
|
||||
for i := 0; i < 3; i++ {
|
||||
hc := plugin.TestCatalog(t, conn, pWithOtherCatalogs.GetPublicId(), plg.GetPublicId())
|
||||
wantOtherCatalogs = append(wantOtherCatalogs, &pb.HostCatalog{
|
||||
Id: hc.GetPublicId(),
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/host/plugin"
|
||||
"github.com/hashicorp/boundary/internal/host/static"
|
||||
"github.com/hashicorp/boundary/internal/host/static/store"
|
||||
"github.com/hashicorp/boundary/internal/libs/endpoint"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/auth"
|
||||
@@ -80,6 +81,10 @@ func NewService(staticRepoFn common.StaticRepoFactory, pluginRepoFn common.Plugi
|
||||
}
|
||||
|
||||
func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest) (*pbs.ListHostSetsResponse, error) {
|
||||
return s.ListHostSetsWithOptions(ctx, req)
|
||||
}
|
||||
|
||||
func (s Service) ListHostSetsWithOptions(ctx context.Context, req *pbs.ListHostSetsRequest, opt ...host.Option) (*pbs.ListHostSetsResponse, error) {
|
||||
if err := validateListRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -87,7 +92,7 @@ func (s Service) ListHostSets(ctx context.Context, req *pbs.ListHostSetsRequest)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
hl, err := s.listFromRepo(ctx, req.GetHostCatalogId())
|
||||
hl, err := s.listFromRepo(ctx, req.GetHostCatalogId(), opt...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -177,7 +182,7 @@ func (s Service) GetHostSet(ctx context.Context, req *pbs.GetHostSetRequest) (*p
|
||||
func (s Service) CreateHostSet(ctx context.Context, req *pbs.CreateHostSetRequest) (*pbs.CreateHostSetResponse, error) {
|
||||
const op = "host_sets.(Service).CreateHostSet"
|
||||
|
||||
if err := validateCreateRequest(req); err != nil {
|
||||
if err := validateCreateRequest(ctx, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, authResults := s.parentAndAuthResult(ctx, req.GetItem().GetHostCatalogId(), action.Create)
|
||||
@@ -218,7 +223,7 @@ func (s Service) CreateHostSet(ctx context.Context, req *pbs.CreateHostSetReques
|
||||
func (s Service) UpdateHostSet(ctx context.Context, req *pbs.UpdateHostSetRequest) (*pbs.UpdateHostSetResponse, error) {
|
||||
const op = "host_sets.(Service).UpdateHostSet"
|
||||
|
||||
if err := validateUpdateRequest(req); err != nil {
|
||||
if err := validateUpdateRequest(ctx, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cat, authResults := s.parentAndAuthResult(ctx, req.GetId(), action.Update)
|
||||
@@ -444,6 +449,9 @@ func (s Service) createInRepo(ctx context.Context, scopeId, catalogId string, it
|
||||
hSet = out
|
||||
case plugin.Subtype:
|
||||
h, err := toStoragePluginSet(ctx, catalogId, item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repo, err := s.pluginRepoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -505,7 +513,7 @@ func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool,
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]host.Set, error) {
|
||||
func (s Service) listFromRepo(ctx context.Context, catalogId string, opt ...host.Option) ([]host.Set, error) {
|
||||
const op = "host_sets.(Service).listFromRepo"
|
||||
var sets []host.Set
|
||||
switch host.SubtypeFromId(catalogId) {
|
||||
@@ -526,7 +534,7 @@ func (s Service) listFromRepo(ctx context.Context, catalogId string) ([]host.Set
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sl, err := repo.ListSets(ctx, catalogId)
|
||||
sl, err := repo.ListSets(ctx, catalogId, opt...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
@@ -742,9 +750,12 @@ func toProto(ctx context.Context, in host.Set, hosts []host.Host, opt ...handler
|
||||
}
|
||||
}
|
||||
|
||||
if outputFields.Has(globals.AttributesField) {
|
||||
switch h := in.(type) {
|
||||
case *plugin.HostSet:
|
||||
switch h := in.(type) {
|
||||
case *plugin.HostSet:
|
||||
if outputFields.Has(globals.PreferredEndpointsField) {
|
||||
out.PreferredEndpoints = h.GetPreferredEndpoints()
|
||||
}
|
||||
if outputFields.Has(globals.AttributesField) {
|
||||
attrs := map[string]interface{}{}
|
||||
err := json.Unmarshal(h.Attributes, &attrs)
|
||||
if err != nil {
|
||||
@@ -790,6 +801,9 @@ func toStoragePluginSet(ctx context.Context, catalogId string, item *pb.HostSet)
|
||||
if item.GetAttributes() != nil {
|
||||
opts = append(opts, plugin.WithAttributes(item.GetAttributes().AsMap()))
|
||||
}
|
||||
if item.GetPreferredEndpoints() != nil {
|
||||
opts = append(opts, plugin.WithPreferredEndpoints(item.GetPreferredEndpoints()))
|
||||
}
|
||||
hs, err := plugin.NewHostSet(ctx, catalogId, opts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("Unable to build host set for creation"))
|
||||
@@ -814,7 +828,7 @@ func validateGetRequest(req *pbs.GetHostSetRequest) error {
|
||||
return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, static.HostSetPrefix, pluginPrefix)
|
||||
}
|
||||
|
||||
func validateCreateRequest(req *pbs.CreateHostSetRequest) error {
|
||||
func validateCreateRequest(ctx context.Context, req *pbs.CreateHostSetRequest) error {
|
||||
return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string {
|
||||
badFields := map[string]string{}
|
||||
pluginPrefix := plugin.HostCatalogPrefix
|
||||
@@ -826,6 +840,12 @@ func validateCreateRequest(req *pbs.CreateHostSetRequest) error {
|
||||
if !handlers.ValidId(handlers.Id(req.GetItem().GetHostCatalogId()), static.HostCatalogPrefix, pluginPrefix) {
|
||||
badFields["host_catalog_id"] = "The field is incorrectly formatted."
|
||||
}
|
||||
if len(req.GetItem().GetPreferredEndpoints()) > 0 {
|
||||
_, err := endpoint.NewPreferencer(ctx, endpoint.WithPreferenceOrder(req.GetItem().GetPreferredEndpoints()))
|
||||
if err != nil {
|
||||
badFields[globals.PreferredEndpointsField] = fmt.Errorf("Error parsing preferred endpoints: %w.", err).Error()
|
||||
}
|
||||
}
|
||||
switch host.SubtypeFromId(req.GetItem().GetHostCatalogId()) {
|
||||
case static.Subtype:
|
||||
if req.GetItem().GetType() != "" && req.GetItem().GetType() != static.Subtype.String() {
|
||||
@@ -850,17 +870,23 @@ func validateCreateRequest(req *pbs.CreateHostSetRequest) error {
|
||||
})
|
||||
}
|
||||
|
||||
func validateUpdateRequest(req *pbs.UpdateHostSetRequest) error {
|
||||
func validateUpdateRequest(ctx context.Context, req *pbs.UpdateHostSetRequest) error {
|
||||
return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string {
|
||||
badFields := map[string]string{}
|
||||
if len(req.GetItem().GetPreferredEndpoints()) > 0 {
|
||||
_, err := endpoint.NewPreferencer(ctx, endpoint.WithPreferenceOrder(req.GetItem().GetPreferredEndpoints()))
|
||||
if err != nil {
|
||||
badFields[globals.PreferredEndpointsField] = fmt.Errorf("Error parsing preferred endpoints: %w.", err).Error()
|
||||
}
|
||||
}
|
||||
switch host.SubtypeFromId(req.GetId()) {
|
||||
case static.Subtype:
|
||||
if req.GetItem().GetType() != "" && req.GetItem().GetType() != static.Subtype.String() {
|
||||
badFields["type"] = "Cannot modify the resource type."
|
||||
badFields[globals.TypeField] = "Cannot modify the resource type."
|
||||
}
|
||||
}
|
||||
return badFields
|
||||
}, static.HostSetPrefix)
|
||||
}, static.HostSetPrefix, plugin.HostSetPrefix)
|
||||
}
|
||||
|
||||
func validateDeleteRequest(req *pbs.DeleteHostSetRequest) error {
|
||||
|
||||
@@ -11,11 +11,12 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/host"
|
||||
"github.com/hashicorp/boundary/internal/host/plugin"
|
||||
"github.com/hashicorp/boundary/internal/host/static"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/plugin/host"
|
||||
hostplugin "github.com/hashicorp/boundary/internal/plugin/host"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/auth"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers/host_sets"
|
||||
@@ -150,20 +151,22 @@ func TestGet_Plugin(t *testing.T) {
|
||||
}
|
||||
|
||||
name := "test"
|
||||
plg := host.TestPlugin(t, conn, name, name)
|
||||
prefEndpoints := []string{"cidr:1.2.3.4", "cidr:2.3.4.5/24"}
|
||||
plg := hostplugin.TestPlugin(t, conn, name, name)
|
||||
hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId())
|
||||
hs := plugin.TestSet(t, conn, hc.GetPublicId())
|
||||
hs := plugin.TestSet(t, conn, kms, hc, plugin.WithPreferredEndpoints(prefEndpoints))
|
||||
|
||||
toMerge := &pbs.GetHostSetRequest{}
|
||||
|
||||
pHost := &pb.HostSet{
|
||||
HostCatalogId: hc.GetPublicId(),
|
||||
Id: hs.GetPublicId(),
|
||||
CreatedTime: hs.CreateTime.GetTimestamp(),
|
||||
UpdatedTime: hs.UpdateTime.GetTimestamp(),
|
||||
Type: name,
|
||||
Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()},
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
HostCatalogId: hc.GetPublicId(),
|
||||
Id: hs.GetPublicId(),
|
||||
CreatedTime: hs.CreateTime.GetTimestamp(),
|
||||
UpdatedTime: hs.UpdateTime.GetTimestamp(),
|
||||
Type: name,
|
||||
Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()},
|
||||
PreferredEndpoints: prefEndpoints,
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
@@ -179,7 +182,7 @@ func TestGet_Plugin(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Get a non existing Host Set",
|
||||
req: &pbs.GetHostSetRequest{Id: static.HostSetPrefix + "_DoesntExis"},
|
||||
req: &pbs.GetHostSetRequest{Id: plugin.HostSetPrefix + "_DoesntExis"},
|
||||
res: nil,
|
||||
err: handlers.ApiErrorWithCode(codes.NotFound),
|
||||
},
|
||||
@@ -191,7 +194,7 @@ func TestGet_Plugin(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "space in id",
|
||||
req: &pbs.GetHostSetRequest{Id: static.HostPrefix + "_1 23456789"},
|
||||
req: &pbs.GetHostSetRequest{Id: plugin.HostSetPrefix + "_1 23456789"},
|
||||
res: nil,
|
||||
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
||||
},
|
||||
@@ -339,22 +342,24 @@ func TestList_Plugin(t *testing.T) {
|
||||
return plugin.NewRepository(rw, rw, kms)
|
||||
}
|
||||
name := "test"
|
||||
plg := host.TestPlugin(t, conn, name, name)
|
||||
plg := hostplugin.TestPlugin(t, conn, name, name)
|
||||
hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId())
|
||||
hcNoHosts := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId())
|
||||
preferredEndpoints := []string{"cidr:1.2.3.4", "dns:*.foobar.com"}
|
||||
|
||||
var wantHs []*pb.HostSet
|
||||
for i := 0; i < 10; i++ {
|
||||
h := plugin.TestSet(t, conn, hc.GetPublicId())
|
||||
h := plugin.TestSet(t, conn, kms, hc, plugin.WithPreferredEndpoints(preferredEndpoints))
|
||||
wantHs = append(wantHs, &pb.HostSet{
|
||||
Id: h.GetPublicId(),
|
||||
HostCatalogId: h.GetCatalogId(),
|
||||
Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()},
|
||||
CreatedTime: h.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: h.GetUpdateTime().GetTimestamp(),
|
||||
Version: h.GetVersion(),
|
||||
Type: name,
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
Id: h.GetPublicId(),
|
||||
HostCatalogId: h.GetCatalogId(),
|
||||
Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()},
|
||||
CreatedTime: h.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: h.GetUpdateTime().GetTimestamp(),
|
||||
Version: h.GetVersion(),
|
||||
Type: name,
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
PreferredEndpoints: preferredEndpoints,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -397,7 +402,7 @@ func TestList_Plugin(t *testing.T) {
|
||||
require.NoError(err, "Couldn't create new host set service.")
|
||||
|
||||
// Test with non-anon user
|
||||
got, gErr := s.ListHostSets(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req)
|
||||
got, gErr := s.ListHostSetsWithOptions(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req, host.WithOrderByCreateTime(false))
|
||||
if tc.err != nil {
|
||||
require.Error(gErr)
|
||||
assert.True(errors.Is(gErr, tc.err), "ListHostSets(%q) got error %v, wanted %v", tc.req, gErr, tc.err)
|
||||
@@ -699,7 +704,7 @@ func TestCreate_Plugin(t *testing.T) {
|
||||
return plugin.NewRepository(rw, rw, kms)
|
||||
}
|
||||
name := "test"
|
||||
plg := host.TestPlugin(t, conn, name, name)
|
||||
plg := hostplugin.TestPlugin(t, conn, name, name)
|
||||
hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId())
|
||||
|
||||
testAttrs, err := structpb.NewStruct(map[string]interface{}{
|
||||
@@ -724,6 +729,8 @@ func TestCreate_Plugin(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
prefEndpoints := []string{"cidr:1.2.3.4", "cidr:2.3.4.5/24"}
|
||||
|
||||
defaultHcCreated := hc.GetCreateTime().GetTimestamp().AsTime()
|
||||
|
||||
cases := []struct {
|
||||
@@ -774,6 +781,39 @@ func TestCreate_Plugin(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With Preferred Endpoints",
|
||||
req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{
|
||||
HostCatalogId: hc.GetPublicId(),
|
||||
Name: &wrappers.StringValue{Value: "name"},
|
||||
Description: &wrappers.StringValue{Value: "desc"},
|
||||
Type: name,
|
||||
PreferredEndpoints: prefEndpoints,
|
||||
}},
|
||||
res: &pbs.CreateHostSetResponse{
|
||||
Uri: fmt.Sprintf("host-sets/%s_%s_", plugin.HostSetPrefix, name),
|
||||
Item: &pb.HostSet{
|
||||
HostCatalogId: hc.GetPublicId(),
|
||||
Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()},
|
||||
Name: &wrappers.StringValue{Value: "name"},
|
||||
Description: &wrappers.StringValue{Value: "desc"},
|
||||
Type: name,
|
||||
PreferredEndpoints: prefEndpoints,
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Bad Preferred Endpoints",
|
||||
req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{
|
||||
HostCatalogId: hc.GetPublicId(),
|
||||
Name: &wrappers.StringValue{Value: "name"},
|
||||
Description: &wrappers.StringValue{Value: "desc"},
|
||||
Type: name,
|
||||
PreferredEndpoints: append(prefEndpoints, "foobar:1.2.3.4"),
|
||||
}},
|
||||
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
||||
},
|
||||
{
|
||||
name: "Create with mismatched type/name",
|
||||
req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{
|
||||
|
||||
@@ -1731,7 +1731,7 @@ func validateSetSetsRequest(req *pbs.SetTargetHostSetsRequest) error {
|
||||
badFields[globals.VersionField] = "Required field."
|
||||
}
|
||||
for _, id := range req.GetHostSetIds() {
|
||||
if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) &&
|
||||
if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) &&
|
||||
!strings.HasPrefix(id, fmt.Sprintf("%s_", plugin.HostSetPrefix)) {
|
||||
badFields[globals.HostSetIdsField] = fmt.Sprintf("Incorrectly formatted host set identifier %q.", id)
|
||||
break
|
||||
@@ -1755,7 +1755,7 @@ func validateRemoveSetsRequest(req *pbs.RemoveTargetHostSetsRequest) error {
|
||||
badFields[globals.HostSetIdsField] = "Must be non-empty."
|
||||
}
|
||||
for _, id := range req.GetHostSetIds() {
|
||||
if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) &&
|
||||
if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) &&
|
||||
!strings.HasPrefix(id, fmt.Sprintf("%s_", plugin.HostSetPrefix)) {
|
||||
badFields[globals.HostSetIdsField] = fmt.Sprintf("Incorrectly formatted host set identifier %q.", id)
|
||||
break
|
||||
@@ -1779,7 +1779,7 @@ func validateAddHostSourcesRequest(req *pbs.AddTargetHostSourcesRequest) error {
|
||||
badFields[globals.HostSourceIdsField] = "Must be non-empty."
|
||||
}
|
||||
for _, id := range req.GetHostSourceIds() {
|
||||
if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) &&
|
||||
if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) &&
|
||||
!strings.HasPrefix(id, fmt.Sprintf("%s_", plugin.HostSetPrefix)) {
|
||||
badFields[globals.HostSourceIdsField] = fmt.Sprintf("Incorrectly formatted host source identifier %q.", id)
|
||||
break
|
||||
@@ -1800,7 +1800,7 @@ func validateSetHostSourcesRequest(req *pbs.SetTargetHostSourcesRequest) error {
|
||||
badFields[globals.VersionField] = "Required field."
|
||||
}
|
||||
for _, id := range req.GetHostSourceIds() {
|
||||
if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) &&
|
||||
if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) &&
|
||||
!strings.HasPrefix(id, fmt.Sprintf("%s_", plugin.HostSetPrefix)) {
|
||||
badFields[globals.HostSourceIdsField] = fmt.Sprintf("Incorrectly formatted host source identifier %q.", id)
|
||||
break
|
||||
@@ -1824,7 +1824,7 @@ func validateRemoveHostSourcesRequest(req *pbs.RemoveTargetHostSourcesRequest) e
|
||||
badFields[globals.HostSourceIdsField] = "Must be non-empty."
|
||||
}
|
||||
for _, id := range req.GetHostSourceIds() {
|
||||
if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) &&
|
||||
if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) &&
|
||||
!strings.HasPrefix(id, fmt.Sprintf("%s_", plugin.HostSetPrefix)) {
|
||||
badFields[globals.HostSourceIdsField] = fmt.Sprintf("Incorrectly formatted host source identifier %q.", id)
|
||||
break
|
||||
|
||||
@@ -983,7 +983,7 @@ func TestAddTargetHostSets(t *testing.T) {
|
||||
|
||||
plg := host.TestPlugin(t, conn, "test", "test")
|
||||
pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId())
|
||||
pluginHs := plugin.TestSet(t, conn, pluginHc.GetPublicId())
|
||||
pluginHs := plugin.TestSet(t, conn, kms, pluginHc)
|
||||
|
||||
addCases := []struct {
|
||||
name string
|
||||
@@ -1115,7 +1115,7 @@ func TestSetTargetHostSets(t *testing.T) {
|
||||
|
||||
plg := host.TestPlugin(t, conn, "test", "test")
|
||||
pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId())
|
||||
pluginHs := plugin.TestSet(t, conn, pluginHc.GetPublicId())
|
||||
pluginHs := plugin.TestSet(t, conn, kms, pluginHc)
|
||||
|
||||
setCases := []struct {
|
||||
name string
|
||||
@@ -1241,7 +1241,7 @@ func TestRemoveTargetHostSets(t *testing.T) {
|
||||
|
||||
plg := host.TestPlugin(t, conn, "test", "test")
|
||||
pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId())
|
||||
pluginHs := plugin.TestSet(t, conn, pluginHc.GetPublicId())
|
||||
pluginHs := plugin.TestSet(t, conn, kms, pluginHc)
|
||||
|
||||
removeCases := []struct {
|
||||
name string
|
||||
@@ -1379,7 +1379,7 @@ func TestAddTargetHostSources(t *testing.T) {
|
||||
|
||||
plg := host.TestPlugin(t, conn, "test", "test")
|
||||
pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId())
|
||||
pluginHs := plugin.TestSet(t, conn, pluginHc.GetPublicId())
|
||||
pluginHs := plugin.TestSet(t, conn, kms, pluginHc)
|
||||
|
||||
addCases := []struct {
|
||||
name string
|
||||
@@ -1511,7 +1511,7 @@ func TestSetTargetHostSources(t *testing.T) {
|
||||
|
||||
plg := host.TestPlugin(t, conn, "test", "test")
|
||||
pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId())
|
||||
pluginHs := plugin.TestSet(t, conn, pluginHc.GetPublicId())
|
||||
pluginHs := plugin.TestSet(t, conn, kms, pluginHc)
|
||||
|
||||
setCases := []struct {
|
||||
name string
|
||||
@@ -1631,7 +1631,7 @@ func TestRemoveTargetHostSources(t *testing.T) {
|
||||
|
||||
plg := host.TestPlugin(t, conn, "test", "test")
|
||||
pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId())
|
||||
pluginHs := plugin.TestSet(t, conn, pluginHc.GetPublicId())
|
||||
pluginHs := plugin.TestSet(t, conn, kms, pluginHc)
|
||||
|
||||
removeCases := []struct {
|
||||
name string
|
||||
|
||||
@@ -52,6 +52,9 @@ type HostSet struct {
|
||||
Type string `protobuf:"bytes,90,opt,name=type,proto3" json:"type,omitempty"`
|
||||
// Output only. A list of Hosts in this Host Set.
|
||||
HostIds []string `protobuf:"bytes,100,rep,name=host_ids,proto3" json:"host_ids,omitempty"`
|
||||
// An ordered list of endpoint preferences used to choose from among
|
||||
// multiple possible endpoints for a host.
|
||||
PreferredEndpoints []string `protobuf:"bytes,101,rep,name=preferred_endpoints,proto3" json:"preferred_endpoints,omitempty"`
|
||||
// The attributes that are applicable for the specific Host Set type.
|
||||
Attributes *structpb.Struct `protobuf:"bytes,110,opt,name=attributes,proto3" json:"attributes,omitempty"`
|
||||
// Output only. The available actions on this resource for this user.
|
||||
@@ -160,6 +163,13 @@ func (x *HostSet) GetHostIds() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *HostSet) GetPreferredEndpoints() []string {
|
||||
if x != nil {
|
||||
return x.PreferredEndpoints
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *HostSet) GetAttributes() *structpb.Struct {
|
||||
if x != nil {
|
||||
return x.Attributes
|
||||
@@ -194,7 +204,7 @@ var file_controller_api_resources_hostsets_v1_host_set_proto_rawDesc = []byte{
|
||||
0x63, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe8, 0x04, 0x0a, 0x07, 0x48, 0x6f, 0x73, 0x74, 0x53,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa0, 0x05, 0x0a, 0x07, 0x48, 0x6f, 0x73, 0x74, 0x53,
|
||||
0x65, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
|
||||
0x69, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c,
|
||||
0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x68, 0x6f, 0x73,
|
||||
@@ -226,19 +236,23 @@ var file_controller_api_resources_hostsets_v1_host_set_proto_rawDesc = []byte{
|
||||
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
|
||||
0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x68,
|
||||
0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x64, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x68,
|
||||
0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x12, 0x37, 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, 0x52, 0x0a, 0x61, 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, 0x52, 0x5a, 0x50, 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, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x65, 0x74, 0x73, 0x3b, 0x68, 0x6f, 0x73,
|
||||
0x74, 0x73, 0x65, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x12, 0x36, 0x0a, 0x13, 0x70, 0x72, 0x65, 0x66, 0x65,
|
||||
0x72, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x65,
|
||||
0x20, 0x03, 0x28, 0x09, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x13, 0x70, 0x72, 0x65, 0x66,
|
||||
0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12,
|
||||
0x37, 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, 0x52, 0x0a, 0x61, 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, 0x52, 0x5a, 0x50, 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, 0x68, 0x6f, 0x73, 0x74,
|
||||
0x73, 0x65, 0x74, 0x73, 0x3b, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x65, 0x74, 0x73, 0x62, 0x06, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
Reference in new issue
Block a user