From 23baaac36b7bdb5113bedb333fb33de9aa53d991 Mon Sep 17 00:00:00 2001 From: Todd Date: Wed, 20 Oct 2021 12:20:28 -0700 Subject: [PATCH] Add DB changes for host, set members, and host addresses for plugins. (#1584) * Add DB changes for host, set members, and host addresses for plugins. * Add priority, address review feedback, update tests, naming, protos Co-authored-by: Jeff Mitchell Co-authored-by: Michael Gaffney --- .../oss/postgres/17/01_domain_types.up.sql | 18 + .../17/{01_plugin.up.sql => 02_plugin.up.sql} | 0 ...hostplugin.up.sql => 03_hostplugin.up.sql} | 0 .../17/{03_host.up.sql => 04_host.up.sql} | 0 ...ugin_host.up.sql => 05_plugin_host.up.sql} | 209 ++++++- ...s.up.sql => 06_preferred_endpoints.up.sql} | 4 +- ...up.sql => 07_wh_session_dimensions.up.sql} | 0 internal/host/host.go | 2 + internal/host/host_dns_address.go | 76 +++ internal/host/host_ip_address.go | 81 +++ internal/host/plugin/host.go | 145 ++++- internal/host/plugin/host_address_test.go | 548 ++++++++++++++++++ internal/host/plugin/host_test.go | 75 ++- internal/host/plugin/options.go | 34 ++ internal/host/plugin/options_test.go | 24 + internal/host/plugin/repository_host.go | 284 +++++++++ internal/host/plugin/repository_host_set.go | 11 +- .../host/plugin/repository_host_set_test.go | 7 +- internal/host/plugin/repository_host_test.go | 245 ++++++++ internal/host/plugin/store/host.pb.go | 509 +++++++++------- internal/host/plugin/testing.go | 65 +++ internal/host/static/host.go | 10 + .../host/static/repository_host_set_member.go | 3 + internal/host/store/host.pb.go | 175 +++++- .../storage/host/plugin/store/v1/host.proto | 94 +-- .../storage/host/store/v1/host.proto | 22 + 26 files changed, 2315 insertions(+), 326 deletions(-) create mode 100644 internal/db/schema/migrations/oss/postgres/17/01_domain_types.up.sql rename internal/db/schema/migrations/oss/postgres/17/{01_plugin.up.sql => 02_plugin.up.sql} (100%) rename internal/db/schema/migrations/oss/postgres/17/{02_hostplugin.up.sql => 03_hostplugin.up.sql} (100%) rename internal/db/schema/migrations/oss/postgres/17/{03_host.up.sql => 04_host.up.sql} (100%) rename internal/db/schema/migrations/oss/postgres/17/{04_plugin_host.up.sql => 05_plugin_host.up.sql} (56%) rename internal/db/schema/migrations/oss/postgres/17/{05_preferred_endpoints.up.sql => 06_preferred_endpoints.up.sql} (95%) rename internal/db/schema/migrations/oss/postgres/17/{06_wh_session_dimensions.up.sql => 07_wh_session_dimensions.up.sql} (100%) create mode 100644 internal/host/host_dns_address.go create mode 100644 internal/host/host_ip_address.go create mode 100644 internal/host/plugin/host_address_test.go create mode 100644 internal/host/plugin/repository_host.go create mode 100644 internal/host/plugin/repository_host_test.go diff --git a/internal/db/schema/migrations/oss/postgres/17/01_domain_types.up.sql b/internal/db/schema/migrations/oss/postgres/17/01_domain_types.up.sql new file mode 100644 index 0000000000..0686914e9b --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/17/01_domain_types.up.sql @@ -0,0 +1,18 @@ +begin; + +create domain wt_priority as int not null + constraint priority_must_be_greater_than_zero + check(value > 0); +comment on domain wt_priority is +'Represents a priority value which must not be null and must be greater than zero'; + +-- wt_dns_name defines a type for dns names +create domain wt_dns_name as text not null + constraint wt_dns_name_too_short + check (length(trim(value)) > 0) + constraint wt_dns_name_too_long + check (length(trim(value)) < 256); +comment on domain wt_dns_name is +'standard column for dns names'; + +commit; \ No newline at end of file diff --git a/internal/db/schema/migrations/oss/postgres/17/01_plugin.up.sql b/internal/db/schema/migrations/oss/postgres/17/02_plugin.up.sql similarity index 100% rename from internal/db/schema/migrations/oss/postgres/17/01_plugin.up.sql rename to internal/db/schema/migrations/oss/postgres/17/02_plugin.up.sql diff --git a/internal/db/schema/migrations/oss/postgres/17/02_hostplugin.up.sql b/internal/db/schema/migrations/oss/postgres/17/03_hostplugin.up.sql similarity index 100% rename from internal/db/schema/migrations/oss/postgres/17/02_hostplugin.up.sql rename to internal/db/schema/migrations/oss/postgres/17/03_hostplugin.up.sql diff --git a/internal/db/schema/migrations/oss/postgres/17/03_host.up.sql b/internal/db/schema/migrations/oss/postgres/17/04_host.up.sql similarity index 100% rename from internal/db/schema/migrations/oss/postgres/17/03_host.up.sql rename to internal/db/schema/migrations/oss/postgres/17/04_host.up.sql diff --git a/internal/db/schema/migrations/oss/postgres/17/04_plugin_host.up.sql b/internal/db/schema/migrations/oss/postgres/17/05_plugin_host.up.sql similarity index 56% rename from internal/db/schema/migrations/oss/postgres/17/04_plugin_host.up.sql rename to internal/db/schema/migrations/oss/postgres/17/05_plugin_host.up.sql index 1ca7386f23..9d08550260 100644 --- a/internal/db/schema/migrations/oss/postgres/17/04_plugin_host.up.sql +++ b/internal/db/schema/migrations/oss/postgres/17/05_plugin_host.up.sql @@ -56,10 +56,10 @@ begin; version wt_version, attributes bytea not null, constraint host_catalog_fkey - foreign key (scope_id, public_id) - references host_catalog (scope_id, public_id) - on delete cascade - on update cascade, + foreign key (scope_id, public_id) + references host_catalog (scope_id, public_id) + on delete cascade + on update cascade, constraint host_plugin_catalog_scope_id_name_uq unique(scope_id, name) ); @@ -128,10 +128,10 @@ begin; constraint host_plugin_set_catalog_id_name_uq unique(catalog_id, name), constraint host_set_fkey - foreign key (catalog_id, public_id) - references host_set (catalog_id, public_id) - on delete cascade - on update cascade, + foreign key (catalog_id, public_id) + references host_set (catalog_id, public_id) + on delete cascade + on update cascade, constraint host_plugin_set_catalog_id_public_id_uq unique(catalog_id, public_id) ); @@ -154,10 +154,7 @@ begin; create trigger delete_host_set_subtype after delete on host_plugin_set for each row execute procedure delete_host_set_subtype(); - - -- TODO: Rebuild this table to appropriately cache or sync data from the - -- plugin'ed service. - -- host_plugin_host captures plugin based host data. This is only written to +-- host_plugin_host captures plugin based host data. This is only written to -- from the controller and is not mutable directly by actions from the end -- user. create table host_plugin_host ( @@ -167,37 +164,28 @@ begin; references host_plugin_catalog (public_id) on delete cascade on update cascade, + external_id text not null + constraint external_id_must_not_be_empty + check(length(trim(external_id)) > 0), name wt_name, description text, create_time wt_timestamp, + -- update_time is the last time the data was synced with what is provided + -- from the plugin. update_time wt_timestamp, - version wt_version, - - -- TODO: Break out the address to match the domain model where a host - -- can have multiple addresses and relies on the set to select the - -- prefered one. - address text not null - constraint address_must_be_more_than_2_characters - check(length(trim(address)) > 2) - constraint address_must_be_less_than_256_characters - check(length(trim(address)) < 256), - constraint host_fkey foreign key (catalog_id, public_id) 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 unique(catalog_id, public_id) ); - create trigger update_version_column after update on host_plugin_host - for each row execute procedure update_version_column(); - create trigger update_time_column before update on host_plugin_host for each row execute procedure update_time_column(); @@ -205,7 +193,7 @@ begin; for each row execute procedure default_create_time(); create trigger immutable_columns before update on host_plugin_host - for each row execute procedure immutable_columns('public_id', 'catalog_id','create_time'); + 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(); @@ -213,6 +201,141 @@ begin; create trigger delete_host_subtype after delete on host_plugin_host for each row execute procedure delete_host_subtype(); + -- host_ip_address contains the IP addresses associated with + -- a host, one per row. + create table host_ip_address ( + host_id wt_public_id + constraint host_fkey + references host(public_id) + on delete cascade + on update cascade, + priority wt_priority, + address inet not null, + create_time wt_timestamp, + primary key (host_id, priority), + constraint host_ip_address_host_id_address_uq + unique(host_id, address) + ); + comment on table host_ip_address is + 'host_ip_address entries are ip addresses set on a host with a preserved order.'; + + create trigger default_create_time_column before insert on host_ip_address + for each row execute procedure default_create_time(); + + -- host_immutable_ip_address() ensures that ip addresses assigned to hosts are + -- immutable. + create function + host_immutable_ip_address() + returns trigger + as $$ + begin + raise exception 'host ip addresses are immutable'; + end; + $$ language plpgsql; + + create trigger immutable_ip_address + before update on host_ip_address + for each row execute procedure host_immutable_ip_address(); + + -- host_dns_name contains the DNS names associated with a host, one per row. + create table host_dns_name ( + host_id wt_public_id + constraint host_fkey + references host(public_id) + on delete cascade + on update cascade, + priority wt_priority, + name wt_dns_name, + create_time wt_timestamp, + primary key (host_id, priority), + constraint host_dns_name_host_id_name_uq + unique(host_id, name) + ); + comment on table host_dns_name is + 'host_dns_name entries are dns names set on a host with a preserved order.'; + + create trigger default_create_time_column before insert on host_dns_name + for each row execute procedure default_create_time(); + + -- host_immutable_dns_name() ensures that dns names assigned to hosts are + -- immutable. + create function + host_immutable_dns_name() + returns trigger + as $$ + begin + raise exception 'host dns names are immutable'; + end; + $$ language plpgsql; + + create trigger immutable_dns_name + before update on host_dns_name + for each row execute procedure host_immutable_dns_name(); + + create table host_plugin_set_member ( + host_id wt_public_id not null, + set_id wt_public_id not null, + catalog_id wt_public_id not null, + create_time wt_timestamp, + primary key(host_id, set_id), + constraint host_plugin_host_fkey + foreign key (catalog_id, host_id) + references host_plugin_host (catalog_id, public_id) + on delete cascade + on update cascade, + constraint host_plugin_set_fkey + foreign key (catalog_id, set_id) + references host_plugin_set (catalog_id, public_id) + on delete cascade + on update cascade + ); + comment on table host_plugin_set_member is + 'host_plugin_set_member entries are the membership relationships from plugin hosts in plugin sets.'; + + create trigger default_create_time_column before insert on host_plugin_set_member + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on host_plugin_set_member + for each row execute procedure immutable_columns('host_id', 'set_id', 'catalog_id', 'create_time'); + + create function insert_host_plugin_set_member() + returns trigger + as $$ + begin + select host_plugin_set.catalog_id + into new.catalog_id + from host_plugin_set + where host_plugin_set.public_id = new.set_id; + return new; + end; + $$ language plpgsql; + comment on function insert_host_plugin_set_member is + 'insert_host_plugin_set_member entries are the membership relationships from plugin hosts in plugin sets.'; + + 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 @@ -220,4 +343,32 @@ begin; ('host_plugin_catalog_secret', 1), ('host_plugin_set', 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 + select + h.public_id, + h.catalog_id, + h.external_id, + hc.plugin_id, + h.name, + h.description, + h.create_time, + h.update_time, + -- the string_agg(..) column will be null if there are no associated value objects + string_agg(distinct concat_ws('=', hip.priority, hip.address), '|') as ip_addresses, + string_agg(distinct concat_ws('=', hdns.priority, hdns.name), '|') as dns_names + 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 + group by h.public_id, hc.plugin_id; + comment on view host_plugin_host_with_value_obj is + 'host plugin host with its associated value objects'; + commit; diff --git a/internal/db/schema/migrations/oss/postgres/17/05_preferred_endpoints.up.sql b/internal/db/schema/migrations/oss/postgres/17/06_preferred_endpoints.up.sql similarity index 95% rename from internal/db/schema/migrations/oss/postgres/17/05_preferred_endpoints.up.sql rename to internal/db/schema/migrations/oss/postgres/17/06_preferred_endpoints.up.sql index eafa2c5e84..aedabd48e0 100644 --- a/internal/db/schema/migrations/oss/postgres/17/05_preferred_endpoints.up.sql +++ b/internal/db/schema/migrations/oss/postgres/17/06_preferred_endpoints.up.sql @@ -7,9 +7,7 @@ create table host_set_preferred_endpoint ( references host_set(public_id) on delete cascade on update cascade, - priority int not null - constraint priority_must_be_greater_than_zero - check(priority > 0), + priority wt_priority, condition text not null constraint condition_must_not_be_too_short check(length(trim(condition)) > 4) -- minimum is 'dns:*' diff --git a/internal/db/schema/migrations/oss/postgres/17/06_wh_session_dimensions.up.sql b/internal/db/schema/migrations/oss/postgres/17/07_wh_session_dimensions.up.sql similarity index 100% rename from internal/db/schema/migrations/oss/postgres/17/06_wh_session_dimensions.up.sql rename to internal/db/schema/migrations/oss/postgres/17/07_wh_session_dimensions.up.sql diff --git a/internal/host/host.go b/internal/host/host.go index 02bc579635..1c54995fb4 100644 --- a/internal/host/host.go +++ b/internal/host/host.go @@ -20,4 +20,6 @@ type Host interface { boundary.Resource GetCatalogId() string GetAddress() string + GetIpAddresses() []string + GetDnsNames() []string } diff --git a/internal/host/host_dns_address.go b/internal/host/host_dns_address.go new file mode 100644 index 0000000000..a038478b00 --- /dev/null +++ b/internal/host/host_dns_address.go @@ -0,0 +1,76 @@ +package host + +import ( + "context" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/store" + "google.golang.org/protobuf/proto" +) + +// NOTE: Tests for this are in the plugin subtype directory in host_address_test.go + +const defaultDnsNameTableName = "host_dns_name" + +type DnsName struct { + *store.DnsName + tableName string `gorm:"-"` +} + +func NewDnsName(ctx context.Context, hostId string, priority uint32, name string) (*DnsName, error) { + const op = "host.NewDnsName" + dn := &DnsName{ + DnsName: &store.DnsName{ + HostId: hostId, + Name: name, + Priority: priority, + }, + } + if err := dn.validate(ctx, op); err != nil { + return nil, err + } + return dn, nil +} + +// validate the host dns name. On success, it will return nil. +func (dn *DnsName) validate(ctx context.Context, caller errors.Op) error { + if dn.HostId == "" { + return errors.New(ctx, errors.InvalidParameter, caller, "missing host id") + } + if dn.Priority < 1 { + return errors.New(ctx, errors.InvalidParameter, caller, "invalid priority value") + } + if dn.Name == "" { + return errors.New(ctx, errors.InvalidParameter, caller, "missing dns name") + } + + return nil +} + +// allocDnsName make an empty one in memory. +func allocDnsName() DnsName { + return DnsName{ + DnsName: &store.DnsName{}, + } +} + +// Clone an DnsName +func (c *DnsName) Clone() *DnsName { + cp := proto.Clone(c.DnsName) + return &DnsName{ + DnsName: cp.(*store.DnsName), + } +} + +// TableName returns the table name. +func (c *DnsName) TableName() string { + if c.tableName != "" { + return c.tableName + } + return defaultDnsNameTableName +} + +// SetTableName sets the table name. +func (c *DnsName) SetTableName(n string) { + c.tableName = n +} diff --git a/internal/host/host_ip_address.go b/internal/host/host_ip_address.go new file mode 100644 index 0000000000..d0644276e4 --- /dev/null +++ b/internal/host/host_ip_address.go @@ -0,0 +1,81 @@ +package host + +import ( + "context" + "net" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/store" + "google.golang.org/protobuf/proto" +) + +// NOTE: Tests for this are in the plugin subtype directory in host_address_test.go + +const defaultIpAddressTableName = "host_ip_address" + +type IpAddress struct { + *store.IpAddress + tableName string `gorm:"-"` +} + +func NewIpAddress(ctx context.Context, hostId string, priority uint32, address string) (*IpAddress, error) { + const op = "host.NewIpAddress" + + ia := &IpAddress{ + IpAddress: &store.IpAddress{ + HostId: hostId, + Address: address, + Priority: priority, + }, + } + if err := ia.validate(ctx, op); err != nil { + return nil, err + } + return ia, nil +} + +// validate the host ip address. On success, it will return nil. +func (ia *IpAddress) validate(ctx context.Context, caller errors.Op) error { + if ia.HostId == "" { + return errors.New(ctx, errors.InvalidParameter, caller, "missing host id") + } + if ia.Priority < 1 { + return errors.New(ctx, errors.InvalidParameter, caller, "invalid priority value") + } + if ia.Address == "" { + return errors.New(ctx, errors.InvalidParameter, caller, "missing ip address") + } + if ip := net.ParseIP(ia.Address); ip == nil { + return errors.New(ctx, errors.InvalidParameter, caller, "given address is not an ip address") + } + + return nil +} + +// allocIpAddress make an empty one in memory. +func allocIpAddress() IpAddress { + return IpAddress{ + IpAddress: &store.IpAddress{}, + } +} + +// Clone an IpAddress +func (c *IpAddress) Clone() *IpAddress { + cp := proto.Clone(c.IpAddress) + return &IpAddress{ + IpAddress: cp.(*store.IpAddress), + } +} + +// TableName returns the table name. +func (c *IpAddress) TableName() string { + if c.tableName != "" { + return c.tableName + } + return defaultIpAddressTableName +} + +// SetTableName sets the table name. +func (c *IpAddress) SetTableName(n string) { + c.tableName = n +} diff --git a/internal/host/plugin/host.go b/internal/host/plugin/host.go index 96b431dfb8..197eacef9e 100644 --- a/internal/host/plugin/host.go +++ b/internal/host/plugin/host.go @@ -2,9 +2,14 @@ package plugin import ( "context" + "fmt" + "sort" + "strconv" + "strings" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/host/plugin/store" - "github.com/hashicorp/boundary/internal/oplog" "google.golang.org/protobuf/proto" ) @@ -14,27 +19,42 @@ import ( // field for this host's host catalog. type Host struct { *store.Host + PluginId string `gorm:"-"` tableName string `gorm:"-"` } // newHost creates a new in memory Host assigned to catalogId with an address. -// Name and description are the only valid options. All other options are -// ignored. -func newHost(ctx context.Context, catalogId, address string, opt ...Option) *Host { +// Supported options: WithName, WithDescription, WithIpAddresses, WithDnsNames, +// WithPluginId. Others ignored. +func newHost(ctx context.Context, catalogId, externalId string, opt ...Option) *Host { opts := getOpts(opt...) h := &Host{ + PluginId: opts.withPluginId, Host: &store.Host{ CatalogId: catalogId, + ExternalId: externalId, Name: opts.withName, Description: opts.withDescription, - Address: address, }, } + if len(opts.withIpAddresses) > 0 { + h.IpAddresses = make([]string, 0, len(opts.withIpAddresses)) + h.IpAddresses = append(h.IpAddresses, opts.withIpAddresses...) + } + if len(opts.withDnsNames) > 0 { + h.DnsNames = make([]string, 0, len(opts.withDnsNames)) + h.DnsNames = append(h.DnsNames, opts.withDnsNames...) + } return h } +// For compatibility with the general Host type +func (h *Host) GetAddress() string { + return "" +} + // TableName returns the table name for the host set. func (s *Host) TableName() string { if s.tableName != "" { @@ -55,22 +75,111 @@ func allocHost() *Host { } } -func (s *Host) clone() *Host { - cp := proto.Clone(s.Host) - hs := &Host{ - Host: cp.(*store.Host), +func (h *Host) clone() *Host { + cp := proto.Clone(h.Host) + return &Host{ + PluginId: h.PluginId, + Host: cp.(*store.Host), } - return hs } -func (s *Host) oplog(op oplog.OpType) oplog.Metadata { - metadata := oplog.Metadata{ - "resource-public-id": []string{s.PublicId}, - "resource-type": []string{"plugin-host"}, - "op-type": []string{op.String()}, +// hostAgg is a view that aggregates the host's value objects in to +// string fields delimited with the aggregateDelimiter of "|" +type hostAgg struct { + PublicId string `gorm:"primary_key"` + CatalogId string + ExternalId string + PluginId string + Name string + Description string + CreateTime *timestamp.Timestamp + UpdateTime *timestamp.Timestamp + IpAddresses string + DnsNames string +} + +func (agg *hostAgg) toHost(ctx context.Context) (*Host, error) { + const op = "plugin.(hostAgg).toHost" + const aggregateDelimiter = "|" + const priorityDelimiter = "=" + h := allocHost() + h.PublicId = agg.PublicId + h.CatalogId = agg.CatalogId + h.ExternalId = agg.ExternalId + h.PluginId = agg.PluginId + h.Name = agg.Name + h.Description = agg.Description + h.CreateTime = agg.CreateTime + h.UpdateTime = agg.UpdateTime + + // This function is used to protect against someone messing with the order + // in the DB by doing some validation + prioritySortFunc := func(in []string) error { + var sortErr error + sort.Slice(in, func(i, j int) bool { + ini := strings.Split(in[i], priorityDelimiter) + if len(ini) != 2 { + sortErr = errors.New(ctx, errors.NotSpecificIntegrity, op, fmt.Sprintf("value %s had unexpected fields", in[i])) + return false + } + inj := strings.Split(in[j], priorityDelimiter) + if len(inj) != 2 { + sortErr = errors.New(ctx, errors.NotSpecificIntegrity, op, fmt.Sprintf("value %s had unexpected fields", in[j])) + return false + } + indexi, err := strconv.Atoi(ini[0]) + if err != nil { + sortErr = errors.Wrap(ctx, err, op) + return false + } + indexj, err := strconv.Atoi(inj[0]) + if err != nil { + sortErr = errors.Wrap(ctx, err, op) + return false + } + return indexi < indexj + }) + return sortErr } - if s.CatalogId != "" { - metadata["catalog-id"] = []string{s.CatalogId} + + if agg.IpAddresses != "" { + ips := strings.Split(agg.IpAddresses, aggregateDelimiter) + if len(ips) > 0 { + if err := prioritySortFunc(ips); err != nil { + return nil, err + } + for i, ip := range ips { + // At this point they're in the correct order, but we still + // have to strip off the priority + ips[i] = strings.Split(ip, priorityDelimiter)[1] + } + h.IpAddresses = ips + } + } + + if agg.DnsNames != "" { + names := strings.Split(agg.DnsNames, aggregateDelimiter) + if len(names) > 0 { + if err := prioritySortFunc(names); err != nil { + return nil, err + } + for i, name := range names { + // At this point they're in the correct order, but we still + // have to strip off the priority + names[i] = strings.Split(name, priorityDelimiter)[1] + } + h.DnsNames = names + } } - return metadata + + return h, nil +} + +// TableName returns the table name for gorm +func (agg *hostAgg) TableName() string { + return "host_plugin_host_with_value_obj" +} + +func (agg *hostAgg) GetPublicId() string { + return agg.PublicId } diff --git a/internal/host/plugin/host_address_test.go b/internal/host/plugin/host_address_test.go new file mode 100644 index 0000000000..e4dc1079fe --- /dev/null +++ b/internal/host/plugin/host_address_test.go @@ -0,0 +1,548 @@ +package plugin + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/host" + "github.com/hashicorp/boundary/internal/host/store" + "github.com/hashicorp/boundary/internal/iam" + hostplugin "github.com/hashicorp/boundary/internal/plugin/host" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHostDnsName_Create(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := hostplugin.TestPlugin(t, conn, "test") + cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + host1 := testHost(t, conn, cat.GetPublicId(), "external") + + type args struct { + hostId string + name string + priority uint32 + } + + tests := []struct { + name string + args args + want *host.DnsName + wantNewErr bool + skipNewFunc bool + wantDbErr bool + }{ + { + name: "blank-host-id-validate", + args: args{ + hostId: "", + name: "foo.bar.com", + priority: 1, + }, + wantNewErr: true, + }, + { + name: "blank-name-validate", + args: args{ + hostId: host1.GetPublicId(), + name: "", + priority: 1, + }, + wantNewErr: true, + }, + { + name: "blank-priority-validate", + args: args{ + hostId: host1.GetPublicId(), + name: "foo.bar.com", + }, + wantNewErr: true, + }, + { + name: "blank-host-id-db", + args: args{ + name: "foo.bar.com", + priority: 1, + }, + skipNewFunc: true, + wantDbErr: true, + }, + { + name: "blank-name-db", + args: args{ + hostId: host1.GetPublicId(), + priority: 1, + }, + skipNewFunc: true, + wantDbErr: true, + }, + { + name: "blank-priority-db", + args: args{ + hostId: host1.GetPublicId(), + name: "foo.bar.com", + }, + skipNewFunc: true, + wantDbErr: true, + }, + { + name: "valid", + args: args{ + hostId: host1.GetPublicId(), + name: "foo.bar.com", + priority: 1, + }, + want: &host.DnsName{ + DnsName: &store.DnsName{ + HostId: host1.GetPublicId(), + Name: "foo.bar.com", + Priority: 1, + }, + }, + }, + { + name: "duplicate-name", + args: args{ + hostId: host1.GetPublicId(), + name: "foo.bar.com", + priority: 2, + }, + want: &host.DnsName{ + DnsName: &store.DnsName{ + HostId: host1.GetPublicId(), + Name: "foo.bar.com", + Priority: 2, + }, + }, + wantDbErr: true, + }, + { + name: "duplicate-priority", + args: args{ + hostId: host1.GetPublicId(), + name: "baz.bar.com", + priority: 1, + }, + want: &host.DnsName{ + DnsName: &store.DnsName{ + HostId: host1.GetPublicId(), + Name: "baz.bar.com", + Priority: 1, + }, + }, + wantDbErr: true, + }, + { + name: "valid-second", + args: args{ + hostId: host1.GetPublicId(), + name: "baz.bar.com", + priority: 2, + }, + want: &host.DnsName{ + DnsName: &store.DnsName{ + HostId: host1.GetPublicId(), + Name: "baz.bar.com", + Priority: 2, + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + var got *host.DnsName + var err error + if !tt.skipNewFunc { + got, err = host.NewDnsName(ctx, tt.args.hostId, tt.args.priority, tt.args.name) + if tt.wantNewErr { + require.Error(err) + return + } + require.NoError(err) + require.Equal(tt.want, got) + } else { + got = &host.DnsName{ + DnsName: &store.DnsName{ + HostId: tt.args.hostId, + Name: tt.args.name, + Priority: tt.args.priority, + }, + } + } + + require.NotNil(got) + err = w.Create(ctx, got) + if tt.wantDbErr { + require.Error(err) + return + } + require.NoError(err) + }) + } +} + +func TestHostIpAddress_Create(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := hostplugin.TestPlugin(t, conn, "test") + cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + host1 := testHost(t, conn, cat.GetPublicId(), "external") + + type args struct { + hostId string + address string + priority uint32 + } + + tests := []struct { + name string + args args + want *host.IpAddress + wantNewErr bool + skipNewFunc bool + wantDbErr bool + }{ + { + name: "blank-host-id-validate", + args: args{ + hostId: "", + address: "1.2.3.4", + priority: 1, + }, + wantNewErr: true, + }, + { + name: "blank-name-validate", + args: args{ + hostId: host1.GetPublicId(), + address: "", + priority: 1, + }, + wantNewErr: true, + }, + { + name: "blank-priority-validate", + args: args{ + hostId: host1.GetPublicId(), + address: "1.2.3.4", + }, + wantNewErr: true, + }, + { + name: "bad-address-validate", + args: args{ + hostId: host1.GetPublicId(), + address: "foo.bar.com", + }, + wantNewErr: true, + }, + { + name: "blank-host-id-db", + args: args{ + address: "1.2.3.4", + priority: 1, + }, + skipNewFunc: true, + wantDbErr: true, + }, + { + name: "blank-address-db", + args: args{ + hostId: host1.GetPublicId(), + priority: 1, + }, + skipNewFunc: true, + wantDbErr: true, + }, + { + name: "blank-priority-db", + args: args{ + hostId: host1.GetPublicId(), + address: "1.2.3.4", + }, + skipNewFunc: true, + wantDbErr: true, + }, + { + name: "valid", + args: args{ + hostId: host1.GetPublicId(), + address: "1.2.3.4", + priority: 1, + }, + want: &host.IpAddress{ + IpAddress: &store.IpAddress{ + HostId: host1.GetPublicId(), + Address: "1.2.3.4", + Priority: 1, + }, + }, + }, + { + name: "duplicate-name", + args: args{ + hostId: host1.GetPublicId(), + address: "1.2.3.4", + priority: 2, + }, + want: &host.IpAddress{ + IpAddress: &store.IpAddress{ + HostId: host1.GetPublicId(), + Address: "1.2.3.4", + Priority: 2, + }, + }, + wantDbErr: true, + }, + { + name: "duplicate-priority", + args: args{ + hostId: host1.GetPublicId(), + address: "2.3.4.5", + priority: 1, + }, + want: &host.IpAddress{ + IpAddress: &store.IpAddress{ + HostId: host1.GetPublicId(), + Address: "2.3.4.5", + Priority: 1, + }, + }, + wantDbErr: true, + }, + { + name: "valid-second", + args: args{ + hostId: host1.GetPublicId(), + address: "2.3.4.5", + priority: 2, + }, + want: &host.IpAddress{ + IpAddress: &store.IpAddress{ + HostId: host1.GetPublicId(), + Address: "2.3.4.5", + Priority: 2, + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + var got *host.IpAddress + var err error + if !tt.skipNewFunc { + got, err = host.NewIpAddress(ctx, tt.args.hostId, tt.args.priority, tt.args.address) + if tt.wantNewErr { + require.Error(err) + return + } + require.NoError(err) + require.Equal(tt.want, got) + } else { + got = &host.IpAddress{ + IpAddress: &store.IpAddress{ + HostId: tt.args.hostId, + Address: tt.args.address, + Priority: tt.args.priority, + }, + } + } + + require.NotNil(got) + err = w.Create(ctx, got) + if tt.wantDbErr { + require.Error(err) + return + } + require.NoError(err) + }) + } +} + +func TestHostDnsName_Delete(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := hostplugin.TestPlugin(t, conn, "test") + cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + host1 := testHost(t, conn, cat.GetPublicId(), "external") + addr1, err := host.NewDnsName(ctx, host1.GetPublicId(), 1, "addr1.foo.com") + require.NoError(t, err) + require.NoError(t, w.Create(ctx, addr1)) + + type args struct { + hostId string + name string + priority uint32 + } + + tests := []struct { + name string + args args + wantDelete bool + wantError bool + }{ + { + name: "wrong_host_id", + args: args{ + hostId: "something", + name: addr1.GetName(), + priority: 1, + }, + }, + { + name: "missing_priority", + args: args{ + hostId: addr1.GetHostId(), + name: addr1.GetName(), + }, + wantError: true, + }, + { + name: "wrong_priority", + args: args{ + hostId: addr1.GetHostId(), + name: addr1.GetName(), + priority: 2, + }, + }, + { + name: "valid", + args: args{ + hostId: addr1.GetHostId(), + name: addr1.GetName(), + priority: 1, + }, + wantDelete: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got := &host.DnsName{ + DnsName: &store.DnsName{ + HostId: tt.args.hostId, + Name: tt.args.name, + Priority: tt.args.priority, + }, + } + require.NoError(t, err) + require.NotNil(t, got) + k, err := w.Delete(ctx, got) + if tt.wantError { + assert.Error(t, err) + return + } + require.NoError(t, err) + if tt.wantDelete { + assert.Equal(t, 1, k) + } else { + assert.Equal(t, 0, k) + } + }) + } +} + +func TestHostIpAddress_Delete(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + w := db.New(conn) + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + plg := hostplugin.TestPlugin(t, conn, "test") + cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + host1 := testHost(t, conn, cat.GetPublicId(), "external") + addr1, err := host.NewIpAddress(ctx, host1.GetPublicId(), 1, "1.2.3.4") + require.NoError(t, err) + require.NoError(t, w.Create(ctx, addr1)) + + type args struct { + hostId string + address string + priority uint32 + } + + tests := []struct { + name string + args args + wantDelete bool + wantError bool + }{ + { + name: "wrong_host_id", + args: args{ + hostId: "something", + address: addr1.GetAddress(), + priority: 1, + }, + }, + { + name: "missing_priority", + args: args{ + hostId: addr1.GetHostId(), + address: addr1.GetAddress(), + }, + wantError: true, + }, + { + name: "wrong_priority", + args: args{ + hostId: addr1.GetHostId(), + address: addr1.GetAddress(), + priority: 2, + }, + }, + { + name: "valid", + args: args{ + hostId: addr1.GetHostId(), + address: addr1.GetAddress(), + priority: 1, + }, + wantDelete: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got := &host.IpAddress{ + IpAddress: &store.IpAddress{ + HostId: tt.args.hostId, + Address: tt.args.address, + Priority: tt.args.priority, + }, + } + require.NoError(t, err) + require.NotNil(t, got) + k, err := w.Delete(ctx, got) + if tt.wantError { + assert.Error(t, err) + return + } + require.NoError(t, err) + if tt.wantDelete { + assert.Equal(t, 1, k) + } else { + assert.Equal(t, 0, k) + } + }) + } +} diff --git a/internal/host/plugin/host_test.go b/internal/host/plugin/host_test.go index ee7bd501c2..b82a5c3f81 100644 --- a/internal/host/plugin/host_test.go +++ b/internal/host/plugin/host_test.go @@ -21,9 +21,9 @@ func TestHost_Create(t *testing.T) { cat2 := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) type args struct { - catalogId string - address string - opts []Option + catalogId string + externalId string + opts []Option } tests := []struct { @@ -35,16 +35,16 @@ func TestHost_Create(t *testing.T) { { name: "blank-catalogId", args: args{ - catalogId: "", - address: "foo.bar.com", + catalogId: "", + externalId: "external_id", }, want: &Host{Host: &store.Host{ - Address: "foo.bar.com", + ExternalId: "external_id", }}, wantErr: true, }, { - name: "blank-address", + name: "blank-external-id", args: args{ catalogId: cat.GetPublicId(), }, @@ -56,47 +56,47 @@ func TestHost_Create(t *testing.T) { { name: "valid-no-options", args: args{ - catalogId: cat.GetPublicId(), - address: "foo.bar.com", + catalogId: cat.GetPublicId(), + externalId: "valid-no-options", }, want: &Host{ Host: &store.Host{ - CatalogId: cat.GetPublicId(), - Address: "foo.bar.com", + CatalogId: cat.GetPublicId(), + ExternalId: "valid-no-options", }, }, }, { name: "valid-with-name", args: args{ - catalogId: cat.GetPublicId(), - address: "foo.bar.com", + catalogId: cat.GetPublicId(), + externalId: "valid-with-name", opts: []Option{ WithName("test-name"), }, }, want: &Host{ Host: &store.Host{ - CatalogId: cat.GetPublicId(), - Address: "foo.bar.com", - Name: "test-name", + CatalogId: cat.GetPublicId(), + ExternalId: "valid-with-name", + Name: "test-name", }, }, }, { name: "duplicate-name", args: args{ - catalogId: cat.GetPublicId(), - address: "foo.bar.com", + catalogId: cat.GetPublicId(), + externalId: "duplicate-name", opts: []Option{ WithName("test-name"), }, }, want: &Host{ Host: &store.Host{ - CatalogId: cat.GetPublicId(), - Address: "foo.bar.com", - Name: "test-name", + CatalogId: cat.GetPublicId(), + ExternalId: "duplicate-name", + Name: "test-name", }, }, wantErr: true, @@ -104,25 +104,25 @@ func TestHost_Create(t *testing.T) { { name: "valid-duplicate-name-different-catalog", args: args{ - catalogId: cat2.GetPublicId(), - address: "foo.bar.com", + catalogId: cat2.GetPublicId(), + externalId: "valid-duplicate-name-different-catalog", opts: []Option{ WithName("test-name"), }, }, want: &Host{ Host: &store.Host{ - CatalogId: cat2.GetPublicId(), - Address: "foo.bar.com", - Name: "test-name", + CatalogId: cat2.GetPublicId(), + ExternalId: "valid-duplicate-name-different-catalog", + Name: "test-name", }, }, }, { name: "valid-with-description", args: args{ - catalogId: cat.GetPublicId(), - address: "foo.bar.com", + catalogId: cat.GetPublicId(), + externalId: "valid-with-description", opts: []Option{ WithDescription("test-description"), }, @@ -130,7 +130,7 @@ func TestHost_Create(t *testing.T) { want: &Host{ Host: &store.Host{ CatalogId: cat.GetPublicId(), - Address: "foo.bar.com", + ExternalId: "valid-with-description", Description: "test-description", }, }, @@ -141,7 +141,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.address, 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) @@ -163,6 +163,9 @@ func TestHost_Create(t *testing.T) { } } +// TODO: Test Deletion directly of host, of set membership, and of cascading +// from the set + func TestHost_SetTableName(t *testing.T) { defaultTableName := "host_plugin_host" tests := []struct { @@ -200,3 +203,15 @@ func TestHost_SetTableName(t *testing.T) { }) } } + +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) + var err error + host1.PublicId, err = newHostId(ctx, catId, externId) + require.NoError(t, err) + require.NoError(t, w.Create(ctx, host1)) + return host1 +} \ No newline at end of file diff --git a/internal/host/plugin/options.go b/internal/host/plugin/options.go index b711478cbf..0467bfa822 100644 --- a/internal/host/plugin/options.go +++ b/internal/host/plugin/options.go @@ -16,11 +16,15 @@ type Option func(*options) // options = how options are represented type options struct { + withPluginId string withName string withDescription string withAttributes *structpb.Struct withSecrets *structpb.Struct withPreferredEndpoints []string + withIpAddresses []string + withDnsNames []string + withLimit int } func getDefaultOptions() options { @@ -29,6 +33,13 @@ func getDefaultOptions() options { } } +// WithPluginId provides an optional plugin id. +func withPluginId(with string) Option { + return func(o *options) { + o.withPluginId = with + } +} + // WithDescription provides an optional description. func WithDescription(desc string) Option { return func(o *options) { @@ -63,3 +74,26 @@ func WithPreferredEndpoints(with []string) Option { o.withPreferredEndpoints = with } } + +// withIpAddresses provides an optional list of ip addresses. +func withIpAddresses(with []string) Option { + return func(o *options) { + o.withIpAddresses = with + } +} + +// withDnsNames provides an optional list of dns names. +func withDnsNames(with []string) Option { + return func(o *options) { + o.withDnsNames = with + } +} + +// WithLimit provides an option to provide a limit. Intentionally allowing +// negative integers. If WithLimit < 0, then unlimited results are +// returned. If WithLimit == 0, then default limits are used for results. +func WithLimit(l int) Option { + return func(o *options) { + o.withLimit = l + } +} diff --git a/internal/host/plugin/options_test.go b/internal/host/plugin/options_test.go index aab698c2aa..758e3d619e 100644 --- a/internal/host/plugin/options_test.go +++ b/internal/host/plugin/options_test.go @@ -14,16 +14,40 @@ func Test_GetOpts(t *testing.T) { testOpts.withName = "test" assert.Equal(t, opts, testOpts) }) + t.Run("WithPluginId", func(t *testing.T) { + opts := getOpts(withPluginId("test")) + testOpts := getDefaultOptions() + testOpts.withPluginId = "test" + assert.Equal(t, opts, testOpts) + }) t.Run("WithDescription", func(t *testing.T) { opts := getOpts(WithDescription("test desc")) testOpts := getDefaultOptions() testOpts.withDescription = "test desc" assert.Equal(t, opts, testOpts) }) + t.Run("WithLimit", func(t *testing.T) { + opts := getOpts(WithLimit(5)) + testOpts := getDefaultOptions() + testOpts.withLimit = 5 + assert.Equal(t, opts, testOpts) + }) t.Run("WithPreferredEndpoints", func(t *testing.T) { opts := getOpts(WithPreferredEndpoints([]string{"foo"})) testOpts := getDefaultOptions() testOpts.withPreferredEndpoints = []string{"foo"} assert.EqualValues(t, opts, testOpts) }) + t.Run("withDnsNames", func(t *testing.T) { + opts := getOpts(withDnsNames([]string{"foo"})) + testOpts := getDefaultOptions() + testOpts.withDnsNames = []string{"foo"} + assert.EqualValues(t, opts, testOpts) + }) + t.Run("withIpAddresses", func(t *testing.T) { + opts := getOpts(withIpAddresses([]string{"foo"})) + testOpts := getDefaultOptions() + testOpts.withIpAddresses = []string{"foo"} + assert.EqualValues(t, opts, testOpts) + }) } diff --git a/internal/host/plugin/repository_host.go b/internal/host/plugin/repository_host.go new file mode 100644 index 0000000000..2979d87b4d --- /dev/null +++ b/internal/host/plugin/repository_host.go @@ -0,0 +1,284 @@ +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" +) + +// UpsertHost inserts phs into the repository or updates its current +// attributes/set memberships and returns Hosts. h is not changed. hc must +// contain a valid public ID and scope ID. Each ph in phs must not contain a +// PublicId but must contain an external ID. The PublicId is generated and +// assigned by this method. +// +// NOTE: If phs is empty, this assumes that there are simply no hosts that +// matched the given sets! Which means it will remove all hosts from the given +// sets. +func (r *Repository) UpsertHosts( + ctx context.Context, + hc *HostCatalog, + sets []string, + phs []*plgpb.ListHostsResponseHost, + _ ...Option) ([]*Host, error) { + const op = "plugin.(Repository).UpsertHosts" + if phs == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "nil plugin hosts") + } + for _, ph := range phs { + if ph.GetExternalId() == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing host external id") + } + } + if hc == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "nil host catalog") + } + if hc.GetPublicId() == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no catalog id") + } + if hc.GetScopeId() == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no scope id") + } + if sets == 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 + 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 i, a := range h.GetIpAddresses() { + obj, err := host.NewIpAddress(ctx, h.PublicId, uint32(i+1), 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 i, n := range h.GetDnsNames() { + obj, err := host.NewDnsName(ctx, h.PublicId, uint32(i+1), 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")) + } + + var returnedHosts []*Host + + // 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) + } + 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 + } + 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 + } + msgs = append(msgs, dnsOplogMsgs...) + } + + // FIXME: Add set memberships here + + 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, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in catalog: %s", hc.PublicId))) + } + return returnedHosts, nil +} + +// LookupHost will look up a host in the repository. If the host is not +// found, it will return nil, nil. All options are ignored. +func (r *Repository) LookupHost(ctx context.Context, publicId string, opt ...Option) (*Host, error) { + const op = "plugin.(Repository).LookupHost" + if publicId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id") + } + ha := &hostAgg{ + PublicId: publicId, + } + if err := r.reader.LookupByPublicId(ctx, ha); err != nil { + if errors.IsNotFoundError(err) { + return nil, nil + } + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", publicId))) + } + h, err := ha.toHost(ctx) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed to convert host agg for %s", publicId))) + } + return h, nil +} + +// ListHosts 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" + if catalogId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no catalog id") + } + opts := getOpts(opt...) + limit := r.defaultLimit + if opts.withLimit != 0 { + // non-zero signals an override of the default limit for the repo. + limit = opts.withLimit + } + var hostAggs []*hostAgg + err := r.reader.SearchWhere(ctx, &hostAggs, "catalog_id = ?", []interface{}{catalogId}, 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 +} diff --git a/internal/host/plugin/repository_host_set.go b/internal/host/plugin/repository_host_set.go index 63d6f6e440..06fecbeb3e 100644 --- a/internal/host/plugin/repository_host_set.go +++ b/internal/host/plugin/repository_host_set.go @@ -87,11 +87,6 @@ func (r *Repository) CreateSet(ctx context.Context, scopeId string, s *HostSet, } } - oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) - if err != nil { - return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) - } - var preferredEndpoints []interface{} if s.PreferredEndpoints != nil { preferredEndpoints = make([]interface{}, 0, len(s.PreferredEndpoints)) @@ -104,6 +99,11 @@ func (r *Repository) CreateSet(ctx context.Context, scopeId string, s *HostSet, } } + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + var returnedHostSet *HostSet _, err = r.writer.DoTx( ctx, @@ -185,6 +185,7 @@ func (r *Repository) LookupSet(ctx context.Context, publicId string, opt ...host setToReturn := sets[0] var hostIdsToReturn []string + // FIXME: change to use the database if plg != nil && opts.WithSetMembers { cat, err := r.getCatalog(ctx, setToReturn.GetCatalogId()) if err != nil { diff --git a/internal/host/plugin/repository_host_set_test.go b/internal/host/plugin/repository_host_set_test.go index 9d177a0014..cffa56bc9e 100644 --- a/internal/host/plugin/repository_host_set_test.go +++ b/internal/host/plugin/repository_host_set_test.go @@ -489,16 +489,15 @@ func TestRepository_Endpoints(t *testing.T) { }) assert.Empty(cmp.Diff(got, tt.want, protocmp.Transform())) - // TODO: Remove this once we no longer persist all host lookup calls - // when retrieving the endpoints. for _, ep := range got { h := allocHost() h.PublicId = ep.HostId require.NoError(rw.LookupByPublicId(ctx, h)) - assert.Equal(uint32(1), h.Version) assert.Equal(ep.HostId, h.PublicId) - assert.Equal(ep.Address, h.Address) + // TODO: Uncomment when we have a better way to lookup host + // with it's address + // assert.Equal(ep.Address, h.Address) assert.Equal(catalog.GetPublicId(), h.GetCatalogId()) } }) diff --git a/internal/host/plugin/repository_host_test.go b/internal/host/plugin/repository_host_test.go new file mode 100644 index 0000000000..de59125a9d --- /dev/null +++ b/internal/host/plugin/repository_host_test.go @@ -0,0 +1,245 @@ +package plugin + +import ( + "context" + "fmt" + "sort" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/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" +) + +func TestRepository_UpsertHosts(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) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, prj := iam.TestScopes(t, iamRepo) + + plg := hostplg.TestPlugin(t, conn, "create") + plgm := map[string]plgpb.HostPluginServiceClient{ + plg.GetPublicId(): NewWrappingPluginClient(&plgpb.UnimplementedHostPluginServiceServer{}), + } + + catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + const setCount int = 3 + setIds := make([]string, 0, setCount) + for i := 0; i < setCount; i++ { + set := TestSet(t, conn, kms, catalog, plgm) + setIds = append(setIds, set.GetPublicId()) + } + phs, exp := TestExternalHosts(t, catalog, setIds, setCount) + + type input struct { + catalog *HostCatalog + sets []string + phs []*plgpb.ListHostsResponseHost + exp []*Host + } + + tests := []struct { + name string + in func() *input + opts []Option + wantIsErr errors.Code + }{ + { + name: "nil-hosts", + in: func() *input { + return &input{ + catalog: catalog, + sets: setIds, + } + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "no-external-id-hosts", + in: func() *input { + testPhs, _ := TestExternalHosts(t, catalog, setIds, setCount) + testPhs[1].ExternalId = "" + return &input{ + catalog: catalog, + sets: setIds, + phs: testPhs, + } + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-catalog", + in: func() *input { + return &input{ + sets: setIds, + phs: phs, + } + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "no-catalog-id", + in: func() *input { + cat := catalog.clone() + cat.PublicId = "" + return &input{ + catalog: cat, + sets: setIds, + phs: phs, + } + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "no-scope-id", + in: func() *input { + cat := catalog.clone() + cat.ScopeId = "" + return &input{ + catalog: cat, + sets: setIds, + phs: phs, + } + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-sets", + in: func() *input { + return &input{ + catalog: catalog, + phs: phs, + } + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "no-sets", + in: func() *input { + return &input{ + catalog: catalog, + sets: make([]string, 0), + phs: phs, + } + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "valid", + in: func() *input { + return &input{ + catalog: catalog, + sets: setIds, + phs: phs, + exp: exp, + } + }, + }, + { + name: "valid-changed-values", + in: func() *input { + ph := phs[1] + e := exp[1] + newIp := testGetIpAddress(t) + newName := testGetDnsName(t) + ph.IpAddresses = append(ph.IpAddresses, newIp) + e.IpAddresses = append(e.IpAddresses, newIp) + ph.DnsNames = append(ph.DnsNames, newName) + e.DnsNames = append(e.DnsNames, newName) + // These are sorted by the repo function, so we need to match + sort.Strings(e.IpAddresses) + sort.Strings(e.DnsNames) + + ph.SetIds = ph.SetIds[0 : len(ph.SetIds)-1] + return &input{ + catalog: catalog, + sets: setIds, + phs: phs, + exp: exp, + } + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + repo, err := NewRepository(rw, rw, kms, plgm) + require.NoError(err) + require.NotNil(repo) + in := tt.in() + got, err := repo.UpsertHosts(ctx, in.catalog, in.sets, in.phs, tt.opts...) + if tt.wantIsErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err) + assert.Nil(got) + return + } + require.NoError(err, fmt.Sprintf("%v", in.catalog)) + require.NotNil(got) + + // 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))) + + // Make sure outputs match. Ignore timestamps. + assert.Empty( + cmp.Diff( + in.exp, + got, + cmpopts.IgnoreUnexported(Host{}, store.Host{}), + cmpopts.IgnoreTypes(×tamp.Timestamp{}), + cmpopts.SortSlices(func(x, y *Host) bool { + return x.GetPublicId() < y.GetPublicId() + }), + ), + ) + + // Check again, but via performing an explicit list + got, err = repo.ListHosts(ctx, in.catalog.GetPublicId()) + require.NoError(err) + require.NotNil(got) + assert.Len(got, len(in.phs)) + assert.Empty( + cmp.Diff( + in.exp, + got, + cmpopts.IgnoreUnexported(Host{}, store.Host{}), + cmpopts.IgnoreTypes(×tamp.Timestamp{}), + cmpopts.SortSlices(func(x, y *Host) bool { + return x.GetPublicId() < y.GetPublicId() + }), + ), + ) + + // Now individually call read on each host + for _, exp := range in.exp { + got, err := repo.LookupHost(ctx, exp.GetPublicId()) + require.NoError(err) + require.NotNil(got) + assert.Empty( + cmp.Diff( + exp, + got, + cmpopts.IgnoreUnexported(Host{}, store.Host{}), + cmpopts.IgnoreTypes(×tamp.Timestamp{}), + ), + ) + } + }) + } +} diff --git a/internal/host/plugin/store/host.pb.go b/internal/host/plugin/store/host.pb.go index 1f2d7dcecd..b0771bf353 100644 --- a/internal/host/plugin/store/host.pb.go +++ b/internal/host/plugin/store/host.pb.go @@ -155,7 +155,7 @@ func (x *HostCatalog) GetAttributes() []byte { return nil } -type Host struct { +type HostSet struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -183,13 +183,16 @@ type Host struct { // version allows optimistic locking of the resource // @inject_tag: `gorm:"default:null"` Version uint32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` - // address is the IP Address or DNS name of the host. - // @inject_tag: `gorm:"default:null"` - Address string `protobuf:"bytes,8,opt,name=address,proto3" json:"address,omitempty" gorm:"default:null"` + // attributes is a byte field containing marshaled JSON data + // @inject_tag: `gorm:"not_null"` + Attributes []byte `protobuf:"bytes,8,opt,name=attributes,proto3" json:"attributes,omitempty" gorm:"not_null"` + // preferred_endpoints stores string preference values + // @inject_tag: `gorm:"-"` + PreferredEndpoints []string `protobuf:"bytes,9,rep,name=preferred_endpoints,json=preferredEndpoints,proto3" json:"preferred_endpoints,omitempty" gorm:"-"` } -func (x *Host) Reset() { - *x = Host{} +func (x *HostSet) Reset() { + *x = HostSet{} if protoimpl.UnsafeEnabled { mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -197,13 +200,13 @@ func (x *Host) Reset() { } } -func (x *Host) String() string { +func (x *HostSet) String() string { return protoimpl.X.MessageStringOf(x) } -func (*Host) ProtoMessage() {} +func (*HostSet) ProtoMessage() {} -func (x *Host) ProtoReflect() protoreflect.Message { +func (x *HostSet) ProtoReflect() protoreflect.Message { mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -215,68 +218,178 @@ func (x *Host) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Host.ProtoReflect.Descriptor instead. -func (*Host) Descriptor() ([]byte, []int) { +// Deprecated: Use HostSet.ProtoReflect.Descriptor instead. +func (*HostSet) Descriptor() ([]byte, []int) { return file_controller_storage_host_plugin_store_v1_host_proto_rawDescGZIP(), []int{1} } -func (x *Host) GetPublicId() string { +func (x *HostSet) GetPublicId() string { if x != nil { return x.PublicId } return "" } -func (x *Host) GetCreateTime() *timestamp.Timestamp { +func (x *HostSet) GetCreateTime() *timestamp.Timestamp { if x != nil { return x.CreateTime } return nil } -func (x *Host) GetUpdateTime() *timestamp.Timestamp { +func (x *HostSet) GetUpdateTime() *timestamp.Timestamp { if x != nil { return x.UpdateTime } return nil } -func (x *Host) GetName() string { +func (x *HostSet) GetName() string { if x != nil { return x.Name } return "" } -func (x *Host) GetDescription() string { +func (x *HostSet) GetDescription() string { if x != nil { return x.Description } return "" } -func (x *Host) GetCatalogId() string { +func (x *HostSet) GetCatalogId() string { if x != nil { return x.CatalogId } return "" } -func (x *Host) GetVersion() uint32 { +func (x *HostSet) GetVersion() uint32 { if x != nil { return x.Version } return 0 } -func (x *Host) GetAddress() string { +func (x *HostSet) GetAttributes() []byte { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *HostSet) GetPreferredEndpoints() []string { if x != nil { - return x.Address + return x.PreferredEndpoints + } + return nil +} + +type HostCatalogSecret struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // catalog_id is the public id of the catalog containing this secret. + // @inject_tag: `gorm:"primary_key"` + CatalogId string `protobuf:"bytes,1,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty" gorm:"primary_key"` + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` + // attributes is the plain-text of the attribute data. We are not storing + // this plain-text value in the database. + // @inject_tag: `gorm:"-" wrapping:"pt,secret_data"` + Secret []byte `protobuf:"bytes,4,opt,name=secret,proto3" json:"secret,omitempty" gorm:"-" wrapping:"pt,secret_data"` + // ct_attributes is the ciphertext of the attribute data stored in the db. + // @inject_tag: `gorm:"column:secret;not_null" wrapping:"ct,secret_data"` + CtSecret []byte `protobuf:"bytes,5,opt,name=ct_secret,json=ctSecret,proto3" json:"ct_secret,omitempty" gorm:"column:secret;not_null" wrapping:"ct,secret_data"` + // The key_id of the kms database key used for encrypting this entry. + // It must be set. + // @inject_tag: `gorm:"not_null"` + KeyId string `protobuf:"bytes,6,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"not_null"` +} + +func (x *HostCatalogSecret) Reset() { + *x = HostCatalogSecret{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HostCatalogSecret) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HostCatalogSecret) ProtoMessage() {} + +func (x *HostCatalogSecret) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[2] + 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 HostCatalogSecret.ProtoReflect.Descriptor instead. +func (*HostCatalogSecret) Descriptor() ([]byte, []int) { + return file_controller_storage_host_plugin_store_v1_host_proto_rawDescGZIP(), []int{2} +} + +func (x *HostCatalogSecret) GetCatalogId() string { + if x != nil { + return x.CatalogId } return "" } -type HostSet struct { +func (x *HostCatalogSecret) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *HostCatalogSecret) GetUpdateTime() *timestamp.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *HostCatalogSecret) GetSecret() []byte { + if x != nil { + return x.Secret + } + return nil +} + +func (x *HostCatalogSecret) GetCtSecret() []byte { + if x != nil { + return x.CtSecret + } + return nil +} + +func (x *HostCatalogSecret) GetKeyId() string { + if x != nil { + return x.KeyId + } + return "" +} + +// TODO: Add a field which tracks if the host in cache should be considered +// invalid and fall back to the plugin provided data. +type Host struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -284,51 +397,55 @@ type HostSet struct { // public_id is a surrogate key suitable for use in a public API. // @inject_tag: `gorm:"primary_key"` PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // external_id is an id provided by the plugin. + // @inject_tag: `gorm:"not_null"` + ExternalId string `protobuf:"bytes,2,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty" gorm:"not_null"` // The create_time is set by the database. // @inject_tag: `gorm:"default:current_timestamp"` - CreateTime *timestamp.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` // The update_time is set by the database. // @inject_tag: `gorm:"default:current_timestamp"` - UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` + UpdateTime *timestamp.Timestamp `protobuf:"bytes,4,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` // name is optional. If set, it must be unique within // catalog_id. // @inject_tag: `gorm:"default:null"` - Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"` + Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"` // description is optional. // @inject_tag: `gorm:"default:null"` - Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"` + Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"` // catalog_id is the public_id of the owning // plugin_host_catalog and must be set. // @inject_tag: `gorm:"not_null"` - CatalogId string `protobuf:"bytes,6,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty" gorm:"not_null"` - // version allows optimistic locking of the resource - // @inject_tag: `gorm:"default:null"` - Version uint32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` - // attributes is a byte field containing marshaled JSON data - // @inject_tag: `gorm:"not_null"` - Attributes []byte `protobuf:"bytes,8,opt,name=attributes,proto3" json:"attributes,omitempty" gorm:"not_null"` - // preferred_endpoints stores string preference values + CatalogId string `protobuf:"bytes,7,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty" gorm:"not_null"` // @inject_tag: `gorm:"-"` - PreferredEndpoints []string `protobuf:"bytes,9,rep,name=preferred_endpoints,json=preferredEndpoints,proto3" json:"preferred_endpoints,omitempty" gorm:"-"` + Version uint32 `protobuf:"varint,8,opt,name=version,proto3" json:"version,omitempty" gorm:"-"` + // ip_addresses are the ip addresses associated with this host and will + // be persisted in the db through the HostAddress message. + // @inject_tag: `gorm:"-"` + IpAddresses []string `protobuf:"bytes,9,rep,name=ip_addresses,json=ipAddresses,proto3" json:"ip_addresses,omitempty" gorm:"-"` + // dns_names are the dns names associated with this host and will + // be persisted in the db through the HostAddress message. + // @inject_tag: `gorm:"-"` + DnsNames []string `protobuf:"bytes,10,rep,name=dns_names,json=dnsNames,proto3" json:"dns_names,omitempty" gorm:"-"` } -func (x *HostSet) Reset() { - *x = HostSet{} +func (x *Host) Reset() { + *x = Host{} if protoimpl.UnsafeEnabled { - mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[2] + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *HostSet) String() string { +func (x *Host) String() string { return protoimpl.X.MessageStringOf(x) } -func (*HostSet) ProtoMessage() {} +func (*Host) ProtoMessage() {} -func (x *HostSet) ProtoReflect() protoreflect.Message { - mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[2] +func (x *Host) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -339,118 +456,111 @@ func (x *HostSet) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use HostSet.ProtoReflect.Descriptor instead. -func (*HostSet) Descriptor() ([]byte, []int) { - return file_controller_storage_host_plugin_store_v1_host_proto_rawDescGZIP(), []int{2} +// Deprecated: Use Host.ProtoReflect.Descriptor instead. +func (*Host) Descriptor() ([]byte, []int) { + return file_controller_storage_host_plugin_store_v1_host_proto_rawDescGZIP(), []int{3} } -func (x *HostSet) GetPublicId() string { +func (x *Host) GetPublicId() string { if x != nil { return x.PublicId } return "" } -func (x *HostSet) GetCreateTime() *timestamp.Timestamp { +func (x *Host) GetExternalId() string { + if x != nil { + return x.ExternalId + } + return "" +} + +func (x *Host) GetCreateTime() *timestamp.Timestamp { if x != nil { return x.CreateTime } return nil } -func (x *HostSet) GetUpdateTime() *timestamp.Timestamp { +func (x *Host) GetUpdateTime() *timestamp.Timestamp { if x != nil { return x.UpdateTime } return nil } -func (x *HostSet) GetName() string { +func (x *Host) GetName() string { if x != nil { return x.Name } return "" } -func (x *HostSet) GetDescription() string { +func (x *Host) GetDescription() string { if x != nil { return x.Description } return "" } -func (x *HostSet) GetCatalogId() string { +func (x *Host) GetCatalogId() string { if x != nil { return x.CatalogId } return "" } -func (x *HostSet) GetVersion() uint32 { +func (x *Host) GetVersion() uint32 { if x != nil { return x.Version } return 0 } -func (x *HostSet) GetAttributes() []byte { +func (x *Host) GetIpAddresses() []string { if x != nil { - return x.Attributes + return x.IpAddresses } return nil } -func (x *HostSet) GetPreferredEndpoints() []string { +func (x *Host) GetDnsNames() []string { if x != nil { - return x.PreferredEndpoints + return x.DnsNames } return nil } -type HostCatalogSecret struct { +type HostSetMember struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // catalog_id is the public id of the catalog containing this secret. // @inject_tag: `gorm:"primary_key"` - CatalogId string `protobuf:"bytes,1,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty" gorm:"primary_key"` - // The create_time is set by the database. - // @inject_tag: `gorm:"default:current_timestamp"` - CreateTime *timestamp.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` - // The update_time is set by the database. - // @inject_tag: `gorm:"default:current_timestamp"` - UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` - // attributes is the plain-text of the attribute data. We are not storing - // this plain-text value in the database. - // @inject_tag: `gorm:"-" wrapping:"pt,secret_data"` - Secret []byte `protobuf:"bytes,4,opt,name=secret,proto3" json:"secret,omitempty" gorm:"-" wrapping:"pt,secret_data"` - // ct_attributes is the ciphertext of the attribute data stored in the db. - // @inject_tag: `gorm:"column:secret;not_null" wrapping:"ct,secret_data"` - CtSecret []byte `protobuf:"bytes,5,opt,name=ct_secret,json=ctSecret,proto3" json:"ct_secret,omitempty" gorm:"column:secret;not_null" wrapping:"ct,secret_data"` - // The key_id of the kms database key used for encrypting this entry. - // It must be set. - // @inject_tag: `gorm:"not_null"` - KeyId string `protobuf:"bytes,6,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"not_null"` + HostId string `protobuf:"bytes,1,opt,name=host_id,json=hostId,proto3" json:"host_id,omitempty" gorm:"primary_key"` + // @inject_tag: `gorm:"primary_key"` + SetId string `protobuf:"bytes,2,opt,name=set_id,json=setId,proto3" json:"set_id,omitempty" gorm:"primary_key"` + // @inject_tag: `gorm:"default:null"` + CatalogId string `protobuf:"bytes,3,opt,name=catalog_id,json=catalogId,proto3" json:"catalog_id,omitempty" gorm:"default:null"` } -func (x *HostCatalogSecret) Reset() { - *x = HostCatalogSecret{} +func (x *HostSetMember) Reset() { + *x = HostSetMember{} if protoimpl.UnsafeEnabled { - mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[3] + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *HostCatalogSecret) String() string { +func (x *HostSetMember) String() string { return protoimpl.X.MessageStringOf(x) } -func (*HostCatalogSecret) ProtoMessage() {} +func (*HostSetMember) ProtoMessage() {} -func (x *HostCatalogSecret) ProtoReflect() protoreflect.Message { - mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[3] +func (x *HostSetMember) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -461,49 +571,28 @@ func (x *HostCatalogSecret) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use HostCatalogSecret.ProtoReflect.Descriptor instead. -func (*HostCatalogSecret) Descriptor() ([]byte, []int) { - return file_controller_storage_host_plugin_store_v1_host_proto_rawDescGZIP(), []int{3} +// Deprecated: Use HostSetMember.ProtoReflect.Descriptor instead. +func (*HostSetMember) Descriptor() ([]byte, []int) { + return file_controller_storage_host_plugin_store_v1_host_proto_rawDescGZIP(), []int{4} } -func (x *HostCatalogSecret) GetCatalogId() string { +func (x *HostSetMember) GetHostId() string { if x != nil { - return x.CatalogId + return x.HostId } return "" } -func (x *HostCatalogSecret) GetCreateTime() *timestamp.Timestamp { - if x != nil { - return x.CreateTime - } - return nil -} - -func (x *HostCatalogSecret) GetUpdateTime() *timestamp.Timestamp { +func (x *HostSetMember) GetSetId() string { if x != nil { - return x.UpdateTime + return x.SetId } - return nil -} - -func (x *HostCatalogSecret) GetSecret() []byte { - if x != nil { - return x.Secret - } - return nil -} - -func (x *HostCatalogSecret) GetCtSecret() []byte { - if x != nil { - return x.CtSecret - } - return nil + return "" } -func (x *HostCatalogSecret) GetKeyId() string { +func (x *HostSetMember) GetCatalogId() string { if x != nil { - return x.KeyId + return x.CatalogId } return "" } @@ -550,77 +639,88 @@ 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, 0xc6, 0x02, 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, - 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x22, 0xb2, 0x03, 0x0a, 0x07, 0x48, 0x6f, + 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 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, 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, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x04, 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, 0x05, 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, 0x06, 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, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x22, 0xb2, 0x03, 0x0a, 0x07, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x12, 0x1b, - 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 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, 0x24, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 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, 0x06, 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, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, - 0x72, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x09, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x12, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x45, 0x6e, - 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0x98, 0x02, 0x0a, 0x11, 0x48, 0x6f, 0x73, 0x74, - 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1d, 0x0a, - 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, - 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, - 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, - 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1b, - 0x0a, 0x09, 0x63, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x08, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6b, - 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, - 0x49, 0x64, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, - 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x68, 0x6f, 0x73, - 0x74, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, 0x0c, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 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, 0x06, 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, 0x07, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, + 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x0a, + 0x13, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x70, 0x72, 0x65, 0x66, + 0x65, 0x72, 0x72, 0x65, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0x98, + 0x02, 0x0a, 0x11, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x53, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, + 0x67, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x74, 0x53, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 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, } var ( @@ -635,23 +735,24 @@ func file_controller_storage_host_plugin_store_v1_host_proto_rawDescGZIP() []byt return file_controller_storage_host_plugin_store_v1_host_proto_rawDescData } -var file_controller_storage_host_plugin_store_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_controller_storage_host_plugin_store_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_controller_storage_host_plugin_store_v1_host_proto_goTypes = []interface{}{ (*HostCatalog)(nil), // 0: controller.storage.host.plugin.store.v1.HostCatalog - (*Host)(nil), // 1: controller.storage.host.plugin.store.v1.Host - (*HostSet)(nil), // 2: controller.storage.host.plugin.store.v1.HostSet - (*HostCatalogSecret)(nil), // 3: controller.storage.host.plugin.store.v1.HostCatalogSecret - (*timestamp.Timestamp)(nil), // 4: controller.storage.timestamp.v1.Timestamp + (*HostSet)(nil), // 1: controller.storage.host.plugin.store.v1.HostSet + (*HostCatalogSecret)(nil), // 2: controller.storage.host.plugin.store.v1.HostCatalogSecret + (*Host)(nil), // 3: controller.storage.host.plugin.store.v1.Host + (*HostSetMember)(nil), // 4: controller.storage.host.plugin.store.v1.HostSetMember + (*timestamp.Timestamp)(nil), // 5: controller.storage.timestamp.v1.Timestamp } var file_controller_storage_host_plugin_store_v1_host_proto_depIdxs = []int32{ - 4, // 0: controller.storage.host.plugin.store.v1.HostCatalog.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 1: controller.storage.host.plugin.store.v1.HostCatalog.update_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 2: controller.storage.host.plugin.store.v1.Host.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 3: controller.storage.host.plugin.store.v1.Host.update_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 4: controller.storage.host.plugin.store.v1.HostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 5: controller.storage.host.plugin.store.v1.HostSet.update_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 6: controller.storage.host.plugin.store.v1.HostCatalogSecret.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // 7: controller.storage.host.plugin.store.v1.HostCatalogSecret.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 0: controller.storage.host.plugin.store.v1.HostCatalog.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 1: controller.storage.host.plugin.store.v1.HostCatalog.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 2: controller.storage.host.plugin.store.v1.HostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 3: controller.storage.host.plugin.store.v1.HostSet.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 4: controller.storage.host.plugin.store.v1.HostCatalogSecret.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 5: controller.storage.host.plugin.store.v1.HostCatalogSecret.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 6: controller.storage.host.plugin.store.v1.Host.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 7: controller.storage.host.plugin.store.v1.Host.update_time:type_name -> controller.storage.timestamp.v1.Timestamp 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 @@ -678,7 +779,7 @@ func file_controller_storage_host_plugin_store_v1_host_proto_init() { } } file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Host); i { + switch v := v.(*HostSet); i { case 0: return &v.state case 1: @@ -690,7 +791,7 @@ func file_controller_storage_host_plugin_store_v1_host_proto_init() { } } file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HostSet); i { + switch v := v.(*HostCatalogSecret); i { case 0: return &v.state case 1: @@ -702,7 +803,19 @@ func file_controller_storage_host_plugin_store_v1_host_proto_init() { } } file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HostCatalogSecret); i { + switch v := v.(*Host); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_host_plugin_store_v1_host_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HostSetMember); i { case 0: return &v.state case 1: @@ -720,7 +833,7 @@ func file_controller_storage_host_plugin_store_v1_host_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_storage_host_plugin_store_v1_host_proto_rawDesc, NumEnums: 0, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/host/plugin/testing.go b/internal/host/plugin/testing.go index a4159e724e..45d38c42e0 100644 --- a/internal/host/plugin/testing.go +++ b/internal/host/plugin/testing.go @@ -2,12 +2,18 @@ package plugin import ( "context" + "crypto/rand" + "fmt" + "io" + "net" "testing" "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/host/plugin/store" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/plugin/host" plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "github.com/hashicorp/go-secure-stdlib/base62" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -67,6 +73,65 @@ func TestSet(t *testing.T, conn *db.DB, kmsCache *kms.Kms, hc *HostCatalog, plgm return set } +func TestExternalHosts(t *testing.T, catalog *HostCatalog, setIds []string, count int) ([]*plgpb.ListHostsResponseHost, []*Host) { + t.Helper() + require := require.New(t) + retRH := make([]*plgpb.ListHostsResponseHost, 0, count) + retH := make([]*Host, 0, count) + + for i := 0; i < count; i++ { + externalId, err := base62.Random(10) + require.NoError(err) + + ipStr := testGetIpAddress(t) + dnsName := testGetDnsName(t) + + retRH = append(retRH, &plgpb.ListHostsResponseHost{ + ExternalId: externalId, + SetIds: setIds, + IpAddresses: []string{ipStr}, + DnsNames: []string{dnsName}, + }) + + publicId, err := newHostId(context.Background(), catalog.PublicId, externalId) + require.NoError(err) + + retH = append(retH, &Host{ + PluginId: catalog.PluginId, + Host: &store.Host{ + CatalogId: catalog.PublicId, + PublicId: publicId, + ExternalId: externalId, + IpAddresses: []string{ipStr}, + DnsNames: []string{dnsName}, + }, + }) + } + + return retRH, retH +} + +func testGetDnsName(t *testing.T) string { + dnsName, err := base62.Random(10) + require.NoError(t, err) + return fmt.Sprintf("%s.example.com", dnsName) +} + +func testGetIpAddress(t *testing.T) string { + ipBytes := make([]byte, 4) + for { + lr := io.LimitReader(rand.Reader, 4) + n, err := lr.Read(ipBytes) + require.NoError(t, err) + require.Equal(t, n, 4) + ip := net.IP(ipBytes) + v4 := ip.To4() + if v4 != nil { + return v4.String() + } + } +} + var _ plgpb.HostPluginServiceServer = (*TestPluginServer)(nil) // TestPluginServer provides a host plugin service server where each method can be overwritten for tests. diff --git a/internal/host/static/host.go b/internal/host/static/host.go index b37341f9e4..b367111b3d 100644 --- a/internal/host/static/host.go +++ b/internal/host/static/host.go @@ -38,6 +38,16 @@ func NewHost(catalogId string, opt ...Option) (*Host, error) { return host, nil } +// For compatibility with the general Host type +func (h *Host) GetIpAddresses() []string { + return nil +} + +// For compatibility with the general Host type +func (h *Host) GetDnsNames() []string { + return nil +} + // TableName returns the table name for the host. func (h *Host) TableName() string { if h.tableName != "" { diff --git a/internal/host/static/repository_host_set_member.go b/internal/host/static/repository_host_set_member.go index dead39500b..76b25fb99b 100644 --- a/internal/host/static/repository_host_set_member.go +++ b/internal/host/static/repository_host_set_member.go @@ -100,8 +100,11 @@ func updateVersion(ctx context.Context, w db.Writer, wrapper wrapping.Wrapper, m switch { case err != nil: return errors.Wrap(ctx, err, op) + case rowsUpdated == 0: + return errors.New(ctx, errors.RecordNotFound, op, "no matching version for host set found") case rowsUpdated > 1: return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated") + } msgs = append(msgs, setMsg) diff --git a/internal/host/store/host.pb.go b/internal/host/store/host.pb.go index a781d80827..c9c8fcaf86 100644 --- a/internal/host/store/host.pb.go +++ b/internal/host/store/host.pb.go @@ -268,6 +268,138 @@ func (x *PreferredEndpoint) GetCondition() string { return "" } +type IpAddress struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // @inject_tag: `gorm:"primary_key"` + HostId string `protobuf:"bytes,1,opt,name=host_id,json=hostId,proto3" json:"host_id,omitempty" gorm:"primary_key"` + // @inject_tag: `gorm:"primary_key"` + Priority uint32 `protobuf:"varint,2,opt,name=priority,proto3" json:"priority,omitempty" gorm:"primary_key"` + // @inject_tag: `gorm:"not_null` + Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` +} + +func (x *IpAddress) Reset() { + *x = IpAddress{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_host_store_v1_host_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IpAddress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IpAddress) ProtoMessage() {} + +func (x *IpAddress) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_host_store_v1_host_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 IpAddress.ProtoReflect.Descriptor instead. +func (*IpAddress) Descriptor() ([]byte, []int) { + return file_controller_storage_host_store_v1_host_proto_rawDescGZIP(), []int{4} +} + +func (x *IpAddress) GetHostId() string { + if x != nil { + return x.HostId + } + return "" +} + +func (x *IpAddress) GetPriority() uint32 { + if x != nil { + return x.Priority + } + return 0 +} + +func (x *IpAddress) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +type DnsName struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // @inject_tag: `gorm:"primary_key"` + HostId string `protobuf:"bytes,1,opt,name=host_id,json=hostId,proto3" json:"host_id,omitempty" gorm:"primary_key"` + // @inject_tag: `gorm:"primary_key"` + Priority uint32 `protobuf:"varint,2,opt,name=priority,proto3" json:"priority,omitempty" gorm:"primary_key"` + // @inject_tag: `gorm:"not_null` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *DnsName) Reset() { + *x = DnsName{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_host_store_v1_host_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DnsName) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DnsName) ProtoMessage() {} + +func (x *DnsName) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_host_store_v1_host_proto_msgTypes[5] + 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 DnsName.ProtoReflect.Descriptor instead. +func (*DnsName) Descriptor() ([]byte, []int) { + return file_controller_storage_host_store_v1_host_proto_rawDescGZIP(), []int{5} +} + +func (x *DnsName) GetHostId() string { + if x != nil { + return x.HostId + } + return "" +} + +func (x *DnsName) GetPriority() uint32 { + if x != nil { + return x.Priority + } + return 0 +} + +func (x *DnsName) GetName() string { + if x != nil { + return x.Name + } + return "" +} + var File_controller_storage_host_store_v1_host_proto protoreflect.FileDescriptor var file_controller_storage_host_store_v1_host_proto_rawDesc = []byte{ @@ -295,7 +427,18 @@ var file_controller_storage_host_store_v1_host_proto_rawDesc = []byte{ 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, - 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, + 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x5a, 0x0a, 0x09, 0x49, 0x70, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 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, 0x1a, + 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x22, 0x52, 0x0a, 0x07, 0x44, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 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, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x68, 0x6f, 0x73, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, @@ -314,12 +457,14 @@ func file_controller_storage_host_store_v1_host_proto_rawDescGZIP() []byte { return file_controller_storage_host_store_v1_host_proto_rawDescData } -var file_controller_storage_host_store_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_controller_storage_host_store_v1_host_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_controller_storage_host_store_v1_host_proto_goTypes = []interface{}{ (*Catalog)(nil), // 0: controller.storage.host.store.v1.Catalog (*Host)(nil), // 1: controller.storage.host.store.v1.Host (*Set)(nil), // 2: controller.storage.host.store.v1.Set (*PreferredEndpoint)(nil), // 3: controller.storage.host.store.v1.PreferredEndpoint + (*IpAddress)(nil), // 4: controller.storage.host.store.v1.IpAddress + (*DnsName)(nil), // 5: controller.storage.host.store.v1.DnsName } var file_controller_storage_host_store_v1_host_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type @@ -383,6 +528,30 @@ func file_controller_storage_host_store_v1_host_proto_init() { return nil } } + file_controller_storage_host_store_v1_host_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IpAddress); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_host_store_v1_host_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DnsName); 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{ @@ -390,7 +559,7 @@ func file_controller_storage_host_store_v1_host_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_storage_host_store_v1_host_proto_rawDesc, NumEnums: 0, - NumMessages: 4, + NumMessages: 6, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/proto/local/controller/storage/host/plugin/store/v1/host.proto b/internal/proto/local/controller/storage/host/plugin/store/v1/host.proto index 5d9722bea7..6b38203b72 100644 --- a/internal/proto/local/controller/storage/host/plugin/store/v1/host.proto +++ b/internal/proto/local/controller/storage/host/plugin/store/v1/host.proto @@ -47,42 +47,6 @@ message HostCatalog { bytes attributes = 9; } -message Host { - // public_id is a surrogate key suitable for use in a public API. - // @inject_tag: `gorm:"primary_key"` - string public_id = 1; - - // The create_time is set by the database. - // @inject_tag: `gorm:"default:current_timestamp"` - timestamp.v1.Timestamp create_time = 2; - - // The update_time is set by the database. - // @inject_tag: `gorm:"default:current_timestamp"` - timestamp.v1.Timestamp update_time = 3; - - // name is optional. If set, it must be unique within - // catalog_id. - // @inject_tag: `gorm:"default:null"` - string name = 4; - - // description is optional. - // @inject_tag: `gorm:"default:null"` - string description = 5; - - // catalog_id is the public_id of the owning - // plugin_host_catalog and must be set. - // @inject_tag: `gorm:"not_null"` - string catalog_id = 6; - - // version allows optimistic locking of the resource - // @inject_tag: `gorm:"default:null"` - uint32 version = 7; - - // address is the IP Address or DNS name of the host. - // @inject_tag: `gorm:"default:null"` - string address = 8; -} - message HostSet { // public_id is a surrogate key suitable for use in a public API. // @inject_tag: `gorm:"primary_key"` @@ -149,4 +113,62 @@ message HostCatalogSecret { // It must be set. // @inject_tag: `gorm:"not_null"` string key_id = 6; +} + +// TODO: Add a field which tracks if the host in cache should be considered +// invalid and fall back to the plugin provided data. +message Host { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 1; + + // external_id is an id provided by the plugin. + // @inject_tag: `gorm:"not_null"` + string external_id = 2; + + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 3; + + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp update_time = 4; + + // name is optional. If set, it must be unique within + // catalog_id. + // @inject_tag: `gorm:"default:null"` + string name = 5; + + // description is optional. + // @inject_tag: `gorm:"default:null"` + string description = 6; + + // catalog_id is the public_id of the owning + // plugin_host_catalog and must be set. + // @inject_tag: `gorm:"not_null"` + string catalog_id = 7; + + // @inject_tag: `gorm:"-"` + uint32 version = 8; + + // ip_addresses are the ip addresses associated with this host and will + // be persisted in the db through the HostAddress message. + // @inject_tag: `gorm:"-"` + repeated string ip_addresses = 9; + + // dns_names are the dns names associated with this host and will + // be persisted in the db through the HostAddress message. + // @inject_tag: `gorm:"-"` + repeated string dns_names = 10; +} + +message HostSetMember { + // @inject_tag: `gorm:"primary_key"` + string host_id = 1; + + // @inject_tag: `gorm:"primary_key"` + string set_id = 2; + + // @inject_tag: `gorm:"default:null"` + string catalog_id = 3; } \ No newline at end of file diff --git a/internal/proto/local/controller/storage/host/store/v1/host.proto b/internal/proto/local/controller/storage/host/store/v1/host.proto index e6123406ca..deb95f14b9 100644 --- a/internal/proto/local/controller/storage/host/store/v1/host.proto +++ b/internal/proto/local/controller/storage/host/store/v1/host.proto @@ -46,4 +46,26 @@ message PreferredEndpoint { // The string text of the preference condition // @inject_tag: `gorm:"not_null"` string condition = 3; +} + +message IpAddress { + // @inject_tag: `gorm:"primary_key"` + string host_id = 1; + + // @inject_tag: `gorm:"primary_key"` + uint32 priority = 2; + + // @inject_tag: `gorm:"not_null` + string address = 3; +} + +message DnsName { + // @inject_tag: `gorm:"primary_key"` + string host_id = 1; + + // @inject_tag: `gorm:"primary_key"` + uint32 priority = 2; + + // @inject_tag: `gorm:"not_null` + string name = 3; } \ No newline at end of file