diff --git a/internal/db/schema/postgres_migration.gen.go b/internal/db/schema/postgres_migration.gen.go index d6373fb498..1c8e4724d8 100644 --- a/internal/db/schema/postgres_migration.gen.go +++ b/internal/db/schema/postgres_migration.gen.go @@ -7059,7 +7059,7 @@ alter table wh_host_dimension check(length(trim(plugin_name)) > 0) constraint plugin_name_must_be_lowercase check(lower(trim(plugin_name)) = plugin_name) - constraint plugin_host_name_uq + constraint plugin_host_plugin_name_uq unique, id_prefix text not null constraint plugin_id_prefix_must_be_not_empty @@ -7217,7 +7217,7 @@ alter table wh_host_dimension update_time wt_timestamp, version wt_version, attributes bytea not null - constraint attributes_must_not_empty + constraint attributes_must_not_be_empty check(length(attributes) > 0), constraint host_catalog_fkey foreign key (scope_id, public_id) @@ -7289,7 +7289,7 @@ alter table wh_host_dimension update_time wt_timestamp, version wt_version, attributes bytea not null - constraint attributes_must_not_empty + constraint attributes_must_not_be_empty check(length(attributes) > 0), constraint host_plugin_set_catalog_id_name_uq unique(catalog_id, name), diff --git a/internal/plugin/host/plugin.go b/internal/plugin/host/plugin.go index ddabc58ec0..ca339ec8b3 100644 --- a/internal/plugin/host/plugin.go +++ b/internal/plugin/host/plugin.go @@ -1,8 +1,10 @@ package host import ( + "github.com/hashicorp/boundary/internal/oplog" "github.com/hashicorp/boundary/internal/plugin/host/store" "github.com/hashicorp/boundary/internal/types/scope" + "google.golang.org/protobuf/proto" ) // A Plugin enables additional logic to be used by boundary. @@ -14,7 +16,7 @@ type Plugin struct { // NewPlugin creates a new in memory Plugin assigned to the global scope. // Name, Description are the only allowed option. All other options are ignored. -func NewPlugin(pluginName, idPrefix string, opt ...Option) *Plugin { +func NewPlugin(pluginName string, idPrefix string, opt ...Option) *Plugin { opts := getOpts(opt...) p := &Plugin{ Plugin: &store.Plugin{ @@ -41,3 +43,28 @@ func (c *Plugin) TableName() string { func (c *Plugin) SetTableName(n string) { c.tableName = n } + +func allocPlugin() *Plugin { + return &Plugin{ + Plugin: &store.Plugin{}, + } +} + +func (c *Plugin) clone() *Plugin { + cp := proto.Clone(c.Plugin) + return &Plugin{ + Plugin: cp.(*store.Plugin), + } +} + +func newPluginMetadata(p *Plugin, op oplog.OpType) oplog.Metadata { + metadata := oplog.Metadata{ + "resource-public-id": []string{p.GetPublicId()}, + "resource-type": []string{"host plugin"}, + "op-type": []string{op.String()}, + } + if p.ScopeId != "" { + metadata["scope-id"] = []string{p.ScopeId} + } + return metadata +} diff --git a/internal/plugin/host/plugin_test.go b/internal/plugin/host/plugin_test.go index 01bccd8aec..0d8715e9fa 100644 --- a/internal/plugin/host/plugin_test.go +++ b/internal/plugin/host/plugin_test.go @@ -18,7 +18,6 @@ func TestPlugin_Create(t *testing.T) { type args struct { pluginName string idPrefix string - semVer string opts []Option } @@ -54,51 +53,6 @@ func TestPlugin_Create(t *testing.T) { }, wantErr: true, }, - { - name: "idprefix-capitalized", - args: args{ - pluginName: "idprefixcapitalized", - idPrefix: "IdPrefixCapitalized", - }, - want: &Plugin{ - Plugin: &store.Plugin{ - PluginName: "idprefixcapitalized", - IdPrefix: "IdPrefixCapitalized", - ScopeId: scope.Global.String(), - }, - }, - wantErr: true, - }, - { - name: "idprefix-space", - args: args{ - pluginName: "idprefix space", - idPrefix: "idprefix space", - }, - want: &Plugin{ - Plugin: &store.Plugin{ - PluginName: "idprefix space", - IdPrefix: "idprefix space", - ScopeId: scope.Global.String(), - }, - }, - wantErr: true, - }, - { - name: "pluginName-capitalized", - args: args{ - pluginName: "PluginNameCapitalized", - idPrefix: "pluginnamecapitalized", - }, - want: &Plugin{ - Plugin: &store.Plugin{ - PluginName: "PluginNameCapitalized", - IdPrefix: "pluginnamecapitalized", - ScopeId: scope.Global.String(), - }, - }, - wantErr: true, - }, { name: "valid-no-options", args: args{ diff --git a/internal/plugin/host/repository.go b/internal/plugin/host/repository.go new file mode 100644 index 0000000000..a8604342ad --- /dev/null +++ b/internal/plugin/host/repository.go @@ -0,0 +1,47 @@ +package host + +import ( + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" +) + +// A Repository stores and retrieves the persistent types in the host +// package. It is not safe to use a repository concurrently. +type Repository struct { + reader db.Reader + writer db.Writer + kms *kms.Kms + // defaultLimit provides a default for limiting the number of results + // returned from the repo + defaultLimit int +} + +// NewRepository creates a new Repository. The returned repository should +// only be used for one transaction and it is not safe for concurrent go +// routines to access it. WithLimit option is used as a repo wide default +// limit applied to all ListX methods. +func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) { + const op = "static.NewRepository" + switch { + case r == nil: + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "db.Reader") + case w == nil: + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "db.Writer") + case kms == nil: + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "kms") + } + + opts := getOpts(opt...) + if opts.withLimit == 0 { + // zero signals the boundary defaults should be used. + opts.withLimit = db.DefaultLimit + } + + return &Repository{ + reader: r, + writer: w, + kms: kms, + defaultLimit: opts.withLimit, + }, nil +} diff --git a/internal/plugin/host/repository_plugin.go b/internal/plugin/host/repository_plugin.go new file mode 100644 index 0000000000..b64ec8c690 --- /dev/null +++ b/internal/plugin/host/repository_plugin.go @@ -0,0 +1,148 @@ +package host + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/types/scope" +) + +// CreatePlugin inserts p into the repository and returns a new +// Plugin containing the plugin's PublicId. p is not changed. p must +// contain a valid ScopeID. p must not contain a PublicId. The PublicId is +// generated and assigned by this method. opt is ignored. +// +// Both p.Name and p.Description are optional. If p.Name is set, it must be +// unique within p.ScopeID. +// +// Both p.CreateTime and c.UpdateTime are ignored. +func (r *Repository) CreatePlugin(ctx context.Context, p *Plugin, _ ...Option) (*Plugin, error) { + const op = "host.(Repository).CreatePlugin" + if p == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "nil Plugin") + } + if p.Plugin == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "nil embedded Plugin") + } + if p.ScopeId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no scope id") + } + if p.ScopeId != scope.Global.String() { + return nil, errors.New(ctx, errors.InvalidParameter, op, "scope id is not 'global'") + } + if p.PublicId != "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty") + } + if p.PluginName == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no plugin name") + } + if p.IdPrefix == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no id prefix") + } + // TODO: remove the restriction on prefix having to be the same as plugin name + if p.PluginName != p.IdPrefix { + return nil, errors.New(ctx, errors.InvalidParameter, op, "id prefix and plugin name don't match") + } + + p = p.clone() + + id, err := newPluginId() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + p.PublicId = id + + oplogWrapper, err := r.kms.GetWrapper(ctx, p.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + metadata := newPluginMetadata(p, oplog.OpType_OP_TYPE_CREATE) + + var newPlugin *Plugin + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + newPlugin = p.clone() + err := w.Create( + ctx, + newPlugin, + db.WithOplog(oplogWrapper, metadata), + ) + if err != nil { + return errors.Wrap(ctx, err, op) + } + return nil + }, + ) + + if err != nil { + if errors.IsUniqueError(err) { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in scope: %s: name %s already exists", p.ScopeId, p.Name))) + } + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in scope: %s", p.ScopeId))) + } + return newPlugin, nil +} + +// LookupPlugin returns the Plugin for id. Returns nil, nil if no +// Plugin is found for id. +func (r *Repository) LookupPlugin(ctx context.Context, id string, _ ...Option) (*Plugin, error) { + const op = "host.(Repository).LookupPlugin" + if id == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id") + } + c := allocPlugin() + c.PublicId = id + if err := r.reader.LookupByPublicId(ctx, c); err != nil { + if errors.IsNotFoundError(err) { + return nil, nil + } + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", id))) + } + return c, nil +} + +// LookupPluginByPluginName returns the Plugin for a given name. Returns nil, nil if no +// Plugin is found with that plugin name. +func (r *Repository) LookupPluginByPluginName(ctx context.Context, name string, _ ...Option) (*Plugin, error) { + const op = "host.(Repository).LookupPluginByPluginName" + if name == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no plugin name") + } + p := allocPlugin() + + if err := r.reader.LookupWhere(ctx, p, "plugin_name=?", name); err != nil { + if errors.IsNotFoundError(err) { + return nil, nil + } + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", name))) + } + return p, nil +} + +// ListPlugins returns a slice of Plugins for the scope IDs. WithLimit is the only option supported. +func (r *Repository) ListPlugins(ctx context.Context, scopeIds []string, opt ...Option) ([]*Plugin, error) { + const op = "host.(Repository).ListPlugins" + if len(scopeIds) == 0 { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no scope 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 plugins []*Plugin + err := r.reader.SearchWhere(ctx, &plugins, "scope_id in (?)", []interface{}{scopeIds}, db.WithLimit(limit)) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + return plugins, nil +} diff --git a/internal/plugin/host/repository_plugin_test.go b/internal/plugin/host/repository_plugin_test.go new file mode 100644 index 0000000000..262aaf99c3 --- /dev/null +++ b/internal/plugin/host/repository_plugin_test.go @@ -0,0 +1,326 @@ +package host + +import ( + "context" + "strings" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/plugin/host/store" + "github.com/hashicorp/boundary/internal/types/scope" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRepository_CreatePlugin(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + iam.TestRepo(t, conn, wrapper) + kmsCache := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache) + assert.NoError(t, err) + assert.NotNil(t, repo) + + tests := []struct { + name string + in *Plugin + opts []Option + want *Plugin + wantIsErr errors.Code + }{ + { + name: "nil-plugin", + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-embedded-plugin", + in: &Plugin{}, + wantIsErr: errors.InvalidParameter, + }, + { + name: "valid-no-options", + in: &Plugin{ + Plugin: &store.Plugin{ + ScopeId: scope.Global.String(), + IdPrefix: "validnooptions", + PluginName: "validnooptions", + }, + }, + want: &Plugin{ + Plugin: &store.Plugin{ + ScopeId: scope.Global.String(), + IdPrefix: "validnooptions", + PluginName: "validnooptions", + }, + }, + }, + { + name: "valid-with-name", + in: &Plugin{ + Plugin: &store.Plugin{ + Name: "test-name-repo", + ScopeId: scope.Global.String(), + IdPrefix: "validwithname", + PluginName: "validwithname", + }, + }, + want: &Plugin{ + Plugin: &store.Plugin{ + Name: "test-name-repo", + ScopeId: scope.Global.String(), + IdPrefix: "validwithname", + PluginName: "validwithname", + }, + }, + }, + { + name: "valid-with-description", + in: &Plugin{ + Plugin: &store.Plugin{ + Description: "test-description-repo", + ScopeId: scope.Global.String(), + IdPrefix: "validwithdescription", + PluginName: "validwithdescription", + }, + }, + want: &Plugin{ + Plugin: &store.Plugin{ + Description: "test-description-repo", + ScopeId: scope.Global.String(), + IdPrefix: "validwithdescription", + PluginName: "validwithdescription", + }, + }, + }, + { + name: "mismatching-prefix-plugin-name", + in: &Plugin{ + Plugin: &store.Plugin{ + ScopeId: scope.Global.String(), + IdPrefix: "foo", + PluginName: "bar", + }, + }, + wantIsErr: errors.InvalidParameter, + }, + { + name: "non-global-scope", + in: &Plugin{ + Plugin: &store.Plugin{ + ScopeId: "o_1234567890", + IdPrefix: "foo", + PluginName: "foo", + }, + }, + wantIsErr: errors.InvalidParameter, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + got, err := repo.CreatePlugin(context.Background(), tt.in, 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(t, err) + assert.NoError(err) + assert.Empty(tt.in.PublicId) + assert.NotNil(got) + assertPublicId(t, PluginPrefix, got.PublicId) + assert.NotSame(tt.in, got) + assert.Equal(tt.want.Name, got.Name) + assert.Equal(tt.want.Description, got.Description) + assert.Equal(got.CreateTime, got.UpdateTime) + }) + } + + 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) + assert.NoError(err) + assert.NotNil(repo) + in := &Plugin{ + Plugin: &store.Plugin{ + ScopeId: scope.Global.String(), + IdPrefix: "invalidduplicatenames", + PluginName: "invalidduplicatenames", + Name: "invalid-duplicate-names", + }, + } + + got, err := repo.CreatePlugin(context.Background(), in) + assert.NoError(err) + assert.NotNil(got) + assertPublicId(t, PluginPrefix, got.PublicId) + assert.NotSame(in, got) + assert.Equal(in.Name, got.Name) + assert.Equal(in.Description, got.Description) + assert.Equal(got.CreateTime, got.UpdateTime) + + got2, err := repo.CreatePlugin(context.Background(), in) + assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err) + assert.Nil(got2) + }) + + t.Run("invalid-duplicate-plugin-names-prefixes", func(t *testing.T) { + assert := assert.New(t) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) + assert.NoError(err) + assert.NotNil(repo) + in := &Plugin{ + Plugin: &store.Plugin{ + ScopeId: scope.Global.String(), + IdPrefix: "invalidduplicatepluginnames", + PluginName: "invalidduplicatepluginnames", + }, + } + + got, err := repo.CreatePlugin(context.Background(), in) + assert.NoError(err) + assert.NotNil(got) + assertPublicId(t, PluginPrefix, got.PublicId) + assert.NotSame(in, got) + assert.Equal(in.Name, got.Name) + assert.Equal(in.Description, got.Description) + assert.Equal(got.CreateTime, got.UpdateTime) + + got2, err := repo.CreatePlugin(context.Background(), in) + assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err) + assert.Nil(got2) + }) +} + +func assertPublicId(t *testing.T, prefix, actual string) { + t.Helper() + assert.NotEmpty(t, actual) + parts := strings.Split(actual, "_") + assert.Equalf(t, 2, len(parts), "want one '_' in PublicId, got multiple in %q", actual) + assert.Equalf(t, prefix, parts[0], "PublicId want prefix: %q, got: %q in %q", prefix, parts[0], actual) +} + +func TestRepository_LookupPlugin(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + plg := TestPlugin(t, conn, "test", "test") + badId, err := newPluginId() + assert.NoError(t, err) + assert.NotNil(t, badId) + + tests := []struct { + name string + id string + want *Plugin + wantErr errors.Code + }{ + { + name: "found", + id: plg.GetPublicId(), + want: plg, + }, + { + name: "not-found", + id: badId, + want: nil, + }, + { + name: "bad-public-id", + id: "", + want: nil, + wantErr: errors.InvalidParameter, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) + assert.NoError(err) + assert.NotNil(repo) + + got, err := repo.LookupPlugin(context.Background(), tt.id) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + return + } + assert.NoError(err) + + switch { + case tt.want == nil: + assert.Nil(got) + case tt.want != nil: + assert.NotNil(got) + assert.Equal(got, tt.want) + } + }) + } +} + +func TestRepository_LookupPluginByPluginName(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + plg := TestPlugin(t, conn, "name123", "name123") + + tests := []struct { + name string + pluginName string + want *Plugin + wantErr errors.Code + }{ + { + name: "found", + pluginName: plg.GetPluginName(), + want: plg, + }, + { + name: "not-found", + pluginName: "randomname", + want: nil, + }, + { + name: "emptyname", + pluginName: "", + want: nil, + wantErr: errors.InvalidParameter, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms) + assert.NoError(err) + assert.NotNil(repo) + + got, err := repo.LookupPluginByPluginName(context.Background(), tt.pluginName) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + return + } + assert.NoError(err) + + switch { + case tt.want == nil: + assert.Nil(got) + case tt.want != nil: + assert.NotNil(got) + assert.Equal(got, tt.want) + } + }) + } +} diff --git a/internal/plugin/host/repository_test.go b/internal/plugin/host/repository_test.go new file mode 100644 index 0000000000..ba4b57797d --- /dev/null +++ b/internal/plugin/host/repository_test.go @@ -0,0 +1,118 @@ +package host + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" +) + +func TestRepository_New(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + type args struct { + r db.Reader + w db.Writer + kms *kms.Kms + opts []Option + } + + tests := []struct { + name string + args args + want *Repository + wantIsErr errors.Code + }{ + { + name: "valid", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + }, + want: &Repository{ + reader: rw, + writer: rw, + kms: kmsCache, + defaultLimit: db.DefaultLimit, + }, + }, + { + name: "valid-with-limit", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + opts: []Option{WithLimit(5)}, + }, + want: &Repository{ + reader: rw, + writer: rw, + kms: kmsCache, + defaultLimit: 5, + }, + }, + { + name: "nil-reader", + args: args{ + r: nil, + w: rw, + kms: kmsCache, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-writer", + args: args{ + r: rw, + w: nil, + kms: kmsCache, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-kms", + args: args{ + r: rw, + w: rw, + kms: nil, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "all-nils", + args: args{ + r: nil, + w: nil, + kms: nil, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + } + for _, tt := range tests { + 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...) + 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 + } + assert.NoError(err) + require.NotNil(got) + assert.Equal(tt.want, got) + }) + } +} diff --git a/internal/plugin/store/plugin.pb.go b/internal/plugin/store/plugin.pb.go index 54d39c5264..3f3116aa87 100644 --- a/internal/plugin/store/plugin.pb.go +++ b/internal/plugin/store/plugin.pb.go @@ -9,7 +9,7 @@ package store import ( - _ "github.com/hashicorp/boundary/internal/db/timestamp" + timestamp "github.com/hashicorp/boundary/internal/db/timestamp" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -94,6 +94,174 @@ func (x *Plugin) GetName() string { return "" } +type PluginVersion struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + PublicId string `protobuf:"bytes,10,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // plugin_id is a foreign key to the plugin table. + // @inject_tag: `gorm:"default:null"` + PluginId string `protobuf:"bytes,20,opt,name=plugin_id,json=pluginId,proto3" json:"plugin_id,omitempty" gorm:"default:null"` + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,30,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // semantic_version is required and is the symantic version for the above plugin. + // @inject_tag: `gorm:"default:null"` + SemanticVersion string `protobuf:"bytes,4,opt,name=semantic_version,json=semanticVersion,proto3" json:"semantic_version,omitempty" gorm:"default:null"` +} + +func (x *PluginVersion) Reset() { + *x = PluginVersion{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_plugin_store_v1_plugin_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PluginVersion) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PluginVersion) ProtoMessage() {} + +func (x *PluginVersion) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_plugin_store_v1_plugin_proto_msgTypes[1] + 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 PluginVersion.ProtoReflect.Descriptor instead. +func (*PluginVersion) Descriptor() ([]byte, []int) { + return file_controller_storage_plugin_store_v1_plugin_proto_rawDescGZIP(), []int{1} +} + +func (x *PluginVersion) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *PluginVersion) GetPluginId() string { + if x != nil { + return x.PluginId + } + return "" +} + +func (x *PluginVersion) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *PluginVersion) GetSemanticVersion() string { + if x != nil { + return x.SemanticVersion + } + return "" +} + +type PluginExecutable struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // version_id is a foreign key to the plugin version table. + // @inject_tag: `gorm:"primary_key"` + VersionId string `protobuf:"bytes,10,opt,name=version_id,json=versionId,proto3" json:"version_id,omitempty" gorm:"primary_key"` + // operating_system to the operating system this executable is built to run on. + // @inject_tag: `gorm:"primary_key"` + OperatingSystem string `protobuf:"bytes,20,opt,name=operating_system,json=operatingSystem,proto3" json:"operating_system,omitempty" gorm:"primary_key"` + // architecture to the architecture this executable is built to run on. + // @inject_tag: `gorm:"primary_key"` + Architecture string `protobuf:"bytes,30,opt,name=architecture,proto3" json:"architecture,omitempty" gorm:"primary_key"` + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,40,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // executable is required and is the compressed executable for this plugin. + // @inject_tag: `gorm:"default:null"` + Executable []byte `protobuf:"bytes,50,opt,name=executable,proto3" json:"executable,omitempty" gorm:"default:null"` +} + +func (x *PluginExecutable) Reset() { + *x = PluginExecutable{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_plugin_store_v1_plugin_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PluginExecutable) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PluginExecutable) ProtoMessage() {} + +func (x *PluginExecutable) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_plugin_store_v1_plugin_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 PluginExecutable.ProtoReflect.Descriptor instead. +func (*PluginExecutable) Descriptor() ([]byte, []int) { + return file_controller_storage_plugin_store_v1_plugin_proto_rawDescGZIP(), []int{2} +} + +func (x *PluginExecutable) GetVersionId() string { + if x != nil { + return x.VersionId + } + return "" +} + +func (x *PluginExecutable) GetOperatingSystem() string { + if x != nil { + return x.OperatingSystem + } + return "" +} + +func (x *PluginExecutable) GetArchitecture() string { + if x != nil { + return x.Architecture + } + return "" +} + +func (x *PluginExecutable) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *PluginExecutable) GetExecutable() []byte { + if x != nil { + return x.Executable + } + return nil +} + var File_controller_storage_plugin_store_v1_plugin_proto protoreflect.FileDescriptor var file_controller_storage_plugin_store_v1_plugin_proto_rawDesc = []byte{ @@ -110,12 +278,39 @@ var file_controller_storage_plugin_store_v1_plugin_proto_rawDesc = []byte{ 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x3b, 0x5a, 0x39, - 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, 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, + 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xc1, 0x01, 0x0a, + 0x0d, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, + 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 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, 0x29, 0x0a, 0x10, 0x73, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, + 0x63, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0f, 0x73, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x22, 0xed, 0x01, 0x0a, 0x10, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, + 0x67, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, + 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, + 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, + 0x75, 0x72, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x28, 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, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x32, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x42, 0x3b, 0x5a, 0x39, 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, 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 ( @@ -130,16 +325,21 @@ func file_controller_storage_plugin_store_v1_plugin_proto_rawDescGZIP() []byte { return file_controller_storage_plugin_store_v1_plugin_proto_rawDescData } -var file_controller_storage_plugin_store_v1_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_controller_storage_plugin_store_v1_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_controller_storage_plugin_store_v1_plugin_proto_goTypes = []interface{}{ - (*Plugin)(nil), // 0: controller.storage.plugin.store.v1.Plugin + (*Plugin)(nil), // 0: controller.storage.plugin.store.v1.Plugin + (*PluginVersion)(nil), // 1: controller.storage.plugin.store.v1.PluginVersion + (*PluginExecutable)(nil), // 2: controller.storage.plugin.store.v1.PluginExecutable + (*timestamp.Timestamp)(nil), // 3: controller.storage.timestamp.v1.Timestamp } var file_controller_storage_plugin_store_v1_plugin_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 3, // 0: controller.storage.plugin.store.v1.PluginVersion.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 3, // 1: controller.storage.plugin.store.v1.PluginExecutable.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_controller_storage_plugin_store_v1_plugin_proto_init() } @@ -160,6 +360,30 @@ func file_controller_storage_plugin_store_v1_plugin_proto_init() { return nil } } + file_controller_storage_plugin_store_v1_plugin_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PluginVersion); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_plugin_store_v1_plugin_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PluginExecutable); 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{ @@ -167,7 +391,7 @@ func file_controller_storage_plugin_store_v1_plugin_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_storage_plugin_store_v1_plugin_proto_rawDesc, NumEnums: 0, - NumMessages: 1, + NumMessages: 3, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/proto/local/controller/storage/plugin/store/v1/plugin.proto b/internal/proto/local/controller/storage/plugin/store/v1/plugin.proto index 8a349da462..6db0bd94e2 100644 --- a/internal/proto/local/controller/storage/plugin/store/v1/plugin.proto +++ b/internal/proto/local/controller/storage/plugin/store/v1/plugin.proto @@ -20,4 +20,44 @@ message Plugin { // name is optional. If set, it must be unique within scope_id. // @inject_tag: `gorm:"default:null"` string name = 30; +} + +message PluginVersion { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 10; + + // plugin_id is a foreign key to the plugin table. + // @inject_tag: `gorm:"default:null"` + string plugin_id = 20; + + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 30; + + // semantic_version is required and is the symantic version for the above plugin. + // @inject_tag: `gorm:"default:null"` + string semantic_version = 4; +} + +message PluginExecutable { + // version_id is a foreign key to the plugin version table. + // @inject_tag: `gorm:"primary_key"` + string version_id = 10; + + // operating_system to the operating system this executable is built to run on. + // @inject_tag: `gorm:"primary_key"` + string operating_system = 20; + + // architecture to the architecture this executable is built to run on. + // @inject_tag: `gorm:"primary_key"` + string architecture = 30; + + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 40; + + // executable is required and is the compressed executable for this plugin. + // @inject_tag: `gorm:"default:null"` + bytes executable = 50; } \ No newline at end of file diff --git a/sdk/pbs/plugin/host_plugin_service.pb.go b/sdk/pbs/plugin/host_plugin_service.pb.go index 40c4aed5c6..e90bad60ed 100644 --- a/sdk/pbs/plugin/host_plugin_service.pb.go +++ b/sdk/pbs/plugin/host_plugin_service.pb.go @@ -82,7 +82,7 @@ type OnCreateCatalogResponse struct { // Secret data to persist encrypted within Boundary. This should be used to // store authentication data and other necessary configuration to be used in // later hooks and calls. Returning an error from the call will cause this - // data to not be persisted. If this is nil, no changes are written. + // data to not be persisted. If this is nil, nothing is written. Persisted *HostCatalogPersisted `protobuf:"bytes,10,opt,name=persisted,proto3" json:"persisted,omitempty"` } @@ -132,9 +132,9 @@ type OnUpdateCatalogRequest struct { // The existing state of the catalog. CurrentCatalog *hostcatalogs.HostCatalog `protobuf:"bytes,10,opt,name=current_catalog,json=currentCatalog,proto3" json:"current_catalog,omitempty"` - // The requested new state of the catalog. This field may contain - // optional secret data that may have been updated from old - // authentication data contained within the persisted state. + // The requested new state of the catalog. This field may contain optional + // secret data that may have been updated from old authentication data + // contained within the persisted state. NewCatalog *hostcatalogs.HostCatalog `protobuf:"bytes,20,opt,name=new_catalog,json=newCatalog,proto3" json:"new_catalog,omitempty"` // The existing persisted secret data. Persisted *HostCatalogPersisted `protobuf:"bytes,30,opt,name=persisted,proto3" json:"persisted,omitempty"` @@ -198,11 +198,10 @@ type OnUpdateCatalogResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // The updated secret data to persist encrypted within Boundary. - // It's important that this be returned if it existed previously, - // as the returned data overwrites the previously existing copy. If - // an error is returned, the update of the persisted data is - // aborted. If this is nil, no changes are written. + // The updated secret data to persist encrypted within Boundary. If an error + // is returned, the update of the persisted data is aborted. If this is nil, + // no changes are written. To remove all values, simply return an allocated + // but empty map. Persisted *HostCatalogPersisted `protobuf:"bytes,10,opt,name=persisted,proto3" json:"persisted,omitempty"` }