Delete actions for plugin based catalogs and sets (#1622)

* Add ability to delete plugin based host catalogs and host sets

* Add plugin host catalog and set tests for API.
This commit is contained in:
Todd authored and GitHub committed 2021-10-25 09:37:05 -07:00
1 parent 804cb22699
commit 8795b20fe2
12 files changed
+745 -34

No files matched your search

+131 -13
View File
@@ -1,21 +1,14 @@
module github.com/hashicorp/boundary
go 1.16
go 1.17
replace github.com/hashicorp/boundary/api => ./api
replace github.com/hashicorp/boundary/sdk => ./sdk
require (
github.com/Azure/azure-sdk-for-go v57.4.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.21 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/armon/go-metrics v0.3.9
github.com/aws/aws-sdk-go v1.40.44 // indirect
github.com/bufbuild/buf v0.37.0
github.com/dhui/dktest v0.3.4 // indirect
github.com/fatih/color v1.12.0
github.com/fatih/structs v1.1.0
github.com/favadi/protoc-go-inject-tag v1.3.0
@@ -24,7 +17,7 @@ require (
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe
github.com/golang/protobuf v1.5.2
github.com/google/go-cmp v0.5.6
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0
github.com/hashicorp/boundary/api v0.0.19
github.com/hashicorp/boundary/sdk v0.0.12-0.20211007161358-65f3725bf3e1
@@ -40,7 +33,6 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-retryablehttp v0.7.0
github.com/hashicorp/go-rootcerts v1.0.2
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.4 // indirect
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1
github.com/hashicorp/go-secure-stdlib/configutil v0.1.2
github.com/hashicorp/go-secure-stdlib/gatedwriter v0.1.1
@@ -52,7 +44,6 @@ require (
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1
github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/go-version v1.3.0 // indirect
github.com/hashicorp/hcl v1.0.0
github.com/hashicorp/vault/api v1.1.1
github.com/hashicorp/vault/sdk v0.2.1
@@ -77,7 +68,6 @@ require (
github.com/pkg/errors v0.9.1
github.com/posener/complete v1.2.3
github.com/ryanuber/go-glob v1.0.0
github.com/spf13/cobra v1.1.1 // indirect
github.com/stretchr/testify v1.7.0
github.com/zalando/go-keyring v0.1.1
go.uber.org/atomic v1.9.0
@@ -85,7 +75,6 @@ require (
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf
golang.org/x/tools v0.1.6
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83
google.golang.org/grpc v1.40.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
@@ -95,3 +84,132 @@ require (
mvdan.cc/gofumpt v0.1.1
nhooyr.io/websocket v1.8.7
)
require (
cloud.google.com/go v0.65.0 // indirect
github.com/AlecAivazis/survey/v2 v2.2.9 // indirect
github.com/Azure/azure-sdk-for-go v57.4.0+incompatible // indirect
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.21 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.14 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Masterminds/goutils v1.1.0 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f // indirect
github.com/apex/log v1.9.0 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/aws/aws-sdk-go v1.40.44 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc // indirect
github.com/coreos/go-oidc/v3 v3.0.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/danieljoos/wincred v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dhui/dktest v0.3.4 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/docker/cli v20.10.7+incompatible // indirect
github.com/docker/docker v20.10.7+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b // indirect
github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/gofrs/flock v0.8.0 // indirect
github.com/gofrs/uuid v4.0.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.1.2 // indirect
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-immutable-radix v1.1.0 // indirect
github.com/hashicorp/go-plugin v1.0.1 // indirect
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.4 // indirect
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1 // indirect
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/go-version v1.3.0 // indirect
github.com/hashicorp/golang-lru v0.5.3 // indirect
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.1.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.7.0 // indirect
github.com/jefferai/go-libsecret v0.0.0-20210105015933-d08a58b018bc // indirect
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f // indirect
github.com/jhump/protoreflect v1.8.1 // indirect
github.com/jinzhu/gorm v1.9.12 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.2 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.10 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect
github.com/klauspost/compress v1.11.7 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/lib/pq v1.8.0 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/iochan v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/opencontainers/runc v1.0.0-rc9 // indirect
github.com/oracle/oci-go-sdk v12.5.0+incompatible // indirect
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
github.com/pkg/profile v1.5.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.6.2 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.7.0 // indirect
github.com/spf13/cobra v1.1.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/twitchtv/twirp v7.1.0+incompatible // indirect
github.com/urfave/cli/v2 v2.3.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.opencensus.io v0.22.6 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0 // indirect
golang.org/x/mod v0.4.2 // indirect
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/api v0.30.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/ini.v1 v1.51.0 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
@@ -195,6 +195,80 @@ func (r *Repository) ListCatalogs(ctx context.Context, scopeIds []string, opt ..
return hostCatalogs, plgs, nil
}
// DeleteCatalog deletes catalog for the provided id from the repository
// returning a count of the number of records deleted. All options are ignored.
func (r *Repository) DeleteCatalog(ctx context.Context, id string, _ ...Option) (int, error) {
const op = "plugin.(Repository).DeleteCatalog"
if id == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no public id")
}
c, err := r.getCatalog(ctx, id)
if err != nil && !errors.IsNotFoundError(err) {
return db.NoRowsAffected, errors.Wrap(ctx, err, op)
}
if c == nil {
return db.NoRowsAffected, nil
}
p, err := r.getPersistedDataForCatalog(ctx, c)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op)
}
plgClient, ok := r.plugins[c.GetPluginId()]
if !ok || plgClient == nil {
return 0, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("plugin %q not available", c.GetPluginId()))
}
plgHc, err := toPluginCatalog(ctx, c)
if err != nil {
return 0, errors.Wrap(ctx, err, op)
}
// TODO: Include all the sets for this catalog in the delete catalog request. We don't need it for now
// since our currently provided plugins only read data for set configuration, but in the future that might
// change.
_, err = plgClient.OnDeleteCatalog(ctx, &plgpb.OnDeleteCatalogRequest{Catalog: plgHc, Persisted: p})
if err != nil {
// Even if the plugin returns an error, we ignore it and proceed with
// deleting the catalog.
}
oplogWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
}
metadata := c.oplog(oplog.OpType_OP_TYPE_DELETE)
var rowsDeleted int
var deleteCatalog *HostCatalog
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
deleteCatalog = c.clone()
var err error
rowsDeleted, err = w.Delete(
ctx,
deleteCatalog,
db.WithOplog(oplogWrapper, metadata),
)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("delete failed for %s", c.PublicId)))
}
return rowsDeleted, 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) {
@@ -2,6 +2,7 @@ package plugin
import (
"context"
"fmt"
"strings"
"testing"
"time"
@@ -470,3 +471,154 @@ func TestRepository_ListCatalogs_Multiple_Scopes(t *testing.T) {
assert.Equal(t, total, len(got))
assert.Contains(t, plgs, plg)
}
func TestRepository_DeleteCatalog(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
plg := hostplg.TestPlugin(t, conn, "test")
pluginInstance := &TestPluginServer{}
plgm := map[string]plgpb.HostPluginServiceClient{
plg.GetPublicId(): &WrappingPluginClient{Server: pluginInstance},
}
cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
cat2 := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
badId, err := newHostCatalogId(ctx)
assert.NoError(t, err)
assert.NotNil(t, badId)
kms := kms.TestKms(t, conn, wrapper)
repo, err := NewRepository(rw, rw, kms, plgm)
assert.NoError(t, err)
assert.NotNil(t, repo)
tests := []struct {
name string
id string
pluginChecker func(*testing.T, *plgpb.OnDeleteCatalogRequest) error
want int
wantErr errors.Code
}{
{
name: "found",
id: cat.GetPublicId(),
pluginChecker: func(t *testing.T, req *plgpb.OnDeleteCatalogRequest) error {
assert.Equal(t, cat.GetPublicId(), req.GetCatalog().GetId())
return nil
},
want: 1,
},
{
name: "ignore error",
id: cat2.GetPublicId(),
pluginChecker: func(t *testing.T, req *plgpb.OnDeleteCatalogRequest) error {
assert.Equal(t, cat2.GetPublicId(), req.GetCatalog().GetId())
return fmt.Errorf("This is a test error")
},
want: 1,
},
{
name: "not-found",
id: badId,
pluginChecker: func(t *testing.T, req *plgpb.OnDeleteCatalogRequest) error {
assert.Fail(t, "Should not call the plugin when catalog isn't found")
return nil
},
want: 0,
},
{
name: "bad-public-id",
id: "",
pluginChecker: func(t *testing.T, req *plgpb.OnDeleteCatalogRequest) error {
assert.Fail(t, "Should not call the plugin for a bad id")
return nil
},
want: 0,
wantErr: errors.InvalidParameter,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
pluginInstance.OnDeleteCatalogFn = func(_ context.Context, request *plgpb.OnDeleteCatalogRequest) (*plgpb.OnDeleteCatalogResponse, error) {
return nil, tt.pluginChecker(t, request)
}
got, err := repo.DeleteCatalog(context.Background(), tt.id)
if tt.wantErr != 0 {
assert.Truef(t, errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got, "row count")
})
}
}
func TestRepository_DeleteCatalogX(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
plg := hostplg.TestPlugin(t, conn, "test")
plgm := map[string]plgpb.HostPluginServiceClient{
plg.GetPublicId(): &WrappingPluginClient{Server: &TestPluginServer{}},
}
cat := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
badId, err := newHostCatalogId(ctx)
assert.NoError(t, err)
assert.NotNil(t, badId)
tests := []struct {
name string
id string
want *HostCatalog
wantErr errors.Code
}{
{
name: "found",
id: cat.GetPublicId(),
want: cat,
},
{
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, plgm)
assert.NoError(err)
assert.NotNil(repo)
got, _, err := repo.LookupCatalog(ctx, 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)
}
})
}
}
@@ -249,6 +249,85 @@ func (r *Repository) ListSets(ctx context.Context, catalogId string, opt ...host
return sets, plg, nil
}
// DeleteSet deletes the host set for the provided id from the repository
// returning a count of the number of records deleted. All options are
// ignored.
func (r *Repository) DeleteSet(ctx context.Context, scopeId string, publicId string, _ ...Option) (int, error) {
const op = "plugin.(Repository).DeleteSet"
if publicId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no public id")
}
if scopeId == "" {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no scope id")
}
sets, plg, err := r.getSets(ctx, publicId, "")
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op)
}
if len(sets) != 1 {
return db.NoRowsAffected, nil
}
s := sets[0]
c, err := r.getCatalog(ctx, s.GetCatalogId())
if err != nil && errors.IsNotFoundError(err) {
return db.NoRowsAffected, errors.Wrap(ctx, err, op)
}
if c == nil {
return db.NoRowsAffected, nil
}
p, err := r.getPersistedDataForCatalog(ctx, c)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op)
}
plgClient, ok := r.plugins[plg.GetPublicId()]
if !ok || plgClient == nil {
return 0, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("plugin %q not available", c.GetPluginId()))
}
plgHc, err := toPluginCatalog(ctx, c)
if err != nil {
return 0, errors.Wrap(ctx, err, op)
}
plgHs, err := toPluginSet(ctx, s)
if err != nil {
return 0, errors.Wrap(ctx, err, op)
}
_, err = plgClient.OnDeleteSet(ctx, &plgpb.OnDeleteSetRequest{Catalog: plgHc, Persisted: p, Set: plgHs})
if err != nil {
// Even if the plugin returns an error, we ignore it and proceed
// with deleting the set.
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
}
var rowsDeleted int
_, err = r.writer.DoTx(
ctx, db.StdRetryCnt, db.ExpBackoff{},
func(_ db.Reader, w db.Writer) (err error) {
ds := s.clone()
rowsDeleted, err = w.Delete(ctx, ds, db.WithOplog(oplogWrapper, s.oplog(oplog.OpType_OP_TYPE_DELETE)))
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsDeleted > 1 {
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
return nil
},
)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("delete failed for %s", s.PublicId)))
}
return rowsDeleted, nil
}
func (r *Repository) getSets(ctx context.Context, publicId string, catalogId string, opt ...host.Option) ([]*HostSet, *hostplugin.Plugin, error) {
const op = "plugin.(Repository).getSets"
if publicId == "" && catalogId == "" {
@@ -2,6 +2,7 @@ package plugin
import (
"context"
"fmt"
"sort"
"strings"
"testing"
@@ -654,6 +655,92 @@ func TestRepository_ListSets_Limits(t *testing.T) {
}
}
func TestRepository_DeleteSet(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
_, prj := iam.TestScopes(t, iamRepo)
plg := hostplg.TestPlugin(t, conn, "create")
plgm := map[string]plgpb.HostPluginServiceClient{
plg.GetPublicId(): NewWrappingPluginClient(TestPluginServer{OnCreateSetFn: func(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) {
return &plgpb.OnCreateSetResponse{}, nil
}}),
}
c := TestCatalog(t, conn, prj.PublicId, plg.GetPublicId())
hostSet := TestSet(t, conn, kms, c, plgm)
hostSet2 := TestSet(t, conn, kms, c, plgm)
newHostSetId, err := newHostSetId(ctx)
require.NoError(t, err)
tests := []struct {
name string
in string
pluginChecker func(*plgpb.OnDeleteSetRequest) error
want int
wantIsErr errors.Code
}{
{
name: "With no public id",
pluginChecker: func(req *plgpb.OnDeleteSetRequest) error {
assert.Fail(t, "The plugin shouldn't be called")
return nil
},
wantIsErr: errors.InvalidParameter,
},
{
name: "With non existing host set id",
in: newHostSetId,
pluginChecker: func(req *plgpb.OnDeleteSetRequest) error {
assert.Fail(t, "The plugin shouldn't be called")
return nil
},
want: 0,
},
{
name: "With existing host set id",
in: hostSet.PublicId,
pluginChecker: func(req *plgpb.OnDeleteSetRequest) error {
assert.Equal(t, c.GetPublicId(), req.GetCatalog().GetId())
assert.Equal(t, hostSet.GetPublicId(), req.GetSet().GetId())
return nil
},
want: 1,
},
{
name: "Ignores plugin errors",
in: hostSet2.PublicId,
pluginChecker: func(req *plgpb.OnDeleteSetRequest) error {
assert.Equal(t, c.GetPublicId(), req.GetCatalog().GetId())
assert.Equal(t, hostSet2.GetPublicId(), req.GetSet().GetId())
return fmt.Errorf("test error")
},
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, kms, plgm)
assert.NoError(err)
require.NotNil(repo)
got, err := repo.DeleteSet(ctx, prj.PublicId, tt.in)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err)
assert.Zero(got)
return
}
require.NoError(err)
assert.EqualValues(tt.want, got)
})
}
}
func printoutTable(t *testing.T, rw *db.Db) {
ctx := context.Background()
hsas := []*hostSetAgg{}
+1 -1
View File
@@ -123,7 +123,7 @@ func New(ctx context.Context, conf *Config) (*Controller, error) {
external_host_plugins.WithHostPluginExecutionDir(conf.RawConfig.Plugins.ExecutionDir),
external_host_plugins.WithLogger(hclog.NewNullLogger()))
if err != nil {
return nil, fmt.Errorf("error creating azure host plugin")
return nil, fmt.Errorf("error creating azure host plugin: %w", err)
}
conf.ShutdownFuncs = append(conf.ShutdownFuncs, azureCleanup)
if _, err := conf.RegisterHostPlugin(ctx, "azure", azureSvcClient, hostplugin.WithDescription("Built-in Azure host plugin")); err != nil {
@@ -534,13 +534,26 @@ func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []str
func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
const op = "host_catalogs.(Service).deleteFromRepo"
repo, err := s.staticRepoFn()
if err != nil {
return false, errors.Wrap(ctx, err, op)
}
rows, err := repo.DeleteCatalog(ctx, id)
if err != nil {
return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete host"))
rows := 0
switch host.SubtypeFromId(id) {
case static.Subtype:
repo, err := s.staticRepoFn()
if err != nil {
return false, errors.Wrap(ctx, err, op)
}
rows, err = repo.DeleteCatalog(ctx, id)
if err != nil {
return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete host"))
}
case plugin.Subtype:
repo, err := s.pluginHostRepoFn()
if err != nil {
return false, errors.Wrap(ctx, err, op)
}
rows, err = repo.DeleteCatalog(ctx, id)
if err != nil {
return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete host"))
}
}
return rows > 0, nil
}
@@ -455,7 +455,7 @@ func TestList(t *testing.T) {
}
}
func TestDelete(t *testing.T) {
func TestDelete_Static(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
@@ -524,6 +524,76 @@ func TestDelete(t *testing.T) {
}
}
func TestDelete_Plugin(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
_, proj := iam.TestScopes(t, iamRepo)
rw := db.New(conn)
repo := func() (*static.Repository, error) {
return static.NewRepository(rw, rw, kms)
}
pluginHostRepo := func() (*plugin.Repository, error) {
return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceClient{})
}
pluginRepo := func() (*host.Repository, error) {
return host.NewRepository(rw, rw, kms)
}
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
plg := host.TestPlugin(t, conn, "test")
hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId())
s, err := host_catalogs.NewService(repo, pluginHostRepo, pluginRepo, iamRepoFn)
require.NoError(t, err, "Couldn't create a new host catalog service.")
cases := []struct {
name string
scopeId string
req *pbs.DeleteHostCatalogRequest
res *pbs.DeleteHostCatalogResponse
err error
}{
{
name: "Delete an Existing HostCatalog",
scopeId: proj.GetPublicId(),
req: &pbs.DeleteHostCatalogRequest{
Id: hc.GetPublicId(),
},
},
{
name: "Delete bad id HostCatalog",
scopeId: proj.GetPublicId(),
req: &pbs.DeleteHostCatalogRequest{
Id: plugin.HostCatalogPrefix + "_doesntexis",
},
err: handlers.ApiErrorWithCode(codes.NotFound),
},
{
name: "Bad HostCatalog Id formatting",
scopeId: proj.GetPublicId(),
req: &pbs.DeleteHostCatalogRequest{
Id: plugin.HostCatalogPrefix + "_bad_format",
},
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, gErr := s.DeleteHostCatalog(auth.DisabledAuthTestContext(iamRepoFn, tc.scopeId), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "DeleteHostCatalog(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
}
assert.EqualValuesf(tc.res, got, "DeleteHostCatalog(%q) got response %q, wanted %q", tc.req, got, tc.res)
})
}
}
func TestDelete_twice(t *testing.T) {
t.Parallel()
assert, require := assert.New(t), require.New(t)
@@ -525,13 +525,26 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, catalogId string, re
func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) {
const op = "host_sets.(Service).deleteFromRepo"
repo, err := s.staticRepoFn()
if err != nil {
return false, err
}
rows, err := repo.DeleteSet(ctx, scopeId, id)
if err != nil {
return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete host"))
rows := 0
switch host.SubtypeFromId(id) {
case static.Subtype:
repo, err := s.staticRepoFn()
if err != nil {
return false, err
}
rows, err = repo.DeleteSet(ctx, scopeId, id)
if err != nil {
return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete host"))
}
case plugin.Subtype:
repo, err := s.pluginRepoFn()
if err != nil {
return false, err
}
rows, err = repo.DeleteSet(ctx, scopeId, id)
if err != nil {
return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete host"))
}
}
return rows > 0, nil
}
@@ -900,7 +913,7 @@ func validateUpdateRequest(ctx context.Context, req *pbs.UpdateHostSetRequest) e
}
func validateDeleteRequest(req *pbs.DeleteHostSetRequest) error {
return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, static.HostSetPrefix)
return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, static.HostSetPrefix, plugin.HostSetPrefix)
}
func validateListRequest(req *pbs.ListHostSetsRequest) error {
@@ -445,7 +445,7 @@ func TestList_Plugin(t *testing.T) {
}
}
func TestDelete(t *testing.T) {
func TestDelete_Static(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
@@ -494,18 +494,86 @@ func TestDelete(t *testing.T) {
err: handlers.ApiErrorWithCode(codes.NotFound),
},
{
name: "Delete bad host catalog id in Host Set",
name: "Bad Host Id formatting",
scopeId: proj.GetPublicId(),
req: &pbs.DeleteHostSetRequest{
Id: static.HostSetPrefix + "_bad_format",
},
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, gErr := s.DeleteHostSet(auth.DisabledAuthTestContext(iamRepoFn, tc.scopeId), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "DeleteHostSet(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
}
assert.Empty(cmp.Diff(tc.res, got, protocmp.Transform()), "DeleteHostSet(%q) got response %q, wanted %q", tc.req, got, tc.res)
})
}
}
func TestDelete_Plugin(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
_, proj := iam.TestScopes(t, iamRepo)
rw := db.New(conn)
repoFn := func() (*static.Repository, error) {
return static.NewRepository(rw, rw, kms)
}
pluginRepoFn := func() (*plugin.Repository, error) {
return plugin.NewRepository(rw, rw, kms, map[string]plgpb.HostPluginServiceClient{})
}
name := "test"
plg := hostplugin.TestPlugin(t, conn, name)
plgm := map[string]plgpb.HostPluginServiceClient{
plg.GetPublicId(): plugin.NewWrappingPluginClient(&plugin.TestPluginServer{}),
}
hc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId())
h := plugin.TestSet(t, conn, kms, hc, plgm)
s, err := host_sets.NewService(repoFn, pluginRepoFn)
require.NoError(t, err, "Couldn't create a new host set service.")
cases := []struct {
name string
scopeId string
req *pbs.DeleteHostSetRequest
res *pbs.DeleteHostSetResponse
err error
}{
{
name: "Delete an Existing Host Set",
scopeId: proj.GetPublicId(),
req: &pbs.DeleteHostSetRequest{
Id: h.GetPublicId(),
},
},
{
name: "Delete bad id Host Set",
scopeId: proj.GetPublicId(),
req: &pbs.DeleteHostSetRequest{
Id: plugin.HostSetPrefix + "_doesntexis",
},
err: handlers.ApiErrorWithCode(codes.NotFound),
},
{
name: "Bad Host Id formatting",
scopeId: proj.GetPublicId(),
req: &pbs.DeleteHostSetRequest{
Id: static.HostSetPrefix + "_bad_format",
Id: plugin.HostSetPrefix + "_bad_format",
},
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
@@ -117,6 +117,23 @@ func TestCrud(t *testing.T) {
apiErr := api.AsServerError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode())
// Plugin catalogs
c, err := hcClient.Create(tc.Context(), "plugin", proj.GetPublicId(), hostcatalogs.WithName("pluginfoo"), hostcatalogs.WithPluginId("pl_1234567890"),
hostcatalogs.WithAttributes(map[string]interface{}{"foo": "bar"}))
require.NoError(err)
c, err = hcClient.Read(tc.Context(), c.Item.Id)
assert.NoError(err)
_, err = hcClient.Delete(tc.Context(), c.Item.Id)
assert.NoError(err)
_, err = hcClient.Delete(tc.Context(), c.Item.Id)
require.Error(err)
apiErr = api.AsServerError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode())
}
func TestErrors(t *testing.T) {
+21 -1
View File
@@ -127,7 +127,8 @@ func TestCrud(t *testing.T) {
client.SetToken(token.Token)
_, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId))
hc, err := hostcatalogs.NewClient(client).Create(tc.Context(), "static", proj.GetPublicId())
hcClient := hostcatalogs.NewClient(client)
hc, err := hcClient.Create(tc.Context(), "static", proj.GetPublicId())
require.NoError(err)
require.NotNil(hc)
@@ -164,6 +165,25 @@ func TestCrud(t *testing.T) {
apiErr := api.AsServerError(err)
require.NotNil(apiErr)
assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode())
// Plugin Sets
c, err := hcClient.Create(tc.Context(), "plugin", proj.GetPublicId(), hostcatalogs.WithName("pluginfoo"), hostcatalogs.WithPluginId("pl_1234567890"),
hostcatalogs.WithAttributes(map[string]interface{}{"foo": "bar"}))
require.NoError(err)
h, err = hClient.Create(tc.Context(), c.Item.Id, hostsets.WithName("foo"))
checkHost(t, "create", h.Item, err, "foo", 1)
h, err = hClient.Read(tc.Context(), h.Item.Id)
checkHost(t, "read", h.Item, err, "foo", 1)
_, err = hClient.Delete(tc.Context(), h.Item.Id)
assert.NoError(err)
_, err = hClient.Delete(tc.Context(), h.Item.Id)
require.Error(err)
apiErr = api.AsServerError(err)
require.NotNil(apiErr)
assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode())
}
// TODO: Get better coverage for expected errors and error formats.