test(plugin): Support storage service in loopback plugin

This commit is contained in:
Damian Debkowski authored and Timothy Messier committed 2023-05-31 12:18:12 +00:00
1 parent 351444f1bd
commit 9ef94892f4
17 files changed
+183 -739

No files matched your search

+85 -73
View File
@@ -5,92 +5,104 @@ package loopback
import (
"context"
"errors"
plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"google.golang.org/grpc"
)
var _ plgpb.HostPluginServiceClient = (*WrappingPluginClient)(nil)
var (
_ plgpb.HostPluginServiceClient = (*WrappingPluginHostClient)(nil)
_ plgpb.StoragePluginServiceClient = (*WrappingPluginStorageClient)(nil)
)
// WrappingPluginClient provides a wrapper around a Server implementation that
// WrappingPluginHostClient provides a wrapper around a Server implementation that
// can be used when loading a plugin in-memory. Supports HostServiceServer
type WrappingPluginClient struct {
Server any
type WrappingPluginHostClient struct {
Server plgpb.HostPluginServiceServer
}
func NewWrappingPluginClient(s any) *WrappingPluginClient {
return &WrappingPluginClient{Server: s}
func NewWrappingPluginHostClient(s plgpb.HostPluginServiceServer) *WrappingPluginHostClient {
return &WrappingPluginHostClient{Server: s}
}
func (tpc *WrappingPluginClient) NormalizeCatalogData(ctx context.Context, req *plgpb.NormalizeCatalogDataRequest, opts ...grpc.CallOption) (*plgpb.NormalizeCatalogDataResponse, error) {
svc, ok := tpc.Server.(plgpb.HostPluginServiceServer)
if !ok {
return nil, errors.New("wrapping client not initialized with a HostServiceServer")
func (tpc *WrappingPluginHostClient) NormalizeCatalogData(ctx context.Context, req *plgpb.NormalizeCatalogDataRequest, opts ...grpc.CallOption) (*plgpb.NormalizeCatalogDataResponse, error) {
return tpc.Server.NormalizeCatalogData(ctx, req)
}
func (tpc *WrappingPluginHostClient) OnCreateCatalog(ctx context.Context, req *plgpb.OnCreateCatalogRequest, opts ...grpc.CallOption) (*plgpb.OnCreateCatalogResponse, error) {
return tpc.Server.OnCreateCatalog(ctx, req)
}
func (tpc *WrappingPluginHostClient) OnUpdateCatalog(ctx context.Context, req *plgpb.OnUpdateCatalogRequest, opts ...grpc.CallOption) (*plgpb.OnUpdateCatalogResponse, error) {
return tpc.Server.OnUpdateCatalog(ctx, req)
}
func (tpc *WrappingPluginHostClient) OnDeleteCatalog(ctx context.Context, req *plgpb.OnDeleteCatalogRequest, opts ...grpc.CallOption) (*plgpb.OnDeleteCatalogResponse, error) {
return tpc.Server.OnDeleteCatalog(ctx, req)
}
func (tpc *WrappingPluginHostClient) NormalizeSetData(ctx context.Context, req *plgpb.NormalizeSetDataRequest, opts ...grpc.CallOption) (*plgpb.NormalizeSetDataResponse, error) {
return tpc.Server.NormalizeSetData(ctx, req)
}
func (tpc *WrappingPluginHostClient) OnCreateSet(ctx context.Context, req *plgpb.OnCreateSetRequest, opts ...grpc.CallOption) (*plgpb.OnCreateSetResponse, error) {
return tpc.Server.OnCreateSet(ctx, req)
}
func (tpc *WrappingPluginHostClient) OnUpdateSet(ctx context.Context, req *plgpb.OnUpdateSetRequest, opts ...grpc.CallOption) (*plgpb.OnUpdateSetResponse, error) {
return tpc.Server.OnUpdateSet(ctx, req)
}
func (tpc *WrappingPluginHostClient) OnDeleteSet(ctx context.Context, req *plgpb.OnDeleteSetRequest, opts ...grpc.CallOption) (*plgpb.OnDeleteSetResponse, error) {
return tpc.Server.OnDeleteSet(ctx, req)
}
func (tpc *WrappingPluginHostClient) ListHosts(ctx context.Context, req *plgpb.ListHostsRequest, opts ...grpc.CallOption) (*plgpb.ListHostsResponse, error) {
return tpc.Server.ListHosts(ctx, req)
}
// WrappingPluginStorageClient provides a wrapper around a Server implementation that
// can be used when loading a plugin in-memory. Supports StoragePluginServiceClient
type WrappingPluginStorageClient struct {
Server plgpb.StoragePluginServiceServer
}
func NewWrappingPluginStorageClient(s plgpb.StoragePluginServiceServer) *WrappingPluginStorageClient {
return &WrappingPluginStorageClient{Server: s}
}
func (tpc *WrappingPluginStorageClient) OnCreateStorageBucket(ctx context.Context, req *plgpb.OnCreateStorageBucketRequest, opts ...grpc.CallOption) (*plgpb.OnCreateStorageBucketResponse, error) {
return tpc.Server.OnCreateStorageBucket(ctx, req)
}
func (tpc *WrappingPluginStorageClient) OnUpdateStorageBucket(ctx context.Context, req *plgpb.OnUpdateStorageBucketRequest, opts ...grpc.CallOption) (*plgpb.OnUpdateStorageBucketResponse, error) {
return tpc.Server.OnUpdateStorageBucket(ctx, req)
}
func (tpc *WrappingPluginStorageClient) OnDeleteStorageBucket(ctx context.Context, req *plgpb.OnDeleteStorageBucketRequest, opts ...grpc.CallOption) (*plgpb.OnDeleteStorageBucketResponse, error) {
return tpc.Server.OnDeleteStorageBucket(ctx, req)
}
func (tpc *WrappingPluginStorageClient) HeadObject(ctx context.Context, req *plgpb.HeadObjectRequest, opts ...grpc.CallOption) (*plgpb.HeadObjectResponse, error) {
return tpc.Server.HeadObject(ctx, req)
}
func (tpc *WrappingPluginStorageClient) ValidatePermissions(ctx context.Context, req *plgpb.ValidatePermissionsRequest, opts ...grpc.CallOption) (*plgpb.ValidatePermissionsResponse, error) {
return tpc.Server.ValidatePermissions(ctx, req)
}
func (tpc *WrappingPluginStorageClient) GetObject(ctx context.Context, req *plgpb.GetObjectRequest, opts ...grpc.CallOption) (plgpb.StoragePluginService_GetObjectClient, error) {
stream := newGetObjectStream()
if err := tpc.Server.GetObject(req, stream.server); err != nil {
return nil, err
}
return svc.NormalizeCatalogData(ctx, req)
return stream.client, nil
}
func (tpc *WrappingPluginClient) OnCreateCatalog(ctx context.Context, req *plgpb.OnCreateCatalogRequest, opts ...grpc.CallOption) (*plgpb.OnCreateCatalogResponse, error) {
svc, ok := tpc.Server.(plgpb.HostPluginServiceServer)
if !ok {
return nil, errors.New("wrapping client not initialized with a HostServiceServer")
func (tpc *WrappingPluginStorageClient) PutObject(ctx context.Context, opts ...grpc.CallOption) (plgpb.StoragePluginService_PutObjectClient, error) {
stream := newPutObjectStream()
if err := tpc.Server.PutObject(stream.server); err != nil {
return nil, err
}
return svc.OnCreateCatalog(ctx, req)
}
func (tpc *WrappingPluginClient) OnUpdateCatalog(ctx context.Context, req *plgpb.OnUpdateCatalogRequest, opts ...grpc.CallOption) (*plgpb.OnUpdateCatalogResponse, error) {
svc, ok := tpc.Server.(plgpb.HostPluginServiceServer)
if !ok {
return nil, errors.New("wrapping client not initialized with a HostServiceServer")
}
return svc.OnUpdateCatalog(ctx, req)
}
func (tpc *WrappingPluginClient) OnDeleteCatalog(ctx context.Context, req *plgpb.OnDeleteCatalogRequest, opts ...grpc.CallOption) (*plgpb.OnDeleteCatalogResponse, error) {
svc, ok := tpc.Server.(plgpb.HostPluginServiceServer)
if !ok {
return nil, errors.New("wrapping client not initialized with a HostServiceServer")
}
return svc.OnDeleteCatalog(ctx, req)
}
func (tpc *WrappingPluginClient) NormalizeSetData(ctx context.Context, req *plgpb.NormalizeSetDataRequest, opts ...grpc.CallOption) (*plgpb.NormalizeSetDataResponse, error) {
svc, ok := tpc.Server.(plgpb.HostPluginServiceServer)
if !ok {
return nil, errors.New("wrapping client not initialized with a HostServiceServer")
}
return svc.NormalizeSetData(ctx, req)
}
func (tpc *WrappingPluginClient) OnCreateSet(ctx context.Context, req *plgpb.OnCreateSetRequest, opts ...grpc.CallOption) (*plgpb.OnCreateSetResponse, error) {
svc, ok := tpc.Server.(plgpb.HostPluginServiceServer)
if !ok {
return nil, errors.New("wrapping client not initialized with a HostServiceServer")
}
return svc.OnCreateSet(ctx, req)
}
func (tpc *WrappingPluginClient) OnUpdateSet(ctx context.Context, req *plgpb.OnUpdateSetRequest, opts ...grpc.CallOption) (*plgpb.OnUpdateSetResponse, error) {
svc, ok := tpc.Server.(plgpb.HostPluginServiceServer)
if !ok {
return nil, errors.New("wrapping client not initialized with a HostServiceServer")
}
return svc.OnUpdateSet(ctx, req)
}
func (tpc *WrappingPluginClient) OnDeleteSet(ctx context.Context, req *plgpb.OnDeleteSetRequest, opts ...grpc.CallOption) (*plgpb.OnDeleteSetResponse, error) {
svc, ok := tpc.Server.(plgpb.HostPluginServiceServer)
if !ok {
return nil, errors.New("wrapping client not initialized with a HostServiceServer")
}
return svc.OnDeleteSet(ctx, req)
}
func (tpc *WrappingPluginClient) ListHosts(ctx context.Context, req *plgpb.ListHostsRequest, opts ...grpc.CallOption) (*plgpb.ListHostsResponse, error) {
svc, ok := tpc.Server.(plgpb.HostPluginServiceServer)
if !ok {
return nil, errors.New("wrapping client not initialized with a HostServiceServer")
}
return svc.ListHosts(ctx, req)
return stream.client, nil
}
+43 -236
View File
@@ -4,121 +4,57 @@
package loopback
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/errors"
plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/mitchellh/mapstructure"
)
const loopbackPluginHostInfoAttrField = "host_info"
var (
_ plgpb.HostPluginServiceServer = (*LoopbackPlugin)(nil)
_ plgpb.HostPluginServiceServer = (*TestPluginServer)(nil)
_ plgpb.HostPluginServiceServer = (*LoopbackPlugin)(nil)
_ plgpb.StoragePluginServiceServer = (*LoopbackPlugin)(nil)
_ plgpb.HostPluginServiceServer = (*TestPluginServer)(nil)
_ plgpb.StoragePluginServiceServer = (*TestPluginServer)(nil)
)
// TestPluginServer provides a host plugin service server where each method can be overwritten for tests.
// TestPluginServer provides a host and storage plugin service server where each method can be overwritten for tests.
type TestPluginServer struct {
NormalizeCatalogDataFn func(context.Context, *plgpb.NormalizeCatalogDataRequest) (*plgpb.NormalizeCatalogDataResponse, error)
OnCreateCatalogFn func(context.Context, *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error)
OnUpdateCatalogFn func(context.Context, *plgpb.OnUpdateCatalogRequest) (*plgpb.OnUpdateCatalogResponse, error)
OnDeleteCatalogFn func(context.Context, *plgpb.OnDeleteCatalogRequest) (*plgpb.OnDeleteCatalogResponse, error)
NormalizeSetDataFn func(context.Context, *plgpb.NormalizeSetDataRequest) (*plgpb.NormalizeSetDataResponse, error)
OnCreateSetFn func(context.Context, *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error)
OnUpdateSetFn func(context.Context, *plgpb.OnUpdateSetRequest) (*plgpb.OnUpdateSetResponse, error)
OnDeleteSetFn func(context.Context, *plgpb.OnDeleteSetRequest) (*plgpb.OnDeleteSetResponse, error)
ListHostsFn func(context.Context, *plgpb.ListHostsRequest) (*plgpb.ListHostsResponse, error)
plgpb.UnimplementedHostPluginServiceServer
TestPluginHostServer
TestPluginStorageServer
}
func (t TestPluginServer) NormalizeCatalogData(ctx context.Context, req *plgpb.NormalizeCatalogDataRequest) (*plgpb.NormalizeCatalogDataResponse, error) {
if t.NormalizeCatalogDataFn == nil {
return t.UnimplementedHostPluginServiceServer.NormalizeCatalogData(ctx, req)
}
return t.NormalizeCatalogDataFn(ctx, req)
}
func (t TestPluginServer) OnCreateCatalog(ctx context.Context, req *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) {
if t.OnCreateCatalogFn == nil {
return t.UnimplementedHostPluginServiceServer.OnCreateCatalog(ctx, req)
}
return t.OnCreateCatalogFn(ctx, req)
}
func (t TestPluginServer) OnUpdateCatalog(ctx context.Context, req *plgpb.OnUpdateCatalogRequest) (*plgpb.OnUpdateCatalogResponse, error) {
if t.OnUpdateCatalogFn == nil {
return t.UnimplementedHostPluginServiceServer.OnUpdateCatalog(ctx, req)
}
return t.OnUpdateCatalogFn(ctx, req)
}
func (t TestPluginServer) OnDeleteCatalog(ctx context.Context, req *plgpb.OnDeleteCatalogRequest) (*plgpb.OnDeleteCatalogResponse, error) {
if t.OnDeleteCatalogFn == nil {
return t.UnimplementedHostPluginServiceServer.OnDeleteCatalog(ctx, req)
}
return t.OnDeleteCatalogFn(ctx, req)
}
func (t TestPluginServer) NormalizeSetData(ctx context.Context, req *plgpb.NormalizeSetDataRequest) (*plgpb.NormalizeSetDataResponse, error) {
if t.NormalizeSetDataFn == nil {
return t.UnimplementedHostPluginServiceServer.NormalizeSetData(ctx, req)
}
return t.NormalizeSetDataFn(ctx, req)
}
func (t TestPluginServer) OnCreateSet(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) {
if t.OnCreateSetFn == nil {
return t.UnimplementedHostPluginServiceServer.OnCreateSet(ctx, req)
}
return t.OnCreateSetFn(ctx, req)
}
func (t TestPluginServer) OnUpdateSet(ctx context.Context, req *plgpb.OnUpdateSetRequest) (*plgpb.OnUpdateSetResponse, error) {
if t.OnUpdateSetFn == nil {
return t.UnimplementedHostPluginServiceServer.OnUpdateSet(ctx, req)
}
return t.OnUpdateSetFn(ctx, req)
}
func (t TestPluginServer) OnDeleteSet(ctx context.Context, req *plgpb.OnDeleteSetRequest) (*plgpb.OnDeleteSetResponse, error) {
if t.OnDeleteSetFn == nil {
return t.UnimplementedHostPluginServiceServer.OnDeleteSet(ctx, req)
}
return t.OnDeleteSetFn(ctx, req)
}
func (t TestPluginServer) ListHosts(ctx context.Context, req *plgpb.ListHostsRequest) (*plgpb.ListHostsResponse, error) {
if t.ListHostsFn == nil {
return t.UnimplementedHostPluginServiceServer.ListHosts(ctx, req)
}
return t.ListHostsFn(ctx, req)
}
// LoopbackPlugin provides a host plugin with functionality useful for certain
// kinds of testing. It is not (currently) thread-safe.
// LoopbackPlugin provides a host and storage plugin with functionality useful for certain
// kinds of testing.
//
// It is not (currently) thread-safe.
//
// Over time, if useful, it can be enhanced to do things like handle multiple
// hosts per set.
type LoopbackPlugin struct {
*TestPluginServer
hostMap map[string][]*loopbackPluginHostInfo
}
type loopbackPluginHostInfo struct {
ExternalId string `mapstructure:"external_id"`
IpAddresses []string `mapstructure:"ip_addresses"`
DnsNames []string `mapstructure:"dns_names"`
*LoopbackHost
*LoopbackStorage
}
// NewLoopbackPlugin returns a new loopback plugin
func NewLoopbackPlugin() (*LoopbackPlugin, error) {
func NewLoopbackPlugin(opt ...TestOption) (*LoopbackPlugin, error) {
opts, err := getTestOpts(opt...)
if err != nil {
return nil, err
}
ret := &LoopbackPlugin{
TestPluginServer: new(TestPluginServer),
hostMap: make(map[string][]*loopbackPluginHostInfo),
LoopbackHost: &LoopbackHost{
hostMap: make(map[string][]*loopbackPluginHostInfo),
},
LoopbackStorage: &LoopbackStorage{
buckets: make(map[BucketName]Bucket),
errs: make([]PluginMockError, 0),
},
}
// Set host methods
ret.OnCreateCatalogFn = ret.onCreateCatalog
ret.OnUpdateCatalogFn = ret.onUpdateCatalog
ret.OnCreateSetFn = ret.onCreateSet
@@ -126,149 +62,20 @@ func NewLoopbackPlugin() (*LoopbackPlugin, error) {
ret.OnDeleteSetFn = ret.onDeleteSet
ret.ListHostsFn = ret.listHosts
// Set storage methods
ret.OnCreateStorageBucketFn = ret.onCreateStorageBucket
ret.OnUpdateStorageBucketFn = ret.onUpdateStorageBucket
ret.OnDeleteStorageBucketFn = ret.onDeleteStorageBucket
ret.ValidatePermissionsFn = ret.validatePermissions
ret.HeadObjectFn = ret.headObject
ret.GetObjectFn = ret.getObject
ret.PutObjectFn = ret.putObject
if len(opts.withMockBuckets) > 0 {
ret.buckets = opts.withMockBuckets
}
if len(opts.withMockError) > 0 {
ret.errs = opts.withMockError
}
return ret, nil
}
func (l *LoopbackPlugin) onCreateCatalog(ctx context.Context, req *plgpb.OnCreateCatalogRequest) (*plgpb.OnCreateCatalogResponse, error) {
const op = "loopback.(loopbackPlugin).onCreateCatalog"
if req == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil")
}
if cat := req.GetCatalog(); cat != nil {
if secrets := cat.GetSecrets(); secrets != nil {
return &plgpb.OnCreateCatalogResponse{
Persisted: &plgpb.HostCatalogPersisted{
Secrets: secrets,
},
}, nil
}
}
return &plgpb.OnCreateCatalogResponse{}, nil
}
func (l *LoopbackPlugin) onUpdateCatalog(ctx context.Context, req *plgpb.OnUpdateCatalogRequest) (*plgpb.OnUpdateCatalogResponse, error) {
const op = "loopback.(loopbackPlugin).onUpdateCatalog"
if req == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil")
}
if cat := req.GetNewCatalog(); cat != nil {
if secrets := cat.GetSecrets(); secrets != nil {
return &plgpb.OnUpdateCatalogResponse{
Persisted: &plgpb.HostCatalogPersisted{
Secrets: secrets,
},
}, nil
}
}
return &plgpb.OnUpdateCatalogResponse{}, nil
}
func (l *LoopbackPlugin) onCreateSet(ctx context.Context, req *plgpb.OnCreateSetRequest) (*plgpb.OnCreateSetResponse, error) {
const op = "loopback.(loopbackPlugin).onCreateSet"
if req == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil")
}
set := req.GetSet()
if set == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "set is nil")
}
if attrs := set.GetAttributes(); attrs != nil {
attrsMap := attrs.AsMap()
if field := attrsMap[loopbackPluginHostInfoAttrField]; field != nil {
switch t := field.(type) {
case []any:
for _, h := range t {
hostInfo := new(loopbackPluginHostInfo)
if err := mapstructure.Decode(h, hostInfo); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
l.hostMap[set.GetId()] = append(l.hostMap[set.GetId()], hostInfo)
}
case map[string]any:
hostInfo := new(loopbackPluginHostInfo)
if err := mapstructure.Decode(t, hostInfo); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
l.hostMap[set.GetId()] = append(l.hostMap[set.GetId()], hostInfo)
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown host info type %T", t))
}
}
}
return &plgpb.OnCreateSetResponse{}, nil
}
func (l *LoopbackPlugin) onUpdateSet(ctx context.Context, req *plgpb.OnUpdateSetRequest) (*plgpb.OnUpdateSetResponse, error) {
const op = "loopback.(loopbackPlugin).onCreateSet"
if req == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil")
}
set := req.GetNewSet()
if set == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "set is nil")
}
var hosts []*loopbackPluginHostInfo
if attrs := set.GetAttributes(); attrs != nil {
attrsMap := attrs.AsMap()
if field := attrsMap[loopbackPluginHostInfoAttrField]; field != nil {
switch t := field.(type) {
case []any:
for _, h := range t {
hostInfo := new(loopbackPluginHostInfo)
if err := mapstructure.Decode(h, hostInfo); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
hosts = append(hosts, hostInfo)
}
case map[string]any:
hostInfo := new(loopbackPluginHostInfo)
if err := mapstructure.Decode(t, hostInfo); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
hosts = append(hosts, hostInfo)
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown host info type %T", t))
}
}
}
if hosts != nil {
l.hostMap[set.GetId()] = hosts
}
return &plgpb.OnUpdateSetResponse{}, nil
}
func (l *LoopbackPlugin) onDeleteSet(ctx context.Context, req *plgpb.OnDeleteSetRequest) (*plgpb.OnDeleteSetResponse, error) {
const op = "loopback.(loopbackPlugin).onDeleteSet"
if req == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil")
}
set := req.GetSet()
if set == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "set is nil")
}
delete(l.hostMap, set.GetId())
return nil, nil
}
func (l *LoopbackPlugin) listHosts(ctx context.Context, req *plgpb.ListHostsRequest) (*plgpb.ListHostsResponse, error) {
const op = "loopback.(loopbackPlugin).listHosts"
if req == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "req is nil")
}
resp := new(plgpb.ListHostsResponse)
for _, set := range req.GetSets() {
hostInfos := l.hostMap[set.GetId()]
if len(hostInfos) == 0 {
continue
}
for _, host := range hostInfos {
resp.Hosts = append(resp.Hosts, &plgpb.ListHostsResponseHost{
SetIds: []string{set.GetId()},
ExternalId: host.ExternalId,
IpAddresses: host.IpAddresses,
DnsNames: host.DnsNames,
})
}
}
return resp, nil
}
-375
View File
@@ -1,375 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package loopback
import (
"context"
"testing"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin"
ta "github.com/stretchr/testify/assert"
tr "github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
)
// TestLoopbackPlugin is a quick test of basic functionality.
func TestLoopbackPlugin(t *testing.T) {
require, assert := tr.New(t), ta.New(t)
ctx := context.Background()
plg, err := NewLoopbackPlugin()
require.NoError(err)
secretsMap := map[string]any{
"key1": "key2",
"baz": true,
}
secrets, err := structpb.NewStruct(secretsMap)
require.NoError(err)
// First, test that if we give it secrets, those secrets come back as
// persisted data
catResp, err := plg.OnCreateCatalog(ctx, &plgpb.OnCreateCatalogRequest{
Catalog: &hostcatalogs.HostCatalog{
Secrets: secrets,
},
})
require.NoError(err)
require.NotNil(catResp)
require.NotNil(catResp.GetPersisted())
require.NotNil(catResp.GetPersisted().GetSecrets())
assert.EqualValues(secretsMap, catResp.GetPersisted().GetSecrets().AsMap())
newSecretsMap := map[string]any{
"key1": "key2",
"baz": true,
}
newSecrets, err := structpb.NewStruct(newSecretsMap)
require.NoError(err)
// First, test that if we give it secrets, those secrets come back as
// persisted data
upResp, err := plg.OnUpdateCatalog(ctx, &plgpb.OnUpdateCatalogRequest{
CurrentCatalog: &hostcatalogs.HostCatalog{},
NewCatalog: &hostcatalogs.HostCatalog{
Secrets: newSecrets,
},
Persisted: &plgpb.HostCatalogPersisted{
Secrets: secrets,
},
})
require.NoError(err)
require.NotNil(upResp)
require.NotNil(upResp.GetPersisted())
require.NotNil(upResp.GetPersisted().GetSecrets())
assert.EqualValues(newSecretsMap, upResp.GetPersisted().GetSecrets().AsMap())
// Add data to some sets
hostInfo1 := map[string]any{
loopbackPluginHostInfoAttrField: map[string]any{
"set_ids": []any{"set1"},
"external_id": "host1",
"ip_addresses": []any{"1.2.3.4", "2.3.4.5"},
"dns_names": []any{"foo.com"},
},
}
attrs, err := structpb.NewStruct(hostInfo1)
require.NoError(err)
_, err = plg.OnCreateSet(ctx, &plgpb.OnCreateSetRequest{
Set: &hostsets.HostSet{
Id: "set1",
Attrs: &hostsets.HostSet_Attributes{
Attributes: attrs,
},
},
})
require.NoError(err)
hostInfo2 := map[string]any{
loopbackPluginHostInfoAttrField: map[string]any{
"set_ids": []any{"set2"},
"external_id": "host2",
"ip_addresses": []any{"5.6.7.8", "6.7.8.9"},
"dns_names": []any{"bar.com"},
},
}
attrs, err = structpb.NewStruct(hostInfo2)
require.NoError(err)
_, err = plg.OnCreateSet(ctx, &plgpb.OnCreateSetRequest{
Set: &hostsets.HostSet{
Id: "set2",
Attrs: &hostsets.HostSet_Attributes{
Attributes: attrs,
},
},
})
require.NoError(err)
// Define test struct and validation function
type testInfo struct {
name string
sets []string
found []map[string]any
}
validateSets := func(t *testing.T, tt testInfo) {
require, assert := tr.New(t), ta.New(t)
var hostSets []*hostsets.HostSet
for _, set := range tt.sets {
hostSets = append(hostSets, &hostsets.HostSet{Id: set})
}
resp, err := plg.ListHosts(ctx, &plgpb.ListHostsRequest{
Sets: hostSets,
})
require.NoError(err)
if len(tt.found) == 0 {
assert.Len(resp.GetHosts(), 0)
return
}
require.Greater(len(resp.GetHosts()), 0)
var found []map[string]any
for _, host := range resp.GetHosts() {
hostMap := map[string]any{
"external_id": host.GetExternalId(),
}
var sets []any
for _, set := range host.SetIds {
sets = append(sets, set)
}
var ips []any
for _, ip := range host.GetIpAddresses() {
ips = append(ips, ip)
}
var names []any
for _, name := range host.GetDnsNames() {
names = append(names, name)
}
if len(sets) > 0 {
hostMap["set_ids"] = sets
}
if len(ips) > 0 {
hostMap["ip_addresses"] = ips
}
if len(names) > 0 {
hostMap["dns_names"] = names
}
found = append(found, hostMap)
}
assert.ElementsMatch(tt.found, found)
}
// First set of tests: check that we can look up sets individually and
// together
setTests := []testInfo{
{
name: "set 1",
sets: []string{"set1"},
found: []map[string]any{
hostInfo1[loopbackPluginHostInfoAttrField].(map[string]any),
},
},
{
name: "set 2",
sets: []string{"set2"},
found: []map[string]any{
hostInfo2[loopbackPluginHostInfoAttrField].(map[string]any),
},
},
{
name: "sets 1 and 2",
sets: []string{"set1", "set2"},
found: []map[string]any{
hostInfo1[loopbackPluginHostInfoAttrField].(map[string]any),
hostInfo2[loopbackPluginHostInfoAttrField].(map[string]any),
},
},
}
for _, tt := range setTests {
t.Run(tt.name, func(t *testing.T) {
validateSets(t, tt)
})
}
// Remove a set
_, err = plg.OnDeleteSet(ctx, &plgpb.OnDeleteSetRequest{
Set: &hostsets.HostSet{
Id: "set1",
},
})
require.NoError(err)
// Run tests again, making sure we no longer find that host either
// individually or together
setTests = []testInfo{
{
name: "set 1 deleted",
sets: []string{"set1"},
found: []map[string]any{},
},
{
name: "set 2 not deleted",
sets: []string{"set2"},
found: []map[string]any{
hostInfo2[loopbackPluginHostInfoAttrField].(map[string]any),
},
},
{
name: "sets 1 and 2 set 1 deleted",
sets: []string{"set1", "set2"},
found: []map[string]any{
hostInfo2[loopbackPluginHostInfoAttrField].(map[string]any),
},
},
}
for _, tt := range setTests {
t.Run(tt.name, func(t *testing.T) {
validateSets(t, tt)
})
}
}
func TestLoopbackPluginArrays(t *testing.T) {
require := tr.New(t)
ctx := context.Background()
plg, err := NewLoopbackPlugin()
require.NoError(err)
// Add data to some sets
hostInfo1 := map[string]any{
loopbackPluginHostInfoAttrField: []any{
map[string]any{
"set_ids": []any{"set1"},
"external_id": "host1a",
"ip_addresses": []any{"1.2.3.4", "2.3.4.5"},
"dns_names": []any{"foo.com"},
},
map[string]any{
"set_ids": []any{"set1"},
"external_id": "host1b",
"ip_addresses": []any{"3.4.5.6", "4.5.6.7"},
"dns_names": []any{"bar.com"},
},
},
}
attrs, err := structpb.NewStruct(hostInfo1)
require.NoError(err)
_, err = plg.OnCreateSet(ctx, &plgpb.OnCreateSetRequest{
Set: &hostsets.HostSet{
Id: "set1",
Attrs: &hostsets.HostSet_Attributes{
Attributes: attrs,
},
},
})
require.NoError(err)
hostInfo2 := map[string]any{
loopbackPluginHostInfoAttrField: []any{
map[string]any{
"set_ids": []any{"set2"},
"external_id": "host2a",
"ip_addresses": []any{"10.20.30.40", "20.30.40.50"},
"dns_names": []any{"foz.com"},
},
map[string]any{
"set_ids": []any{"set2"},
"external_id": "host2b",
"ip_addresses": []any{"30.40.50.60", "40.50.60.70"},
"dns_names": []any{"baz.com"},
},
},
}
attrs, err = structpb.NewStruct(hostInfo2)
require.NoError(err)
_, err = plg.OnCreateSet(ctx, &plgpb.OnCreateSetRequest{
Set: &hostsets.HostSet{
Id: "set2",
Attrs: &hostsets.HostSet_Attributes{
Attributes: attrs,
},
},
})
require.NoError(err)
// Define test struct and validation function
type testInfo struct {
name string
sets []string
found []any
}
validateSets := func(t *testing.T, tt testInfo) {
require, assert := tr.New(t), ta.New(t)
var hostSets []*hostsets.HostSet
for _, set := range tt.sets {
hostSets = append(hostSets, &hostsets.HostSet{Id: set})
}
resp, err := plg.ListHosts(ctx, &plgpb.ListHostsRequest{
Sets: hostSets,
})
require.NoError(err)
if len(tt.found) == 0 {
assert.Len(resp.GetHosts(), 0)
return
}
require.Greater(len(resp.GetHosts()), 0)
var found []any
for _, host := range resp.GetHosts() {
hostMap := map[string]any{
"external_id": host.GetExternalId(),
}
var sets []any
for _, set := range host.SetIds {
sets = append(sets, set)
}
var ips []any
for _, ip := range host.GetIpAddresses() {
ips = append(ips, ip)
}
var names []any
for _, name := range host.GetDnsNames() {
names = append(names, name)
}
if len(sets) > 0 {
hostMap["set_ids"] = sets
}
if len(ips) > 0 {
hostMap["ip_addresses"] = ips
}
if len(names) > 0 {
hostMap["dns_names"] = names
}
found = append(found, hostMap)
}
assert.ElementsMatch(tt.found, found)
}
// First set of tests: check that we can look up sets individually and
// together
setTests := []testInfo{
{
name: "set 1",
sets: []string{"set1"},
found: hostInfo1[loopbackPluginHostInfoAttrField].([]any),
},
{
name: "set 2",
sets: []string{"set2"},
found: hostInfo2[loopbackPluginHostInfoAttrField].([]any),
},
{
name: "sets 1 and 2",
sets: []string{"set1", "set2"},
found: append(hostInfo1[loopbackPluginHostInfoAttrField].([]any),
hostInfo2[loopbackPluginHostInfoAttrField].([]any)...),
},
}
for _, tt := range setTests {
t.Run(tt.name, func(t *testing.T) {
validateSets(t, tt)
})
}
}