Add sync interval to host sets (#1680)

This adds a parameter to host sets allowing the sync interval to be
user-specified on a per-host-set basis. A value of -1 disables syncing;
a value of null uses the current default.
pull/1684/head
Jeff Mitchell 5 years ago committed by GitHub
parent dba7c6b531
commit 30e9f944a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -14,20 +14,21 @@ import (
)
type HostSet struct {
Id string `json:"id,omitempty"`
HostCatalogId string `json:"host_catalog_id,omitempty"`
Scope *scopes.ScopeInfo `json:"scope,omitempty"`
Plugin *plugins.PluginInfo `json:"plugin,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"`
Id string `json:"id,omitempty"`
HostCatalogId string `json:"host_catalog_id,omitempty"`
Scope *scopes.ScopeInfo `json:"scope,omitempty"`
Plugin *plugins.PluginInfo `json:"plugin,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"`
SyncIntervalSeconds int32 `json:"sync_interval_seconds,omitempty"`
Attributes map[string]interface{} `json:"attributes,omitempty"`
AuthorizedActions []string `json:"authorized_actions,omitempty"`
response *api.Response
}

@ -121,3 +121,15 @@ func DefaultPreferredEndpoints() Option {
o.postMap["preferred_endpoints"] = nil
}
}
func WithSyncIntervalSeconds(inSyncIntervalSeconds int32) Option {
return func(o *options) {
o.postMap["sync_interval_seconds"] = inSyncIntervalSeconds
}
}
func DefaultSyncIntervalSeconds() Option {
return func(o *options) {
o.postMap["sync_interval_seconds"] = nil
}
}

@ -59,6 +59,7 @@ const (
ApplicationCredentialSourceIdsField = "application_credential_source_ids"
ApplicationCredentialSourcesField = "application_credential_sources"
PreferredEndpointsField = "preferred_endpoints"
SyncIntervalSecondsField = "sync_interval_seconds"
PluginIdField = "plugin_id"
PluginField = "plugin"
PluginNameField = "plugin_name"

@ -215,6 +215,11 @@ func (c *Command) printListTable(items []*hostsets.HostSet) string {
fmt.Sprintf(" Description: %s", item.Description),
)
}
if item.SyncIntervalSeconds != 0 {
output = append(output,
fmt.Sprintf(" Sync Interval: %d seconds", item.SyncIntervalSeconds),
)
}
if len(item.AuthorizedActions) > 0 {
output = append(output,
" Authorized Actions:",
@ -256,6 +261,9 @@ func printItemTable(result api.GenericResult) string {
if item.PreferredEndpoints != nil {
nonAttributeMap["Preferred Endpoints"] = item.PreferredEndpoints
}
if item.SyncIntervalSeconds != 0 {
nonAttributeMap["Sync Interval"] = fmt.Sprintf("%d seconds", item.SyncIntervalSeconds)
}
maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap)

@ -6,6 +6,7 @@ import (
"github.com/hashicorp/boundary/api/hostsets"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/libs/endpoint"
"github.com/hashicorp/go-secure-stdlib/parseutil"
)
func init() {
@ -16,12 +17,13 @@ func init() {
type extraPluginCmdVars struct {
flagPreferredEndpoints []string
flagSyncInterval string
}
func extraPluginActionsFlagsMapFuncImpl() map[string][]string {
return map[string][]string{
"create": {"preferred-endpoint"},
"update": {"preferred-endpoint"},
"create": {"preferred-endpoint", "sync-interval"},
"update": {"preferred-endpoint", "sync-interval"},
}
}
@ -68,8 +70,19 @@ func extraPluginFlagsFuncImpl(c *PluginCommand, set *base.FlagSets, f *base.Flag
`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.`,
`If no preferences are specified, a value will be chosen from among all ` +
`available values using a built-in priority order. May not be valid ` +
`for all plugin types.`,
})
case "sync-interval":
fs.StringVar(&base.StringVar{
Name: "sync-interval",
Target: &c.flagSyncInterval,
Usage: `An interger number of seconds, or a string such as "400s", "5m", or "6h", ` +
"indicating the amount of time that should elapse between syncs of the host set. " +
"The interval will be applied to the end of the previous sync operation, not the start. " +
"Setting to any negative value will disable syncing for that host set; setting to null " +
"will cause the set to use Boundary's default. The default may change between releases.",
})
}
}
@ -93,5 +106,19 @@ func extraPluginFlagsHandlingFuncImpl(c *PluginCommand, _ *base.FlagSets, opts *
*opts = append(*opts, hostsets.WithPreferredEndpoints(c.flagPreferredEndpoints))
}
switch c.flagSyncInterval {
case "":
case "null":
*opts = append(*opts, hostsets.DefaultSyncIntervalSeconds())
default:
interval, err := parseutil.ParseDurationSecond(c.flagSyncInterval)
if err != nil {
c.UI.Error(fmt.Sprintf("Unable to successfully parse given sync interval: %s", err))
return false
}
*opts = append(*opts, hostsets.WithSyncIntervalSeconds(int32(interval.Seconds())))
}
return true
}

