Add Plugin Host Set Repo Create, List, Read Operations (#1497)

Add repo for plugin based host sets as well as tests.
This commit is contained in:
Todd Knight authored and GitHub committed 2021-09-09 10:45:41 -07:00
1 parent 7769452f3b
commit 1249f9668f
13 files changed
+819 -43

No files matched your search

+4 -19
View File
@@ -1,7 +1,7 @@
package auth
import (
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/boundary"
"github.com/hashicorp/boundary/internal/types/subtypes"
)
@@ -28,32 +28,17 @@ func Register(subtype subtypes.Subtype, prefixes ...string) error {
// AuthMethod contains the common methods across all the different types of auth methods.
type AuthMethod interface {
GetPublicId() string
GetCreateTime() *timestamp.Timestamp
GetUpdateTime() *timestamp.Timestamp
GetName() string
GetDescription() string
boundary.Resource
GetScopeId() string
GetVersion() uint32
GetIsPrimaryAuthMethod() bool
}
type Account interface {
GetPublicId() string
GetCreateTime() *timestamp.Timestamp
GetUpdateTime() *timestamp.Timestamp
GetName() string
GetDescription() string
boundary.Resource
GetAuthMethodId() string
GetVersion() uint32
}
type ManagedGroup interface {
GetPublicId() string
GetCreateTime() *timestamp.Timestamp
GetUpdateTime() *timestamp.Timestamp
GetName() string
GetDescription() string
boundary.Resource
GetAuthMethodId() string
GetVersion() uint32
}
+6
View File
@@ -44,6 +44,12 @@ func NewHostCatalog(ctx context.Context, pluginId, scopeId string, opt ...Option
return hc, nil
}
func allocHostCatalog() *HostCatalog {
return &HostCatalog{
HostCatalog: &store.HostCatalog{},
}
}
func (c *HostCatalog) clone() *HostCatalog {
cp := proto.Clone(c.HostCatalog)
return &HostCatalog{
+12 -11
View File
@@ -17,6 +17,7 @@ import (
)
func TestHostCatalog_Create(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
@@ -140,21 +141,21 @@ func TestHostCatalog_Create(t *testing.T) {
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
got, err := NewHostCatalog(context.Background(), tt.args.pluginId, tt.args.scopeId, tt.args.opts...)
got, err := NewHostCatalog(ctx, tt.args.pluginId, tt.args.scopeId, tt.args.opts...)
require.NoError(t, err)
require.NotNil(t, got)
assert.Emptyf(t, got.PublicId, "PublicId set")
assert.Equal(t, tt.want, got)
id, err := newHostCatalogId()
id, err := newHostCatalogId(plg.GetIdPrefix())
assert.NoError(t, err)
tt.want.PublicId = id
got.PublicId = id
w := db.New(conn)
err = w.Create(context.Background(), got)
err = w.Create(ctx, got)
if tt.wantCreateErr {
assert.Error(t, err)
} else {
@@ -164,7 +165,7 @@ func TestHostCatalog_Create(t *testing.T) {
PublicId: id,
},
}
require.NoError(t, w.LookupById(context.Background(), found))
require.NoError(t, w.LookupById(ctx, found))
assert.Empty(t, cmp.Diff(got.HostCatalog, found.HostCatalog, protocmp.Transform()), "%q compared to %q", got.Attributes, found.Attributes)
}
})
@@ -181,28 +182,28 @@ func TestHostCatalog_Create_DuplicateNames(t *testing.T) {
plg := host.TestPlugin(t, conn, "test1", "prefix1")
plg2 := host.TestPlugin(t, conn, "test2", "prefix2")
got, err := NewHostCatalog(context.Background(), plg.GetPublicId(), prj.GetPublicId(), WithName("duplicate"))
got, err := NewHostCatalog(ctx, plg.GetPublicId(), prj.GetPublicId(), WithName("duplicate"))
require.NoError(t, err)
got.PublicId, err = newHostCatalogId()
got.PublicId, err = newHostCatalogId(plg.GetIdPrefix())
require.NoError(t, err)
w.Create(ctx, got)
// Can't create another resource with the same pluginName in the same scope
got.PublicId, err = newHostCatalogId()
got.PublicId, err = newHostCatalogId(plg.GetIdPrefix())
require.NoError(t, err)
assert.Error(t, w.Create(ctx, got))
// Can't create another resource with same pluginName in same scope even for different plugin
got, err = NewHostCatalog(context.Background(), plg2.GetPublicId(), prj.GetPublicId(), WithName("duplicate"))
got, err = NewHostCatalog(ctx, plg2.GetPublicId(), prj.GetPublicId(), WithName("duplicate"))
require.NoError(t, err)
got.PublicId, err = newHostCatalogId()
got.PublicId, err = newHostCatalogId(plg2.GetIdPrefix())
require.NoError(t, err)
assert.Error(t, w.Create(ctx, got))
// Can create another resource with same pluginName in different scope even for same plugin
got, err = NewHostCatalog(context.Background(), plg.GetPublicId(), prj2.GetPublicId(), WithName("duplicate"))
got, err = NewHostCatalog(ctx, plg.GetPublicId(), prj2.GetPublicId(), WithName("duplicate"))
require.NoError(t, err)
got.PublicId, err = newHostCatalogId()
got.PublicId, err = newHostCatalogId(plg.GetIdPrefix())
require.NoError(t, err)
assert.NoError(t, w.Create(ctx, got))
}
+27
View File
@@ -6,6 +6,8 @@ import (
"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"
)
// A HostSet is a collection of hosts from the set's catalog.
@@ -51,3 +53,28 @@ func (s *HostSet) TableName() string {
func (s *HostSet) SetTableName(n string) {
s.tableName = n
}
func allocHostSet() *HostSet {
return &HostSet{
HostSet: &store.HostSet{},
}
}
func (s *HostSet) clone() *HostSet {
cp := proto.Clone(s.HostSet)
return &HostSet{
HostSet: cp.(*store.HostSet),
}
}
func (s *HostSet) oplog(op oplog.OpType) oplog.Metadata {
metadata := oplog.Metadata{
"resource-public-id": []string{s.PublicId},
"resource-type": []string{"plugin-host-set"},
"op-type": []string{op.String()},
}
if s.CatalogId != "" {
metadata["catalog-id"] = []string{s.CatalogId}
}
return metadata
}
+1 -1
View File
@@ -145,7 +145,7 @@ func TestHostSet_Create(t *testing.T) {
assert.Emptyf(t, got.PublicId, "PublicId set")
assert.Equal(t, tt.want, got)
id, err := newHostSetId()
id, err := newHostSetId(plg.GetIdPrefix())
assert.NoError(t, err)
tt.want.PublicId = id
+8 -6
View File
@@ -1,6 +1,8 @@
package plugin
import (
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/host"
@@ -22,18 +24,18 @@ const (
Subtype = subtypes.Subtype("plugin")
)
func newHostCatalogId() (string, error) {
// TODO: Take in a prefix portion as an argument.
id, err := db.NewPublicId(HostCatalogPrefix)
func newHostCatalogId(upre string) (string, error) {
prefix := fmt.Sprintf("%s_%s", HostCatalogPrefix, upre)
id, err := db.NewPublicId(prefix)
if err != nil {
return "", errors.WrapDeprecated(err, "plugin.newHostCatalogId")
}
return id, nil
}
func newHostSetId() (string, error) {
// TODO: Take in a prefix portion as an argument.
id, err := db.NewPublicId(HostSetPrefix)
func newHostSetId(upre string) (string, error) {
prefix := fmt.Sprintf("%s_%s", HostSetPrefix, upre)
id, err := db.NewPublicId(prefix)
if err != nil {
return "", errors.WrapDeprecated(err, "plugin.newHostSetId")
}
+47
View File
@@ -0,0 +1,47 @@
package plugin
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 plugin
// 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 = "plugin.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
}
+131
View File
@@ -0,0 +1,131 @@
package plugin
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"
hostplg "github.com/hashicorp/boundary/internal/plugin/host"
)
// CreateSet inserts s into the repository and returns a new HostSet
// containing the host set's PublicId. s is not changed. s must contain a
// valid CatalogId. s must not contain a PublicId. The PublicId is
// generated and assigned by this method. opt is ignored.
//
// Both s.Name and s.Description are optional. If s.Name is set, it must be
// unique within s.CatalogId.
func (r *Repository) CreateSet(ctx context.Context, scopeId string, s *HostSet, _ ...Option) (*HostSet, error) {
const op = "plugin.(Repository).CreateSet"
if s == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil HostSet")
}
if s.HostSet == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil embedded HostSet")
}
if s.CatalogId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no catalog id")
}
if s.PublicId != "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty")
}
if scopeId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no scope id")
}
if s.Attributes == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil attributes")
}
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"))
}
// 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 {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get host plugin"))
}
id, err := newHostSetId(plg.GetIdPrefix())
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
s.PublicId = id
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"))
}
var newHostSet *HostSet
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
newHostSet = s.clone()
err := w.Create(ctx, newHostSet, db.WithOplog(oplogWrapper, s.oplog(oplog.OpType_OP_TYPE_CREATE)))
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 catalog: %s: name %s already exists", s.CatalogId, s.Name)))
}
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in catalog: %s", s.CatalogId)))
}
return newHostSet, nil
}
// LookupSet will look up a host set in the repository and return the host
// set. If the host set is not found, it will return nil, nil.
// All options are ignored.
func (r *Repository) LookupSet(ctx context.Context, publicId string, _ ...Option) (*HostSet, error) {
const op = "plugin.(Repository).LookupSet"
if publicId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id")
}
s := allocHostSet()
s.PublicId = publicId
err := r.reader.LookupByPublicId(ctx, s)
if errors.IsNotFoundError(err) {
return nil, nil
}
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in %s", s.PublicId)))
}
return s, nil
}
// ListSets returns a slice of HostSets for the catalogId. WithLimit is the
// only option supported.
func (r *Repository) ListSets(ctx context.Context, catalogId string, opt ...Option) ([]*HostSet, error) {
const op = "plugin.(Repository).ListSets"
if catalogId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "no catalog id")
}
opts := getOpts(opt...)
limit := r.defaultLimit
if opts.withLimit != 0 {
// non-zero signals an override of the default limit for the repo.
limit = opts.withLimit
}
var sets []*HostSet
err := r.reader.SearchWhere(ctx, &sets, "catalog_id = ?", []interface{}{catalogId}, db.WithLimit(limit))
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return sets, nil
}
@@ -0,0 +1,416 @@
package plugin
import (
"context"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/host/plugin/store"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
hostplg "github.com/hashicorp/boundary/internal/plugin/host"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"
)
func TestRepository_CreateSet(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
_, prj := iam.TestScopes(t, iamRepo)
plg := hostplg.TestPlugin(t, conn, "create", "create")
catalog := TestCatalog(t, conn, plg.GetPublicId(), prj.PublicId)
attrs := []byte("{}")
tests := []struct {
name string
in *HostSet
opts []Option
want *HostSet
wantIsErr errors.Code
}{
{
name: "nil-HostSet",
wantIsErr: errors.InvalidParameter,
},
{
name: "nil-embedded-HostSet",
in: &HostSet{},
wantIsErr: errors.InvalidParameter,
},
{
name: "invalid-no-catalog-id",
in: &HostSet{
HostSet: &store.HostSet{
Attributes: attrs,
},
},
wantIsErr: errors.InvalidParameter,
},
{
name: "invalid-public-id-set",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
PublicId: "abcd_OOOOOOOOOO",
Attributes: attrs,
},
},
wantIsErr: errors.InvalidParameter,
},
{
name: "invalid-no-attribte",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
},
},
wantIsErr: errors.InvalidParameter,
},
{
name: "valid-no-options",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Attributes: attrs,
},
},
want: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Attributes: attrs,
},
},
},
{
name: "valid-with-name",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Name: "test-name-repo",
Attributes: attrs,
},
},
want: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Name: "test-name-repo",
Attributes: attrs,
},
},
},
{
name: "valid-with-description",
in: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Description: ("test-description-repo"),
Attributes: attrs,
},
},
want: &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Description: ("test-description-repo"),
Attributes: attrs,
},
},
},
}
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)
require.NoError(err)
require.NotNil(repo)
got, err := repo.CreateSet(context.Background(), prj.GetPublicId(), 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(err)
assert.Empty(tt.in.PublicId)
require.NotNil(got)
assert.True(strings.HasPrefix(got.GetPublicId(), HostSetPrefix))
assert.NotSame(tt.in, got)
assert.Equal(tt.want.Name, got.GetName())
assert.Equal(tt.want.Description, got.GetDescription())
assert.Equal(got.GetCreateTime(), got.GetUpdateTime())
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)
require.NoError(err)
require.NotNil(repo)
_, prj := iam.TestScopes(t, iamRepo)
catalog := TestCatalog(t, conn, plg.GetPublicId(), prj.PublicId)
in := &HostSet{
HostSet: &store.HostSet{
CatalogId: catalog.PublicId,
Name: "test-name-repo",
Attributes: []byte("{}"),
},
}
got, err := repo.CreateSet(context.Background(), prj.GetPublicId(), in)
require.NoError(err)
require.NotNil(got)
assert.True(strings.HasPrefix(got.GetPublicId(), HostSetPrefix))
assert.NotSame(in, got)
assert.Equal(in.Name, got.GetName())
assert.Equal(in.Description, got.GetDescription())
assert.Equal(got.GetCreateTime(), got.GetUpdateTime())
got2, err := repo.CreateSet(context.Background(), prj.GetPublicId(), 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("valid-duplicate-names-diff-catalogs", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
_, prj := iam.TestScopes(t, iamRepo)
catalogA := TestCatalog(t, conn, plg.GetPublicId(), prj.PublicId)
catalogB := TestCatalog(t, conn, plg.GetPublicId(), prj.PublicId)
in := &HostSet{
HostSet: &store.HostSet{
Name: "test-name-repo",
Attributes: []byte("{}"),
},
}
in2 := in.clone()
in.CatalogId = catalogA.PublicId
got, err := repo.CreateSet(context.Background(), prj.GetPublicId(), in)
require.NoError(err)
require.NotNil(got)
assert.True(strings.HasPrefix(got.GetPublicId(), HostSetPrefix))
assert.NotSame(in, got)
assert.Equal(in.Name, got.GetName())
assert.Equal(in.Description, got.GetDescription())
assert.Equal(got.GetCreateTime(), got.GetUpdateTime())
in2.CatalogId = catalogB.PublicId
got2, err := repo.CreateSet(context.Background(), prj.GetPublicId(), in2)
require.NoError(err)
require.NotNil(got2)
assert.True(strings.HasPrefix(got.GetPublicId(), HostSetPrefix))
assert.NotSame(in2, got2)
assert.Equal(in2.Name, got2.GetName())
assert.Equal(in2.Description, got2.GetDescription())
assert.Equal(got2.GetCreateTime(), got2.GetUpdateTime())
})
}
func TestRepository_LookupSet(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
_, prj := iam.TestScopes(t, iamRepo)
plg := hostplg.TestPlugin(t, conn, "lookup", "lookup")
catalog := TestCatalog(t, conn, plg.GetPublicId(), prj.PublicId)
hostSet := TestSet(t, conn, catalog.PublicId)
hostSetId, err := newHostSetId(plg.GetIdPrefix())
require.NoError(t, err)
tests := []struct {
name string
in string
want *HostSet
wantIsErr errors.Code
}{
{
name: "with-no-public-id",
wantIsErr: errors.InvalidParameter,
},
{
name: "with-non-existing-host-set-id",
in: hostSetId,
},
{
name: "with-existing-host-set-id",
in: hostSet.PublicId,
want: hostSet,
},
}
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)
assert.NoError(err)
require.NotNil(repo)
got, err := repo.LookupSet(context.Background(), tt.in)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err)
assert.Nil(got)
return
}
require.NoError(err)
assert.EqualValues(tt.want, got)
})
}
}
func TestRepository_ListSets(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
_, prj := iam.TestScopes(t, iamRepo)
plg := hostplg.TestPlugin(t, conn, "list", "list")
catalogA := TestCatalog(t, conn, plg.GetPublicId(), prj.PublicId)
catalogB := TestCatalog(t, conn, plg.GetPublicId(), prj.PublicId)
hostSets := []*HostSet{
TestSet(t, conn, catalogA.PublicId),
TestSet(t, conn, catalogA.PublicId),
TestSet(t, conn, catalogA.PublicId),
}
tests := []struct {
name string
in string
opts []Option
want []*HostSet
wantIsErr errors.Code
}{
{
name: "with-no-catalog-id",
wantIsErr: errors.InvalidParameter,
},
{
name: "Catalog-with-no-host-sets",
in: catalogB.PublicId,
want: []*HostSet{},
},
{
name: "Catalog-with-host-sets",
in: catalogA.PublicId,
want: hostSets,
},
}
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)
assert.NoError(err)
require.NotNil(repo)
got, err := repo.ListSets(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(err)
opts := []cmp.Option{
cmpopts.SortSlices(func(x, y *HostSet) bool { return x.PublicId < y.PublicId }),
protocmp.Transform(),
}
assert.Empty(cmp.Diff(tt.want, got, opts...))
})
}
}
func TestRepository_ListSets_Limits(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
_, prj := iam.TestScopes(t, iamRepo)
plg := hostplg.TestPlugin(t, conn, "listlimit", "listlimit")
catalog := TestCatalog(t, conn, plg.GetPublicId(), prj.PublicId)
count := 10
var hostSets []*HostSet
for i := 0; i < count; i++ {
hostSets = append(hostSets, TestSet(t, conn, catalog.PublicId))
}
tests := []struct {
name string
repoOpts []Option
listOpts []Option
wantLen int
}{
{
name: "With no limits",
wantLen: count,
},
{
name: "With repo limit",
repoOpts: []Option{WithLimit(3)},
wantLen: 3,
},
{
name: "With negative repo limit",
repoOpts: []Option{WithLimit(-1)},
wantLen: count,
},
{
name: "With List limit",
listOpts: []Option{WithLimit(3)},
wantLen: 3,
},
{
name: "With negative List limit",
listOpts: []Option{WithLimit(-1)},
wantLen: count,
},
{
name: "With repo smaller than list limit",
repoOpts: []Option{WithLimit(2)},
listOpts: []Option{WithLimit(6)},
wantLen: 6,
},
{
name: "With repo larger than list limit",
repoOpts: []Option{WithLimit(6)},
listOpts: []Option{WithLimit(2)},
wantLen: 2,
},
}
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, tt.repoOpts...)
assert.NoError(err)
require.NotNil(repo)
got, err := repo.ListSets(context.Background(), hostSets[0].CatalogId, tt.listOpts...)
require.NoError(err)
assert.Len(got, tt.wantLen)
})
}
}
+118
View File
@@ -0,0 +1,118 @@
package plugin
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)
})
}
}
+22 -5
View File
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/plugin/host"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -16,35 +17,51 @@ import (
func TestCatalog(t *testing.T, conn *gorm.DB, pluginId, scopeId string, opt ...Option) *HostCatalog {
t.Helper()
ctx := context.Background()
w := db.New(conn)
cat, err := NewHostCatalog(ctx, pluginId, scopeId, opt...)
require.NoError(t, err)
assert.NotNil(t, cat)
id, err := newHostCatalogId()
plg := host.NewPlugin("", "")
plg.PublicId = pluginId
require.NoError(t, w.LookupByPublicId(ctx, plg))
id, err := newHostCatalogId(plg.GetIdPrefix())
assert.NoError(t, err)
assert.NotEmpty(t, id)
cat.PublicId = id
w := db.New(conn)
require.NoError(t, w.Create(ctx, cat))
return cat
}
// TestSet creates count number of plugin host sets in the provided DB
// 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, catalogId string, opt ...Option) *HostSet {
t.Helper()
ctx := context.Background()
w := db.New(conn)
assert := assert.New(t)
set, err := NewHostSet(ctx, catalogId, opt...)
require.NoError(t, err)
assert.NotNil(set)
id, err := newHostSetId()
cg := allocHostCatalog()
cg.PublicId = catalogId
require.NoError(t, w.LookupByPublicId(ctx, cg))
plg := host.NewPlugin("", "")
plg.PublicId = cg.GetPluginId()
require.NoError(t, w.LookupByPublicId(ctx, plg))
id, err := newHostSetId(plg.GetIdPrefix())
assert.NoError(err)
assert.NotEmpty(id)
set.PublicId = id
w := db.New(conn)
require.NoError(t, w.Create(ctx, set))
return set
}
+4
View File
@@ -1,6 +1,8 @@
package plugin
import (
"fmt"
"strings"
"testing"
"github.com/hashicorp/boundary/internal/db"
@@ -24,6 +26,7 @@ func Test_TestCatalogs(t *testing.T) {
cs := TestCatalog(t, conn, plg.GetPublicId(), proj.GetPublicId(), WithName("foo"), WithDescription("bar"))
assert.NotEmpty(cs.GetPublicId())
assert.True(strings.HasPrefix(cs.GetPublicId(), fmt.Sprintf("%s_%s", HostCatalogPrefix, "prefix")))
assert.Equal("foo", cs.GetName())
assert.Equal("bar", cs.GetDescription())
}
@@ -43,6 +46,7 @@ func Test_TestSet(t *testing.T) {
c := TestCatalog(t, conn, plg.GetPublicId(), prj.GetPublicId())
set := TestSet(t, conn, c.GetPublicId(), 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())
assert.Equal("bar", set.GetDescription())
}
+23 -1
View File
@@ -1,6 +1,9 @@
package host
import "github.com/hashicorp/boundary/internal/types/subtypes"
import (
"github.com/hashicorp/boundary/internal/boundary"
"github.com/hashicorp/boundary/internal/types/subtypes"
)
var registry = subtypes.NewRegistry()
@@ -22,3 +25,22 @@ func SubtypeFromId(id string) subtypes.Subtype {
func Register(subtype subtypes.Subtype, prefixes ...string) error {
return registry.Register(subtype, prefixes...)
}
// Catalog contains the common methods across all the different types of host catalogs.
type Catalog interface {
boundary.Resource
GetScopeId() string
}
// Set contains the common methods across all the different types of host sets.
type Set interface {
boundary.Resource
GetCatalogId() string
}
// Host contains the common methods across all the different types of hosts.
type Host interface {
boundary.Resource
GetCatalogId() string
GetAddress() string
}