Add repository and functions around plugin host set membership (#1629)
Co-authored-by: Todd <T.Alan.Knight@gmail.com>
This commit is contained in:
1 parent
b1a72bd445
commit
b64761f38f
23 files changed
+1392
-286
No files matched your search
@@ -33,7 +33,8 @@ require (
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0
|
||||
github.com/hashicorp/go-rootcerts v1.0.2
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.4 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2
|
||||
github.com/hashicorp/go-secure-stdlib/configutil v0.1.2
|
||||
github.com/hashicorp/go-secure-stdlib/gatedwriter v0.1.1
|
||||
github.com/hashicorp/go-secure-stdlib/kv-builder v0.1.1
|
||||
|
||||
@@ -523,8 +523,9 @@ github.com/hashicorp/go-secure-stdlib/awsutil v0.1.1/go.mod h1:QRJZ7siKie+SZJB9j
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.2/go.mod h1:QRJZ7siKie+SZJB9jLbfKrs0Gd0yPWMtbneg0iU1PrY=
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.4 h1:aCPwC2WPyomaw5gk5ZZ7DPevN8d9DS3igkmq6DxGMjU=
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.4/go.mod h1:MpCPSPGLDILGb4JMm94/mMi3YysIqsXzGCzkEZjcjXg=
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1 h1:6KMBnfEv0/kLAz0O76sliN5mXbCDcLfs2kP7ssP7+DQ=
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
||||
github.com/hashicorp/go-secure-stdlib/configutil v0.1.2 h1:ai0P91rxlyGWkYUH/zFr7mduW2Q+2FMrXawkLM8e7NU=
|
||||
github.com/hashicorp/go-secure-stdlib/configutil v0.1.2/go.mod h1:EN1DJMjv9y5e/uRAuP2WPibAox5KHEiq2BjZl00aNPQ=
|
||||
github.com/hashicorp/go-secure-stdlib/gatedwriter v0.1.1 h1:9um9R8i0+HbRHS9d64kdvWR0/LJvo12sIonvR9zr1+U=
|
||||
|
||||
@@ -48,6 +48,10 @@ type Options struct {
|
||||
withGormFormatter hclog.Logger
|
||||
withMaxOpenConnections int
|
||||
|
||||
// withDebug indicates that the given operation should invoke Gorm's debug
|
||||
// mode
|
||||
withDebug bool
|
||||
|
||||
withOnConflict *OnConflict
|
||||
withRowsAffected *int64
|
||||
}
|
||||
@@ -189,6 +193,13 @@ func WithMaxOpenConnections(max int) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithDebug specifies the given operation should invoke debug mode in Gorm
|
||||
func WithDebug(with bool) Option {
|
||||
return func(o *Options) {
|
||||
o.withDebug = with
|
||||
}
|
||||
}
|
||||
|
||||
// WithOnConflict specifies an optional on conflict criteria which specify
|
||||
// alternative actions to take when an insert results in a unique constraint or
|
||||
// exclusion constraint error
|
||||
|
||||
@@ -192,13 +192,22 @@ func Test_getOpts(t *testing.T) {
|
||||
testOpts.withMaxOpenConnections = 22
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
t.Run("WithDebug", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
// test default of false
|
||||
opts := GetOpts()
|
||||
testOpts := getDefaultOptions()
|
||||
assert.Equal(opts, testOpts)
|
||||
// try setting to true
|
||||
opts = GetOpts(WithDebug(true))
|
||||
testOpts.withDebug = true
|
||||
})
|
||||
t.Run("WithOnConflict", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
// test default of false
|
||||
opts := GetOpts()
|
||||
testOpts := getDefaultOptions()
|
||||
assert.Equal(opts, testOpts)
|
||||
|
||||
columns := SetColumns([]string{"name", "description"})
|
||||
columnValues := SetColumnValues(map[string]interface{}{"expiration": "NULL"})
|
||||
testOnConflict := OnConflict{
|
||||
|
||||
@@ -86,14 +86,14 @@ type Writer interface {
|
||||
// rows updated or an error. Supported options: WithOplog.
|
||||
Update(ctx context.Context, i interface{}, fieldMaskPaths []string, setToNullPaths []string, opt ...Option) (int, error)
|
||||
|
||||
// Create an object in the db with options: WithOplog
|
||||
// the caller is responsible for the transaction life cycle of the writer
|
||||
// Create an object in the db with options: WithOplog, WithOnConflict.
|
||||
// The caller is responsible for the transaction life cycle of the writer
|
||||
// and if an error is returned the caller must decide what to do with
|
||||
// the transaction, which almost always should be to rollback.
|
||||
Create(ctx context.Context, i interface{}, opt ...Option) error
|
||||
|
||||
// CreateItems will create multiple items of the same type.
|
||||
// Supported options: WithOplog and WithOplogMsgs. WithOplog and
|
||||
// Supported options: WithOplog and WithOplogMsgs. WithOplog and
|
||||
// WithOplogMsgs may not be used together. WithLookup is not a
|
||||
// supported option. The caller is responsible for the transaction life
|
||||
// cycle of the writer and if an error is returned the caller must decide
|
||||
@@ -101,15 +101,15 @@ type Writer interface {
|
||||
// rollback.
|
||||
CreateItems(ctx context.Context, createItems []interface{}, opt ...Option) error
|
||||
|
||||
// Delete an object in the db with options: WithOplog
|
||||
// the caller is responsible for the transaction life cycle of the writer
|
||||
// Delete an object in the db with options: WithOplog, WithDebug.
|
||||
// The caller is responsible for the transaction life cycle of the writer
|
||||
// and if an error is returned the caller must decide what to do with
|
||||
// the transaction, which almost always should be to rollback. Delete
|
||||
// returns the number of rows deleted or an error.
|
||||
Delete(ctx context.Context, i interface{}, opt ...Option) (int, error)
|
||||
|
||||
// DeleteItems will delete multiple items of the same type.
|
||||
// Supported options: WithOplog and WithOplogMsgs. WithOplog and
|
||||
// Supported options: WithOplog and WithOplogMsgs. WithOplog and
|
||||
// WithOplogMsgs may not be used together. The caller is responsible for the
|
||||
// transaction life cycle of the writer and if an error is returned the
|
||||
// caller must decide what to do with the transaction, which almost always
|
||||
@@ -664,6 +664,9 @@ func (rw *Db) Delete(ctx context.Context, i interface{}, opt ...Option) (int, er
|
||||
if opts.withWhereClause != "" {
|
||||
db = db.Where(opts.withWhereClause, opts.withWhereClauseArgs...)
|
||||
}
|
||||
if opts.withDebug {
|
||||
db = db.Debug()
|
||||
}
|
||||
db = db.Delete(i)
|
||||
if db.Error != nil {
|
||||
return NoRowsAffected, errors.Wrap(ctx, db.Error, op, errors.WithoutEvent())
|
||||
|
||||
@@ -178,8 +178,6 @@ begin;
|
||||
references host (catalog_id, public_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
constraint host_plugin_host_catalog_id_name_uq
|
||||
unique(catalog_id, name),
|
||||
constraint host_plugin_host_catalog_id_external_id_uq
|
||||
unique(catalog_id, external_id),
|
||||
constraint host_plugin_host_catalog_id_public_id_uq
|
||||
@@ -195,8 +193,29 @@ begin;
|
||||
create trigger immutable_columns before update on host_plugin_host
|
||||
for each row execute procedure immutable_columns('public_id', 'catalog_id', 'external_id', 'create_time');
|
||||
|
||||
create trigger insert_host_subtype before insert on host_plugin_host
|
||||
for each row execute procedure insert_host_subtype();
|
||||
-- insert_host_plugin_host_subtype is intended as a before insert trigger on
|
||||
-- host_plugin_host. Its purpose is to insert a base host for new plugin-based
|
||||
-- hosts. It's a bit different than the standard trigger for this, because it
|
||||
-- will have conflicting PKs and we just want to "do nothing" on those
|
||||
-- conflicts, deferring the raising on an error to insert into the
|
||||
-- host_plugin_host table. This allows the upsert-style workflow.
|
||||
create or replace function
|
||||
insert_host_plugin_host_subtype()
|
||||
returns trigger
|
||||
as $$
|
||||
begin
|
||||
insert into host
|
||||
(public_id, catalog_id)
|
||||
values
|
||||
(new.public_id, new.catalog_id)
|
||||
on conflict do nothing;
|
||||
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create trigger insert_host_plugin_host_subtype before insert on host_plugin_host
|
||||
for each row execute procedure insert_host_plugin_host_subtype();
|
||||
|
||||
create trigger delete_host_subtype after delete on host_plugin_host
|
||||
for each row execute procedure delete_host_subtype();
|
||||
@@ -309,40 +328,19 @@ begin;
|
||||
create trigger insert_host_plugin_set_member before insert on host_plugin_set_member
|
||||
for each row execute procedure insert_host_plugin_set_member();
|
||||
|
||||
-- delete_orphaned_host_plugin_host is an after delete trigger
|
||||
-- function to delete plugin hosts when no more set members
|
||||
-- reference it.
|
||||
create function delete_orphaned_host_plugin_host()
|
||||
returns trigger
|
||||
as $$
|
||||
begin
|
||||
delete from host_plugin_host
|
||||
where not exists (select host_id
|
||||
from host_plugin_set_member
|
||||
where host_id = old.host_id)
|
||||
and
|
||||
public_id = old.host_id;
|
||||
return null;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
comment on function delete_orphaned_host_plugin_host is
|
||||
'delete_orphaned_host_plugin_host deletes a host when all set members with that host are deleted.';
|
||||
|
||||
create trigger delete_orphaned_host_plugin_host after delete on host_plugin_set_member
|
||||
for each row execute procedure delete_orphaned_host_plugin_host();
|
||||
|
||||
insert into oplog_ticket (name, version)
|
||||
values
|
||||
('host_plugin_catalog', 1),
|
||||
('host_plugin_catalog_secret', 1),
|
||||
('host_plugin_set', 1);
|
||||
('host_plugin_set', 1),
|
||||
('host_plugin_host', 1);
|
||||
|
||||
|
||||
-- host_plugin_host_with_value_obj is useful for reading a plugin host with
|
||||
-- its associated value objects (ip addresses, dns names, set membership) as
|
||||
-- columns with delimited values. The delimiter depends on the value objects
|
||||
-- (e.g. if they need ordering).
|
||||
create view host_plugin_host_with_value_obj as
|
||||
-- host_plugin_host_with_value_obj_and_set_memberships is useful for reading a
|
||||
-- plugin host with its associated value objects (ip addresses, dns names) and
|
||||
-- set membership as columns with delimited values. The delimiter depends on
|
||||
-- the value objects (e.g. if they need ordering).
|
||||
create view host_plugin_host_with_value_obj_and_set_memberships as
|
||||
select
|
||||
h.public_id,
|
||||
h.catalog_id,
|
||||
@@ -354,15 +352,16 @@ begin;
|
||||
h.update_time,
|
||||
-- the string_agg(..) column will be null if there are no associated value objects
|
||||
string_agg(distinct host(hip.address), '|') as ip_addresses,
|
||||
string_agg(distinct hdns.name, '|') as dns_names
|
||||
string_agg(distinct hdns.name, '|') as dns_names,
|
||||
string_agg(distinct hpsm.set_id, '|') as set_ids
|
||||
from
|
||||
host_plugin_host h
|
||||
join host_plugin_catalog hc on h.catalog_id = hc.public_id
|
||||
left outer join host_ip_address hip on h.public_id = hip.host_id
|
||||
left outer join host_dns_name hdns on h.public_id = hdns.host_id
|
||||
-- FIXME: add set membership once that's shaken out
|
||||
join host_plugin_catalog hc on h.catalog_id = hc.public_id
|
||||
left outer join host_ip_address hip on h.public_id = hip.host_id
|
||||
left outer join host_dns_name hdns on h.public_id = hdns.host_id
|
||||
left outer join host_plugin_set_member hpsm on h.public_id = hpsm.host_id
|
||||
group by h.public_id, hc.plugin_id;
|
||||
comment on view host_plugin_host_with_value_obj is
|
||||
comment on view host_plugin_host_with_value_obj_and_set_memberships is
|
||||
'host plugin host with its associated value objects';
|
||||
|
||||
commit;
|
||||
@@ -89,6 +89,10 @@ const (
|
||||
// reasons related to the state of the DDL and/or inputs (such as we're
|
||||
// already in the right state and don't want to end up writing oplogs).
|
||||
GracefullyAborted Code = 1106
|
||||
// UnexpectedRowsAffected indicates that an action expected to operate on a
|
||||
// specific number of records returned a different count, e.g. if you
|
||||
// expected to delete three items and only one was deleted.
|
||||
UnexpectedRowsAffected Code = 1107
|
||||
|
||||
// Migration setup errors are codes 2000-2999
|
||||
MigrationIntegrity Code = 2000 // MigrationIntegrity represents an error with the generated migration related code
|
||||
|
||||
@@ -292,6 +292,11 @@ func TestCode_Both_String_Info(t *testing.T) {
|
||||
c: GracefullyAborted,
|
||||
want: GracefullyAborted,
|
||||
},
|
||||
{
|
||||
name: "UnexpectedRowsAffected",
|
||||
c: UnexpectedRowsAffected,
|
||||
want: UnexpectedRowsAffected,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -236,4 +236,8 @@ var errorCodeInfo = map[Code]Info{
|
||||
Message: "purposefully aborted without error",
|
||||
Kind: Other,
|
||||
},
|
||||
UnexpectedRowsAffected: {
|
||||
Message: "unexpected number of rows affected",
|
||||
Kind: Integrity,
|
||||
},
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/host/plugin/store"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -16,14 +17,15 @@ import (
|
||||
// field for this host's host catalog.
|
||||
type Host struct {
|
||||
*store.Host
|
||||
PluginId string `gorm:"-"`
|
||||
tableName string `gorm:"-"`
|
||||
PluginId string `gorm:"-"`
|
||||
SetIds []string `gorm:"-"`
|
||||
tableName string `gorm:"-"`
|
||||
}
|
||||
|
||||
// newHost creates a new in memory Host assigned to catalogId with an address.
|
||||
// Supported options: WithName, WithDescription, WithIpAddresses, WithDnsNames,
|
||||
// WithPluginId. Others ignored.
|
||||
func newHost(ctx context.Context, catalogId, externalId string, opt ...Option) *Host {
|
||||
func NewHost(ctx context.Context, catalogId, externalId string, opt ...Option) *Host {
|
||||
opts := getOpts(opt...)
|
||||
|
||||
h := &Host{
|
||||
@@ -74,10 +76,31 @@ func allocHost() *Host {
|
||||
|
||||
func (h *Host) clone() *Host {
|
||||
cp := proto.Clone(h.Host)
|
||||
return &Host{
|
||||
nh := &Host{
|
||||
PluginId: h.PluginId,
|
||||
Host: cp.(*store.Host),
|
||||
}
|
||||
switch {
|
||||
case h.SetIds == nil:
|
||||
case len(h.SetIds) == 0:
|
||||
nh.SetIds = make([]string, 0)
|
||||
default:
|
||||
nh.SetIds = make([]string, len(h.SetIds))
|
||||
copy(nh.SetIds, h.SetIds)
|
||||
}
|
||||
return nh
|
||||
}
|
||||
|
||||
func (h *Host) oplog(op oplog.OpType) oplog.Metadata {
|
||||
metadata := oplog.Metadata{
|
||||
"resource-public-id": []string{h.PublicId},
|
||||
"resource-type": []string{"plugin-host"},
|
||||
"op-type": []string{op.String()},
|
||||
}
|
||||
if h.CatalogId != "" {
|
||||
metadata["catalog-id"] = []string{h.CatalogId}
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
// hostAgg is a view that aggregates the host's value objects in to
|
||||
@@ -93,6 +116,7 @@ type hostAgg struct {
|
||||
UpdateTime *timestamp.Timestamp
|
||||
IpAddresses string
|
||||
DnsNames string
|
||||
SetIds string
|
||||
}
|
||||
|
||||
func (agg *hostAgg) toHost(ctx context.Context) (*Host, error) {
|
||||
@@ -118,12 +142,17 @@ func (agg *hostAgg) toHost(ctx context.Context) (*Host, error) {
|
||||
sort.Strings(h.DnsNames)
|
||||
}
|
||||
|
||||
if agg.SetIds != "" {
|
||||
h.SetIds = strings.Split(agg.SetIds, aggregateDelimiter)
|
||||
sort.Strings(h.SetIds)
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// TableName returns the table name for gorm
|
||||
func (agg *hostAgg) TableName() string {
|
||||
return "host_plugin_host_with_value_obj"
|
||||
return "host_plugin_host_with_value_obj_and_set_memberships"
|
||||
}
|
||||
|
||||
func (agg *hostAgg) GetPublicId() string {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/host/plugin/store"
|
||||
)
|
||||
|
||||
// A HostSetMember represents the membership of a host in a host set.
|
||||
type HostSetMember struct {
|
||||
*store.HostSetMember
|
||||
tableName string `gorm:"-"`
|
||||
}
|
||||
|
||||
// NewHostSetMember creates a new in memory HostSetMember representing the
|
||||
// membership of hostId in hostSetId.
|
||||
func NewHostSetMember(ctx context.Context, setId, hostId string, opt ...Option) (*HostSetMember, error) {
|
||||
const op = "plugin.NewHostSetMember"
|
||||
if setId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no set id")
|
||||
}
|
||||
if hostId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no host id")
|
||||
}
|
||||
member := &HostSetMember{
|
||||
HostSetMember: &store.HostSetMember{
|
||||
SetId: setId,
|
||||
HostId: hostId,
|
||||
},
|
||||
}
|
||||
return member, nil
|
||||
}
|
||||
|
||||
// VetForWrite implements db.VetForWrite() interface for host set members.
|
||||
func (m *HostSetMember) VetForWrite(ctx context.Context, _ db.Reader, _ db.OpType, _ ...db.Option) error {
|
||||
const op = "plugin.(HostSetMember).VetForWrite"
|
||||
if m.SetId == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing set id")
|
||||
}
|
||||
if m.HostId == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing host id")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TableName returns the table name for the host set.
|
||||
func (m *HostSetMember) TableName() string {
|
||||
if m.tableName != "" {
|
||||
return m.tableName
|
||||
}
|
||||
return "host_plugin_set_member"
|
||||
}
|
||||
|
||||
// SetTableName sets the table name. If the caller attempts to
|
||||
// set the name to "" the name will be reset to the default name.
|
||||
func (m *HostSetMember) SetTableName(n string) {
|
||||
m.tableName = n
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"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/oplog"
|
||||
hostplg "github.com/hashicorp/boundary/internal/plugin/host"
|
||||
plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestHostSetMember_InsertDelete tests insertion and validates the lookup via set IDs
|
||||
// function as well. After, we remove set memberships one at a time and validate
|
||||
// the cleanup function removes the hosts.
|
||||
func TestHostSetMember_InsertDelete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
|
||||
plg := hostplg.TestPlugin(t, conn, "create")
|
||||
plgm := map[string]plgpb.HostPluginServiceClient{
|
||||
plg.GetPublicId(): NewWrappingPluginClient(&plgpb.UnimplementedHostPluginServiceServer{}),
|
||||
}
|
||||
|
||||
cats := TestCatalogs(t, conn, prj.PublicId, plg.PublicId, 2)
|
||||
|
||||
blueCat := cats[0]
|
||||
blueSet1 := TestSet(t, conn, kms, blueCat, plgm)
|
||||
blueSet2 := TestSet(t, conn, kms, blueCat, plgm)
|
||||
|
||||
hostId, err := db.NewPublicId(HostPrefix)
|
||||
require.NoError(t, err)
|
||||
blueHost1 := NewHost(ctx, blueCat.PublicId, "abcd", withPluginId(plg.GetPublicId()))
|
||||
blueHost1.PublicId = hostId
|
||||
require.NoError(t, rw.Create(ctx, blueHost1))
|
||||
|
||||
hostId, err = db.NewPublicId(HostPrefix)
|
||||
require.NoError(t, err)
|
||||
blueHost2 := NewHost(ctx, blueCat.PublicId, "zyxw", withPluginId(plg.GetPublicId()))
|
||||
blueHost2.PublicId = hostId
|
||||
require.NoError(t, rw.Create(ctx, blueHost2))
|
||||
|
||||
greenCat := cats[1]
|
||||
greenSet := TestSet(t, conn, kms, greenCat, plgm)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
sets []string
|
||||
host *Host
|
||||
wantErr bool
|
||||
direct bool
|
||||
}{
|
||||
{
|
||||
name: "valid-host-in-set",
|
||||
sets: []string{blueSet1.PublicId},
|
||||
host: blueHost1,
|
||||
},
|
||||
{
|
||||
name: "valid-other-host-in-set",
|
||||
sets: []string{blueSet2.PublicId},
|
||||
host: blueHost2,
|
||||
},
|
||||
{
|
||||
name: "invalid-diff-catalogs",
|
||||
sets: []string{greenSet.PublicId},
|
||||
host: blueHost1,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "test-vet-for-write-no-set",
|
||||
host: blueHost1,
|
||||
sets: []string{""},
|
||||
wantErr: true,
|
||||
direct: true,
|
||||
},
|
||||
{
|
||||
name: "test-vet-for-write-no-host",
|
||||
sets: []string{blueSet1.PublicId},
|
||||
wantErr: true,
|
||||
direct: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
for _, set := range tt.sets {
|
||||
var got *HostSetMember
|
||||
var err error
|
||||
if !tt.direct {
|
||||
got, err = NewHostSetMember(ctx, set, tt.host.PublicId)
|
||||
} else {
|
||||
got = &HostSetMember{
|
||||
HostSetMember: &store.HostSetMember{
|
||||
SetId: set,
|
||||
},
|
||||
}
|
||||
if tt.host != nil {
|
||||
got.HostId = tt.host.PublicId
|
||||
}
|
||||
}
|
||||
require.NoError(err)
|
||||
require.NotNil(got)
|
||||
err2 := rw.Create(ctx, got)
|
||||
if tt.wantErr {
|
||||
assert.Error(err2)
|
||||
return
|
||||
}
|
||||
assert.NoError(err2)
|
||||
}
|
||||
|
||||
// Run a test on the aggregate to validate looking up sets
|
||||
agg := &hostAgg{PublicId: tt.host.PublicId}
|
||||
require.NoError(rw.LookupByPublicId(ctx, agg))
|
||||
h, err := agg.toHost(ctx)
|
||||
require.NoError(err)
|
||||
assert.ElementsMatch(h.SetIds, tt.sets)
|
||||
})
|
||||
}
|
||||
repo, err := NewRepository(rw, rw, kms, plgm)
|
||||
require.NoError(t, err)
|
||||
hosts, err := repo.ListHostsBySetIds(ctx, []string{blueSet1.PublicId, blueSet2.PublicId})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hosts, 2)
|
||||
|
||||
// Base case the count by catalog ID
|
||||
hosts, err = repo.ListHostsByCatalogId(ctx, blueCat.PublicId)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, hosts, 2)
|
||||
|
||||
// Delete first membership, validate host is gone
|
||||
got, err := NewHostSetMember(ctx, blueSet1.PublicId, blueHost1.PublicId)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
num, err := rw.Delete(ctx, got)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
require.NoError(t, repo.DeleteOrphanedHosts(ctx, blueCat.ScopeId))
|
||||
assert.NoError(t, db.TestVerifyOplog(t, rw, blueHost1.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)))
|
||||
hosts, err = repo.ListHostsByCatalogId(ctx, blueCat.PublicId)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hosts, 1)
|
||||
|
||||
// Delete second, validate second host is gone
|
||||
got, err = NewHostSetMember(ctx, blueSet2.PublicId, blueHost2.PublicId)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
num, err = rw.Delete(ctx, got)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, num)
|
||||
require.NoError(t, repo.DeleteOrphanedHosts(ctx, blueCat.ScopeId))
|
||||
assert.NoError(t, db.TestVerifyOplog(t, rw, blueHost2.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)))
|
||||
hosts, err = repo.ListHostsByCatalogId(ctx, blueCat.PublicId)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hosts, 0)
|
||||
}
|
||||
|
||||
func TestHostSetMember_SetTableName(t *testing.T) {
|
||||
defaultTableName := "host_plugin_set_member"
|
||||
tests := []struct {
|
||||
name string
|
||||
initialName string
|
||||
setNameTo string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "new-name",
|
||||
initialName: "",
|
||||
setNameTo: "new-name",
|
||||
want: "new-name",
|
||||
},
|
||||
{
|
||||
name: "reset to default",
|
||||
initialName: "initial",
|
||||
setNameTo: "",
|
||||
want: defaultTableName,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
def := &HostSetMember{
|
||||
HostSetMember: &store.HostSetMember{},
|
||||
}
|
||||
require.Equal(defaultTableName, def.TableName())
|
||||
s := &HostSetMember{
|
||||
HostSetMember: &store.HostSetMember{},
|
||||
tableName: tt.initialName,
|
||||
}
|
||||
s.SetTableName(tt.setNameTo)
|
||||
assert.Equal(tt.want, s.TableName())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ func TestHost_Create(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "duplicate-name",
|
||||
name: "valid-duplicate-name-same-catalog",
|
||||
args: args{
|
||||
catalogId: cat.GetPublicId(),
|
||||
externalId: "duplicate-name",
|
||||
@@ -99,7 +99,6 @@ func TestHost_Create(t *testing.T) {
|
||||
Name: "test-name",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid-duplicate-name-different-catalog",
|
||||
@@ -141,7 +140,7 @@ func TestHost_Create(t *testing.T) {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
got := newHost(ctx, tt.args.catalogId, tt.args.externalId, tt.args.opts...)
|
||||
got := NewHost(ctx, tt.args.catalogId, tt.args.externalId, tt.args.opts...)
|
||||
require.NotNil(t, got)
|
||||
assert.Emptyf(t, got.PublicId, "PublicId set")
|
||||
assert.Equal(t, tt.want, got)
|
||||
@@ -208,10 +207,10 @@ func testHost(t *testing.T, conn *db.DB, catId, externId string) *Host {
|
||||
t.Helper()
|
||||
w := db.New(conn)
|
||||
ctx := context.Background()
|
||||
host1 := newHost(ctx, catId, externId)
|
||||
host1 := NewHost(ctx, catId, externId)
|
||||
var err error
|
||||
host1.PublicId, err = newHostId(ctx, catId, externId)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, w.Create(ctx, host1))
|
||||
return host1
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ type options struct {
|
||||
withIpAddresses []string
|
||||
withDnsNames []string
|
||||
withLimit int
|
||||
withSetIds []string
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
@@ -97,3 +98,11 @@ func WithLimit(l int) Option {
|
||||
o.withLimit = l
|
||||
}
|
||||
}
|
||||
|
||||
// WithSetIds provides the ability to restrict lookups to particular matching
|
||||
// sets.
|
||||
func WithSetIds(with []string) Option {
|
||||
return func(o *options) {
|
||||
o.withSetIds = with
|
||||
}
|
||||
}
|
||||
@@ -50,4 +50,10 @@ func Test_GetOpts(t *testing.T) {
|
||||
testOpts.withIpAddresses = []string{"foo"}
|
||||
assert.EqualValues(t, opts, testOpts)
|
||||
})
|
||||
t.Run("withSetIds", func(t *testing.T) {
|
||||
opts := getOpts(WithSetIds([]string{"foo"}))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withSetIds = []string{"foo"}
|
||||
assert.EqualValues(t, opts, testOpts)
|
||||
})
|
||||
}
|
||||
@@ -3,11 +3,9 @@ package plugin
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"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"
|
||||
plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
@@ -25,7 +23,7 @@ import (
|
||||
func (r *Repository) UpsertHosts(
|
||||
ctx context.Context,
|
||||
hc *HostCatalog,
|
||||
sets []string,
|
||||
setIds []string,
|
||||
phs []*plgpb.ListHostsResponseHost,
|
||||
_ ...Option) ([]*Host, error) {
|
||||
const op = "plugin.(Repository).UpsertHosts"
|
||||
@@ -46,182 +44,204 @@ func (r *Repository) UpsertHosts(
|
||||
if hc.GetScopeId() == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no scope id")
|
||||
}
|
||||
if sets == nil {
|
||||
if setIds == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil sets")
|
||||
}
|
||||
if len(sets) == 0 { // At least one must have been given to the plugin
|
||||
if len(setIds) == 0 { // At least one must have been given to the plugin
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "empty sets")
|
||||
}
|
||||
|
||||
// hostInfo stores the info we need for the transaction below as well as
|
||||
// which sets they matched
|
||||
type hostInfo struct {
|
||||
h *Host
|
||||
ips []interface{}
|
||||
dnsNames []interface{}
|
||||
sets map[string]struct{}
|
||||
}
|
||||
hostMapping := make(map[string]hostInfo, len(phs))
|
||||
var err error
|
||||
var totalMsgs uint32
|
||||
|
||||
for _, ph := range phs {
|
||||
totalMsgs += 2 // delete and create
|
||||
|
||||
h := newHost(ctx,
|
||||
hc.GetPublicId(),
|
||||
ph.GetExternalId(),
|
||||
withIpAddresses(ph.GetIpAddresses()),
|
||||
withDnsNames(ph.GetDnsNames()),
|
||||
withPluginId(hc.GetPluginId()))
|
||||
h.PublicId, err = newHostId(ctx, hc.GetPublicId(), ph.GetExternalId())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
var ipAddresses []interface{}
|
||||
if len(h.GetIpAddresses()) > 0 {
|
||||
sort.Strings(h.IpAddresses)
|
||||
ipAddresses = make([]interface{}, 0, len(h.GetIpAddresses()))
|
||||
for _, a := range h.GetIpAddresses() {
|
||||
obj, err := host.NewIpAddress(ctx, h.PublicId, a)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
ipAddresses = append(ipAddresses, obj)
|
||||
}
|
||||
}
|
||||
|
||||
var dnsNames []interface{}
|
||||
if len(h.GetDnsNames()) > 0 {
|
||||
sort.Strings(h.DnsNames)
|
||||
dnsNames = make([]interface{}, 0, len(h.GetDnsNames()))
|
||||
for _, n := range h.GetDnsNames() {
|
||||
obj, err := host.NewDnsName(ctx, h.PublicId, n)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
dnsNames = append(dnsNames, obj)
|
||||
}
|
||||
}
|
||||
|
||||
hi := hostInfo{
|
||||
h: h,
|
||||
ips: ipAddresses,
|
||||
dnsNames: dnsNames,
|
||||
}
|
||||
|
||||
for _, id := range ph.GetSetIds() {
|
||||
if hi.sets == nil {
|
||||
hi.sets = make(map[string]struct{})
|
||||
}
|
||||
hi.sets[id] = struct{}{}
|
||||
}
|
||||
|
||||
hostMapping[h.PublicId] = hi
|
||||
|
||||
totalMsgs += uint32(len(ipAddresses) + len(dnsNames))
|
||||
}
|
||||
|
||||
oplogWrapper, err := r.kms.GetWrapper(ctx, hc.GetScopeId(), kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
||||
}
|
||||
|
||||
// First, fetch existing hosts for the set IDs passed in, and organize them
|
||||
// into a lookup map by host ID for later usage
|
||||
var currentHosts []*Host
|
||||
var currentHostMap map[string]*Host
|
||||
{
|
||||
var err error
|
||||
currentHosts, err = r.ListHostsBySetIds(ctx, setIds)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("looking up current hosts for returned sets"))
|
||||
}
|
||||
|
||||
currentHostMap = make(map[string]*Host, len(currentHosts))
|
||||
for _, h := range currentHosts {
|
||||
currentHostMap[h.PublicId] = h
|
||||
}
|
||||
}
|
||||
|
||||
// Now, parse the externally defined hosts into hostInfo values, which
|
||||
// stores info useful for later comparisons
|
||||
newHostMap, err := createNewHostMap(ctx, hc, phs, currentHostMap)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create new host map"))
|
||||
}
|
||||
|
||||
var returnedHosts []*Host
|
||||
// Iterate over hosts and add or update them
|
||||
for _, hi := range newHostMap {
|
||||
ret := hi.h.clone()
|
||||
|
||||
// Here's what this function does: first it deletes a host, which causes
|
||||
// cascading deletes on ip addresses and dns names (but all within the DB).
|
||||
// Then the host is recreated, which uses the same public ID (since it's
|
||||
// based on the host catalog/external ID), along with new/updated
|
||||
// information.
|
||||
//
|
||||
// If we ever do start allowing hosts directly in targets this may cause a
|
||||
// problem because the host deletion would probably cascade to the target
|
||||
// and remove it from the target host sources, but at this point there are
|
||||
// several issues with that scenario so it's not soon...
|
||||
//
|
||||
// The main reason I'm doing it this way is because I'm not sure how to do
|
||||
// oplogs with custom queries. Otherwise a custom insert/on conflict query
|
||||
// could replace the initial deletion. @jimlambrt @mgaffney help? :-D
|
||||
_, err = r.writer.DoTx(
|
||||
ctx,
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(r db.Reader, w db.Writer) error {
|
||||
// FIXME: We need to ensure hosts no longer found have their
|
||||
// memberships removed. To do this, first list all set memberships.
|
||||
// This is waiting on membership to be plumbed through to gorm,
|
||||
// which in turn is waiting on figuring out whether or not we can
|
||||
// share that between static and plugin.
|
||||
//
|
||||
// Next, for each set, check whether a given host ID is in the
|
||||
// hostMapping and if so, if the set ID is in the host's sets. If
|
||||
// not, queue that membership entry for deletion.
|
||||
msgs := make([]*oplog.Message, 0, totalMsgs+3)
|
||||
ticket, err := w.GetTicket(hc)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
|
||||
}
|
||||
|
||||
for _, hi := range hostMapping {
|
||||
ret := hi.h.clone()
|
||||
|
||||
var hOplogMsg oplog.Message
|
||||
// We don't need to check whether something was deleted because
|
||||
// it's okay if it fails
|
||||
if _, err := w.Delete(ctx, ret, db.NewOplogMsg(&hOplogMsg)); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
if !hi.dirtyHost &&
|
||||
len(hi.ipsToAdd) == 0 &&
|
||||
len(hi.ipsToRemove) == 0 &&
|
||||
len(hi.dnsNamesToAdd) == 0 &&
|
||||
len(hi.dnsNamesToRemove) == 0 {
|
||||
returnedHosts = append(returnedHosts, ret)
|
||||
continue
|
||||
}
|
||||
_, err = r.writer.DoTx(
|
||||
ctx,
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(r db.Reader, w db.Writer) error {
|
||||
msgs := make([]*oplog.Message, 0)
|
||||
ticket, err := w.GetTicket(hi.h)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
|
||||
}
|
||||
msgs = append(msgs, &hOplogMsg)
|
||||
|
||||
hOplogMsg = oplog.Message{}
|
||||
if err := w.Create(ctx, ret, db.NewOplogMsg(&hOplogMsg)); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
msgs = append(msgs, &hOplogMsg)
|
||||
|
||||
if len(hi.ips) > 0 {
|
||||
ipOplogMsgs := make([]*oplog.Message, 0, len(hi.ips))
|
||||
if err := w.CreateItems(ctx, hi.ips, db.NewOplogMsgs(&ipOplogMsgs)); err != nil {
|
||||
return err
|
||||
if hi.dirtyHost {
|
||||
var hOplogMsg oplog.Message
|
||||
onConflict := &db.OnConflict{
|
||||
Target: db.Constraint("host_plugin_host_pkey"),
|
||||
Action: db.SetColumns([]string{"name", "description"}),
|
||||
}
|
||||
msgs = append(msgs, ipOplogMsgs...)
|
||||
}
|
||||
|
||||
if len(hi.dnsNames) > 0 {
|
||||
dnsOplogMsgs := make([]*oplog.Message, 0, len(hi.dnsNames))
|
||||
if err := w.CreateItems(ctx, hi.dnsNames, db.NewOplogMsgs(&dnsOplogMsgs)); err != nil {
|
||||
return err
|
||||
if err := w.Create(ctx, ret, db.NewOplogMsg(&hOplogMsg), db.WithOnConflict(onConflict)); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
msgs = append(msgs, dnsOplogMsgs...)
|
||||
msgs = append(msgs, &hOplogMsg)
|
||||
}
|
||||
|
||||
// FIXME: Add set memberships here
|
||||
// IP handling
|
||||
{
|
||||
if len(hi.ipsToRemove) > 0 {
|
||||
oplogMsgs := make([]*oplog.Message, 0, len(hi.ipsToRemove))
|
||||
count, err := w.DeleteItems(ctx, hi.ipsToRemove.toArray(), db.NewOplogMsgs(&oplogMsgs))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count != len(hi.ipsToRemove) {
|
||||
return errors.New(ctx, errors.UnexpectedRowsAffected, op, fmt.Sprintf("expected to remove %d ips from host %s, removed %d", len(hi.ipsToRemove), hi.h.PublicId, count))
|
||||
}
|
||||
msgs = append(msgs, oplogMsgs...)
|
||||
}
|
||||
if len(hi.ipsToAdd) > 0 {
|
||||
oplogMsgs := make([]*oplog.Message, 0, len(hi.ipsToAdd))
|
||||
if err := w.CreateItems(ctx, hi.ipsToAdd.toArray(), db.NewOplogMsgs(&oplogMsgs)); err != nil {
|
||||
return err
|
||||
}
|
||||
msgs = append(msgs, oplogMsgs...)
|
||||
}
|
||||
}
|
||||
|
||||
// DNS handling
|
||||
{
|
||||
if len(hi.dnsNamesToRemove) > 0 {
|
||||
oplogMsgs := make([]*oplog.Message, 0, len(hi.dnsNamesToRemove))
|
||||
count, err := w.DeleteItems(ctx, hi.dnsNamesToRemove.toArray(), db.NewOplogMsgs(&oplogMsgs))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count != len(hi.dnsNamesToRemove) {
|
||||
return errors.New(ctx, errors.UnexpectedRowsAffected, op, fmt.Sprintf("expected to remove %d dns names from host %s, removed %d", len(hi.dnsNamesToRemove), hi.h.PublicId, count))
|
||||
}
|
||||
msgs = append(msgs, oplogMsgs...)
|
||||
}
|
||||
if len(hi.dnsNamesToAdd) > 0 {
|
||||
oplogMsgs := make([]*oplog.Message, 0, len(hi.dnsNamesToAdd))
|
||||
if err := w.CreateItems(ctx, hi.dnsNamesToAdd.toArray(), db.NewOplogMsgs(&oplogMsgs)); err != nil {
|
||||
return err
|
||||
}
|
||||
msgs = append(msgs, oplogMsgs...)
|
||||
}
|
||||
}
|
||||
|
||||
metadata := hi.h.oplog(oplog.OpType_OP_TYPE_UPDATE)
|
||||
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
|
||||
}
|
||||
|
||||
returnedHosts = append(returnedHosts, ret)
|
||||
}
|
||||
|
||||
metadata := hc.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
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if errors.IsCheckConstraintError(err) || errors.IsNotNullError(err) {
|
||||
return nil, errors.New(ctx,
|
||||
errors.InvalidAddress,
|
||||
op,
|
||||
fmt.Sprintf("in catalog: %s", hc.GetPublicId()),
|
||||
errors.WithWrap(err),
|
||||
)
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in catalog: %s", hc.PublicId)))
|
||||
}
|
||||
|
||||
// Now, check set membership changes
|
||||
setMembershipsToAdd, setMembershipsToRemove, allSetIds := getSetChanges(currentHostMap, newHostMap)
|
||||
|
||||
// Iterate through the sets and update memberships, one transaction per set
|
||||
for setId := range allSetIds {
|
||||
hs, err := NewHostSet(ctx, hc.PublicId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
hs.PublicId = setId
|
||||
|
||||
_, err = r.writer.DoTx(
|
||||
ctx,
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(r db.Reader, w db.Writer) error {
|
||||
msgs := make([]*oplog.Message, 0)
|
||||
|
||||
ticket, err := w.GetTicket(hs)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket"))
|
||||
}
|
||||
|
||||
// Perform additions
|
||||
for _, hostId := range setMembershipsToAdd[hs.PublicId] {
|
||||
membership, err := NewHostSetMember(ctx, hs.PublicId, hostId)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
var hOplogMsg oplog.Message
|
||||
if err := w.Create(ctx, membership, db.NewOplogMsg(&hOplogMsg)); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
msgs = append(msgs, &hOplogMsg)
|
||||
}
|
||||
|
||||
// Perform removals
|
||||
for _, hostId := range setMembershipsToRemove[hs.PublicId] {
|
||||
membership, err := NewHostSetMember(ctx, hs.PublicId, hostId)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
var hOplogMsg oplog.Message
|
||||
rows, err := w.Delete(ctx, membership, db.NewOplogMsg(&hOplogMsg))
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if rows != 1 {
|
||||
return errors.New(ctx, errors.RecordNotFound, op, "record not found when deleting set membership")
|
||||
}
|
||||
msgs = append(msgs, &hOplogMsg)
|
||||
}
|
||||
|
||||
metadata := hc.oplog(oplog.OpType_OP_TYPE_UPDATE)
|
||||
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
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
}
|
||||
|
||||
return returnedHosts, nil
|
||||
}
|
||||
|
||||
@@ -248,10 +268,10 @@ func (r *Repository) LookupHost(ctx context.Context, publicId string, opt ...Opt
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// ListHosts returns a slice of Hosts for the catalogId.
|
||||
// ListHostsByCatalogId returns a slice of Hosts for the catalogId.
|
||||
// WithLimit is the only option supported.
|
||||
func (r *Repository) ListHosts(ctx context.Context, catalogId string, opt ...Option) ([]*Host, error) {
|
||||
const op = "plugin.(Repository).ListHosts"
|
||||
func (r *Repository) ListHostsByCatalogId(ctx context.Context, catalogId string, opt ...Option) ([]*Host, error) {
|
||||
const op = "plugin.(Repository).ListHostsByCatalogId"
|
||||
if catalogId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no catalog id")
|
||||
}
|
||||
@@ -282,3 +302,96 @@ func (r *Repository) ListHosts(ctx context.Context, catalogId string, opt ...Opt
|
||||
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
// ListHostsBySetId returns a slice of Hosts for the given set IDs.
|
||||
// WithLimit is the only option supported.
|
||||
func (r *Repository) ListHostsBySetIds(ctx context.Context, setIds []string, opt ...Option) ([]*Host, error) {
|
||||
const op = "plugin.(Repository).ListHostsBySetIds"
|
||||
if len(setIds) == 0 {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no set ids")
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
query := `
|
||||
public_id in
|
||||
(select distinct host_id
|
||||
from host_plugin_set_member
|
||||
where set_id in (?))
|
||||
`
|
||||
|
||||
var hostAggs []*hostAgg
|
||||
err := r.reader.SearchWhere(ctx, &hostAggs, query, []interface{}{setIds}, db.WithLimit(limit))
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
case hostAggs == nil:
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
hosts := make([]*Host, 0, len(hostAggs))
|
||||
for _, ha := range hostAggs {
|
||||
host, err := ha.toHost(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
// DeleteOphanedHosts deletes any hosts that no longer belong to any set.
|
||||
// WithLimit is the only option supported. No options are currently supported.
|
||||
func (r *Repository) DeleteOrphanedHosts(ctx context.Context, scopeId string, _ ...Option) error {
|
||||
const op = "plugin.(Repository).DeleteOrphanedHosts"
|
||||
|
||||
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
||||
}
|
||||
|
||||
query := `
|
||||
public_id in
|
||||
(select public_id
|
||||
from host_plugin_host
|
||||
where public_id not in
|
||||
(select host_id from host_plugin_set_member))
|
||||
`
|
||||
|
||||
var hostAggs []*hostAgg
|
||||
err = r.reader.SearchWhere(ctx, &hostAggs, query, nil)
|
||||
switch {
|
||||
case err != nil:
|
||||
return errors.Wrap(ctx, err, op)
|
||||
case len(hostAggs) == 0:
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = r.writer.DoTx(
|
||||
ctx,
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(r db.Reader, w db.Writer) error {
|
||||
for _, ha := range hostAggs {
|
||||
h := NewHost(ctx, ha.CatalogId, ha.ExternalId)
|
||||
h.PublicId = ha.PublicId
|
||||
metadata := h.oplog(oplog.OpType_OP_TYPE_DELETE)
|
||||
dHost := h.clone()
|
||||
if _, err := w.Delete(ctx, dHost, db.WithOplog(oplogWrapper, metadata)); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -648,7 +648,7 @@ func (r *Repository) Endpoints(ctx context.Context, setIds []string) ([]*host.En
|
||||
|
||||
if _, ok := hostIds[hostId]; !ok {
|
||||
hostIds[hostId] = true
|
||||
host := newHost(ctx, ci.publicId, addr)
|
||||
host := NewHost(ctx, ci.publicId, addr)
|
||||
host.PublicId = hostId
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,10 @@ func TestRepository_UpsertHosts(t *testing.T) {
|
||||
set := TestSet(t, conn, kms, catalog, plgm)
|
||||
setIds = append(setIds, set.GetPublicId())
|
||||
}
|
||||
sort.Strings(setIds)
|
||||
phs, exp := TestExternalHosts(t, catalog, setIds, setCount)
|
||||
phs[2].Name = phs[0].Name
|
||||
exp[2].Name = exp[0].Name
|
||||
|
||||
type input struct {
|
||||
catalog *HostCatalog
|
||||
@@ -139,7 +142,7 @@ func TestRepository_UpsertHosts(t *testing.T) {
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
name: "valid", // Note: this also tests duplicate names
|
||||
in: func() *input {
|
||||
return &input{
|
||||
catalog: catalog,
|
||||
@@ -164,7 +167,11 @@ func TestRepository_UpsertHosts(t *testing.T) {
|
||||
sort.Strings(e.IpAddresses)
|
||||
sort.Strings(e.DnsNames)
|
||||
|
||||
ph.Name, ph.Description = ph.Description, ph.Name
|
||||
e.Name, e.Description = e.Description, e.Name
|
||||
|
||||
ph.SetIds = ph.SetIds[0 : len(ph.SetIds)-1]
|
||||
e.SetIds = e.SetIds[0 : len(e.SetIds)-1]
|
||||
return &input{
|
||||
catalog: catalog,
|
||||
sets: setIds,
|
||||
@@ -194,7 +201,9 @@ func TestRepository_UpsertHosts(t *testing.T) {
|
||||
|
||||
// Basic tests
|
||||
assert.Len(got, len(in.phs))
|
||||
assert.NoError(db.TestVerifyOplog(t, rw, in.catalog.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)))
|
||||
for _, h := range in.exp {
|
||||
assert.NoError(db.TestVerifyOplog(t, rw, h.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
|
||||
}
|
||||
|
||||
// Make sure outputs match. Ignore timestamps.
|
||||
assert.Empty(
|
||||
@@ -210,7 +219,7 @@ func TestRepository_UpsertHosts(t *testing.T) {
|
||||
)
|
||||
|
||||
// Check again, but via performing an explicit list
|
||||
got, err = repo.ListHosts(ctx, in.catalog.GetPublicId())
|
||||
got, err = repo.ListHostsByCatalogId(ctx, in.catalog.GetPublicId())
|
||||
require.NoError(err)
|
||||
require.NotNil(got)
|
||||
assert.Len(got, len(in.phs))
|
||||
@@ -226,11 +235,17 @@ func TestRepository_UpsertHosts(t *testing.T) {
|
||||
),
|
||||
)
|
||||
|
||||
// Now individually call read on each host
|
||||
// Now individually call read on each host, cache the matching set
|
||||
// IDs, and then check membership
|
||||
setIdMap := make(map[string][]string)
|
||||
for _, exp := range in.exp {
|
||||
for _, setId := range exp.SetIds {
|
||||
setIdMap[setId] = append(setIdMap[setId], exp.GetPublicId())
|
||||
}
|
||||
got, err := repo.LookupHost(ctx, exp.GetPublicId())
|
||||
require.NoError(err)
|
||||
require.NotNil(got)
|
||||
assert.NotEmpty(got.SetIds)
|
||||
assert.Empty(
|
||||
cmp.Diff(
|
||||
exp,
|
||||
@@ -240,6 +255,16 @@ func TestRepository_UpsertHosts(t *testing.T) {
|
||||
),
|
||||
)
|
||||
}
|
||||
for setId, expHostIds := range setIdMap {
|
||||
got, err = repo.ListHostsBySetIds(ctx, []string{setId})
|
||||
require.NoError(err)
|
||||
require.NotNil(got)
|
||||
var gotHostIds []string
|
||||
for _, h := range got {
|
||||
gotHostIds = append(gotHostIds, h.GetPublicId())
|
||||
}
|
||||
assert.ElementsMatch(expHostIds, gotHostIds)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/host"
|
||||
plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
"github.com/hashicorp/go-secure-stdlib/strutil"
|
||||
)
|
||||
|
||||
// valueToInterfaceMap is a map that has a function to convert values into an
|
||||
// array
|
||||
type valueToInterfaceMap map[string]interface{}
|
||||
|
||||
func (m valueToInterfaceMap) toArray() []interface{} {
|
||||
switch {
|
||||
case m == nil:
|
||||
return nil
|
||||
case len(m) == 0:
|
||||
return make([]interface{}, 0)
|
||||
default:
|
||||
ret := make([]interface{}, 0, len(m))
|
||||
for _, v := range m {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
// hostInfo stores the info we need for figuring out host, set membership,
|
||||
// and value object differences. It also stores dirty flags to indicate
|
||||
// whether we need to update value objects or the host itself.
|
||||
type hostInfo struct {
|
||||
h *Host
|
||||
ipsToAdd valueToInterfaceMap
|
||||
ipsToRemove valueToInterfaceMap
|
||||
dnsNamesToAdd valueToInterfaceMap
|
||||
dnsNamesToRemove valueToInterfaceMap
|
||||
dirtyHost bool
|
||||
}
|
||||
|
||||
func createNewHostMap(ctx context.Context,
|
||||
catalog *HostCatalog,
|
||||
phs []*plgpb.ListHostsResponseHost,
|
||||
currentHostMap map[string]*Host) (map[string]*hostInfo, error) {
|
||||
|
||||
const op = "plugin.createNewHostMap"
|
||||
newHostMap := make(map[string]*hostInfo, len(phs))
|
||||
|
||||
var err error
|
||||
for _, ph := range phs {
|
||||
newHost := NewHost(ctx,
|
||||
catalog.GetPublicId(),
|
||||
ph.GetExternalId(),
|
||||
WithName(ph.GetName()),
|
||||
WithDescription(ph.GetDescription()),
|
||||
withIpAddresses(ph.GetIpAddresses()),
|
||||
withDnsNames(ph.GetDnsNames()),
|
||||
withPluginId(catalog.GetPluginId()))
|
||||
newHost.PublicId, err = newHostId(ctx, catalog.GetPublicId(), ph.GetExternalId())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
newHost.SetIds = ph.SetIds
|
||||
hi := &hostInfo{
|
||||
h: newHost,
|
||||
}
|
||||
newHostMap[newHost.PublicId] = hi
|
||||
|
||||
// Check if the host is dirty; that is, we need to perform an upsert
|
||||
// operation. If the host isn't dirty, we have nothing to do. Note
|
||||
// that we don't check every value exhaustively; for instance, we
|
||||
// assume catalog ID and external ID don't change because if they do
|
||||
// the public ID will be different as well.
|
||||
currHost := currentHostMap[newHost.PublicId]
|
||||
switch {
|
||||
case currHost == nil,
|
||||
currHost.Name != newHost.Name,
|
||||
currHost.Description != newHost.Description:
|
||||
hi.dirtyHost = true
|
||||
}
|
||||
|
||||
// Get the current set of host IPs/DNS names for comparison. These
|
||||
// will be in sorted order since ordering is kept in the database
|
||||
// and they will have been sorted before insertion.
|
||||
var currHostIps []string
|
||||
var currHostDnsNames []string
|
||||
if currHost != nil {
|
||||
currHostIps = currHost.IpAddresses
|
||||
currHostDnsNames = currHost.DnsNames
|
||||
}
|
||||
|
||||
// Sort these here before comparison. We always use a priority order
|
||||
// based on the behavior of sort.Strings so that we can check for
|
||||
// equivalency.
|
||||
sort.Strings(newHost.IpAddresses)
|
||||
sort.Strings(newHost.DnsNames)
|
||||
|
||||
// IPs
|
||||
{
|
||||
switch {
|
||||
case strutil.EquivalentSlices(currHostIps, newHost.GetIpAddresses()):
|
||||
// Nothing to do...don't remove or add anything
|
||||
|
||||
default:
|
||||
// No match, so we need to remove the old ones and add the new
|
||||
|
||||
// First, build up removals
|
||||
for _, ip := range currHostIps {
|
||||
if hi.ipsToRemove == nil {
|
||||
hi.ipsToRemove = make(valueToInterfaceMap, len(currHostIps))
|
||||
}
|
||||
obj, err := host.NewIpAddress(ctx, newHost.PublicId, ip)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
hi.ipsToRemove[ip] = obj
|
||||
}
|
||||
|
||||
// Now build up additions
|
||||
for _, ip := range newHost.GetIpAddresses() {
|
||||
// If it's in ipsToRemove we found it on the current host;
|
||||
// finding it again here means the host still has it, so
|
||||
// simply do no change.
|
||||
if hi.ipsToRemove != nil && hi.ipsToRemove[ip] != nil {
|
||||
delete(hi.ipsToRemove, ip)
|
||||
if len(hi.ipsToRemove) == 0 {
|
||||
hi.ipsToRemove = nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
if hi.ipsToAdd == nil {
|
||||
hi.ipsToAdd = make(valueToInterfaceMap, len(newHost.GetIpAddresses()))
|
||||
}
|
||||
obj, err := host.NewIpAddress(ctx, newHost.PublicId, ip)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
hi.ipsToAdd[ip] = obj
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DNS names
|
||||
{
|
||||
switch {
|
||||
case strutil.EquivalentSlices(currHostDnsNames, newHost.GetDnsNames()):
|
||||
// Nothing to do...don't remove or add anything
|
||||
|
||||
default:
|
||||
// No match, so we need to remove the old ones and add the new
|
||||
|
||||
// First, build up removals
|
||||
for _, name := range currHostDnsNames {
|
||||
if hi.dnsNamesToRemove == nil {
|
||||
hi.dnsNamesToRemove = make(valueToInterfaceMap, len(currHostDnsNames))
|
||||
}
|
||||
obj, err := host.NewDnsName(ctx, newHost.PublicId, name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
hi.dnsNamesToRemove[name] = obj
|
||||
}
|
||||
|
||||
// Now build up additions
|
||||
for _, name := range newHost.GetDnsNames() {
|
||||
// If it's in dnsNamesToRemove we found it on the current
|
||||
// host; finding it again here means the host still has it,
|
||||
// so simply do no change.
|
||||
if hi.dnsNamesToRemove != nil && hi.dnsNamesToRemove[name] != nil {
|
||||
delete(hi.dnsNamesToRemove, name)
|
||||
if len(hi.dnsNamesToRemove) == 0 {
|
||||
hi.dnsNamesToRemove = nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
if hi.dnsNamesToAdd == nil {
|
||||
hi.dnsNamesToAdd = make(valueToInterfaceMap, len(newHost.GetDnsNames()))
|
||||
}
|
||||
obj, err := host.NewDnsName(ctx, newHost.PublicId, name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
hi.dnsNamesToAdd[name] = obj
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newHostMap, nil
|
||||
}
|
||||
|
||||
func getSetChanges(
|
||||
currentHostMap map[string]*Host,
|
||||
newHostMap map[string]*hostInfo) (
|
||||
setMembershipsToAdd, setMembershipsToRemove map[string][]string,
|
||||
allSetIds map[string]struct{}) {
|
||||
|
||||
// First, find sets that hosts should be added to: hosts that are new or
|
||||
// have new set IDs returned.
|
||||
for newHostId, newHost := range newHostMap {
|
||||
var setsToAdd []string
|
||||
currentHost, ok := currentHostMap[newHostId]
|
||||
if !ok {
|
||||
// If the host was not known about before now, any sets the host
|
||||
// matches will need to be added
|
||||
setsToAdd = newHost.h.SetIds
|
||||
} else {
|
||||
// Otherwise, add to any it doesn't currently match
|
||||
for _, setId := range newHost.h.SetIds {
|
||||
if !strutil.StrListContains(currentHost.SetIds, setId) {
|
||||
setsToAdd = append(setsToAdd, setId)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add to the total set
|
||||
for _, setToAdd := range setsToAdd {
|
||||
if setMembershipsToAdd == nil {
|
||||
setMembershipsToAdd = make(map[string][]string)
|
||||
}
|
||||
if allSetIds == nil {
|
||||
allSetIds = make(map[string]struct{})
|
||||
}
|
||||
setMembershipsToAdd[setToAdd] = append(setMembershipsToAdd[setToAdd], newHostId)
|
||||
allSetIds[setToAdd] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Now, do the inverse: remove hosts from sets that appear there now but no
|
||||
// longer have that set ID in their current list.
|
||||
for currentHostId, currentHost := range currentHostMap {
|
||||
var setsToRemove []string
|
||||
newHost, ok := newHostMap[currentHostId]
|
||||
if !ok {
|
||||
// If the host doesn't even appear now, we obviously want to remove
|
||||
// it from all existing set memberships
|
||||
setsToRemove = currentHost.SetIds
|
||||
} else {
|
||||
// Otherwise, remove it from any it doesn't currently have
|
||||
for _, setId := range currentHost.SetIds {
|
||||
if !strutil.StrListContains(newHost.h.SetIds, setId) {
|
||||
setsToRemove = append(setsToRemove, setId)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add to the total set
|
||||
for _, setToRemove := range setsToRemove {
|
||||
if setMembershipsToRemove == nil {
|
||||
setMembershipsToRemove = make(map[string][]string)
|
||||
}
|
||||
if allSetIds == nil {
|
||||
allSetIds = make(map[string]struct{})
|
||||
}
|
||||
setMembershipsToRemove[setToRemove] = append(setMembershipsToRemove[setToRemove], currentHostId)
|
||||
allSetIds[setToRemove] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hashicorp/boundary/internal/host"
|
||||
hoststore "github.com/hashicorp/boundary/internal/host/store"
|
||||
plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func TestUtilFunctions(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const externalId = "ext_1234567890"
|
||||
const pluginId = "plg_1234567890"
|
||||
|
||||
catalog, err := NewHostCatalog(ctx, "p_1234567890", pluginId)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, catalog)
|
||||
catalog.PublicId = "hc_1234567890"
|
||||
baseHostId, err := newHostId(ctx, catalog.GetPublicId(), externalId)
|
||||
require.NoError(t, err)
|
||||
|
||||
baseResponseHost := &plgpb.ListHostsResponseHost{
|
||||
ExternalId: externalId,
|
||||
Name: "base-name",
|
||||
Description: "base-description",
|
||||
IpAddresses: []string{"1.2.3.4", "5.6.7.8"},
|
||||
DnsNames: []string{"a.b.c", "x.y.z"},
|
||||
SetIds: []string{"set1", "set2"},
|
||||
}
|
||||
baseIpsIfaces := valueToInterfaceMap{}
|
||||
for _, v := range baseResponseHost.IpAddresses {
|
||||
ip, err := host.NewIpAddress(ctx, baseHostId, v)
|
||||
require.NoError(t, err)
|
||||
baseIpsIfaces[v] = ip
|
||||
}
|
||||
baseDnsNamesIfaces := valueToInterfaceMap{}
|
||||
for _, v := range baseResponseHost.DnsNames {
|
||||
name, err := host.NewDnsName(ctx, baseHostId, v)
|
||||
require.NoError(t, err)
|
||||
baseDnsNamesIfaces[v] = name
|
||||
}
|
||||
baseHost := NewHost(ctx, catalog.PublicId, externalId)
|
||||
baseHost.Name = baseResponseHost.Name
|
||||
baseHost.Description = baseResponseHost.Description
|
||||
baseHost.IpAddresses = baseResponseHost.IpAddresses
|
||||
baseHost.DnsNames = baseResponseHost.DnsNames
|
||||
baseHost.PluginId = pluginId
|
||||
baseHost.SetIds = baseResponseHost.SetIds
|
||||
|
||||
defaultHostFunc := func(in *Host) *Host {
|
||||
return in
|
||||
}
|
||||
defaultSetsFunc := func() []string {
|
||||
return baseHost.SetIds
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
host func(*Host) *Host
|
||||
// This function should take in a base host (which will be provided as a
|
||||
// clone) and transform however needed for the particular test
|
||||
in func(*plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo)
|
||||
sets func() []string
|
||||
setsToAdd map[string][]string
|
||||
setsToRemove map[string][]string
|
||||
}{
|
||||
{
|
||||
name: "base",
|
||||
host: func(in *Host) *Host {
|
||||
return nil
|
||||
},
|
||||
sets: func() []string {
|
||||
return nil
|
||||
},
|
||||
in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) {
|
||||
hi := &hostInfo{
|
||||
dirtyHost: true,
|
||||
ipsToAdd: baseIpsIfaces,
|
||||
dnsNamesToAdd: baseDnsNamesIfaces,
|
||||
}
|
||||
return in, hi
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no-change",
|
||||
host: defaultHostFunc,
|
||||
sets: defaultSetsFunc,
|
||||
in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) {
|
||||
hi := &hostInfo{}
|
||||
return in, hi
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "new-name",
|
||||
host: defaultHostFunc,
|
||||
sets: defaultSetsFunc,
|
||||
in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) {
|
||||
in.Name = "newname"
|
||||
hi := &hostInfo{
|
||||
dirtyHost: true,
|
||||
}
|
||||
return in, hi
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "new-description",
|
||||
host: defaultHostFunc,
|
||||
sets: defaultSetsFunc,
|
||||
in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) {
|
||||
in.Description = "newdescription"
|
||||
hi := &hostInfo{
|
||||
dirtyHost: true,
|
||||
}
|
||||
return in, hi
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "extra-ip",
|
||||
host: defaultHostFunc,
|
||||
sets: defaultSetsFunc,
|
||||
in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) {
|
||||
const newIp = "11.22.33.44"
|
||||
in.IpAddresses = append(in.IpAddresses, newIp)
|
||||
ip, err := host.NewIpAddress(ctx, baseHostId, newIp)
|
||||
require.NoError(t, err)
|
||||
hi := &hostInfo{
|
||||
ipsToAdd: valueToInterfaceMap{newIp: ip},
|
||||
}
|
||||
return in, hi
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove-ip",
|
||||
host: defaultHostFunc,
|
||||
sets: defaultSetsFunc,
|
||||
in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) {
|
||||
hi := &hostInfo{
|
||||
ipsToRemove: valueToInterfaceMap{},
|
||||
}
|
||||
for _, ip := range in.IpAddresses[1:] {
|
||||
hi.ipsToRemove[ip] = baseIpsIfaces[ip]
|
||||
}
|
||||
in.IpAddresses = in.IpAddresses[0:1]
|
||||
return in, hi
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "extra-dns-name",
|
||||
host: defaultHostFunc,
|
||||
sets: defaultSetsFunc,
|
||||
in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) {
|
||||
const newName = "this.is.a.test"
|
||||
in.DnsNames = append(in.DnsNames, newName)
|
||||
name, err := host.NewDnsName(ctx, baseHostId, newName)
|
||||
require.NoError(t, err)
|
||||
hi := &hostInfo{
|
||||
dnsNamesToAdd: valueToInterfaceMap{newName: name},
|
||||
}
|
||||
return in, hi
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove-dns-name",
|
||||
host: defaultHostFunc,
|
||||
sets: defaultSetsFunc,
|
||||
in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) {
|
||||
hi := &hostInfo{
|
||||
dnsNamesToRemove: valueToInterfaceMap{},
|
||||
}
|
||||
for _, name := range in.DnsNames[1:] {
|
||||
hi.dnsNamesToRemove[name] = baseDnsNamesIfaces[name]
|
||||
}
|
||||
in.DnsNames = in.DnsNames[0:1]
|
||||
return in, hi
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add-sets",
|
||||
host: defaultHostFunc,
|
||||
sets: func() []string {
|
||||
return append(defaultSetsFunc(), "extra-set", "extra-set-2")
|
||||
},
|
||||
in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) {
|
||||
hi := &hostInfo{}
|
||||
return in, hi
|
||||
},
|
||||
setsToAdd: map[string][]string{
|
||||
"extra-set": {baseHostId},
|
||||
"extra-set-2": {baseHostId},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove-sets",
|
||||
host: defaultHostFunc,
|
||||
sets: func() []string {
|
||||
return defaultSetsFunc()[0:1]
|
||||
},
|
||||
in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) {
|
||||
hi := &hostInfo{}
|
||||
return in, hi
|
||||
},
|
||||
setsToRemove: map[string][]string{
|
||||
"set2": {baseHostId},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add-and-remove-sets",
|
||||
host: defaultHostFunc,
|
||||
sets: func() []string {
|
||||
return append(defaultSetsFunc()[0:1], "extra-set")
|
||||
},
|
||||
in: func(in *plgpb.ListHostsResponseHost) (*plgpb.ListHostsResponseHost, *hostInfo) {
|
||||
hi := &hostInfo{}
|
||||
return in, hi
|
||||
},
|
||||
setsToAdd: map[string][]string{
|
||||
"extra-set": {baseHostId},
|
||||
},
|
||||
setsToRemove: map[string][]string{
|
||||
"set2": {baseHostId},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
var hi *hostInfo
|
||||
currHost := tt.host(baseHost.clone())
|
||||
currentHostMap := map[string]*Host{}
|
||||
if currHost != nil {
|
||||
currentHostMap[baseHostId] = currHost
|
||||
}
|
||||
h := proto.Clone(baseResponseHost).(*plgpb.ListHostsResponseHost)
|
||||
h, hi = tt.in(h)
|
||||
|
||||
newHostMap, err := createNewHostMap(ctx, catalog, []*plgpb.ListHostsResponseHost{h}, currentHostMap)
|
||||
require.NoError(err)
|
||||
require.NotNil(newHostMap)
|
||||
require.NotNil(newHostMap[baseHostId])
|
||||
got := newHostMap[baseHostId]
|
||||
|
||||
// Check the various host/hostinfo bits
|
||||
{
|
||||
assert.Equal(h.ExternalId, got.h.ExternalId)
|
||||
assert.Equal(h.Name, got.h.Name)
|
||||
assert.Equal(h.Description, got.h.Description)
|
||||
assert.ElementsMatch(h.IpAddresses, got.h.IpAddresses)
|
||||
assert.ElementsMatch(h.DnsNames, got.h.DnsNames)
|
||||
assert.ElementsMatch(h.SetIds, got.h.SetIds)
|
||||
|
||||
assert.Equal(hi.dirtyHost, got.dirtyHost)
|
||||
assert.Empty(
|
||||
cmp.Diff(
|
||||
hi.ipsToAdd,
|
||||
got.ipsToAdd,
|
||||
cmpopts.IgnoreUnexported(host.IpAddress{}, hoststore.IpAddress{}),
|
||||
cmpopts.SortSlices(func(x, y interface{}) bool {
|
||||
return x.(*host.IpAddress).Address < y.(*host.IpAddress).Address
|
||||
}),
|
||||
),
|
||||
)
|
||||
assert.Empty(
|
||||
cmp.Diff(
|
||||
hi.ipsToRemove,
|
||||
got.ipsToRemove,
|
||||
cmpopts.IgnoreUnexported(host.IpAddress{}, hoststore.IpAddress{}),
|
||||
cmpopts.SortSlices(func(x, y interface{}) bool {
|
||||
return x.(*host.IpAddress).Address < y.(*host.IpAddress).Address
|
||||
}),
|
||||
),
|
||||
)
|
||||
assert.Empty(
|
||||
cmp.Diff(
|
||||
hi.dnsNamesToAdd,
|
||||
got.dnsNamesToAdd,
|
||||
cmpopts.IgnoreUnexported(host.DnsName{}, hoststore.DnsName{}),
|
||||
cmpopts.SortSlices(func(x, y interface{}) bool {
|
||||
return x.(*host.DnsName).Name < y.(*host.DnsName).Name
|
||||
}),
|
||||
),
|
||||
)
|
||||
assert.Empty(
|
||||
cmp.Diff(
|
||||
hi.dnsNamesToRemove,
|
||||
got.dnsNamesToRemove,
|
||||
cmpopts.IgnoreUnexported(host.DnsName{}, hoststore.DnsName{}),
|
||||
cmpopts.SortSlices(func(x, y interface{}) bool {
|
||||
return x.(*host.DnsName).Name < y.(*host.DnsName).Name
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Run through the sets function
|
||||
{
|
||||
newHostMap[baseHostId].h.SetIds = tt.sets()
|
||||
toAdd, toRemove, _ := getSetChanges(currentHostMap, newHostMap)
|
||||
assert.Equal(tt.setsToAdd, toAdd)
|
||||
assert.Equal(tt.setsToRemove, toRemove)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,19 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestCatalog creates count number of plugin host catalogs to the provided DB
|
||||
// TestCatalogs creates count number of static host catalogs to the provided DB
|
||||
// with the provided scope id. If any errors are encountered during the creation of
|
||||
// the host catalog, the test will fail.
|
||||
func TestCatalogs(t *testing.T, conn *db.DB, scopeId, pluginId string, count int) []*HostCatalog {
|
||||
t.Helper()
|
||||
var cats []*HostCatalog
|
||||
for i := 0; i < count; i++ {
|
||||
cats = append(cats, TestCatalog(t, conn, scopeId, pluginId))
|
||||
}
|
||||
return cats
|
||||
}
|
||||
|
||||
// TestCatalog creates a plugin host catalogs to the provided DB
|
||||
// with the provided scope id. If any errors are encountered during the creation of
|
||||
// the host catalog, the test will fail.
|
||||
func TestCatalog(t *testing.T, conn *db.DB, scopeId, pluginId string, opt ...Option) *HostCatalog {
|
||||
@@ -78,6 +90,10 @@ func TestExternalHosts(t *testing.T, catalog *HostCatalog, setIds []string, coun
|
||||
require := require.New(t)
|
||||
retRH := make([]*plgpb.ListHostsResponseHost, 0, count)
|
||||
retH := make([]*Host, 0, count)
|
||||
if setIds == nil {
|
||||
// Prevent panics
|
||||
setIds = make([]string, 0)
|
||||
}
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
externalId, err := base62.Random(10)
|
||||
@@ -86,19 +102,25 @@ func TestExternalHosts(t *testing.T, catalog *HostCatalog, setIds []string, coun
|
||||
ipStr := testGetIpAddress(t)
|
||||
dnsName := testGetDnsName(t)
|
||||
|
||||
retRH = append(retRH, &plgpb.ListHostsResponseHost{
|
||||
rh := &plgpb.ListHostsResponseHost{
|
||||
ExternalId: externalId,
|
||||
SetIds: setIds,
|
||||
Name: base62.MustRandom(10),
|
||||
Description: base62.MustRandom(10),
|
||||
SetIds: setIds[0 : i+1],
|
||||
IpAddresses: []string{ipStr},
|
||||
DnsNames: []string{dnsName},
|
||||
})
|
||||
}
|
||||
retRH = append(retRH, rh)
|
||||
|
||||
publicId, err := newHostId(context.Background(), catalog.PublicId, externalId)
|
||||
require.NoError(err)
|
||||
|
||||
retH = append(retH, &Host{
|
||||
PluginId: catalog.PluginId,
|
||||
SetIds: setIds[0 : i+1],
|
||||
Host: &store.Host{
|
||||
Name: rh.Name,
|
||||
Description: rh.Description,
|
||||
CatalogId: catalog.PublicId,
|
||||
PublicId: publicId,
|
||||
ExternalId: externalId,
|
||||
|
||||
@@ -149,15 +149,21 @@ message ListHostsResponseHost {
|
||||
// useful, ie: a compute instance ID.
|
||||
string external_id = 10;
|
||||
|
||||
// If supplied, will be set as the managed name of the host
|
||||
string name = 20;
|
||||
|
||||
// If supplied, will be set as the managed description of the host
|
||||
string description = 30;
|
||||
|
||||
// Required. Any and all known IP addresses for the host.
|
||||
repeated string ip_addresses = 20;
|
||||
repeated string ip_addresses = 40;
|
||||
|
||||
// Required. Any and all known DNS names for the host.
|
||||
repeated string dns_names = 30;
|
||||
repeated string dns_names = 50;
|
||||
|
||||
// Required. The host set IDs that match this host, out of the host sets
|
||||
// sent in the request.
|
||||
repeated string set_ids = 40;
|
||||
repeated string set_ids = 60;
|
||||
|
||||
// Optional. Provider-specific metadata that is applicable to this
|
||||
// host. Example: host descriptions, tags, alternate network
|
||||
|
||||
@@ -785,13 +785,17 @@ type ListHostsResponseHost struct {
|
||||
// included in audit logs. It should be set to something unique and
|
||||
// useful, ie: a compute instance ID.
|
||||
ExternalId string `protobuf:"bytes,10,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty"`
|
||||
// If supplied, will be set as the managed name of the host
|
||||
Name string `protobuf:"bytes,20,opt,name=name,proto3" json:"name,omitempty"`
|
||||
// If supplied, will be set as the managed description of the host
|
||||
Description string `protobuf:"bytes,30,opt,name=description,proto3" json:"description,omitempty"`
|
||||
// Required. Any and all known IP addresses for the host.
|
||||
IpAddresses []string `protobuf:"bytes,20,rep,name=ip_addresses,json=ipAddresses,proto3" json:"ip_addresses,omitempty"`
|
||||
IpAddresses []string `protobuf:"bytes,40,rep,name=ip_addresses,json=ipAddresses,proto3" json:"ip_addresses,omitempty"`
|
||||
// Required. Any and all known DNS names for the host.
|
||||
DnsNames []string `protobuf:"bytes,30,rep,name=dns_names,json=dnsNames,proto3" json:"dns_names,omitempty"`
|
||||
DnsNames []string `protobuf:"bytes,50,rep,name=dns_names,json=dnsNames,proto3" json:"dns_names,omitempty"`
|
||||
// Required. The host set IDs that match this host, out of the host sets
|
||||
// sent in the request.
|
||||
SetIds []string `protobuf:"bytes,40,rep,name=set_ids,json=setIds,proto3" json:"set_ids,omitempty"`
|
||||
SetIds []string `protobuf:"bytes,60,rep,name=set_ids,json=setIds,proto3" json:"set_ids,omitempty"`
|
||||
// Optional. Provider-specific metadata that is applicable to this
|
||||
// host. Example: host descriptions, tags, alternate network
|
||||
// addresses, etc.
|
||||
@@ -837,6 +841,20 @@ func (x *ListHostsResponseHost) GetExternalId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListHostsResponseHost) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListHostsResponseHost) GetDescription() string {
|
||||
if x != nil {
|
||||
return x.Description
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListHostsResponseHost) GetIpAddresses() []string {
|
||||
if x != nil {
|
||||
return x.IpAddresses
|
||||
@@ -1058,66 +1076,70 @@ var file_plugin_v1_host_plugin_service_proto_rawDesc = []byte{
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x18,
|
||||
0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x05, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x22, 0xca,
|
||||
0x01, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6e, 0x73, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x05, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x22, 0x80,
|
||||
0x02, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65,
|
||||
0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x70, 0x5f,
|
||||
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x14, 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, 0x1e, 0x20, 0x03, 0x28, 0x09, 0x52,
|
||||
0x08, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x74,
|
||||
0x5f, 0x69, 0x64, 0x73, 0x18, 0x28, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x74, 0x49,
|
||||
0x64, 0x73, 0x12, 0x37, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73,
|
||||
0x18, 0x64, 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, 0x22, 0x49, 0x0a, 0x14, 0x48,
|
||||
0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73,
|
||||
0x74, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x64,
|
||||
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, 0x07, 0x73,
|
||||
0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x32, 0xd3, 0x04, 0x0a, 0x11, 0x48, 0x6f, 0x73, 0x74, 0x50,
|
||||
0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x0f,
|
||||
0x4f, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12,
|
||||
0x21, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x43, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x18, 0x14, 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, 0x1e, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12,
|
||||
0x21, 0x0a, 0x0c, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18,
|
||||
0x28, 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,
|
||||
0x32, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12,
|
||||
0x17, 0x0a, 0x07, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x3c, 0x20, 0x03, 0x28, 0x09,
|
||||
0x52, 0x06, 0x73, 0x65, 0x74, 0x49, 0x64, 0x73, 0x12, 0x37, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72,
|
||||
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x64, 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, 0x22, 0x49, 0x0a, 0x14, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67,
|
||||
0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x07, 0x73, 0x65, 0x63,
|
||||
0x72, 0x65, 0x74, 0x73, 0x18, 0x64, 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, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x32, 0xd3, 0x04, 0x0a,
|
||||
0x11, 0x48, 0x6f, 0x73, 0x74, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69,
|
||||
0x63, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x4f, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x61,
|
||||
0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x21, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x4f, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f,
|
||||
0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69,
|
||||
0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x61, 0x74,
|
||||
0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f,
|
||||
0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12,
|
||||
0x21, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x55, 0x70,
|
||||
0x64, 0x61, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f,
|
||||
0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61,
|
||||
0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65,
|
||||
0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x21, 0x2e, 0x70, 0x6c, 0x75, 0x67,
|
||||
0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x61,
|
||||
0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61,
|
||||
0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70,
|
||||
0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74,
|
||||
0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x12, 0x58, 0x0a, 0x0f, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61,
|
||||
0x6c, 0x6f, 0x67, 0x12, 0x21, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e,
|
||||
0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x74, 0x61, 0x6c,
|
||||
0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4f, 0x6e,
|
||||
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x12, 0x1d, 0x2e, 0x70, 0x6c, 0x75, 0x67,
|
||||
0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65,
|
||||
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69,
|
||||
0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4f, 0x6e, 0x55, 0x70,
|
||||
0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x12, 0x1d, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e,
|
||||
0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65,
|
||||
0x74, 0x65, 0x53, 0x65, 0x74, 0x12, 0x1d, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31,
|
||||
0x2e, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74,
|
||||
0x73, 0x12, 0x1b, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69,
|
||||
0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c,
|
||||
0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48,
|
||||
0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x35, 0x5a, 0x33,
|
||||
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, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x3b, 0x70, 0x6c, 0x75,
|
||||
0x67, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x12, 0x4c, 0x0a, 0x0b, 0x4f, 0x6e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x12,
|
||||
0x1d, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x43, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e,
|
||||
0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x43, 0x72, 0x65,
|
||||
0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c,
|
||||
0x0a, 0x0b, 0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x74, 0x12, 0x1d, 0x2e,
|
||||
0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70,
|
||||
0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x53, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0b,
|
||||
0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x74, 0x12, 0x1d, 0x2e, 0x70, 0x6c,
|
||||
0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
|
||||
0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x6c, 0x75,
|
||||
0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53,
|
||||
0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x09, 0x4c, 0x69,
|
||||
0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e,
|
||||
0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x31,
|
||||
0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x42, 0x35, 0x5a, 0x33, 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, 0x70, 0x6c, 0x75, 0x67,
|
||||
0x69, 0x6e, 0x3b, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
Reference in new issue
Block a user