@ -1137,7 +1137,7 @@ func (rw *Db) LookupWhere(ctx context.Context, resource interface{}, where strin
//
// Supports the WithLimit option. If WithLimit < 0, then unlimited results are returned.
// If WithLimit == 0, then default limits are used for results.
// Supports the WithOrder option.
// Supports the WithOrder and WithDebug options.
func (rw *Db) SearchWhere(ctx context.Context, resources interface{}, where string, args []interface{}, opt ...Option) error {
const op = "db.SearchWhere"
opts := GetOpts(opt...)
@ -1155,6 +1155,9 @@ func (rw *Db) SearchWhere(ctx context.Context, resources interface{}, where stri
if opts.withOrder != "" {
db = db.Order(opts.withOrder)
}
if opts.withDebug {
db = db.Debug()
}
// Perform limiting
switch {
case opts.WithLimit < 0: // any negative number signals unlimited results

@ -125,6 +125,11 @@ begin;
update_time wt_timestamp,
last_sync_time wt_timestamp,
need_sync bool not null,
sync_interval_seconds int
constraint sync_interval_seconds_not_equal_zero
check(sync_interval_seconds != 0)
constraint sync_interval_seconds_not_less_then_negative_one
check(sync_interval_seconds >= -1),
version wt_version,
attributes bytea not null,
constraint host_plugin_set_catalog_id_name_uq
@ -148,7 +153,7 @@ begin;
for each row execute procedure default_create_time();
create trigger immutable_columns before update on host_plugin_set
for each row execute procedure immutable_columns('public_id', 'catalog_id','create_time');
for each row execute procedure immutable_columns('public_id', 'catalog_id', 'create_time');
create trigger insert_host_set_subtype before insert on host_plugin_set
for each row execute procedure insert_host_set_subtype();

@ -60,6 +60,7 @@ create view host_plugin_host_set_with_value_obj as
hs.update_time,
hs.last_sync_time,
hs.need_sync,
hs.sync_interval_seconds,
hs.version,
hs.attributes,
-- the string_agg(..) column will be null if there are no associated value objects

@ -4322,7 +4322,12 @@
"items": {
"type": "string"
},
"description": "An ordered list of endpoint preferences used to choose from among\nmultiple possible endpoints for a host."
"description": "An ordered list of endpoint preferences used to choose from among\nmultiple possible endpoints for a host. Preferences are specified by\n\"cidr:\u003cvalid IPv4/6 CIDR\u003e\" or \"dns:\u003cglobbed name\u003e\", specifying which IP\naddress or DNS name out of a host's available possibilities should be\npreferred. If no preferences are specified, a value will be chosen from\namong all avialable values using a built-in priority order. May not be\nvalid for all plugin types."
},
"sync_interval_seconds": {
"type": "integer",
"format": "int32",
"description": "An interger number of seconds indicating the amount of time that should\nelapse between syncs of the host set. The interval will be applied to the\nend of the previous sync operation, not the start. Setting to any\nnegative value will disable syncing for that host set; setting to zero\nwill cause the set to use Boundary's default. The default may change\nbetween releases. May not be valid for all plugin types."
},
"attributes": {
"type": "object",

@ -17,9 +17,10 @@ import (
// A HostSet is a collection of hosts from the set's catalog.
type HostSet struct {
*store.HostSet
PluginId string `gorm:"-"`
HostIds []string `gorm:"-"`
tableName string `gorm:"-"`
PluginId string `gorm:"-"`
HostIds []string `gorm:"-"`
PreferredEndpoints []string `gorm:"-"`
tableName string `gorm:"-"`
}
// NewHostSet creates a new in memory HostSet assigned to catalogId. Attributes,
@ -35,12 +36,13 @@ func NewHostSet(ctx context.Context, catalogId string, opt ...Option) (*HostSet,
set := &HostSet{
HostSet: &store.HostSet{
CatalogId: catalogId,
Name: opts.withName,
Description: opts.withDescription,
Attributes: attrs,
PreferredEndpoints: opts.withPreferredEndpoints,
CatalogId: catalogId,
Name: opts.withName,
Description: opts.withDescription,
SyncIntervalSeconds: opts.withSyncIntervalSeconds,
Attributes: attrs,
},
PreferredEndpoints: opts.withPreferredEndpoints,
}
return set, nil
@ -69,7 +71,8 @@ func allocHostSet() *HostSet {
func (s *HostSet) clone() *HostSet {
cp := proto.Clone(s.HostSet)
hs := &HostSet{
HostSet: cp.(*store.HostSet),
HostSet: cp.(*store.HostSet),
PreferredEndpoints: s.PreferredEndpoints,
}
if s.Attributes != nil && len(s.Attributes) == 0 && hs.Attributes == nil {
hs.Attributes = []byte{}
@ -92,19 +95,20 @@ func (s *HostSet) oplog(op oplog.OpType) oplog.Metadata {
// 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
LastSyncTime *timestamp.Timestamp
NeedSync bool
Version uint32
Attributes []byte
PreferredEndpoints string
HostIds string
PublicId string `gorm:"primary_key"`
CatalogId string
PluginId string
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
LastSyncTime *timestamp.Timestamp
NeedSync bool
SyncIntervalSeconds int32
Version uint32
Attributes []byte
PreferredEndpoints string
HostIds string
}
func (agg *hostSetAgg) toHostSet(ctx context.Context) (*HostSet, error) {
@ -121,6 +125,7 @@ func (agg *hostSetAgg) toHostSet(ctx context.Context) (*HostSet, error) {
hs.UpdateTime = agg.UpdateTime
hs.LastSyncTime = agg.LastSyncTime
hs.NeedSync = agg.NeedSync
hs.SyncIntervalSeconds = agg.SyncIntervalSeconds
hs.Version = agg.Version
hs.Attributes = agg.Attributes
if agg.HostIds != "" {

@ -150,10 +150,10 @@ func TestHostSet_Create(t *testing.T) {
},
want: &HostSet{
HostSet: &store.HostSet{
CatalogId: cat.GetPublicId(),
PreferredEndpoints: []string{"cidr:1.2.3.4"},
Attributes: []byte{},
CatalogId: cat.GetPublicId(),
Attributes: []byte{},
},
PreferredEndpoints: []string{"cidr:1.2.3.4"},
},
},
}

@ -96,7 +96,7 @@ func (r *SetSyncJob) Run(ctx context.Context) error {
// Fetch all sets that will reach their sync point within the syncWindow.
// This is done to avoid constantly scheduling the set sync job when there
// are multiple sets to sync in sequence.
err := r.reader.SearchWhere(ctx, &setAggs, `need_sync or last_sync_time <= wt_add_seconds_to_now(?)`, []interface{}{-1 * setSyncJobRunInterval.Seconds()}, db.WithLimit(r.limit))
err := r.reader.SearchWhere(ctx, &setAggs, setSyncJobQuery, []interface{}{-1 * setSyncJobRunInterval.Seconds()}, db.WithLimit(r.limit))
if err != nil {
return errors.Wrap(ctx, err, op)
}
@ -152,23 +152,35 @@ func nextSync(j scheduler.Job) (time.Duration, error) {
}
defer rows.Close()
for rows.Next() {
type NextResync struct {
SyncNow bool
ResyncIn time.Duration
}
var n NextResync
err = r.ScanRows(rows, &n)
if err != nil {
return 0, errors.WrapDeprecated(err, op)
}
if n.SyncNow || n.ResyncIn < 0 {
// If we are past the next renewal time, return 0 to schedule immediately
return 0, nil
}
return n.ResyncIn * time.Second, nil
if !rows.Next() {
return setSyncJobRunInterval, nil
}
type NextResync struct {
SyncNow bool
SyncIntervalSeconds int32
ResyncIn time.Duration
}
return setSyncJobRunInterval, nil
var n NextResync
err = r.ScanRows(rows, &n)
if err != nil {
return 0, errors.WrapDeprecated(err, op)
}
switch {
case n.SyncNow:
// Immediate
return 0, nil
case n.SyncIntervalSeconds < 0:
// In this case automatic syncing is disabled; we still sync if SyncNow
// but otherwise do not. We schedule the job at the default cadence but
// it will do nothing, just calculate a next run time to ensure it
// should stay disabled.
return setSyncJobRunInterval, nil
case n.ResyncIn < 0:
// Immediate
return 0, nil
}
return n.ResyncIn * time.Second, nil
}
// syncSets retrieves from their plugins all the host and membership information
@ -226,7 +238,7 @@ func (r *SetSyncJob) syncSets(ctx context.Context, setIds []string) error {
if !ok {
si = &setInfo{}
}
si.preferredEndpoint = endpoint.WithPreferenceOrder(s.GetPreferredEndpoints())
si.preferredEndpoint = endpoint.WithPreferenceOrder(s.PreferredEndpoints)
si.plgSet, err = toPluginSet(ctx, s)
if err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("converting set %q to plugin set", s.GetPublicId())))
@ -288,7 +300,7 @@ func (r *SetSyncJob) syncSets(ctx context.Context, setIds []string) error {
}
if _, err := r.upsertHosts(ctx, ci.storeCat, catSetIds, resp.GetHosts()); err != nil {
errors.Wrap(ctx, err, op, errors.WithMsg("upserting hosts"))
return errors.Wrap(ctx, err, op, errors.WithMsg("upserting hosts"))
}
updateSyncDataQuery := `

@ -13,8 +13,8 @@ import (
hostplg "github.com/hashicorp/boundary/internal/plugin/host"
"github.com/hashicorp/boundary/internal/scheduler"
plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
assertpkg "github.com/stretchr/testify/assert"
requirepkg "github.com/stretchr/testify/require"
)
func TestNewSetSyncJob(t *testing.T) {
@ -109,7 +109,7 @@ func TestNewSetSyncJob(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
assert, require := assertpkg.New(t), requirepkg.New(t)
got, err := newSetSyncJob(ctx, tt.args.r, tt.args.w, tt.args.kms, tt.args.plgm, tt.options...)
if tt.wantErr {
@ -130,7 +130,7 @@ func TestNewSetSyncJob(t *testing.T) {
func TestSetSyncJob_Run(t *testing.T) {
t.Parallel()
assert, require := assert.New(t), require.New(t)
assert, require := assertpkg.New(t), requirepkg.New(t)
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
@ -215,6 +215,136 @@ func TestSetSyncJob_Run(t *testing.T) {
require.NoError(rw.LookupByPublicId(ctx, hs))
assert.Greater(hs.GetLastSyncTime().AsTime().UnixNano(), firstSyncTime.AsTime().UnixNano())
assert.False(hs.GetNeedSync())
// Now, run a battery of tests with values for SyncIntervalSeconds
type setArgs struct {
syncIntervalSeconds int32
lastSyncTime *timestamp.Timestamp
needsSync bool
}
tests := []struct {
name string
setArgs setArgs
expectSync bool
}{
{
name: "never-synced-before-needs-sync-false",
setArgs: setArgs{
lastSyncTime: timestamp.New(time.Unix(0, 0)),
needsSync: false,
},
expectSync: true,
},
{
name: "never-synced-before-needs-sync-true",
setArgs: setArgs{
lastSyncTime: timestamp.New(time.Unix(0, 0)),
needsSync: true,
},
expectSync: true,
},
{
name: "never-synced-before-sync-disabled",
setArgs: setArgs{
syncIntervalSeconds: -1,
lastSyncTime: timestamp.New(time.Unix(0, 0)),
needsSync: true,
},
expectSync: true,
},
{
name: "synced-just-now",
setArgs: setArgs{
lastSyncTime: timestamp.Now(),
needsSync: false,
},
expectSync: false,
},
{
name: "synced-just-now-need-sync",
setArgs: setArgs{
lastSyncTime: timestamp.Now(),
needsSync: true,
},
expectSync: true,
},
{
name: "synced-just-now-need-sync-but-sync-disabled",
setArgs: setArgs{
syncIntervalSeconds: -1,
lastSyncTime: timestamp.Now(),
needsSync: true,
},
expectSync: true,
},
{
name: "synced-30-seconds-ago-default-time",
setArgs: setArgs{
lastSyncTime: timestamp.New(time.Now().Add(-60 * time.Second)),
needsSync: false,
},
expectSync: false,
},
{
name: "synced-30-seconds-ago-custom-time",
setArgs: setArgs{
syncIntervalSeconds: 5,
lastSyncTime: timestamp.New(time.Now().Add(-60 * time.Second)),
needsSync: false,
},
expectSync: true,
},
{
name: "synced-30-seconds-ago-custom-larger-time",
setArgs: setArgs{
syncIntervalSeconds: 90,
lastSyncTime: timestamp.New(time.Now().Add(-60 * time.Second)),
needsSync: false,
},
expectSync: false,
},
{
name: "synced-30-seconds-ago-custom-larger-time-need-sync",
setArgs: setArgs{
syncIntervalSeconds: 60,
lastSyncTime: timestamp.New(time.Now().Add(-60 * time.Second)),
needsSync: true,
},
expectSync: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assertpkg.New(t), requirepkg.New(t)
// Update set
hs.LastSyncTime = tt.setArgs.lastSyncTime
hs.NeedSync = tt.setArgs.needsSync
hs.SyncIntervalSeconds = tt.setArgs.syncIntervalSeconds
fieldMaskPaths := []string{"LastSyncTime", "NeedSync"}
var setToNullPaths []string
if hs.SyncIntervalSeconds == 0 {
setToNullPaths = []string{"SyncIntervalSeconds"}
} else {
fieldMaskPaths = append(fieldMaskPaths, "SyncIntervalSeconds")
}
count, err := rw.Update(ctx, hs, fieldMaskPaths, setToNullPaths)
require.NoError(err)
assert.Equal(1, count)
// Run job
err = r.Run(context.Background())
require.NoError(err)
// Validate results
var expNum int
if tt.expectSync {
expNum = 1
}
assert.Equal(expNum, r.numSets)
assert.Equal(expNum, r.numProcessed)
})
}
}
func TestSetSyncJob_NextRunIn(t *testing.T) {
@ -235,8 +365,9 @@ func TestSetSyncJob_NextRunIn(t *testing.T) {
hostSet := TestSet(t, conn, kmsCache, sched, catalog, plgm)
type setArgs struct {
lastSyncTime *timestamp.Timestamp
needsSync bool
syncIntervalSeconds int32
lastSyncTime *timestamp.Timestamp
needsSync bool
}
tests := []struct {
name string
@ -252,6 +383,15 @@ func TestSetSyncJob_NextRunIn(t *testing.T) {
},
want: 0,
},
{
name: "never-synced-before-with-sync-interval",
setArgs: setArgs{
syncIntervalSeconds: 60,
lastSyncTime: timestamp.New(time.Unix(0, 0)),
needsSync: false,
},
want: 0,
},
{
name: "synced-just-now",
setArgs: setArgs{
@ -260,6 +400,15 @@ func TestSetSyncJob_NextRunIn(t *testing.T) {
},
want: setSyncJobRunInterval,
},
{
name: "synced-just-now-with-sync-interval",
setArgs: setArgs{
syncIntervalSeconds: 180,
lastSyncTime: timestamp.Now(),
needsSync: false,
},
want: 3 * time.Minute,
},
{
name: "synced-just-now-need-sync",
setArgs: setArgs{
@ -268,24 +417,67 @@ func TestSetSyncJob_NextRunIn(t *testing.T) {
},
want: 0,
},
{
name: "synced-just-now-need-sync-with-sync-interval",
setArgs: setArgs{
syncIntervalSeconds: 60,
lastSyncTime: timestamp.Now(),
needsSync: true,
},
want: 0,
},
{
name: "synced-a-bit-ago",
setArgs: setArgs{
lastSyncTime: timestamp.New(time.Now().Add(-4 * time.Minute)),
needsSync: false,
},
want: time.Until(time.Now().Add(setSyncJobRunInterval - (4 * time.Minute))),
},
{
name: "synced-a-bit-ago-with-sync-interval",
setArgs: setArgs{
syncIntervalSeconds: 300,
lastSyncTime: timestamp.New(time.Now().Add(-4 * time.Minute)),
needsSync: false,
},
want: time.Minute,
},
{
name: "automatic-sync-disabled",
setArgs: setArgs{
syncIntervalSeconds: -1,
lastSyncTime: timestamp.New(time.Now().Add(-4 * time.Minute)),
needsSync: false,
},
want: setSyncJobRunInterval,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
assert, require := assertpkg.New(t), requirepkg.New(t)
r, err := newSetSyncJob(ctx, rw, rw, kmsCache, plgm)
assert.NoError(err)
require.NotNil(r)
hostSet.NeedSync = tt.setArgs.needsSync
hostSet.LastSyncTime = tt.setArgs.lastSyncTime
_, err = rw.Update(ctx, hostSet, []string{"LastSyncTime", "NeedSync"}, nil)
hostSet.SyncIntervalSeconds = tt.setArgs.syncIntervalSeconds
fieldMaskPaths := []string{"LastSyncTime", "NeedSync"}
var setToNullPaths []string
if hostSet.SyncIntervalSeconds == 0 {
setToNullPaths = []string{"SyncIntervalSeconds"}
} else {
fieldMaskPaths = append(fieldMaskPaths, "SyncIntervalSeconds")
}
_, err = rw.Update(ctx, hostSet, fieldMaskPaths, setToNullPaths)
require.NoError(err)
got, err := r.NextRunIn()
require.NoError(err)
// Round to time.Minute to account for lost time between updating set and determining next run
assert.Equal(tt.want.Round(time.Minute), got.Round(time.Minute))
// Round to five seconds to account for lost time between updating set and determining next run
assert.Equal(tt.want.Round(5*time.Second), got.Round(5*time.Second))
})
}
}

@ -16,17 +16,18 @@ type Option func(*options)
// options = how options are represented
type options struct {
withPublicId string
withPluginId string
withName string
withDescription string
withAttributes *structpb.Struct
withSecrets *structpb.Struct
withPreferredEndpoints []string
withIpAddresses []string
withDnsNames []string
withLimit int
withSetIds []string
withPublicId string
withPluginId string
withName string
withDescription string
withAttributes *structpb.Struct
withSecrets *structpb.Struct
withPreferredEndpoints []string
withSyncIntervalSeconds int32
withIpAddresses []string
withDnsNames []string
withLimit int
withSetIds []string
}
func getDefaultOptions() options {
@ -84,6 +85,13 @@ func WithPreferredEndpoints(with []string) Option {
}
}
// WithSyncIntervalSeconds provides an optional sync interval, in seconds
func WithSyncIntervalSeconds(with int32) Option {
return func(o *options) {
o.withSyncIntervalSeconds = with
}
}
// withIpAddresses provides an optional list of ip addresses.
func withIpAddresses(with []string) Option {
return func(o *options) {

@ -20,6 +20,12 @@ func Test_GetOpts(t *testing.T) {
testOpts.withName = "test"
assert.Equal(t, opts, testOpts)
})
t.Run("WithSyncIntervalSeconds", func(t *testing.T) {
opts := getOpts(WithSyncIntervalSeconds(5))
testOpts := getDefaultOptions()
testOpts.withSyncIntervalSeconds = 5
assert.Equal(t, opts, testOpts)
})
t.Run("WithPluginId", func(t *testing.T) {
opts := getOpts(withPluginId("test"))
testOpts := getDefaultOptions()

@ -19,10 +19,28 @@ delete from host_plugin_catalog_secret
setSyncNextRunInQuery = `
select
need_sync as sync_now,
extract(epoch from (least(now(), last_sync_time) + ?) - now())::int as resync_in
need_sync as sync_now,
sync_interval_seconds,
case
when sync_interval_seconds is null
then
extract(epoch from (least(now(), last_sync_time) + ?) - now())::int
when sync_interval_seconds > 0
then
extract(epoch from (least(now(), last_sync_time)) - now())::int + sync_interval_seconds
else
0
end resync_in
from host_plugin_set
order by need_sync desc, last_sync_time desc
limit 1;
`
setSyncJobQuery = `
need_sync
or
sync_interval_seconds is null and last_sync_time <= wt_add_seconds_to_now(?)
or
sync_interval_seconds > 0 and wt_add_seconds(sync_interval_seconds, last_sync_time) <= current_timestamp
`
)

@ -49,6 +49,9 @@ func (r *Repository) CreateSet(ctx context.Context, scopeId string, s *HostSet,
if scopeId == "" {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "no scope id")
}
if s.SyncIntervalSeconds < -1 {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "invalid sync interval")
}
if s.Attributes == nil {
return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "nil attributes")
}
@ -779,7 +782,7 @@ func (r *Repository) Endpoints(ctx context.Context, setIds []string) ([]*host.En
h := ha.toHost()
for _, sId := range hostIdToSetIds[h.GetPublicId()] {
s := setIdToSet[sId]
pref, err := endpoint.NewPreferencer(ctx, endpoint.WithPreferenceOrder(s.GetPreferredEndpoints()))
pref, err := endpoint.NewPreferencer(ctx, endpoint.WithPreferenceOrder(s.PreferredEndpoints))
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("getting preferencer for set %q", sId)))
}

@ -46,12 +46,12 @@ func TestRepository_CreateSet(t *testing.T) {
attrs := []byte{}
tests := []struct {
name string
in *HostSet
opts []Option
want *HostSet
name string
in *HostSet
opts []Option
want *HostSet
wantPluginCalled bool
wantIsErr errors.Code
wantIsErr errors.Code
}{
{
name: "nil-HostSet",
@ -91,6 +91,17 @@ func TestRepository_CreateSet(t *testing.T) {
},
wantIsErr: errors.InvalidParameter,
},
{
name: "invalid-sync-interval-too-negative",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
SyncIntervalSeconds: -99,
Attributes: attrs,
},
},
wantIsErr: errors.InvalidParameter,
},
{
name: "valid-no-options",
in: &HostSet{
@ -111,17 +122,17 @@ func TestRepository_CreateSet(t *testing.T) {
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"},
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"},
CatalogId: catalog.PublicId,
Attributes: attrs,
},
PreferredEndpoints: []string{"cidr:1.2.3.4/32", "dns:a.b.c"},
},
wantPluginCalled: true,
},
@ -143,6 +154,42 @@ func TestRepository_CreateSet(t *testing.T) {
},
wantPluginCalled: true,
},
{
name: "valid-sync-interval-disabled",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
SyncIntervalSeconds: -1,
Attributes: attrs,
},
},
want: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
SyncIntervalSeconds: -1,
Attributes: attrs,
},
},
wantPluginCalled: true,
},
{
name: "valid-sync-interval-positive",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
SyncIntervalSeconds: 60,
Attributes: attrs,
},
},
want: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
SyncIntervalSeconds: 60,
Attributes: attrs,
},
},
wantPluginCalled: true,
},
{
name: "valid-unimplemented-plugin",
in: &HostSet{
@ -187,7 +234,7 @@ func TestRepository_CreateSet(t *testing.T) {
Description: ("test-description-repo"),
Attributes: func() []byte {
st, err := structpb.NewStruct(map[string]interface{}{
"k1": "foo",
"k1": "foo",
"removed": nil,
})
require.NoError(t, err)
@ -1129,7 +1176,7 @@ func TestRepository_LookupSet(t *testing.T) {
return &plgpb.ListHostsResponse{}, nil
},
}),
})
}, WithSyncIntervalSeconds(5))
hostSetId, err := newHostSetId(ctx)
require.NoError(t, err)
@ -1192,9 +1239,9 @@ func TestRepository_Endpoints(t *testing.T) {
}
catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
hostSet10 := TestSet(t, conn, kms, sched, catalog, plgm, WithPreferredEndpoints([]string{"cidr:10.0.0.1/24"}))
hostSet192 := TestSet(t, conn, kms, sched, catalog, plgm, WithPreferredEndpoints([]string{"cidr:192.168.0.1/24"}))
hostSet100 := TestSet(t, conn, kms, sched, catalog, plgm, WithPreferredEndpoints([]string{"cidr:100.100.100.100/24"}))
hostSet10 := TestSet(t, conn, kms, sched, catalog, plgm, WithName("hostSet10"), WithPreferredEndpoints([]string{"cidr:10.0.0.1/24"}))
hostSet192 := TestSet(t, conn, kms, sched, catalog, plgm, WithName("hostSet192"), WithPreferredEndpoints([]string{"cidr:192.168.0.1/24"}))
hostSet100 := TestSet(t, conn, kms, sched, catalog, plgm, WithName("hostSet100"), WithPreferredEndpoints([]string{"cidr:100.100.100.100/24"}))
hostlessSet := TestSet(t, conn, kms, sched, hostlessCatalog, plgm)
h1 := TestHost(t, conn, catalog.GetPublicId(), "test", withIpAddresses([]string{"10.0.0.5", "192.168.0.5"}))

@ -195,6 +195,9 @@ type HostSet struct {
// preferred_endpoints stores string preference values
// @inject_tag: `gorm:"-"`
PreferredEndpoints []string `protobuf:"bytes,11,rep,name=preferred_endpoints,json=preferredEndpoints,proto3" json:"preferred_endpoints,omitempty" gorm:"-"`
// Sync interval is a value representing a duration in seconds
// @inject_tag: `gorm:"default:null"`
SyncIntervalSeconds int32 `protobuf:"varint,12,opt,name=sync_interval_seconds,json=syncIntervalSeconds,proto3" json:"sync_interval_seconds,omitempty" gorm:"default:null"`
}
func (x *HostSet) Reset() {
@ -306,6 +309,13 @@ func (x *HostSet) GetPreferredEndpoints() []string {
return nil
}
func (x *HostSet) GetSyncIntervalSeconds() int32 {
if x != nil {
return x.SyncIntervalSeconds
}
return 0
}
type HostCatalogSecret struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -659,7 +669,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, 0xa1, 0x04, 0x0a, 0x07, 0x48, 0x6f,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x22, 0x87, 0x05, 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,
@ -693,61 +703,67 @@ var file_controller_storage_host_plugin_store_v1_host_proto_rawDesc = []byte{
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, 0x0b, 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,
0x72, 0x72, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x64, 0x0a,
0x15, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73,
0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x42, 0x30, 0xc2, 0xdd,
0x29, 0x2c, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x15, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e,
0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x52, 0x13,
0x73, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f,
0x6e, 0x64, 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, 0x22, 0x8d,
0x03, 0x0a, 0x04, 0x48, 0x6f, 0x73, 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, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 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, 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, 0x04, 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, 0x22, 0x8d, 0x03, 0x0a, 0x04, 0x48, 0x6f, 0x73,
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, 0x1f,
0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12,
0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 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, 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, 0x04, 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, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a,
0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12,
0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20,
0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x12, 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, 0x21, 0x0a, 0x0c, 0x69, 0x70, 0x5f, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b,
0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x64,
0x6e, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08,
0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x5e, 0x0a, 0x0d, 0x48, 0x6f, 0x73, 0x74,
0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x6f, 0x73,
0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x6f, 0x73, 0x74,
0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74,
0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63,
0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 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,
0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12,
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67,
0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c,
0x6f, 0x67, 0x49, 0x64, 0x12, 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, 0x21,
0x0a, 0x0c, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x09,
0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65,
0x73, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0a,
0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x5e,
0x0a, 0x0d, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12,
0x17, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x68, 0x6f, 0x73, 0x74, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f,
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12,
0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 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 (

@ -433,6 +433,56 @@ func (x *HostSetMember) GetCatalogId() string {
return ""
}
// These fields are not implemented yet on the host set. They are captured
// here for the purpose of identifying the mask maps which are on the top level
// api set resource but aren't present in the static host set storage.
type UnimplementedSetFields struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
SyncIntervalSeconds int32 `protobuf:"varint,22,opt,name=sync_interval_seconds,json=syncIntervalSeconds,proto3" json:"sync_interval_seconds,omitempty"`
}
func (x *UnimplementedSetFields) Reset() {
*x = UnimplementedSetFields{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_host_static_store_v1_static_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UnimplementedSetFields) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UnimplementedSetFields) ProtoMessage() {}
func (x *UnimplementedSetFields) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_host_static_store_v1_static_proto_msgTypes[4]
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 UnimplementedSetFields.ProtoReflect.Descriptor instead.
func (*UnimplementedSetFields) Descriptor() ([]byte, []int) {
return file_controller_storage_host_static_store_v1_static_proto_rawDescGZIP(), []int{4}
}
func (x *UnimplementedSetFields) GetSyncIntervalSeconds() int32 {
if x != nil {
return x.SyncIntervalSeconds
}
return 0
}
var File_controller_storage_host_static_store_v1_static_proto protoreflect.FileDescriptor
var file_controller_storage_host_static_store_v1_static_proto_rawDesc = []byte{
@ -524,7 +574,15 @@ var file_controller_storage_host_static_store_v1_static_proto_rawDesc = []byte{
0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x65,
0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69,
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67,
0x49, 0x64, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x49, 0x64, 0x22, 0x7e, 0x0a, 0x16, 0x55, 0x6e, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e,
0x74, 0x65, 0x64, 0x53, 0x65, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x64, 0x0a, 0x15,
0x73, 0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65,
0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x16, 0x20, 0x01, 0x28, 0x05, 0x42, 0x30, 0xc2, 0xdd, 0x29,
0x2c, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53,
0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x15, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e, 0x74,
0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x52, 0x13, 0x73,
0x79, 0x6e, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f, 0x6e,
0x64, 0x73, 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, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73,
@ -543,21 +601,22 @@ func file_controller_storage_host_static_store_v1_static_proto_rawDescGZIP() []b
return file_controller_storage_host_static_store_v1_static_proto_rawDescData
}
var file_controller_storage_host_static_store_v1_static_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_controller_storage_host_static_store_v1_static_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_controller_storage_host_static_store_v1_static_proto_goTypes = []interface{}{
(*HostCatalog)(nil), // 0: controller.storage.host.static.store.v1.HostCatalog
(*Host)(nil), // 1: controller.storage.host.static.store.v1.Host
(*HostSet)(nil), // 2: controller.storage.host.static.store.v1.HostSet
(*HostSetMember)(nil), // 3: controller.storage.host.static.store.v1.HostSetMember
(*timestamp.Timestamp)(nil), // 4: controller.storage.timestamp.v1.Timestamp
(*HostCatalog)(nil), // 0: controller.storage.host.static.store.v1.HostCatalog
(*Host)(nil), // 1: controller.storage.host.static.store.v1.Host
(*HostSet)(nil), // 2: controller.storage.host.static.store.v1.HostSet
(*HostSetMember)(nil), // 3: controller.storage.host.static.store.v1.HostSetMember
(*UnimplementedSetFields)(nil), // 4: controller.storage.host.static.store.v1.UnimplementedSetFields
(*timestamp.Timestamp)(nil), // 5: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_host_static_store_v1_static_proto_depIdxs = []int32{
4, // 0: controller.storage.host.static.store.v1.HostCatalog.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
4, // 1: controller.storage.host.static.store.v1.HostCatalog.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
4, // 2: controller.storage.host.static.store.v1.Host.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
4, // 3: controller.storage.host.static.store.v1.Host.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
4, // 4: controller.storage.host.static.store.v1.HostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
4, // 5: controller.storage.host.static.store.v1.HostSet.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 0: controller.storage.host.static.store.v1.HostCatalog.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 1: controller.storage.host.static.store.v1.HostCatalog.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 2: controller.storage.host.static.store.v1.Host.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 3: controller.storage.host.static.store.v1.Host.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 4: controller.storage.host.static.store.v1.HostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
5, // 5: controller.storage.host.static.store.v1.HostSet.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
@ -619,6 +678,18 @@ func file_controller_storage_host_static_store_v1_static_proto_init() {
return nil
}
}
file_controller_storage_host_static_store_v1_static_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UnimplementedSetFields); 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{
@ -626,7 +697,7 @@ func file_controller_storage_host_static_store_v1_static_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_storage_host_static_store_v1_static_proto_rawDesc,
NumEnums: 0,
NumMessages: 4,
NumMessages: 5,
NumExtensions: 0,
NumServices: 0,
},

@ -81,7 +81,7 @@ var testCases = []testCase{
},
{
name: "nested with nil",
dst: nil,
dst: nil,
src: map[string]interface{}{
"a": "b",
"nested": map[string]interface{}{

@ -48,9 +48,22 @@ message HostSet {
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.
// multiple possible endpoints for a host. Preferences are 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. If no preferences are specified, a value will be chosen from
// among all avialable values using a built-in priority order. May not be
// valid for all plugin types.
repeated string preferred_endpoints = 101 [json_name="preferred_endpoints", (custom_options.v1.generate_sdk_option) = true];
// An interger number of seconds indicating the amount of time that should
// elapse between syncs of the host set. The interval will be applied to the
// end of the previous sync operation, not the start. Setting to any
// negative value will disable syncing for that host set; setting to zero
// will cause the set to use Boundary's default. The default may change
// between releases. May not be valid for all plugin types.
google.protobuf.Int32Value sync_interval_seconds = 102 [json_name="sync_interval_seconds", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = {this:"sync_interval_seconds" that: "SyncIntervalSeconds"}];
// The attributes that are applicable for the specific Host Set type.
google.protobuf.Struct attributes = 110 [(custom_options.v1.generate_sdk_option) = true];

@ -93,6 +93,10 @@ message HostSet {
// preferred_endpoints stores string preference values
// @inject_tag: `gorm:"-"`
repeated string preferred_endpoints = 11;
// Sync interval is a value representing a duration in seconds
// @inject_tag: `gorm:"default:null"`
int32 sync_interval_seconds = 12 [(custom_options.v1.mask_mapping) = {this: "SyncIntervalSeconds" that: "sync_interval_seconds"}];
}
message HostCatalogSecret {

@ -117,3 +117,10 @@ message HostSetMember {
// @inject_tag: `gorm:"default:null"`
string catalog_id = 3;
}
// These fields are not implemented yet on the host set. They are captured
// here for the purpose of identifying the mask maps which are on the top level
// api set resource but aren't present in the static host set storage.
message UnimplementedSetFields {
int32 sync_interval_seconds = 22 [(custom_options.v1.mask_mapping) = {this: "SyncIntervalSeconds" that: "sync_interval_seconds"}];
}

@ -11,7 +11,7 @@ import (
"github.com/hashicorp/boundary/internal/host/plugin"
plugstore "github.com/hashicorp/boundary/internal/host/plugin/store"
"github.com/hashicorp/boundary/internal/host/static"
"github.com/hashicorp/boundary/internal/host/static/store"
staticstore "github.com/hashicorp/boundary/internal/host/static/store"
"github.com/hashicorp/boundary/internal/libs/endpoint"
"github.com/hashicorp/boundary/internal/perms"
hostplugin "github.com/hashicorp/boundary/internal/plugin/host"
@ -32,7 +32,7 @@ import (
)
var (
maskManager handlers.MaskManager
maskManager = map[subtypes.Subtype]handlers.MaskManager{}
// IdActions contains the set of actions that can be performed on
// individual resources
@ -64,7 +64,10 @@ var (
func init() {
var err error
if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.HostSet{}}, handlers.MaskSource{&pb.HostSet{}}); err != nil {
if maskManager[static.Subtype], err = handlers.NewMaskManager(handlers.MaskDestination{&staticstore.HostSet{}, &staticstore.UnimplementedSetFields{}}, handlers.MaskSource{&pb.HostSet{}}); err != nil {
panic(err)
}
if maskManager[plugin.Subtype], err = handlers.NewMaskManager(handlers.MaskDestination{&plugstore.HostSet{}}, handlers.MaskSource{&pb.HostSet{}}); err != nil {
panic(err)
}
}
@ -517,7 +520,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId string, re
return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("Unable to build host set for update"))
}
h.PublicId = req.GetId()
dbMask := maskManager.Translate(req.GetUpdateMask().GetPaths())
dbMask := maskManager[static.Subtype].Translate(req.GetUpdateMask().GetPaths())
if len(dbMask) == 0 {
return nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."})
}
@ -817,7 +820,10 @@ func toProto(ctx context.Context, in host.Set, hosts []host.Host, opt ...handler
switch h := in.(type) {
case *plugin.HostSet:
if outputFields.Has(globals.PreferredEndpointsField) {
out.PreferredEndpoints = h.GetPreferredEndpoints()
out.PreferredEndpoints = h.PreferredEndpoints
}
if outputFields.Has(globals.SyncIntervalSecondsField) && h.GetSyncIntervalSeconds() != 0 {
out.SyncIntervalSeconds = &wrapperspb.Int32Value{Value: h.GetSyncIntervalSeconds()}
}
if outputFields.Has(globals.AttributesField) {
attrs := &structpb.Struct{}
@ -865,6 +871,9 @@ func toStoragePluginSet(ctx context.Context, catalogId string, item *pb.HostSet)
if item.GetPreferredEndpoints() != nil {
opts = append(opts, plugin.WithPreferredEndpoints(item.GetPreferredEndpoints()))
}
if item.GetSyncIntervalSeconds() != nil {
opts = append(opts, plugin.WithSyncIntervalSeconds(item.GetSyncIntervalSeconds().GetValue()))
}
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"))
@ -904,6 +913,11 @@ func validateCreateRequest(ctx context.Context, req *pbs.CreateHostSetRequest) e
if req.GetItem().GetType() != "" && req.GetItem().GetType() != plugin.Subtype.String() {
badFields[globals.TypeField] = "Doesn't match the parent resource's type."
}
if val := req.GetItem().GetSyncIntervalSeconds(); val != nil {
if val.GetValue() == 0 || val.GetValue() < -1 {
badFields[globals.SyncIntervalSecondsField] = "Must be -1 or a positive integer."
}
}
}
return badFields
})
@ -923,6 +937,12 @@ func validateUpdateRequest(ctx context.Context, req *pbs.UpdateHostSetRequest) e
if req.GetItem().GetType() != "" && req.GetItem().GetType() != static.Subtype.String() {
badFields[globals.TypeField] = "Cannot modify the resource type."
}
case plugin.Subtype:
if val := req.GetItem().GetSyncIntervalSeconds(); val != nil {
if val.GetValue() == 0 || val.GetValue() < -1 {
badFields[globals.SyncIntervalSecondsField] = "Must be -1 or a positive integer."
}
}
}
return badFields
}, static.HostSetPrefix, plugin.HostSetPrefix)

@ -37,6 +37,7 @@ import (
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
var testAuthorizedActions = map[subtypes.Subtype][]string{
@ -168,7 +169,7 @@ func TestGet_Plugin(t *testing.T) {
}
hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId())
hs := plugin.TestSet(t, conn, kms, sche, hc, plgm, plugin.WithPreferredEndpoints(prefEndpoints))
hs := plugin.TestSet(t, conn, kms, sche, hc, plgm, plugin.WithPreferredEndpoints(prefEndpoints), plugin.WithSyncIntervalSeconds(-1))
toMerge := &pbs.GetHostSetRequest{}
@ -184,8 +185,9 @@ func TestGet_Plugin(t *testing.T) {
Name: plg.GetName(),
Description: plg.GetDescription(),
},
PreferredEndpoints: prefEndpoints,
AuthorizedActions: testAuthorizedActions[plugin.Subtype],
PreferredEndpoints: prefEndpoints,
SyncIntervalSeconds: &wrappers.Int32Value{Value: -1},
AuthorizedActions: testAuthorizedActions[plugin.Subtype],
}
cases := []struct {
@ -373,7 +375,7 @@ func TestList_Plugin(t *testing.T) {
var wantHs []*pb.HostSet
for i := 0; i < 10; i++ {
h := plugin.TestSet(t, conn, kms, sche, hc, plgm, plugin.WithPreferredEndpoints(preferredEndpoints))
h := plugin.TestSet(t, conn, kms, sche, hc, plgm, plugin.WithPreferredEndpoints(preferredEndpoints), plugin.WithSyncIntervalSeconds(5))
wantHs = append(wantHs, &pb.HostSet{
Id: h.GetPublicId(),
HostCatalogId: h.GetCatalogId(),
@ -383,12 +385,13 @@ func TestList_Plugin(t *testing.T) {
Name: plg.GetName(),
Description: plg.GetDescription(),
},
CreatedTime: h.GetCreateTime().GetTimestamp(),
UpdatedTime: h.GetUpdateTime().GetTimestamp(),
Version: h.GetVersion(),
Type: plugin.Subtype.String(),
AuthorizedActions: testAuthorizedActions[plugin.Subtype],
PreferredEndpoints: preferredEndpoints,
CreatedTime: h.GetCreateTime().GetTimestamp(),
UpdatedTime: h.GetUpdateTime().GetTimestamp(),
Version: h.GetVersion(),
Type: plugin.Subtype.String(),
AuthorizedActions: testAuthorizedActions[plugin.Subtype],
PreferredEndpoints: preferredEndpoints,
SyncIntervalSeconds: &wrappers.Int32Value{Value: 5},
})
}
@ -856,10 +859,11 @@ func TestCreate_Plugin(t *testing.T) {
{
name: "No Attributes",
req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{
HostCatalogId: hc.GetPublicId(),
Name: &wrappers.StringValue{Value: "No Attributes"},
Description: &wrappers.StringValue{Value: "desc"},
Type: plugin.Subtype.String(),
HostCatalogId: hc.GetPublicId(),
Name: &wrappers.StringValue{Value: "No Attributes"},
Description: &wrappers.StringValue{Value: "desc"},
Type: plugin.Subtype.String(),
SyncIntervalSeconds: &wrapperspb.Int32Value{Value: -1},
}},
res: &pbs.CreateHostSetResponse{
Uri: fmt.Sprintf("host-sets/%s_", plugin.HostSetPrefix),
@ -871,21 +875,23 @@ func TestCreate_Plugin(t *testing.T) {
Name: plg.GetName(),
Description: plg.GetDescription(),
},
Name: &wrappers.StringValue{Value: "No Attributes"},
Description: &wrappers.StringValue{Value: "desc"},
Type: plugin.Subtype.String(),
AuthorizedActions: testAuthorizedActions[plugin.Subtype],
Name: &wrappers.StringValue{Value: "No Attributes"},
Description: &wrappers.StringValue{Value: "desc"},
Type: plugin.Subtype.String(),
AuthorizedActions: testAuthorizedActions[plugin.Subtype],
SyncIntervalSeconds: &wrapperspb.Int32Value{Value: -1},
},
},
},
{
name: "With Attributes",
req: &pbs.CreateHostSetRequest{Item: &pb.HostSet{
HostCatalogId: hc.GetPublicId(),
Name: &wrappers.StringValue{Value: "With Attributes"},
Description: &wrappers.StringValue{Value: "desc"},
Type: plugin.Subtype.String(),
Attributes: testInputAttrs,
HostCatalogId: hc.GetPublicId(),
Name: &wrappers.StringValue{Value: "With Attributes"},
Description: &wrappers.StringValue{Value: "desc"},
Type: plugin.Subtype.String(),
SyncIntervalSeconds: &wrapperspb.Int32Value{Value: 90},
Attributes: testInputAttrs,
}},
res: &pbs.CreateHostSetResponse{
Uri: fmt.Sprintf("host-sets/%s_", plugin.HostSetPrefix),
@ -897,11 +903,12 @@ func TestCreate_Plugin(t *testing.T) {
Name: plg.GetName(),
Description: plg.GetDescription(),
},
Name: &wrappers.StringValue{Value: "With Attributes"},
Description: &wrappers.StringValue{Value: "desc"},
Type: plugin.Subtype.String(),
AuthorizedActions: testAuthorizedActions[plugin.Subtype],
Attributes: testOutputAttrs,
Name: &wrappers.StringValue{Value: "With Attributes"},
Description: &wrappers.StringValue{Value: "desc"},
Type: plugin.Subtype.String(),
SyncIntervalSeconds: &wrapperspb.Int32Value{Value: 90},
AuthorizedActions: testAuthorizedActions[plugin.Subtype],
Attributes: testOutputAttrs,
},
},
},

@ -56,8 +56,20 @@ type HostSet struct {
// 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.
// multiple possible endpoints for a host. Preferences are 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. If no preferences are specified, a value will be chosen from
// among all avialable values using a built-in priority order. May not be
// valid for all plugin types.
PreferredEndpoints []string `protobuf:"bytes,101,rep,name=preferred_endpoints,proto3" json:"preferred_endpoints,omitempty"`
// An interger number of seconds indicating the amount of time that should
// elapse between syncs of the host set. The interval will be applied to the
// end of the previous sync operation, not the start. Setting to any
// negative value will disable syncing for that host set; setting to zero
// will cause the set to use Boundary's default. The default may change
// between releases. May not be valid for all plugin types.
SyncIntervalSeconds *wrapperspb.Int32Value `protobuf:"bytes,102,opt,name=sync_interval_seconds,proto3" json:"sync_interval_seconds,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.
@ -180,6 +192,13 @@ func (x *HostSet) GetPreferredEndpoints() []string {
return nil
}
func (x *HostSet) GetSyncIntervalSeconds() *wrapperspb.Int32Value {
if x != nil {
return x.SyncIntervalSeconds
}
return nil
}
func (x *HostSet) GetAttributes() *structpb.Struct {
if x != nil {
return x.Attributes
@ -217,7 +236,7 @@ var file_controller_api_resources_hostsets_v1_host_set_proto_rawDesc = []byte{
0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 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, 0xef, 0x05, 0x0a, 0x07, 0x48, 0x6f, 0x73,
0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf9, 0x06, 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,
@ -257,20 +276,28 @@ var file_controller_api_resources_hostsets_v1_host_set_proto_rawDesc = []byte{
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, 0x3d, 0x0a, 0x0a, 0x61, 0x74,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 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,
0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x87, 0x01, 0x0a, 0x15, 0x73,
0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63,
0x6f, 0x6e, 0x64, 0x73, 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74,
0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x34, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29,
0x2c, 0x0a, 0x15, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x49, 0x6e,
0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x52, 0x15, 0x73,
0x79, 0x6e, 0x63, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63,
0x6f, 0x6e, 0x64, 0x73, 0x12, 0x3d, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
0x65, 0x73, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63,
0x74, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 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 (
@ -292,7 +319,8 @@ var file_controller_api_resources_hostsets_v1_host_set_proto_goTypes = []interfa
(*plugins.PluginInfo)(nil), // 2: controller.api.resources.plugins.v1.PluginInfo
(*wrapperspb.StringValue)(nil), // 3: google.protobuf.StringValue
(*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
(*structpb.Struct)(nil), // 5: google.protobuf.Struct
(*wrapperspb.Int32Value)(nil), // 5: google.protobuf.Int32Value
(*structpb.Struct)(nil), // 6: google.protobuf.Struct
}
var file_controller_api_resources_hostsets_v1_host_set_proto_depIdxs = []int32{
1, // 0: controller.api.resources.hostsets.v1.HostSet.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo
@ -301,12 +329,13 @@ var file_controller_api_resources_hostsets_v1_host_set_proto_depIdxs = []int32{
3, // 3: controller.api.resources.hostsets.v1.HostSet.description:type_name -> google.protobuf.StringValue
4, // 4: controller.api.resources.hostsets.v1.HostSet.created_time:type_name -> google.protobuf.Timestamp
4, // 5: controller.api.resources.hostsets.v1.HostSet.updated_time:type_name -> google.protobuf.Timestamp
5, // 6: controller.api.resources.hostsets.v1.HostSet.attributes:type_name -> google.protobuf.Struct
7, // [7:7] is the sub-list for method output_type
7, // [7:7] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] is the sub-list for field type_name
5, // 6: controller.api.resources.hostsets.v1.HostSet.sync_interval_seconds:type_name -> google.protobuf.Int32Value
6, // 7: controller.api.resources.hostsets.v1.HostSet.attributes:type_name -> google.protobuf.Struct
8, // [8:8] is the sub-list for method output_type
8, // [8:8] is the sub-list for method input_type
8, // [8:8] is the sub-list for extension type_name
8, // [8:8] is the sub-list for extension extendee
0, // [0:8] is the sub-list for field type_name
}
func init() { file_controller_api_resources_hostsets_v1_host_set_proto_init() }

Loading…
Cancel
Save