diff --git a/internal/db/schema/migrations/postgres/17/04_plugin_host.up.sql b/internal/db/schema/migrations/postgres/17/04_plugin_host.up.sql index 3b4323de7..d3b80e48c 100644 --- a/internal/db/schema/migrations/postgres/17/04_plugin_host.up.sql +++ b/internal/db/schema/migrations/postgres/17/04_plugin_host.up.sql @@ -54,9 +54,7 @@ begin; create_time wt_timestamp, update_time wt_timestamp, version wt_version, - attributes bytea not null - constraint attributes_must_not_be_empty - check(length(attributes) > 0), + attributes bytea not null, constraint host_catalog_fkey foreign key (scope_id, public_id) references host_catalog (scope_id, public_id) @@ -126,9 +124,7 @@ begin; create_time wt_timestamp, update_time wt_timestamp, version wt_version, - attributes bytea not null - constraint attributes_must_not_be_empty - check(length(attributes) > 0), + attributes bytea not null, constraint host_plugin_set_catalog_id_name_uq unique(catalog_id, name), constraint host_set_fkey diff --git a/internal/db/schema/postgres_migration.gen.go b/internal/db/schema/postgres_migration.gen.go index 81c48f577..1c3d02a05 100644 --- a/internal/db/schema/postgres_migration.gen.go +++ b/internal/db/schema/postgres_migration.gen.go @@ -7742,9 +7742,7 @@ alter table wh_session_connection_accumulating_fact create_time wt_timestamp, update_time wt_timestamp, version wt_version, - attributes bytea not null - constraint attributes_must_not_be_empty - check(length(attributes) > 0), + attributes bytea not null, constraint host_catalog_fkey foreign key (scope_id, public_id) references host_catalog (scope_id, public_id) @@ -7814,9 +7812,7 @@ alter table wh_session_connection_accumulating_fact create_time wt_timestamp, update_time wt_timestamp, version wt_version, - attributes bytea not null - constraint attributes_must_not_be_empty - check(length(attributes) > 0), + attributes bytea not null, constraint host_plugin_set_catalog_id_name_uq unique(catalog_id, name), constraint host_set_fkey @@ -7864,7 +7860,7 @@ create table host_set_preferred_endpoint ( constraint priority_must_be_greater_than_zero check(priority > 0), condition text not null - constraint condition_must_not_be_empty + constraint condition_must_not_be_too_short check(length(trim(condition)) > 4) -- minimum is 'dns:*' constraint condition_must_not_be_too_long check(length(trim(condition)) < 255) diff --git a/internal/host/plugin/host_catalog.go b/internal/host/plugin/host_catalog.go index f64c5f743..437b5ecb4 100644 --- a/internal/host/plugin/host_catalog.go +++ b/internal/host/plugin/host_catalog.go @@ -5,12 +5,12 @@ package plugin import ( "context" - "encoding/json" "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" + "google.golang.org/protobuf/types/known/structpb" ) // A HostCatalog contains plugin host sets. It is owned by @@ -19,7 +19,7 @@ type HostCatalog struct { *store.HostCatalog tableName string `gorm:"-"` - secrets map[string]interface{} `gorm:"-"` + secrets *structpb.Struct `gorm:"-"` } // NewHostCatalog creates a new in memory HostCatalog assigned to a scopeId @@ -28,23 +28,22 @@ type HostCatalog struct { func NewHostCatalog(ctx context.Context, scopeId, pluginId string, opt ...Option) (*HostCatalog, error) { const op = "plugin.NewHostCatalog" opts := getOpts(opt...) + + attrs, err := proto.Marshal(opts.withAttributes) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter)) + } + hc := &HostCatalog{ HostCatalog: &store.HostCatalog{ ScopeId: scopeId, PluginId: pluginId, Name: opts.withName, Description: opts.withDescription, + Attributes: attrs, }, secrets: opts.withSecrets, } - - if opts.withAttributes != nil { - attrs, err := json.Marshal(opts.withAttributes) - if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter)) - } - hc.Attributes = attrs - } return hc, nil } @@ -58,16 +57,16 @@ func allocHostCatalog() *HostCatalog { // secret. The secret shallow copied. func (c *HostCatalog) clone() *HostCatalog { cp := proto.Clone(c.HostCatalog) + newSecret := proto.Clone(c.secrets) hc := &HostCatalog{ HostCatalog: cp.(*store.HostCatalog), + secrets: newSecret.(*structpb.Struct), } - if c.secrets != nil { - newSecret := make(map[string]interface{}, len(c.secrets)) - for k, v := range c.secrets { - newSecret[k] = v - } - hc.secrets = newSecret + // proto.Clone will convert slices with length and capacity of 0 to nil. + // Fix this since gorm treats empty slices differently than nil. + if c.Attributes != nil && len(c.Attributes) == 0 && hc.Attributes == nil { + hc.Attributes = []byte{} } return hc } diff --git a/internal/host/plugin/host_catalog_secret.go b/internal/host/plugin/host_catalog_secret.go index 494e478c4..83916ec02 100644 --- a/internal/host/plugin/host_catalog_secret.go +++ b/internal/host/plugin/host_catalog_secret.go @@ -2,7 +2,6 @@ package plugin import ( "context" - "encoding/json" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/errors" @@ -11,6 +10,7 @@ import ( wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/hashicorp/go-kms-wrapping/structwrapping" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" ) // HostCatalogSecret contains the encrypted secret for a host catalog. @@ -22,7 +22,7 @@ type HostCatalogSecret struct { // newHostCatalogSecret creates an in memory host catalog secret. // All options are ignored. -func newHostCatalogSecret(ctx context.Context, catalogId string, secret map[string]interface{}, _ ...Option) (*HostCatalogSecret, error) { +func newHostCatalogSecret(ctx context.Context, catalogId string, secret *structpb.Struct, _ ...Option) (*HostCatalogSecret, error) { const op = "plugin.newHostCatlogSecret" hcs := &HostCatalogSecret{ HostCatalogSecret: &store.HostCatalogSecret{ @@ -31,7 +31,7 @@ func newHostCatalogSecret(ctx context.Context, catalogId string, secret map[stri } if secret != nil { - attrs, err := json.Marshal(secret) + attrs, err := proto.Marshal(secret) if err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter)) } diff --git a/internal/host/plugin/host_catalog_secret_test.go b/internal/host/plugin/host_catalog_secret_test.go index 0d2137c08..f6559caf5 100644 --- a/internal/host/plugin/host_catalog_secret_test.go +++ b/internal/host/plugin/host_catalog_secret_test.go @@ -2,7 +2,6 @@ package plugin import ( "context" - "encoding/json" "testing" "github.com/google/go-cmp/cmp" @@ -14,7 +13,9 @@ import ( "github.com/hashicorp/boundary/internal/plugin/host" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" ) func TestHostCatalogSecret_New(t *testing.T) { @@ -28,7 +29,7 @@ func TestHostCatalogSecret_New(t *testing.T) { type args struct { catalogId string - attrs map[string]interface{} + attrs *structpb.Struct } tests := []struct { @@ -64,13 +65,19 @@ func TestHostCatalogSecret_New(t *testing.T) { name: "valid", args: args{ catalogId: cat.GetPublicId(), - attrs: map[string]interface{}{"foo": "bar"}, + attrs: func() *structpb.Struct { + st, err := structpb.NewStruct(map[string]interface{}{"foo": "bar"}) + require.NoError(t, err) + return st + }(), }, want: &HostCatalogSecret{ HostCatalogSecret: &store.HostCatalogSecret{ CatalogId: cat.GetPublicId(), Secret: func() []byte { - b, err := json.Marshal(map[string]interface{}{"foo": "bar"}) + st, err := structpb.NewStruct(map[string]interface{}{"foo": "bar"}) + require.NoError(t, err) + b, err := proto.Marshal(st) require.NoError(t, err) return b }(), @@ -124,7 +131,8 @@ func TestHostCatalogSecret_Custom_Queries(t *testing.T) { databaseWrapper, err := kkms.GetWrapper(ctx, prj.PublicId, kms.KeyPurposeDatabase) require.NoError(t, err) - hcs, err := newHostCatalogSecret(ctx, cat.GetPublicId(), map[string]interface{}{"foo": "bar"}) + hcs, err := newHostCatalogSecret(ctx, cat.GetPublicId(), + &structpb.Struct{Fields: map[string]*structpb.Value{"foo": structpb.NewStringValue("bar")}}) require.NoError(t, err) assert.NoError(t, hcs.encrypt(ctx, databaseWrapper)) q, v := hcs.insertQuery() @@ -141,7 +149,8 @@ func TestHostCatalogSecret_Custom_Queries(t *testing.T) { assert.Empty(t, cmp.Diff(hcs, found, protocmp.Transform())) // Update the secret and see the value updated. - updated, err := newHostCatalogSecret(ctx, cat.GetPublicId(), map[string]interface{}{"updated": "value"}) + updated, err := newHostCatalogSecret(ctx, cat.GetPublicId(), + &structpb.Struct{Fields: map[string]*structpb.Value{"updated": structpb.NewStringValue("value")}}) require.NoError(t, err) assert.NoError(t, updated.encrypt(ctx, databaseWrapper)) q, v = updated.insertQuery() diff --git a/internal/host/plugin/host_catalog_test.go b/internal/host/plugin/host_catalog_test.go index ab882498a..761dacb5a 100644 --- a/internal/host/plugin/host_catalog_test.go +++ b/internal/host/plugin/host_catalog_test.go @@ -2,7 +2,6 @@ package plugin import ( "context" - "encoding/json" "testing" "github.com/google/go-cmp/cmp" @@ -13,7 +12,9 @@ import ( "github.com/hashicorp/boundary/internal/plugin/host" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" ) func TestHostCatalog_Create(t *testing.T) { @@ -45,7 +46,7 @@ func TestHostCatalog_Create(t *testing.T) { want: &HostCatalog{ HostCatalog: &store.HostCatalog{ PluginId: plg.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, wantCreateErr: true, @@ -59,7 +60,7 @@ func TestHostCatalog_Create(t *testing.T) { want: &HostCatalog{ HostCatalog: &store.HostCatalog{ ScopeId: prj.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, wantCreateErr: true, @@ -74,7 +75,7 @@ func TestHostCatalog_Create(t *testing.T) { HostCatalog: &store.HostCatalog{ PluginId: plg.GetPublicId(), ScopeId: prj.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, }, @@ -92,7 +93,7 @@ func TestHostCatalog_Create(t *testing.T) { PluginId: plg.GetPublicId(), ScopeId: prj.GetPublicId(), Name: "test-name", - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, }, @@ -110,7 +111,7 @@ func TestHostCatalog_Create(t *testing.T) { PluginId: plg.GetPublicId(), ScopeId: prj.GetPublicId(), Description: "test-description", - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, }, @@ -120,7 +121,7 @@ func TestHostCatalog_Create(t *testing.T) { pluginId: plg.GetPublicId(), scopeId: prj.GetPublicId(), opts: []Option{ - WithAttributes(map[string]interface{}{"foo": "bar"}), + WithAttributes(&structpb.Struct{Fields: map[string]*structpb.Value{"foo": structpb.NewStringValue("bar")}}), }, }, want: func() *HostCatalog { @@ -131,7 +132,7 @@ func TestHostCatalog_Create(t *testing.T) { }, } var err error - hc.Attributes, err = json.Marshal(map[string]interface{}{"foo": "bar"}) + hc.Attributes, err = proto.Marshal(&structpb.Struct{Fields: map[string]*structpb.Value{"foo": structpb.NewStringValue("bar")}}) require.NoError(t, err) return hc }(), diff --git a/internal/host/plugin/host_set.go b/internal/host/plugin/host_set.go index 2785fe814..e0703c83a 100644 --- a/internal/host/plugin/host_set.go +++ b/internal/host/plugin/host_set.go @@ -2,7 +2,6 @@ package plugin import ( "context" - "encoding/json" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/host/plugin/store" @@ -22,23 +21,21 @@ type HostSet struct { func NewHostSet(ctx context.Context, catalogId string, opt ...Option) (*HostSet, error) { const op = "plugin.NewHostSet" opts := getOpts(opt...) + attrs, err := proto.Marshal(opts.withAttributes) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter)) + } + set := &HostSet{ HostSet: &store.HostSet{ CatalogId: catalogId, Name: opts.withName, Description: opts.withDescription, + Attributes: attrs, PreferredEndpoints: opts.withPreferredEndpoints, }, } - if opts.withAttributes != nil { - attrs, err := json.Marshal(opts.withAttributes) - if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.InvalidParameter)) - } - set.Attributes = attrs - } - return set, nil } @@ -64,9 +61,13 @@ func allocHostSet() *HostSet { func (s *HostSet) clone() *HostSet { cp := proto.Clone(s.HostSet) - return &HostSet{ + hs := &HostSet{ HostSet: cp.(*store.HostSet), } + if s.Attributes != nil && len(s.Attributes) == 0 && hs.Attributes == nil { + hs.Attributes = []byte{} + } + return hs } func (s *HostSet) oplog(op oplog.OpType) oplog.Metadata { diff --git a/internal/host/plugin/host_set_test.go b/internal/host/plugin/host_set_test.go index a5fbdf6f0..ecfce316a 100644 --- a/internal/host/plugin/host_set_test.go +++ b/internal/host/plugin/host_set_test.go @@ -10,6 +10,8 @@ import ( "github.com/hashicorp/boundary/internal/plugin/host" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" ) func TestHostSet_Create(t *testing.T) { @@ -37,7 +39,7 @@ func TestHostSet_Create(t *testing.T) { catalogId: "", }, want: &HostSet{HostSet: &store.HostSet{ - Attributes: []byte("{}"), + Attributes: []byte{}, }}, wantErr: true, }, @@ -49,7 +51,7 @@ func TestHostSet_Create(t *testing.T) { want: &HostSet{ HostSet: &store.HostSet{ CatalogId: cat.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, }, @@ -65,7 +67,7 @@ func TestHostSet_Create(t *testing.T) { HostSet: &store.HostSet{ CatalogId: cat.GetPublicId(), Name: "test-name", - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, }, @@ -81,7 +83,7 @@ func TestHostSet_Create(t *testing.T) { HostSet: &store.HostSet{ CatalogId: cat.GetPublicId(), Name: "test-name", - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, wantErr: true, @@ -98,7 +100,7 @@ func TestHostSet_Create(t *testing.T) { HostSet: &store.HostSet{ CatalogId: cat2.GetPublicId(), Name: "test-name", - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, }, @@ -114,7 +116,7 @@ func TestHostSet_Create(t *testing.T) { HostSet: &store.HostSet{ CatalogId: cat.GetPublicId(), Description: "test-description", - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, }, @@ -123,13 +125,18 @@ func TestHostSet_Create(t *testing.T) { args: args{ catalogId: cat.GetPublicId(), opts: []Option{ - WithAttributes(map[string]interface{}{"foo": "bar"}), + WithAttributes(&structpb.Struct{Fields: map[string]*structpb.Value{"foo": structpb.NewStringValue("bar")}}), }, }, want: &HostSet{ HostSet: &store.HostSet{ - CatalogId: cat.GetPublicId(), - Attributes: []byte(`{"foo":"bar"}`), + CatalogId: cat.GetPublicId(), + Attributes: func() []byte { + st := &structpb.Struct{Fields: map[string]*structpb.Value{"foo": structpb.NewStringValue("bar")}} + b, err := proto.Marshal(st) + require.NoError(t, err) + return b + }(), }, }, }, @@ -145,7 +152,7 @@ func TestHostSet_Create(t *testing.T) { HostSet: &store.HostSet{ CatalogId: cat.GetPublicId(), PreferredEndpoints: []string{"cidr:1.2.3.4"}, - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, }, diff --git a/internal/host/plugin/immutable_fields_test.go b/internal/host/plugin/immutable_fields_test.go index e854711bd..641c1b575 100644 --- a/internal/host/plugin/immutable_fields_test.go +++ b/internal/host/plugin/immutable_fields_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "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" "google.golang.org/protobuf/proto" @@ -101,7 +102,9 @@ func TestPluginSet_ImmutableFields(t *testing.T) { _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) plg := host.TestPlugin(t, conn, "test", "prefix") cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) - new := TestSet(t, conn, kmsCache, cat) + new := TestSet(t, conn, kmsCache, cat, map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &TestPluginServer{}, + }) tests := []struct { name string diff --git a/internal/host/plugin/options.go b/internal/host/plugin/options.go index 4c3e260d1..b711478cb 100644 --- a/internal/host/plugin/options.go +++ b/internal/host/plugin/options.go @@ -1,5 +1,7 @@ package plugin +import "google.golang.org/protobuf/types/known/structpb" + // getOpts - iterate the inbound Options and return a struct func getOpts(opt ...Option) options { opts := getDefaultOptions() @@ -16,16 +18,14 @@ type Option func(*options) type options struct { withName string withDescription string - withAttributes map[string]interface{} - withSecrets map[string]interface{} + withAttributes *structpb.Struct + withSecrets *structpb.Struct withPreferredEndpoints []string } func getDefaultOptions() options { return options{ - withDescription: "", - withName: "", - withAttributes: make(map[string]interface{}), + withAttributes: &structpb.Struct{}, } } @@ -44,14 +44,14 @@ func WithName(name string) Option { } // WithAttributes provides an optional attributes field. -func WithAttributes(attrs map[string]interface{}) Option { +func WithAttributes(attrs *structpb.Struct) Option { return func(o *options) { o.withAttributes = attrs } } // WithSecrets provides an optional secrets field. -func WithSecrets(secrets map[string]interface{}) Option { +func WithSecrets(secrets *structpb.Struct) Option { return func(o *options) { o.withSecrets = secrets } diff --git a/internal/host/plugin/repository.go b/internal/host/plugin/repository.go index 09508d1a6..5755b3db3 100644 --- a/internal/host/plugin/repository.go +++ b/internal/host/plugin/repository.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/host" "github.com/hashicorp/boundary/internal/kms" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" ) // A Repository stores and retrieves the persistent types in the plugin @@ -16,6 +17,11 @@ type Repository struct { reader db.Reader writer db.Writer kms *kms.Kms + + // plugins is a map from plugin resource id to host plugin client. + // TODO: When we are using go-plugin change from using a plgpb.HostPluginServiceServer + // to the client. + plugins map[string]plgpb.HostPluginServiceServer // defaultLimit provides a default for limiting the number of results // returned from the repo defaultLimit int @@ -25,7 +31,7 @@ type Repository struct { // only be used for one transaction and it is not safe for concurrent go // routines to access it. WithLimit option is used as a repo wide default // limit applied to all ListX methods. -func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...host.Option) (*Repository, error) { +func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, plgm map[string]plgpb.HostPluginServiceServer, opt ...host.Option) (*Repository, error) { const op = "plugin.NewRepository" switch { case r == nil: @@ -34,6 +40,8 @@ func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...host.Option) ( return nil, errors.NewDeprecated(errors.InvalidParameter, op, "db.Writer") case kms == nil: return nil, errors.NewDeprecated(errors.InvalidParameter, op, "kms") + case plgm == nil: + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "plgm") } opts, err := host.GetOpts(opt...) @@ -45,10 +53,16 @@ func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...host.Option) ( opts.WithLimit = db.DefaultLimit } + plgs := make(map[string]plgpb.HostPluginServiceServer, len(plgm)) + for k, v := range plgm { + plgs[k] = v + } + return &Repository{ reader: r, writer: w, kms: kms, + plugins: plgs, defaultLimit: opts.WithLimit, }, nil } diff --git a/internal/host/plugin/repository_host_catalog.go b/internal/host/plugin/repository_host_catalog.go index 269d67c2c..aac135385 100644 --- a/internal/host/plugin/repository_host_catalog.go +++ b/internal/host/plugin/repository_host_catalog.go @@ -10,6 +10,12 @@ import ( "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" hostplg "github.com/hashicorp/boundary/internal/plugin/host" + pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" ) // CreateCatalog inserts c into the repository and returns a new @@ -44,7 +50,6 @@ func (r *Repository) CreateCatalog(ctx context.Context, c *HostCatalog, _ ...Opt } c = c.clone() - // TODO: Capture this in a plugin manager and call the plugin's OnCreateCatalog function plg := hostplg.NewPlugin("", "") plg.PublicId = c.PluginId if err := r.reader.LookupByPublicId(ctx, plg); err != nil { @@ -60,27 +65,41 @@ func (r *Repository) CreateCatalog(ctx context.Context, c *HostCatalog, _ ...Opt } c.PublicId = id - oplogWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeOplog) - if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + plgClient, ok := r.plugins[plg.GetPublicId()] + if !ok || plgClient == nil { + return nil, errors.New(ctx, errors.Internal, op, fmt.Sprintf("expected plugin %q not available", plg.GetPluginName())) } - dbWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeDatabase) + plgHc, err := toPluginCatalog(ctx, c) if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get db wrapper")) + return nil, errors.Wrap(ctx, err, op) + } + plgResp, err := plgClient.OnCreateCatalog(ctx, &plgpb.OnCreateCatalogRequest{Catalog: plgHc}) + if err != nil { + if status.Code(err) != codes.Unimplemented { + return nil, errors.Wrap(ctx, err, op) + } } var hcSecret *HostCatalogSecret - if c.secrets != nil { - // TODO: Create the secret using the returned value from the call to the plugin. - hcSecret, err = newHostCatalogSecret(ctx, id, c.secrets) + if plgResp != nil && plgResp.GetPersisted().GetData() != nil { + hcSecret, err = newHostCatalogSecret(ctx, id, plgResp.GetPersisted().GetData()) if err != nil { return nil, errors.Wrap(ctx, err, op) } + dbWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get db wrapper")) + } if err := hcSecret.encrypt(ctx, dbWrapper); err != nil { return nil, errors.Wrap(ctx, err, op) } } + oplogWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + var newHostCatalog *HostCatalog _, err = r.writer.DoTx( ctx, @@ -137,9 +156,8 @@ func (r *Repository) LookupCatalog(ctx context.Context, id string, _ ...Option) if id == "" { return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id") } - c := allocHostCatalog() - c.PublicId = id - if err := r.reader.LookupByPublicId(ctx, c); err != nil { + c, err := r.getCatalog(ctx, id) + if err != nil { if errors.IsNotFoundError(err) { return nil, nil } @@ -169,3 +187,75 @@ func (r *Repository) ListCatalogs(ctx context.Context, scopeIds []string, opt .. } return hostCatalogs, nil } + +// getCatalog retrieves the *HostCatalog with the provided id. If it is not found or there +// is an problem getting it from the database an error is returned instead. +func (r *Repository) getCatalog(ctx context.Context, id string) (*HostCatalog, error) { + const op = "plugin.(Repository).getCatalog" + c := allocHostCatalog() + c.PublicId = id + if err := r.reader.LookupByPublicId(ctx, c); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", id))) + } + return c, nil +} + +// getPersistedDataForCatalog returns the persisted data for a catalog if +// present. c must have a valid Public Id and Scope Id set. +// TODO: consider merging the functions for getting catalog and persisted data into a view. +func (r *Repository) getPersistedDataForCatalog(ctx context.Context, c *HostCatalog) (*plgpb.HostCatalogPersisted, error) { + const op = "plugin.(Repository).getPersistedDataForCatalog" + if c.PublicId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "empty public id") + } + if c.ScopeId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "empty scope id") + } + cSecret := allocHostCatalogSecret() + if err := r.reader.LookupWhere(ctx, cSecret, "catalog_id=?", c.GetPublicId()); err != nil { + if errors.IsNotFoundError(err) { + return nil, nil + } + return nil, errors.Wrap(ctx, err, op) + } + if cSecret == nil { + return nil, nil + } + dbWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get db wrapper")) + } + if err := cSecret.decrypt(ctx, dbWrapper); err != nil { + return nil, errors.Wrap(ctx, err, op) + } + + data := &structpb.Struct{} + if err := proto.Unmarshal(cSecret.GetSecret(), data); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unmarshaling secret")) + } + return &plgpb.HostCatalogPersisted{Data: data}, nil +} + +// toPluginCatalog returns a host catalog, with it's secret if available, in the format expected +// by the host plugin system. +func toPluginCatalog(ctx context.Context, in *HostCatalog) (*pb.HostCatalog, error) { + const op = "plugin.toPluginCatalog" + if in == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "nil storage plugin") + } + hc := &pb.HostCatalog{ + Id: in.GetPublicId(), + ScopeId: in.GetScopeId(), + } + if in.GetAttributes() != nil { + attrs := &structpb.Struct{} + if err := proto.Unmarshal(in.GetAttributes(), attrs); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to unmarshal attributes")) + } + hc.Attributes = attrs + } + if in.secrets != nil { + hc.Secrets = in.secrets + } + return hc, nil +} diff --git a/internal/host/plugin/repository_host_catalog_test.go b/internal/host/plugin/repository_host_catalog_test.go index 642295750..9a25af2b7 100644 --- a/internal/host/plugin/repository_host_catalog_test.go +++ b/internal/host/plugin/repository_host_catalog_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/host/plugin/store" @@ -13,8 +14,12 @@ import ( "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" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" ) func TestRepository_CreateCatalog(t *testing.T) { @@ -24,13 +29,27 @@ func TestRepository_CreateCatalog(t *testing.T) { wrapper := db.TestWrapper(t) _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) plg := hostplg.TestPlugin(t, conn, "test", "test") + unimplementedPlugin := hostplg.TestPlugin(t, conn, "unimplemented", "unimplemented") + + // gotPluginAttrs tracks which attributes a plugin has received through a closure and can be compared in the + // test against the expected values sent to the plugin. + var gotPluginAttrs *structpb.Struct + plgm := map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &TestPluginServer{ + OnCreateCatalogFn: func(_ context.Context, req *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) { + gotPluginAttrs = req.GetCatalog().GetAttributes() + return &plgpb.OnCreateCatalogResponse{Persisted: &plgpb.HostCatalogPersisted{Data: req.GetCatalog().GetSecrets()}}, nil + }, + }, + unimplementedPlugin.GetPublicId(): &plgpb.UnimplementedHostPluginServiceServer{}, + } tests := []struct { name string in *HostCatalog opts []Option want *HostCatalog - wantSecret []byte + wantSecret *structpb.Struct wantIsErr errors.Code }{ { @@ -47,7 +66,7 @@ func TestRepository_CreateCatalog(t *testing.T) { in: &HostCatalog{ HostCatalog: &store.HostCatalog{ PluginId: plg.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, wantIsErr: errors.InvalidParameter, @@ -57,7 +76,7 @@ func TestRepository_CreateCatalog(t *testing.T) { in: &HostCatalog{ HostCatalog: &store.HostCatalog{ ScopeId: prj.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, wantIsErr: errors.InvalidParameter, @@ -78,14 +97,31 @@ func TestRepository_CreateCatalog(t *testing.T) { HostCatalog: &store.HostCatalog{ ScopeId: prj.GetPublicId(), PluginId: plg.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, want: &HostCatalog{ HostCatalog: &store.HostCatalog{ ScopeId: prj.GetPublicId(), PluginId: plg.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, + }, + }, + }, + { + name: "valid-unimplemented-plugin", + in: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + PluginId: unimplementedPlugin.GetPublicId(), + Attributes: []byte{}, + }, + }, + want: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + PluginId: unimplementedPlugin.GetPublicId(), + Attributes: []byte{}, }, }, }, @@ -95,7 +131,7 @@ func TestRepository_CreateCatalog(t *testing.T) { HostCatalog: &store.HostCatalog{ ScopeId: prj.GetPublicId(), PluginId: "unknown_plugin", - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, wantIsErr: errors.InvalidParameter, @@ -107,7 +143,7 @@ func TestRepository_CreateCatalog(t *testing.T) { Name: "test-name-repo", ScopeId: prj.GetPublicId(), PluginId: plg.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, want: &HostCatalog{ @@ -115,7 +151,7 @@ func TestRepository_CreateCatalog(t *testing.T) { Name: "test-name-repo", ScopeId: prj.GetPublicId(), PluginId: plg.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, }, @@ -126,7 +162,7 @@ func TestRepository_CreateCatalog(t *testing.T) { Description: "test-description-repo", ScopeId: prj.GetPublicId(), PluginId: plg.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, want: &HostCatalog{ @@ -134,7 +170,36 @@ func TestRepository_CreateCatalog(t *testing.T) { Description: "test-description-repo", ScopeId: prj.GetPublicId(), PluginId: plg.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, + }, + }, + }, + { + name: "valid-with-attributes", + in: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + PluginId: plg.GetPublicId(), + Attributes: func() []byte { + st, err := structpb.NewStruct(map[string]interface{}{"k1": "foo"}) + require.NoError(t, err) + b, err := proto.Marshal(st) + require.NoError(t, err) + return b + }(), + }, + }, + want: &HostCatalog{ + HostCatalog: &store.HostCatalog{ + ScopeId: prj.GetPublicId(), + PluginId: plg.GetPublicId(), + Attributes: func() []byte { + st, err := structpb.NewStruct(map[string]interface{}{"k1": "foo"}) + require.NoError(t, err) + b, err := proto.Marshal(st) + require.NoError(t, err) + return b + }(), }, }, }, @@ -145,23 +210,35 @@ func TestRepository_CreateCatalog(t *testing.T) { Description: "test-description-repo", ScopeId: prj.GetPublicId(), PluginId: plg.GetPublicId(), - Attributes: []byte("{}"), - }, - secrets: map[string]interface{}{ - "k1": "v1", - "k2": 2, - "k3": nil, + Attributes: []byte{}, }, + secrets: func() *structpb.Struct { + st, err := structpb.NewStruct(map[string]interface{}{ + "k1": "v1", + "k2": 2, + "k3": nil, + }) + require.NoError(t, err) + return st + }(), }, want: &HostCatalog{ HostCatalog: &store.HostCatalog{ Description: "test-description-repo", ScopeId: prj.GetPublicId(), PluginId: plg.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, }, }, - wantSecret: []byte(`{"k1":"v1","k2":2,"k3":null}`), + wantSecret: func() *structpb.Struct { + st, err := structpb.NewStruct(map[string]interface{}{ + "k1": "v1", + "k2": 2, + "k3": nil, + }) + require.NoError(t, err) + return st + }(), }, } @@ -170,7 +247,7 @@ func TestRepository_CreateCatalog(t *testing.T) { t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) kmsCache := kms.TestKms(t, conn, wrapper) - repo, err := NewRepository(rw, rw, kmsCache) + repo, err := NewRepository(rw, rw, kmsCache, plgm) assert.NoError(err) assert.NotNil(repo) got, err := repo.CreateCatalog(ctx, tt.in, tt.opts...) @@ -188,6 +265,12 @@ func TestRepository_CreateCatalog(t *testing.T) { assert.Equal(tt.want.Description, got.Description) assert.Equal(got.CreateTime, got.UpdateTime) + // wantedPluginAttributes := &structpb.Struct{} + // require.NoError(t, proto.Unmarshal(tt.want.GetAttributes(), wantedPluginAttributes)) + gotB, err := proto.Marshal(gotPluginAttrs) + require.NoError(t, err) + assert.Equal(tt.want.GetAttributes(), gotB) + assert.NoError(db.TestVerifyOplog(t, rw, got.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))) cSecret := allocHostCatalogSecret() @@ -205,14 +288,17 @@ func TestRepository_CreateCatalog(t *testing.T) { dbWrapper, err := kmsCache.GetWrapper(ctx, got.GetScopeId(), kms.KeyPurposeDatabase) require.NoError(t, err) require.NoError(t, cSecret.decrypt(ctx, dbWrapper)) - assert.Equal(string(tt.wantSecret), string(cSecret.Secret)) + + st := &structpb.Struct{} + require.NoError(t, proto.Unmarshal(cSecret.Secret, st)) + assert.Empty(cmp.Diff(tt.wantSecret, st, protocmp.Transform())) }) } t.Run("invalid-duplicate-names", func(t *testing.T) { assert := assert.New(t) kms := kms.TestKms(t, conn, wrapper) - repo, err := NewRepository(rw, rw, kms) + repo, err := NewRepository(rw, rw, kms, plgm) assert.NoError(err) assert.NotNil(repo) _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) @@ -221,7 +307,7 @@ func TestRepository_CreateCatalog(t *testing.T) { ScopeId: prj.GetPublicId(), Name: "test-name-repo", PluginId: plg.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, }, } @@ -242,7 +328,7 @@ func TestRepository_CreateCatalog(t *testing.T) { t.Run("valid-duplicate-names-diff-scopes", func(t *testing.T) { assert := assert.New(t) kms := kms.TestKms(t, conn, wrapper) - repo, err := NewRepository(rw, rw, kms) + repo, err := NewRepository(rw, rw, kms, plgm) assert.NoError(err) assert.NotNil(repo) org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) @@ -250,7 +336,7 @@ func TestRepository_CreateCatalog(t *testing.T) { HostCatalog: &store.HostCatalog{ Name: "test-name-repo", PluginId: plg.GetPublicId(), - Attributes: []byte("{}"), + Attributes: []byte{}, }, } in2 := in.clone() @@ -292,6 +378,9 @@ func TestRepository_LookupCatalog(t *testing.T) { wrapper := db.TestWrapper(t) _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) plg := hostplg.TestPlugin(t, conn, "test", "test") + plgm := map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &TestPluginServer{}, + } cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) badId, err := newHostCatalogId(ctx, plg.GetIdPrefix()) assert.NoError(t, err) @@ -326,7 +415,7 @@ func TestRepository_LookupCatalog(t *testing.T) { t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) kms := kms.TestKms(t, conn, wrapper) - repo, err := NewRepository(rw, rw, kms) + repo, err := NewRepository(rw, rw, kms, plgm) assert.NoError(err) assert.NotNil(repo) @@ -354,10 +443,13 @@ func TestRepository_ListCatalogs_Multiple_Scopes(t *testing.T) { wrapper := db.TestWrapper(t) rw := db.New(conn) kms := kms.TestKms(t, conn, wrapper) - repo, err := NewRepository(rw, rw, kms) + plg := hostplg.TestPlugin(t, conn, "test", "test") + plgm := map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &TestPluginServer{}, + } + repo, err := NewRepository(rw, rw, kms, plgm) assert.NoError(t, err) assert.NotNil(t, repo) - plg := hostplg.TestPlugin(t, conn, "test", "test") const numPerScope = 10 var projs []string diff --git a/internal/host/plugin/repository_host_set.go b/internal/host/plugin/repository_host_set.go index cc00d3085..a189331bb 100644 --- a/internal/host/plugin/repository_host_set.go +++ b/internal/host/plugin/repository_host_set.go @@ -14,6 +14,12 @@ import ( "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" hostplg "github.com/hashicorp/boundary/internal/plugin/host" + pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" ) // CreateSet inserts s into the repository and returns a new HostSet @@ -45,14 +51,15 @@ func (r *Repository) CreateSet(ctx context.Context, scopeId string, s *HostSet, } s = s.clone() - c := allocHostCatalog() - c.PublicId = s.GetCatalogId() - if err := r.reader.LookupByPublicId(ctx, c); err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get host catalog")) + c, err := r.getCatalog(ctx, s.CatalogId) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("looking up catalog")) + } + per, err := r.getPersistedDataForCatalog(ctx, c) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("looking up persisted data")) } - // TODO: Contain the following plugin logic inside a plugin manager as well as - // calling the plugin which we have looked up. plg := hostplg.NewPlugin("", "") plg.PublicId = c.GetPluginId() if err := r.reader.LookupByPublicId(ctx, plg); err != nil { @@ -65,6 +72,24 @@ func (r *Repository) CreateSet(ctx context.Context, scopeId string, s *HostSet, } s.PublicId = id + plgClient, ok := r.plugins[plg.GetPublicId()] + if !ok || plgClient == nil { + return nil, errors.New(ctx, errors.Internal, op, fmt.Sprintf("plugin with plugin name %q not available", plg.GetPluginName())) + } + plgHc, err := toPluginCatalog(ctx, c) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + plgHs, err := toPluginSet(ctx, s) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + if _, err := plgClient.OnCreateSet(ctx, &plgpb.OnCreateSetRequest{Catalog: plgHc, Set: plgHs, Persisted: per}); err != nil { + if status.Code(err) != codes.Unimplemented { + return nil, errors.Wrap(ctx, err, op) + } + } + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) if err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) @@ -294,3 +319,22 @@ type hostSetAgg struct { // TableName returns the table name for gorm func (agg *hostSetAgg) TableName() string { return "host_plugin_host_set_with_value_obj" } + +// toPluginSet returns a host set in the format expected by the host plugin system. +func toPluginSet(ctx context.Context, in *HostSet) (*pb.HostSet, error) { + const op = "plugin.toPluginCatalog" + if in == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "nil storage plugin") + } + hs := &pb.HostSet{ + Id: in.GetPublicId(), + } + if in.GetAttributes() != nil { + attrs := &structpb.Struct{} + if err := proto.Unmarshal(in.GetAttributes(), attrs); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to marshal attributes")) + } + hs.Attributes = attrs + } + return hs, nil +} diff --git a/internal/host/plugin/repository_host_set_test.go b/internal/host/plugin/repository_host_set_test.go index b09655aab..c32c52853 100644 --- a/internal/host/plugin/repository_host_set_test.go +++ b/internal/host/plugin/repository_host_set_test.go @@ -16,9 +16,12 @@ import ( "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" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/structpb" ) func TestRepository_CreateSet(t *testing.T) { @@ -29,8 +32,20 @@ func TestRepository_CreateSet(t *testing.T) { iamRepo := iam.TestRepo(t, conn, wrapper) _, prj := iam.TestScopes(t, iamRepo) plg := hostplg.TestPlugin(t, conn, "create", "create") + unimplementedPlugin := hostplg.TestPlugin(t, conn, "unimplemented", "unimplemented") + + var pluginReceivedAttrs *structpb.Struct + plgm := map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &TestPluginServer{OnCreateSetFn: func(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) { + pluginReceivedAttrs = req.GetSet().GetAttributes() + return &plgpb.OnCreateSetResponse{}, nil + }}, + unimplementedPlugin.GetPublicId(): &plgpb.UnimplementedHostPluginServiceServer{}, + } + catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) - attrs := []byte("{}") + unimplementedPluginCatalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) + attrs := []byte{} tests := []struct { name string @@ -126,6 +141,23 @@ func TestRepository_CreateSet(t *testing.T) { }, }, }, + { + name: "valid-unimplemented-plugin", + in: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: unimplementedPluginCatalog.PublicId, + Name: "valid-unimplemented-plugin", + Attributes: attrs, + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: unimplementedPluginCatalog.PublicId, + Name: "valid-unimplemented-plugin", + Attributes: attrs, + }, + }, + }, { name: "valid-with-description", in: &HostSet{ @@ -143,13 +175,38 @@ func TestRepository_CreateSet(t *testing.T) { }, }, }, + { + name: "valid-with-custom-attributes", + in: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + Description: ("test-description-repo"), + Attributes: func() []byte { + b, err := proto.Marshal(&structpb.Struct{Fields: map[string]*structpb.Value{"k1": structpb.NewStringValue("foo")}}) + require.NoError(t, err) + return b + }(), + }, + }, + want: &HostSet{ + HostSet: &store.HostSet{ + CatalogId: catalog.PublicId, + Description: ("test-description-repo"), + Attributes: func() []byte { + b, err := proto.Marshal(&structpb.Struct{Fields: map[string]*structpb.Value{"k1": structpb.NewStringValue("foo")}}) + require.NoError(t, err) + return b + }(), + }, + }, + }, } 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) + repo, err := NewRepository(rw, rw, kms, plgm) require.NoError(err) require.NotNil(repo) got, err := repo.CreateSet(context.Background(), prj.GetPublicId(), tt.in, tt.opts...) @@ -166,13 +223,17 @@ func TestRepository_CreateSet(t *testing.T) { assert.Equal(tt.want.Name, got.GetName()) assert.Equal(tt.want.Description, got.GetDescription()) assert.Equal(got.GetCreateTime(), got.GetUpdateTime()) + wantedPluginAttributes := &structpb.Struct{} + require.NoError(proto.Unmarshal(tt.want.Attributes, wantedPluginAttributes)) + assert.Empty(cmp.Diff(wantedPluginAttributes, pluginReceivedAttrs, protocmp.Transform())) + assert.NoError(db.TestVerifyOplog(t, rw, got.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))) }) } t.Run("invalid-duplicate-names", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, kms) + repo, err := NewRepository(rw, rw, kms, plgm) require.NoError(err) require.NotNil(repo) @@ -183,7 +244,7 @@ func TestRepository_CreateSet(t *testing.T) { HostSet: &store.HostSet{ CatalogId: catalog.PublicId, Name: "test-name-repo", - Attributes: []byte("{}"), + Attributes: []byte{}, }, } @@ -203,7 +264,7 @@ func TestRepository_CreateSet(t *testing.T) { t.Run("valid-duplicate-names-diff-catalogs", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, kms) + repo, err := NewRepository(rw, rw, kms, plgm) require.NoError(err) require.NotNil(repo) @@ -214,7 +275,7 @@ func TestRepository_CreateSet(t *testing.T) { in := &HostSet{ HostSet: &store.HostSet{ Name: "test-name-repo", - Attributes: []byte("{}"), + Attributes: []byte{}, }, } in2 := in.clone() @@ -250,9 +311,14 @@ func TestRepository_LookupSet(t *testing.T) { iamRepo := iam.TestRepo(t, conn, wrapper) _, prj := iam.TestScopes(t, iamRepo) plg := hostplg.TestPlugin(t, conn, "lookup", "lookup") + plgm := map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &TestPluginServer{}, + } catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) - hostSet := TestSet(t, conn, kms, catalog) + hostSet := TestSet(t, conn, kms, catalog, map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &TestPluginServer{}, + }) hostSetId, err := newHostSetId(ctx, plg.GetIdPrefix()) require.NoError(t, err) @@ -281,7 +347,7 @@ func TestRepository_LookupSet(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, kms) + repo, err := NewRepository(rw, rw, kms, plgm) assert.NoError(err) require.NotNil(repo) got, err := repo.LookupSet(ctx, tt.in) @@ -307,13 +373,16 @@ func TestRepository_ListSets(t *testing.T) { _, prj := iam.TestScopes(t, iamRepo) plg := hostplg.TestPlugin(t, conn, "list", "list") + plgm := map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &TestPluginServer{}, + } catalogA := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) catalogB := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) hostSets := []*HostSet{ - TestSet(t, conn, kms, catalogA), - TestSet(t, conn, kms, catalogA), - TestSet(t, conn, kms, catalogA), + TestSet(t, conn, kms, catalogA, plgm), + TestSet(t, conn, kms, catalogA, plgm), + TestSet(t, conn, kms, catalogA, plgm), } tests := []struct { @@ -343,7 +412,7 @@ func TestRepository_ListSets(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, kms) + repo, err := NewRepository(rw, rw, kms, plgm) assert.NoError(err) require.NotNil(repo) got, err := repo.ListSets(context.Background(), tt.in, tt.opts...) @@ -371,11 +440,14 @@ func TestRepository_ListSets_Limits(t *testing.T) { _, prj := iam.TestScopes(t, iamRepo) plg := hostplg.TestPlugin(t, conn, "listlimit", "listlimit") + plgm := map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &TestPluginServer{}, + } catalog := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) count := 10 var hostSets []*HostSet for i := 0; i < count; i++ { - hostSets = append(hostSets, TestSet(t, conn, kms, catalog)) + hostSets = append(hostSets, TestSet(t, conn, kms, catalog, plgm)) } tests := []struct { @@ -426,7 +498,7 @@ func TestRepository_ListSets_Limits(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - repo, err := NewRepository(rw, rw, kms, tt.repoOpts...) + repo, err := NewRepository(rw, rw, kms, plgm, tt.repoOpts...) assert.NoError(err) require.NotNil(repo) got, err := repo.ListSets(context.Background(), hostSets[0].CatalogId, tt.listOpts...) diff --git a/internal/host/plugin/repository_test.go b/internal/host/plugin/repository_test.go index 51d03d338..22140d143 100644 --- a/internal/host/plugin/repository_test.go +++ b/internal/host/plugin/repository_test.go @@ -3,6 +3,7 @@ package plugin import ( "testing" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,11 +19,14 @@ func TestRepository_New(t *testing.T) { wrapper := db.TestWrapper(t) kmsCache := kms.TestKms(t, conn, wrapper) + plgs := map[string]plgpb.HostPluginServiceServer{} + type args struct { - r db.Reader - w db.Writer - kms *kms.Kms - opts []host.Option + r db.Reader + w db.Writer + kms *kms.Kms + plugins map[string]plgpb.HostPluginServiceServer + opts []host.Option } tests := []struct { @@ -34,38 +38,43 @@ func TestRepository_New(t *testing.T) { { name: "valid", args: args{ - r: rw, - w: rw, - kms: kmsCache, + r: rw, + w: rw, + kms: kmsCache, + plugins: plgs, }, want: &Repository{ reader: rw, writer: rw, kms: kmsCache, + plugins: plgs, defaultLimit: db.DefaultLimit, }, }, { name: "valid-with-limit", args: args{ - r: rw, - w: rw, - kms: kmsCache, - opts: []host.Option{host.WithLimit(5)}, + r: rw, + w: rw, + kms: kmsCache, + plugins: plgs, + opts: []host.Option{host.WithLimit(5)}, }, want: &Repository{ reader: rw, writer: rw, kms: kmsCache, + plugins: plgs, defaultLimit: 5, }, }, { name: "nil-reader", args: args{ - r: nil, - w: rw, - kms: kmsCache, + r: nil, + w: rw, + kms: kmsCache, + plugins: plgs, }, want: nil, wantIsErr: errors.InvalidParameter, @@ -73,9 +82,10 @@ func TestRepository_New(t *testing.T) { { name: "nil-writer", args: args{ - r: rw, - w: nil, - kms: kmsCache, + r: rw, + w: nil, + kms: kmsCache, + plugins: plgs, }, want: nil, wantIsErr: errors.InvalidParameter, @@ -83,9 +93,21 @@ func TestRepository_New(t *testing.T) { { name: "nil-kms", args: args{ - r: rw, - w: rw, - kms: nil, + r: rw, + w: rw, + kms: nil, + plugins: plgs, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-plugins", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + plugins: nil, }, want: nil, wantIsErr: errors.InvalidParameter, @@ -93,9 +115,10 @@ func TestRepository_New(t *testing.T) { { name: "all-nils", args: args{ - r: nil, - w: nil, - kms: nil, + r: nil, + w: nil, + kms: nil, + plugins: nil, }, want: nil, wantIsErr: errors.InvalidParameter, @@ -105,7 +128,7 @@ func TestRepository_New(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - got, err := NewRepository(tt.args.r, tt.args.w, tt.args.kms, tt.args.opts...) + got, err := NewRepository(tt.args.r, tt.args.w, tt.args.kms, tt.args.plugins, tt.args.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) diff --git a/internal/host/plugin/store/host.pb.go b/internal/host/plugin/store/host.pb.go index 17a7cd29c..2b88558b7 100644 --- a/internal/host/plugin/store/host.pb.go +++ b/internal/host/plugin/store/host.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.15.8 +// protoc v3.17.3 // source: controller/storage/host/plugin/store/v1/host.proto // Package store provides protobufs for storing types in the plugin host @@ -56,8 +56,8 @@ type HostCatalog struct { // @inject_tag: `gorm:"default:null"` Version uint32 `protobuf:"varint,8,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` // attributes is a jsonb formatted field. - // @inject_tag: `gorm:"default:null"` - Attributes []byte `protobuf:"bytes,9,opt,name=attributes,proto3" json:"attributes,omitempty" gorm:"default:null"` + // @inject_tag: `gorm:"not_null"` + Attributes []byte `protobuf:"bytes,9,opt,name=attributes,proto3" json:"attributes,omitempty" gorm:"not_null"` } func (x *HostCatalog) Reset() { @@ -184,8 +184,8 @@ type HostSet struct { // @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:"default:null"` - Attributes []byte `protobuf:"bytes,8,opt,name=attributes,proto3" json:"attributes,omitempty" gorm:"default:null"` + // @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:"-"` diff --git a/internal/host/plugin/testing.go b/internal/host/plugin/testing.go index 1a860399d..cc149478d 100644 --- a/internal/host/plugin/testing.go +++ b/internal/host/plugin/testing.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/plugin/host" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -40,13 +41,13 @@ func TestCatalog(t *testing.T, conn *gorm.DB, scopeId, pluginId string, opt ...O // TestSet creates a plugin host sets in the provided DB // with the provided catalog id. The catalog must have been created // previously. The test will fail if any errors are encountered. -func TestSet(t *testing.T, conn *gorm.DB, kmsCache *kms.Kms, hc *HostCatalog, opt ...Option) *HostSet { +func TestSet(t *testing.T, conn *gorm.DB, kmsCache *kms.Kms, hc *HostCatalog, plgm map[string]plgpb.HostPluginServiceServer, opt ...Option) *HostSet { t.Helper() require := require.New(t) ctx := context.Background() rw := db.New(conn) - repo, err := NewRepository(rw, rw, kmsCache) + repo, err := NewRepository(rw, rw, kmsCache, plgm) require.NoError(err) set, err := NewHostSet(ctx, hc.PublicId, opt...) @@ -66,3 +67,64 @@ func TestSet(t *testing.T, conn *gorm.DB, kmsCache *kms.Kms, hc *HostCatalog, op return set } + +// TestPluginServer provides a host plugin service server where each method can be overwritten for tests. +type TestPluginServer struct { + OnCreateCatalogFn func(context.Context, *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) + OnUpdateCatalogFn func(context.Context, *plgpb.OnUpdateCatalogRequest) (*plgpb.OnUpdateCatalogResponse, error) + OnDeleteCatalogFn func(context.Context, *plgpb.OnDeleteCatalogRequest) (*plgpb.OnDeleteCatalogResponse, error) + OnCreateSetFn func(context.Context, *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) + OnUpdateSetFn func(context.Context, *plgpb.OnUpdateSetRequest) (*plgpb.OnUpdateSetResponse, error) + OnDeleteSetFn func(context.Context, *plgpb.OnDeleteSetRequest) (*plgpb.OnDeleteSetResponse, error) + ListHostsFn func(context.Context, *plgpb.ListHostsRequest) (*plgpb.ListHostsResponse, error) + plgpb.UnimplementedHostPluginServiceServer +} + +func (t TestPluginServer) OnCreateCatalog(ctx context.Context, req *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) { + if t.OnCreateCatalogFn == nil { + return t.UnimplementedHostPluginServiceServer.OnCreateCatalog(ctx, req) + } + return t.OnCreateCatalogFn(ctx, req) +} + +func (t TestPluginServer) OnUpdateCatalog(ctx context.Context, req *plgpb.OnUpdateCatalogRequest) (*plgpb.OnUpdateCatalogResponse, error) { + if t.OnUpdateCatalogFn == nil { + return t.UnimplementedHostPluginServiceServer.OnUpdateCatalog(ctx, req) + } + return t.OnUpdateCatalogFn(ctx, req) +} + +func (t TestPluginServer) OnDeleteCatalog(ctx context.Context, req *plgpb.OnDeleteCatalogRequest) (*plgpb.OnDeleteCatalogResponse, error) { + if t.OnDeleteCatalogFn == nil { + return t.UnimplementedHostPluginServiceServer.OnDeleteCatalog(ctx, req) + } + return t.OnDeleteCatalogFn(ctx, req) +} + +func (t TestPluginServer) OnCreateSet(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) { + if t.OnCreateSetFn == nil { + return t.UnimplementedHostPluginServiceServer.OnCreateSet(ctx, req) + } + return t.OnCreateSetFn(ctx, req) +} + +func (t TestPluginServer) OnUpdateSet(ctx context.Context, req *plgpb.OnUpdateSetRequest) (*plgpb.OnUpdateSetResponse, error) { + if t.OnUpdateSetFn == nil { + return t.UnimplementedHostPluginServiceServer.OnUpdateSet(ctx, req) + } + return t.OnUpdateSetFn(ctx, req) +} + +func (t TestPluginServer) OnDeleteSet(ctx context.Context, req *plgpb.OnDeleteSetRequest) (*plgpb.OnDeleteSetResponse, error) { + if t.OnDeleteSetFn == nil { + return t.UnimplementedHostPluginServiceServer.OnDeleteSet(ctx, req) + } + return t.OnDeleteSetFn(ctx, req) +} + +func (t TestPluginServer) ListHosts(ctx context.Context, req *plgpb.ListHostsRequest) (*plgpb.ListHostsResponse, error) { + if t.ListHostsFn == nil { + return t.UnimplementedHostPluginServiceServer.ListHosts(ctx, req) + } + return t.ListHostsFn(ctx, req) +} diff --git a/internal/host/plugin/testing_test.go b/internal/host/plugin/testing_test.go index 50419aef7..d5b7efbf1 100644 --- a/internal/host/plugin/testing_test.go +++ b/internal/host/plugin/testing_test.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "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" ) @@ -46,7 +47,7 @@ func Test_TestSet(t *testing.T) { assert.NotEmpty(plg.GetPublicId()) c := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId()) - set := TestSet(t, conn, kmsCache, c, WithName("foo"), WithDescription("bar")) + set := TestSet(t, conn, kmsCache, c, map[string]plgpb.HostPluginServiceServer{plg.GetPublicId(): &TestPluginServer{}}, WithName("foo"), WithDescription("bar")) assert.NotEmpty(set.GetPublicId()) assert.True(strings.HasPrefix(set.GetPublicId(), fmt.Sprintf("%s_%s", HostSetPrefix, "prefix"))) assert.Equal("foo", set.GetName()) diff --git a/internal/host/preferred_endpoint_test.go b/internal/host/preferred_endpoint_test.go index a74310f9d..aaaca17e7 100644 --- a/internal/host/preferred_endpoint_test.go +++ b/internal/host/preferred_endpoint_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" 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" ) @@ -26,7 +27,9 @@ func TestPreferredEndpoint_Create(t *testing.T) { _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) plg := hostplg.TestPlugin(t, conn, "create", "create") catalog := plugin.TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) - set := plugin.TestSet(t, conn, kmsCache, catalog) + set := plugin.TestSet(t, conn, kmsCache, catalog, map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &plugin.TestPluginServer{}, + }) type args struct { hostSetId string @@ -154,7 +157,9 @@ func TestPreferredEndpoint_Delete(t *testing.T) { _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) plg := hostplg.TestPlugin(t, conn, "create", "create") catalog := plugin.TestCatalog(t, conn, prj.PublicId, plg.GetPublicId()) - set := plugin.TestSet(t, conn, kmsCache, catalog) + set := plugin.TestSet(t, conn, kmsCache, catalog, map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &plgpb.UnimplementedHostPluginServiceServer{}, + }) peFunc := func(priority uint32, condition string) *host.PreferredEndpoint { ep, err := host.NewPreferredEndpoint(ctx, set.PublicId, priority, condition) diff --git a/internal/plugin/host/store/plugin.pb.go b/internal/plugin/host/store/plugin.pb.go index 05c63df53..ac2cade04 100644 --- a/internal/plugin/host/store/plugin.pb.go +++ b/internal/plugin/host/store/plugin.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.15.8 +// protoc v3.17.3 // source: controller/storage/plugin/host/store/v1/plugin.proto // Package store provides protobufs for storing types in the host package. diff --git a/internal/plugin/store/plugin.pb.go b/internal/plugin/store/plugin.pb.go index 54d39c526..2514d8a1c 100644 --- a/internal/plugin/store/plugin.pb.go +++ b/internal/plugin/store/plugin.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.15.8 +// protoc v3.17.3 // source: controller/storage/plugin/store/v1/plugin.proto // Package store provides protobufs for storing types in the host package. 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 d5c8b9626..37f1c2141 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 @@ -43,7 +43,7 @@ message HostCatalog { uint32 version = 8; // attributes is a jsonb formatted field. - // @inject_tag: `gorm:"default:null"` + // @inject_tag: `gorm:"not_null"` bytes attributes = 9; } @@ -79,7 +79,7 @@ message HostSet { uint32 version = 7; // attributes is a byte field containing marshaled JSON data - // @inject_tag: `gorm:"default:null"` + // @inject_tag: `gorm:"not_null"` bytes attributes = 8; // preferred_endpoints stores string preference values diff --git a/internal/servers/controller/controller.go b/internal/servers/controller/controller.go index 850216571..eca57cf3e 100644 --- a/internal/servers/controller/controller.go +++ b/internal/servers/controller/controller.go @@ -24,6 +24,7 @@ import ( "github.com/hashicorp/boundary/internal/servers/controller/common" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-secure-stdlib/mlock" "github.com/patrickmn/go-cache" @@ -136,7 +137,7 @@ func New(ctx context.Context, conf *Config) (*Controller, error) { return static.NewRepository(dbase, dbase, c.kms) } c.PluginHostRepoFn = func() (*hostplugin.Repository, error) { - return hostplugin.NewRepository(dbase, dbase, c.kms) + return hostplugin.NewRepository(dbase, dbase, c.kms, map[string]plgpb.HostPluginServiceServer{}) } c.HostPluginRepoFn = func() (*host.Repository, error) { return host.NewRepository(dbase, dbase, c.kms) diff --git a/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go b/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go index 6479c12eb..5772131d1 100644 --- a/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go +++ b/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go @@ -2,7 +2,6 @@ package host_catalogs import ( "context" - "encoding/json" "fmt" "strings" @@ -26,6 +25,7 @@ import ( "github.com/hashicorp/boundary/internal/types/scope" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" "google.golang.org/grpc/codes" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -603,16 +603,13 @@ func toProto(ctx context.Context, in host.Catalog, opt ...handlers.Option) (*pb. if outputFields.Has(globals.AttributesField) { switch h := in.(type) { case *plugin.HostCatalog: - attrs := map[string]interface{}{} - err := json.Unmarshal(h.Attributes, &attrs) + attrs := &structpb.Struct{} + err := proto.Unmarshal(h.Attributes, attrs) if err != nil { return nil, errors.Wrap(ctx, err, op) } - if len(attrs) > 0 { - out.Attributes, err = structpb.NewStruct(attrs) - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } + if len(attrs.GetFields()) > 0 { + out.Attributes = attrs } } } @@ -645,10 +642,10 @@ func toStoragePluginCatalog(ctx context.Context, scopeId, plgId string, item *pb opts = append(opts, plugin.WithDescription(desc.GetValue())) } if attrs := item.GetAttributes(); attrs != nil { - opts = append(opts, plugin.WithAttributes(attrs.AsMap())) + opts = append(opts, plugin.WithAttributes(attrs)) } if secrets := item.GetSecrets(); secrets != nil { - opts = append(opts, plugin.WithSecrets(secrets.AsMap())) + opts = append(opts, plugin.WithSecrets(secrets)) } hc, err := plugin.NewHostCatalog(ctx, scopeId, plgId, opts...) if err != nil { diff --git a/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go b/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go index e3a684b1f..6f2c64799 100644 --- a/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go +++ b/internal/servers/controller/handlers/host_catalogs/host_catalog_service_test.go @@ -23,6 +23,7 @@ import ( "github.com/hashicorp/boundary/internal/types/scope" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" scopepb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" "google.golang.org/genproto/protobuf/field_mask" "google.golang.org/grpc/codes" "google.golang.org/protobuf/proto" @@ -62,7 +63,7 @@ func TestGet_Static(t *testing.T) { return static.NewRepository(rw, rw, kms) } pluginHostRepo := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } pluginRepo := func() (*host.Repository, error) { return host.NewRepository(rw, rw, kms) @@ -151,7 +152,7 @@ func TestGet_Plugin(t *testing.T) { return static.NewRepository(rw, rw, kms) } pluginHostRepo := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } pluginRepo := func() (*host.Repository, error) { return host.NewRepository(rw, rw, kms) @@ -240,7 +241,7 @@ func TestList(t *testing.T) { return iam.TestRepo(t, conn, wrapper), nil } pluginHostRepo := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } pluginRepo := func() (*host.Repository, error) { return host.NewRepository(rw, rw, kms) @@ -433,7 +434,7 @@ func TestDelete(t *testing.T) { return static.NewRepository(rw, rw, kms) } pluginHostRepo := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } pluginRepo := func() (*host.Repository, error) { return host.NewRepository(rw, rw, kms) @@ -503,7 +504,7 @@ func TestDelete_twice(t *testing.T) { return static.NewRepository(rw, rw, kms) } pluginHostRepo := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } pluginRepo := func() (*host.Repository, error) { return host.NewRepository(rw, rw, kms) @@ -538,7 +539,7 @@ func TestCreate_Static(t *testing.T) { return static.NewRepository(rw, rw, kms) } pluginHostRepo := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } pluginRepo := func() (*host.Repository, error) { return host.NewRepository(rw, rw, kms) @@ -688,9 +689,6 @@ func TestCreate_Plugin(t *testing.T) { repo := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } - pluginHostRepo := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) - } pluginRepo := func() (*host.Repository, error) { return host.NewRepository(rw, rw, kms) } @@ -700,6 +698,15 @@ func TestCreate_Plugin(t *testing.T) { name := "test" plg := host.TestPlugin(t, conn, name, name) + pluginHostRepo := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &plugin.TestPluginServer{ + OnCreateCatalogFn: func(ctx context.Context, req *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) { + return nil, nil + }, + }, + }) + } defaultHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) defaultHcCreated := defaultHc.GetCreateTime().GetTimestamp().AsTime() toMerge := &pbs.CreateHostCatalogRequest{} @@ -845,7 +852,7 @@ func TestUpdate(t *testing.T) { return static.NewRepository(rw, rw, kms) } pluginHostRepo := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } pluginRepo := func() (*host.Repository, error) { return host.NewRepository(rw, rw, kms) diff --git a/internal/servers/controller/handlers/host_sets/host_set_service.go b/internal/servers/controller/handlers/host_sets/host_set_service.go index f4498fa65..f4d8323f1 100644 --- a/internal/servers/controller/handlers/host_sets/host_set_service.go +++ b/internal/servers/controller/handlers/host_sets/host_set_service.go @@ -2,7 +2,6 @@ package host_sets import ( "context" - "encoding/json" "fmt" "strings" @@ -24,6 +23,7 @@ import ( pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" "github.com/hashicorp/go-secure-stdlib/strutil" "google.golang.org/grpc/codes" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -756,16 +756,13 @@ func toProto(ctx context.Context, in host.Set, hosts []host.Host, opt ...handler out.PreferredEndpoints = h.GetPreferredEndpoints() } if outputFields.Has(globals.AttributesField) { - attrs := map[string]interface{}{} - err := json.Unmarshal(h.Attributes, &attrs) + attrs := &structpb.Struct{} + err := proto.Unmarshal(h.Attributes, attrs) if err != nil { return nil, errors.Wrap(ctx, err, op) } - if len(attrs) > 0 { - out.Attributes, err = structpb.NewStruct(attrs) - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } + if len(attrs.GetFields()) > 0 { + out.Attributes = attrs } } } @@ -799,7 +796,7 @@ func toStoragePluginSet(ctx context.Context, catalogId string, item *pb.HostSet) opts = append(opts, plugin.WithDescription(item.GetDescription().GetValue())) } if item.GetAttributes() != nil { - opts = append(opts, plugin.WithAttributes(item.GetAttributes().AsMap())) + opts = append(opts, plugin.WithAttributes(item.GetAttributes())) } if item.GetPreferredEndpoints() != nil { opts = append(opts, plugin.WithPreferredEndpoints(item.GetPreferredEndpoints())) diff --git a/internal/servers/controller/handlers/host_sets/host_set_service_test.go b/internal/servers/controller/handlers/host_sets/host_set_service_test.go index 7722b5efe..09ab45673 100644 --- a/internal/servers/controller/handlers/host_sets/host_set_service_test.go +++ b/internal/servers/controller/handlers/host_sets/host_set_service_test.go @@ -23,6 +23,7 @@ import ( "github.com/hashicorp/boundary/internal/types/scope" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/genproto/protobuf/field_mask" @@ -54,7 +55,7 @@ func TestGet_Static(t *testing.T) { return static.NewRepository(rw, rw, kms) } pluginRepoFn := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] @@ -147,14 +148,16 @@ func TestGet_Plugin(t *testing.T) { return static.NewRepository(rw, rw, kms) } pluginRepoFn := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } name := "test" prefEndpoints := []string{"cidr:1.2.3.4", "cidr:2.3.4.5/24"} plg := hostplugin.TestPlugin(t, conn, name, name) hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) - hs := plugin.TestSet(t, conn, kms, hc, plugin.WithPreferredEndpoints(prefEndpoints)) + hs := plugin.TestSet(t, conn, kms, hc, map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &plugin.TestPluginServer{}, + }, plugin.WithPreferredEndpoints(prefEndpoints)) toMerge := &pbs.GetHostSetRequest{} @@ -241,7 +244,7 @@ func TestList_Static(t *testing.T) { return static.NewRepository(rw, rw, kms) } pluginRepoFn := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } hcs := static.TestCatalogs(t, conn, proj.GetPublicId(), 2) hc, hcNoHosts := hcs[0], hcs[1] @@ -338,18 +341,21 @@ func TestList_Plugin(t *testing.T) { repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } - pluginRepoFn := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) - } name := "test" plg := hostplugin.TestPlugin(t, conn, name, name) + plgm := map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &plugin.TestPluginServer{}, + } + pluginRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, plgm) + } hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) hcNoHosts := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) preferredEndpoints := []string{"cidr:1.2.3.4", "dns:*.foobar.com"} var wantHs []*pb.HostSet for i := 0; i < 10; i++ { - h := plugin.TestSet(t, conn, kms, hc, plugin.WithPreferredEndpoints(preferredEndpoints)) + h := plugin.TestSet(t, conn, kms, hc, plgm, plugin.WithPreferredEndpoints(preferredEndpoints)) wantHs = append(wantHs, &pb.HostSet{ Id: h.GetPublicId(), HostCatalogId: h.GetCatalogId(), @@ -443,7 +449,7 @@ func TestDelete(t *testing.T) { return static.NewRepository(rw, rw, kms) } pluginRepoFn := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] @@ -522,7 +528,7 @@ func TestDelete_twice(t *testing.T) { return static.NewRepository(rw, rw, kms) } plgRepoFn := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] @@ -558,7 +564,7 @@ func TestCreate_Static(t *testing.T) { return static.NewRepository(rw, rw, kms) } plgRepoFn := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] @@ -700,11 +706,17 @@ func TestCreate_Plugin(t *testing.T) { repoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } - plgRepoFn := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) - } name := "test" plg := hostplugin.TestPlugin(t, conn, name, name) + plgRepoFn := func() (*plugin.Repository, error) { + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &plugin.TestPluginServer{ + OnCreateSetFn: func(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) { + return nil, nil + }, + }, + }) + } hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) testAttrs, err := structpb.NewStruct(map[string]interface{}{ @@ -956,7 +968,7 @@ func TestUpdate(t *testing.T) { } plgRepoFn := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } tested, err := host_sets.NewService(repoFn, plgRepoFn) require.NoError(t, err, "Failed to create a new host set service.") @@ -1295,7 +1307,7 @@ func TestAddHostSetHosts(t *testing.T) { return static.NewRepository(rw, rw, kms) } plgRepoFn := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } s, err := host_sets.NewService(repoFn, plgRepoFn) require.NoError(t, err, "Error when getting new host set service.") @@ -1415,7 +1427,7 @@ func TestSetHostSetHosts(t *testing.T) { return static.NewRepository(rw, rw, kms) } plgRepoFn := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } s, err := host_sets.NewService(repoFn, plgRepoFn) require.NoError(t, err, "Error when getting new host set service.") @@ -1531,7 +1543,7 @@ func TestRemoveHostSetHosts(t *testing.T) { return static.NewRepository(rw, rw, kms) } plgRepoFn := func() (*plugin.Repository, error) { - return plugin.NewRepository(rw, rw, kms) + return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceServer{}) } s, err := host_sets.NewService(repoFn, plgRepoFn) require.NoError(t, err, "Error when getting new host set service.") diff --git a/internal/servers/controller/handlers/targets/target_service_test.go b/internal/servers/controller/handlers/targets/target_service_test.go index ac7d36e02..98da7aab1 100644 --- a/internal/servers/controller/handlers/targets/target_service_test.go +++ b/internal/servers/controller/handlers/targets/target_service_test.go @@ -38,6 +38,7 @@ import ( credpb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentiallibraries" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/targets" + plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" @@ -983,7 +984,9 @@ func TestAddTargetHostSets(t *testing.T) { plg := host.TestPlugin(t, conn, "test", "test") pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) - pluginHs := plugin.TestSet(t, conn, kms, pluginHc) + pluginHs := plugin.TestSet(t, conn, kms, pluginHc, map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &plugin.TestPluginServer{}, + }) addCases := []struct { name string @@ -1115,7 +1118,9 @@ func TestSetTargetHostSets(t *testing.T) { plg := host.TestPlugin(t, conn, "test", "test") pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) - pluginHs := plugin.TestSet(t, conn, kms, pluginHc) + pluginHs := plugin.TestSet(t, conn, kms, pluginHc, map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &plugin.TestPluginServer{}, + }) setCases := []struct { name string @@ -1241,7 +1246,9 @@ func TestRemoveTargetHostSets(t *testing.T) { plg := host.TestPlugin(t, conn, "test", "test") pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) - pluginHs := plugin.TestSet(t, conn, kms, pluginHc) + pluginHs := plugin.TestSet(t, conn, kms, pluginHc, map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &plugin.TestPluginServer{}, + }) removeCases := []struct { name string @@ -1379,7 +1386,9 @@ func TestAddTargetHostSources(t *testing.T) { plg := host.TestPlugin(t, conn, "test", "test") pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) - pluginHs := plugin.TestSet(t, conn, kms, pluginHc) + pluginHs := plugin.TestSet(t, conn, kms, pluginHc, map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &plugin.TestPluginServer{}, + }) addCases := []struct { name string @@ -1511,7 +1520,9 @@ func TestSetTargetHostSources(t *testing.T) { plg := host.TestPlugin(t, conn, "test", "test") pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) - pluginHs := plugin.TestSet(t, conn, kms, pluginHc) + pluginHs := plugin.TestSet(t, conn, kms, pluginHc, map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &plugin.TestPluginServer{}, + }) setCases := []struct { name string @@ -1631,7 +1642,9 @@ func TestRemoveTargetHostSources(t *testing.T) { plg := host.TestPlugin(t, conn, "test", "test") pluginHc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) - pluginHs := plugin.TestSet(t, conn, kms, pluginHc) + pluginHs := plugin.TestSet(t, conn, kms, pluginHc, map[string]plgpb.HostPluginServiceServer{ + plg.GetPublicId(): &plugin.TestPluginServer{}, + }) removeCases := []struct { name string diff --git a/sdk/pbs/plugin/host_plugin_service.pb.go b/sdk/pbs/plugin/host_plugin_service.pb.go index e90bad60e..a8719f737 100644 --- a/sdk/pbs/plugin/host_plugin_service.pb.go +++ b/sdk/pbs/plugin/host_plugin_service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.15.8 +// protoc v3.17.3 // source: plugin/v1/host_plugin_service.proto package plugin