Static Host: Lookup, List and Delete (#283)
* Implement LookupHost * Implement ListHosts * Implement DeleteHost
This commit is contained in:
1 parent
eaae887bbe
commit
a413ad7b84
6 files changed
+356
-13
No files matched your search
@@ -16,6 +16,7 @@ type Option func(*options)
|
||||
type options struct {
|
||||
withName string
|
||||
withDescription string
|
||||
withLimit int
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
@@ -38,3 +39,12 @@ func WithName(name string) Option {
|
||||
o.withName = name
|
||||
}
|
||||
}
|
||||
|
||||
// WithLimit provides an option to provide a limit. Intentionally allowing
|
||||
// negative integers. If WithLimit < 0, then unlimited results are
|
||||
// returned. If WithLimit == 0, then default limits are used for results.
|
||||
func WithLimit(l int) Option {
|
||||
return func(o *options) {
|
||||
o.withLimit = l
|
||||
}
|
||||
}
|
||||
@@ -8,17 +8,22 @@ import (
|
||||
|
||||
func Test_GetOpts(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
t.Run("WithName", func(t *testing.T) {
|
||||
opts := getOpts(WithName("test"))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withName = "test"
|
||||
assert.Equal(opts, testOpts)
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
t.Run("WithDescription", func(t *testing.T) {
|
||||
opts := getOpts(WithDescription("test desc"))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withDescription = "test desc"
|
||||
assert.Equal(opts, testOpts)
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
t.Run("WithLimit", func(t *testing.T) {
|
||||
opts := getOpts(WithLimit(5))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withLimit = 5
|
||||
assert.Equal(t, opts, testOpts)
|
||||
})
|
||||
}
|
||||
@@ -14,12 +14,16 @@ type Repository struct {
|
||||
reader db.Reader
|
||||
writer db.Writer
|
||||
wrapper wrapping.Wrapper
|
||||
// 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.
|
||||
func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper) (*Repository, error) {
|
||||
// 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, wrapper wrapping.Wrapper, opt ...Option) (*Repository, error) {
|
||||
switch {
|
||||
case r == nil:
|
||||
return nil, fmt.Errorf("db.Reader: %w", db.ErrNilParameter)
|
||||
@@ -29,9 +33,16 @@ func NewRepository(r db.Reader, w db.Writer, wrapper wrapping.Wrapper) (*Reposit
|
||||
return nil, fmt.Errorf("wrapping.Wrapper: %w", db.ErrNilParameter)
|
||||
}
|
||||
|
||||
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,
|
||||
wrapper: wrapper,
|
||||
reader: r,
|
||||
writer: w,
|
||||
wrapper: wrapper,
|
||||
defaultLimit: opts.withLimit,
|
||||
}, nil
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package static
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -139,3 +140,70 @@ func (r *Repository) UpdateHost(ctx context.Context, h *Host, version uint32, fi
|
||||
|
||||
return returnedHost, rowsUpdated, nil
|
||||
}
|
||||
|
||||
// LookupHost will look up a host in the repository. If the host is not
|
||||
// found, it will return nil, nil. All options are ignored.
|
||||
func (r *Repository) LookupHost(ctx context.Context, publicId string, opt ...Option) (*Host, error) {
|
||||
if publicId == "" {
|
||||
return nil, fmt.Errorf("lookup: static host: missing public id %w", db.ErrInvalidParameter)
|
||||
}
|
||||
h := allocHost()
|
||||
h.PublicId = publicId
|
||||
if err := r.reader.LookupByPublicId(ctx, h); err != nil {
|
||||
if errors.Is(err, db.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("lookup: static host: failed %w for %s", err, publicId)
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// ListHosts returns a slice of Hosts for the catalogId.
|
||||
// WithLimit is the only option supported.
|
||||
func (r *Repository) ListHosts(ctx context.Context, catalogId string, opt ...Option) ([]*Host, error) {
|
||||
if catalogId == "" {
|
||||
return nil, fmt.Errorf("list: static host: missing catalog id: %w", db.ErrInvalidParameter)
|
||||
}
|
||||
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 hosts []*Host
|
||||
err := r.reader.SearchWhere(ctx, &hosts, "catalog_id = ?", []interface{}{catalogId}, db.WithLimit(limit))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list: static host: %w", err)
|
||||
}
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
// DeleteHost deletes the host for the provided id from the repository
|
||||
// returning a count of the number of records deleted. All options are
|
||||
// ignored.
|
||||
func (r *Repository) DeleteHost(ctx context.Context, publicId string, opt ...Option) (int, error) {
|
||||
if publicId == "" {
|
||||
return db.NoRowsAffected, fmt.Errorf("delete: static host: missing public id: %w", db.ErrInvalidParameter)
|
||||
}
|
||||
h := allocHost()
|
||||
h.PublicId = publicId
|
||||
|
||||
var rowsDeleted int
|
||||
_, err := r.writer.DoTx(
|
||||
ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, w db.Writer) (err error) {
|
||||
dh := h.clone()
|
||||
rowsDeleted, err = w.Delete(ctx, dh, db.WithOplog(r.wrapper, h.oplog(oplog.OpType_OP_TYPE_DELETE)))
|
||||
if err == nil && rowsDeleted > 1 {
|
||||
return db.ErrMultipleRecords
|
||||
}
|
||||
return err
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return db.NoRowsAffected, fmt.Errorf("delete: static host: %s: %w", publicId, err)
|
||||
}
|
||||
|
||||
return rowsDeleted, nil
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
dbassert "github.com/hashicorp/boundary/internal/db/assert"
|
||||
"github.com/hashicorp/boundary/internal/host/static/store"
|
||||
@@ -13,6 +15,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
)
|
||||
|
||||
func TestRepository_CreateHost(t *testing.T) {
|
||||
@@ -723,3 +726,231 @@ func TestRepository_UpdateHost(t *testing.T) {
|
||||
assert.NoError(db.TestVerifyOplog(t, rw, hA.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRepository_LookupHost(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
_, prj := iam.TestScopes(t, conn)
|
||||
catalog := testCatalogs(t, conn, prj.PublicId, 1)[0]
|
||||
host := testHosts(t, conn, catalog.PublicId, 1)[0]
|
||||
|
||||
hostId, err := newHostId()
|
||||
require.NoError(t, err)
|
||||
var tests = []struct {
|
||||
name string
|
||||
in string
|
||||
want *Host
|
||||
wantIsErr error
|
||||
}{
|
||||
{
|
||||
name: "with-no-public-id",
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "with-non-existing-host-id",
|
||||
in: hostId,
|
||||
},
|
||||
{
|
||||
name: "with-existing-host-id",
|
||||
in: host.PublicId,
|
||||
want: host,
|
||||
},
|
||||
}
|
||||
|
||||
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, wrapper)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.LookupHost(context.Background(), tt.in)
|
||||
if tt.wantIsErr != nil {
|
||||
assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err)
|
||||
assert.Nil(got)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.EqualValues(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_ListHosts(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
_, prj := iam.TestScopes(t, conn)
|
||||
catalogs := testCatalogs(t, conn, prj.PublicId, 2)
|
||||
catalogA, catalogB := catalogs[0], catalogs[1]
|
||||
|
||||
hosts := testHosts(t, conn, catalogA.PublicId, 3)
|
||||
|
||||
var tests = []struct {
|
||||
name string
|
||||
in string
|
||||
opts []Option
|
||||
want []*Host
|
||||
wantIsErr error
|
||||
}{
|
||||
{
|
||||
name: "with-no-catalog-id",
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "Catalog-with-no-hosts",
|
||||
in: catalogB.PublicId,
|
||||
want: []*Host{},
|
||||
},
|
||||
{
|
||||
name: "Catalog-with-hosts",
|
||||
in: catalogA.PublicId,
|
||||
want: hosts,
|
||||
},
|
||||
}
|
||||
|
||||
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, wrapper)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.ListHosts(context.Background(), tt.in, tt.opts...)
|
||||
if tt.wantIsErr != nil {
|
||||
assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err)
|
||||
assert.Nil(got)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
opts := []cmp.Option{
|
||||
cmpopts.SortSlices(func(x, y *Host) bool { return x.PublicId < y.PublicId }),
|
||||
protocmp.Transform(),
|
||||
}
|
||||
assert.Empty(cmp.Diff(tt.want, got, opts...))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_ListHosts_Limits(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
_, prj := iam.TestScopes(t, conn)
|
||||
catalog := testCatalogs(t, conn, prj.PublicId, 1)[0]
|
||||
count := 10
|
||||
hosts := testHosts(t, conn, catalog.PublicId, count)
|
||||
|
||||
var 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, wrapper, tt.repoOpts...)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.ListHosts(context.Background(), hosts[0].CatalogId, tt.listOpts...)
|
||||
require.NoError(err)
|
||||
assert.Len(got, tt.wantLen)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_DeleteHost(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
_, prj := iam.TestScopes(t, conn)
|
||||
catalog := testCatalogs(t, conn, prj.PublicId, 1)[0]
|
||||
host := testHosts(t, conn, catalog.PublicId, 1)[0]
|
||||
|
||||
newHostId, err := newHostId()
|
||||
require.NoError(t, err)
|
||||
var tests = []struct {
|
||||
name string
|
||||
in string
|
||||
want int
|
||||
wantIsErr error
|
||||
}{
|
||||
{
|
||||
name: "With no public id",
|
||||
wantIsErr: db.ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "With non existing host id",
|
||||
in: newHostId,
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "With existing host id",
|
||||
in: host.PublicId,
|
||||
want: 1,
|
||||
},
|
||||
}
|
||||
|
||||
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, wrapper)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.DeleteHost(context.Background(), tt.in)
|
||||
if tt.wantIsErr != nil {
|
||||
assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err)
|
||||
assert.Zero(got)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.EqualValues(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
)
|
||||
@@ -19,6 +20,7 @@ func TestRepository_New(t *testing.T) {
|
||||
r db.Reader
|
||||
w db.Writer
|
||||
wrapper wrapping.Wrapper
|
||||
opts []Option
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
@@ -35,9 +37,25 @@ func TestRepository_New(t *testing.T) {
|
||||
wrapper: wrapper,
|
||||
},
|
||||
want: &Repository{
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
wrapper: wrapper,
|
||||
defaultLimit: db.DefaultLimit,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-with-limit",
|
||||
args: args{
|
||||
r: rw,
|
||||
w: rw,
|
||||
wrapper: wrapper,
|
||||
opts: []Option{WithLimit(5)},
|
||||
},
|
||||
want: &Repository{
|
||||
reader: rw,
|
||||
writer: rw,
|
||||
wrapper: wrapper,
|
||||
defaultLimit: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -84,15 +102,15 @@ func TestRepository_New(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
got, err := NewRepository(tt.args.r, tt.args.w, tt.args.wrapper)
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
got, err := NewRepository(tt.args.r, tt.args.w, tt.args.wrapper, tt.args.opts...)
|
||||
if tt.wantIsErr != nil {
|
||||
assert.Truef(errors.Is(err, tt.wantIsErr), "want err: %q got: %q", tt.wantIsErr, err)
|
||||
assert.Nil(got)
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
assert.NotNil(got)
|
||||
require.NotNil(got)
|
||||
assert.Equal(tt.want, got)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user