2024-04-02 15:47:49 -04:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
|
2024-09-16 14:28:54 -04:00
|
|
|
package host
|
2024-04-02 15:47:49 -04:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2024-10-28 11:00:14 -04:00
|
|
|
"fmt"
|
2024-10-18 10:35:05 -04:00
|
|
|
"net/http"
|
2024-04-02 15:47:49 -04:00
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"testing"
|
2024-10-28 10:48:26 -04:00
|
|
|
"time"
|
2024-04-02 15:47:49 -04:00
|
|
|
|
2024-10-21 10:47:06 -04:00
|
|
|
"cloud.google.com/go/compute/apiv1/computepb"
|
|
|
|
|
"cloud.google.com/go/iam/admin/apiv1/adminpb"
|
|
|
|
|
"cloud.google.com/go/iam/apiv1/iampb"
|
|
|
|
|
"cloud.google.com/go/resourcemanager/apiv3/resourcemanagerpb"
|
|
|
|
|
"github.com/google/go-cmp/cmp"
|
2024-09-16 12:20:08 -04:00
|
|
|
cred "github.com/hashicorp/boundary-plugin-gcp/internal/credential"
|
2024-04-02 15:47:49 -04:00
|
|
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
|
|
|
|
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
|
|
|
|
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
|
|
|
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
2024-10-21 10:47:06 -04:00
|
|
|
"google.golang.org/api/option"
|
|
|
|
|
"google.golang.org/grpc"
|
2024-10-28 11:00:14 -04:00
|
|
|
"google.golang.org/grpc/codes"
|
2024-10-21 10:47:06 -04:00
|
|
|
"google.golang.org/grpc/credentials/insecure"
|
2024-10-28 11:00:14 -04:00
|
|
|
"google.golang.org/grpc/status"
|
2024-10-21 10:47:06 -04:00
|
|
|
|
|
|
|
|
"google.golang.org/protobuf/testing/protocmp"
|
2024-04-02 15:47:49 -04:00
|
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func wrapMap(t *testing.T, in map[string]interface{}) *structpb.Struct {
|
|
|
|
|
t.Helper()
|
|
|
|
|
out, err := structpb.NewStruct(in)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListHosts(t *testing.T) {
|
2024-07-23 19:26:59 -07:00
|
|
|
t.Skip("TODO: this needs a secrets file to run - maybe only manually?")
|
2024-04-02 15:47:49 -04:00
|
|
|
ctx := context.Background()
|
2024-09-16 14:28:54 -04:00
|
|
|
p := &HostPlugin{}
|
2024-04-02 15:47:49 -04:00
|
|
|
|
|
|
|
|
wd, err := os.Getwd()
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NotEmpty(t, wd)
|
|
|
|
|
project, err := parseutil.ParsePath("file://" + filepath.Join(wd, "secrets", "project"))
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
zone, err := parseutil.ParsePath("file://" + filepath.Join(wd, "secrets", "zone"))
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
hostCatalogAttributes := &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{
|
2024-09-17 10:50:10 -04:00
|
|
|
cred.ConstProjectId: project,
|
|
|
|
|
cred.ConstZone: zone,
|
2024-04-02 15:47:49 -04:00
|
|
|
}),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
name string
|
|
|
|
|
req *pb.ListHostsRequest
|
|
|
|
|
expected []*pb.ListHostsResponseHost
|
|
|
|
|
expectedErr string
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "nil catalog",
|
|
|
|
|
req: &pb.ListHostsRequest{},
|
|
|
|
|
expectedErr: "catalog is nil",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "project not defined",
|
|
|
|
|
req: &pb.ListHostsRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{
|
|
|
|
|
cred.ConstZone: zone,
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "attributes.project: missing required value \"project\"",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "get all three instances",
|
|
|
|
|
req: &pb.ListHostsRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: hostCatalogAttributes,
|
|
|
|
|
},
|
|
|
|
|
Sets: []*hostsets.HostSet{
|
|
|
|
|
{
|
|
|
|
|
Id: "get-all-instances",
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expected: []*pb.ListHostsResponseHost{
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-0",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-1",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-2",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "get one instance by name",
|
|
|
|
|
req: &pb.ListHostsRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: hostCatalogAttributes,
|
|
|
|
|
},
|
|
|
|
|
Sets: []*hostsets.HostSet{
|
|
|
|
|
{
|
|
|
|
|
Id: "get-one-instance-by-name",
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{
|
|
|
|
|
ConstListInstancesFilter: "name = boundary-1",
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expected: []*pb.ListHostsResponseHost{
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-1",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "get instance group",
|
|
|
|
|
req: &pb.ListHostsRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: hostCatalogAttributes,
|
|
|
|
|
},
|
|
|
|
|
Sets: []*hostsets.HostSet{
|
|
|
|
|
{
|
|
|
|
|
Id: "get-instance-group",
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{
|
|
|
|
|
ConstInstanceGroup: "boundary-servers",
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expected: []*pb.ListHostsResponseHost{
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-0",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-1",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-2",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "get two specific instances with two host sets",
|
|
|
|
|
req: &pb.ListHostsRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: hostCatalogAttributes,
|
|
|
|
|
},
|
|
|
|
|
Sets: []*hostsets.HostSet{
|
|
|
|
|
{
|
|
|
|
|
Id: "get-one-instance-by-name-0",
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{
|
|
|
|
|
ConstListInstancesFilter: "name = boundary-0",
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Id: "get-one-instance-by-name-2",
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{
|
|
|
|
|
ConstListInstancesFilter: "name = boundary-2",
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expected: []*pb.ListHostsResponseHost{
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-0",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-2",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "get one instance and an instance group in two host sets",
|
|
|
|
|
req: &pb.ListHostsRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: hostCatalogAttributes,
|
|
|
|
|
},
|
|
|
|
|
Sets: []*hostsets.HostSet{
|
|
|
|
|
{
|
|
|
|
|
Id: "get-one-instance-by-name",
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{
|
|
|
|
|
ConstListInstancesFilter: "name = boundary-1",
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Id: "get-instance-group",
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{
|
|
|
|
|
ConstInstanceGroup: "boundary-servers",
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expected: []*pb.ListHostsResponseHost{
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-0",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-1",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-2",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "get an instance group and one instance in two host sets",
|
|
|
|
|
req: &pb.ListHostsRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: hostCatalogAttributes,
|
|
|
|
|
},
|
|
|
|
|
Sets: []*hostsets.HostSet{
|
|
|
|
|
{
|
|
|
|
|
Id: "get-instance-group",
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{
|
|
|
|
|
ConstInstanceGroup: "boundary-servers",
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Id: "get-one-instance-by-name",
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{
|
|
|
|
|
ConstListInstancesFilter: "name = boundary-2",
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expected: []*pb.ListHostsResponseHost{
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-0",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-1",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: "boundary-2",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "invalid filter",
|
|
|
|
|
req: &pb.ListHostsRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: hostCatalogAttributes,
|
|
|
|
|
},
|
|
|
|
|
Sets: []*hostsets.HostSet{
|
|
|
|
|
{
|
|
|
|
|
Id: "invalid-filter",
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{
|
|
|
|
|
ConstListInstancesFilter: "not-a-filter",
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "Invalid list filter expression",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
|
tc := tc
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
require := require.New(t)
|
|
|
|
|
|
|
|
|
|
actual, err := p.ListHosts(ctx, tc.req)
|
|
|
|
|
if tc.expectedErr != "" {
|
|
|
|
|
require.Contains(err.Error(), tc.expectedErr)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
require.NoError(err)
|
|
|
|
|
require.Equal(len(tc.expected), len(actual.GetHosts()))
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCreateCatalog(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
name string
|
|
|
|
|
req *pb.OnCreateCatalogRequest
|
|
|
|
|
expected *pb.HostCatalogPersisted
|
2024-10-21 10:47:06 -04:00
|
|
|
catalogOpts []gcpCatalogPersistedStateOption
|
2024-04-02 15:47:49 -04:00
|
|
|
expectedErr string
|
2024-10-21 10:47:06 -04:00
|
|
|
expectedRsp *pb.OnCreateCatalogResponse
|
2024-04-02 15:47:49 -04:00
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "nil catalog",
|
|
|
|
|
req: &pb.OnCreateCatalogRequest{},
|
|
|
|
|
expectedErr: "catalog is nil",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "nil attributes",
|
|
|
|
|
req: &pb.OnCreateCatalogRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{},
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "attributes are required",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "error reading attributes",
|
|
|
|
|
req: &pb.OnCreateCatalogRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: new(structpb.Struct),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-10-21 10:47:06 -04:00
|
|
|
expectedErr: "attributes.project_id: missing required value \"project_id\"",
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
{
|
2024-10-21 10:47:06 -04:00
|
|
|
name: "using static credentials",
|
2024-04-02 15:47:49 -04:00
|
|
|
req: &pb.OnCreateCatalogRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
2024-10-21 10:47:06 -04:00
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
cred.ConstClientEmail: structpb.NewStringValue("test-client-email@email.com"),
|
|
|
|
|
cred.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("test-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("test-private-key"),
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
},
|
2024-10-21 10:47:06 -04:00
|
|
|
catalogOpts: []gcpCatalogPersistedStateOption{
|
|
|
|
|
withTestInstancesAPIFunc(newTestMockInstances(ctx,
|
|
|
|
|
nil,
|
|
|
|
|
testMockInstancesWithListInstancesOutput(&computepb.InstanceList{
|
|
|
|
|
Items: []*computepb.Instance{
|
|
|
|
|
{
|
|
|
|
|
Name: pointer("boundary-0"),
|
|
|
|
|
NetworkInterfaces: []*computepb.NetworkInterface{
|
|
|
|
|
{
|
|
|
|
|
AccessConfigs: []*computepb.AccessConfig{
|
|
|
|
|
{
|
|
|
|
|
NatIP: pointer("102.1.1.1"),
|
|
|
|
|
ExternalIpv6: pointer("2001:db8::1"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
testMockInstancesWithListInstancesError(nil),
|
|
|
|
|
)),
|
|
|
|
|
},
|
|
|
|
|
expectedRsp: &pb.OnCreateCatalogResponse{
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("test-private-key"),
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("test-private-key-id"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "using rotated static credentials",
|
|
|
|
|
req: &pb.OnCreateCatalogRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
cred.ConstClientEmail: structpb.NewStringValue("test-client-email@email.com"),
|
|
|
|
|
cred.ConstDisableCredentialRotation: structpb.NewBoolValue(false),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("test-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("test-private-key"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
catalogOpts: []gcpCatalogPersistedStateOption{
|
|
|
|
|
withTestInstancesAPIFunc(newTestMockInstances(ctx,
|
|
|
|
|
nil,
|
|
|
|
|
testMockInstancesWithListInstancesOutput(&computepb.InstanceList{
|
|
|
|
|
Items: []*computepb.Instance{
|
|
|
|
|
{
|
|
|
|
|
Name: pointer("boundary-0"),
|
|
|
|
|
NetworkInterfaces: []*computepb.NetworkInterface{
|
|
|
|
|
{
|
|
|
|
|
AccessConfigs: []*computepb.AccessConfig{
|
|
|
|
|
{
|
|
|
|
|
NatIP: pointer("102.1.1.1"),
|
|
|
|
|
ExternalIpv6: pointer("2001:db8::1"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
testMockInstancesWithListInstancesError(nil),
|
|
|
|
|
)),
|
|
|
|
|
},
|
|
|
|
|
expectedRsp: &pb.OnCreateCatalogResponse{
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("updated-private-key"),
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("updated-private-key-id"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "using impersonated credentials",
|
|
|
|
|
req: &pb.OnCreateCatalogRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
cred.ConstClientEmail: structpb.NewStringValue("test-client-email@email.com"),
|
|
|
|
|
cred.ConstTargetServiceAccountID: structpb.NewStringValue("test-target-service-account-id"),
|
|
|
|
|
cred.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("test-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("test-private-key"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
catalogOpts: []gcpCatalogPersistedStateOption{
|
|
|
|
|
withTestInstancesAPIFunc(newTestMockInstances(ctx,
|
|
|
|
|
nil,
|
|
|
|
|
testMockInstancesWithListInstancesOutput(&computepb.InstanceList{}),
|
|
|
|
|
testMockInstancesWithListInstancesError(nil),
|
|
|
|
|
)),
|
|
|
|
|
},
|
|
|
|
|
expectedRsp: &pb.OnCreateCatalogResponse{
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("test-private-key"),
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("test-private-key-id"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
require := require.New(t)
|
|
|
|
|
|
2024-10-21 10:47:06 -04:00
|
|
|
testIAMAdminServer := cred.NewTestIAMAdminServer(nil, nil)
|
|
|
|
|
testResourceServer := cred.NewTestResourceServer(&iampb.TestIamPermissionsResponse{
|
|
|
|
|
Permissions: []string{
|
|
|
|
|
cred.ComputeInstancesListPermission,
|
|
|
|
|
cred.IAMServiceAccountKeysCreatePermission,
|
|
|
|
|
cred.IAMServiceAccountKeysDeletePermission,
|
|
|
|
|
},
|
|
|
|
|
}, nil)
|
|
|
|
|
|
|
|
|
|
gsrv := cred.NewGRPCServer()
|
|
|
|
|
adminpb.RegisterIAMServer(gsrv.Server, testIAMAdminServer)
|
|
|
|
|
resourcemanagerpb.RegisterProjectsServer(gsrv.Server, testResourceServer)
|
|
|
|
|
addr, err := gsrv.Start()
|
|
|
|
|
require.NoError(err)
|
|
|
|
|
|
|
|
|
|
p := &HostPlugin{
|
|
|
|
|
testGCPClientOpts: []option.ClientOption{
|
|
|
|
|
option.WithEndpoint(addr),
|
|
|
|
|
option.WithoutAuthentication(),
|
|
|
|
|
option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
|
|
|
|
|
option.WithTokenSource(nil),
|
|
|
|
|
},
|
|
|
|
|
testCatalogStateOpts: tc.catalogOpts,
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-02 15:47:49 -04:00
|
|
|
actual, err := p.OnCreateCatalog(ctx, tc.req)
|
|
|
|
|
if tc.expectedErr != "" {
|
|
|
|
|
require.Contains(err.Error(), tc.expectedErr)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
require.NoError(err)
|
2024-10-21 10:47:06 -04:00
|
|
|
delete(actual.GetPersisted().GetSecrets().GetFields(), cred.ConstCredsLastRotatedTime)
|
|
|
|
|
require.Empty(cmp.Diff(tc.expectedRsp, actual, protocmp.Transform()))
|
2024-04-02 15:47:49 -04:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestUpdateCatalog(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
name string
|
2024-10-28 10:48:26 -04:00
|
|
|
req *pb.OnUpdateCatalogRequest
|
|
|
|
|
catalogOpts []gcpCatalogPersistedStateOption
|
|
|
|
|
expectedRsp *pb.OnUpdateCatalogResponse
|
2024-04-02 15:47:49 -04:00
|
|
|
expectedErr string
|
|
|
|
|
}{
|
|
|
|
|
{
|
2024-10-28 10:48:26 -04:00
|
|
|
name: "nil current catalog",
|
|
|
|
|
req: &pb.OnUpdateCatalogRequest{},
|
|
|
|
|
expectedErr: "current catalog is required",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "nil new catalog",
|
|
|
|
|
req: &pb.OnUpdateCatalogRequest{
|
|
|
|
|
CurrentCatalog: &hostcatalogs.HostCatalog{},
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "new catalog is required",
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "nil attributes",
|
2024-10-28 10:48:26 -04:00
|
|
|
req: &pb.OnUpdateCatalogRequest{
|
|
|
|
|
CurrentCatalog: &hostcatalogs.HostCatalog{},
|
|
|
|
|
NewCatalog: &hostcatalogs.HostCatalog{},
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
expectedErr: "attributes are required",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "error reading attributes",
|
2024-10-28 10:48:26 -04:00
|
|
|
req: &pb.OnUpdateCatalogRequest{
|
|
|
|
|
CurrentCatalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: new(structpb.Struct),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
NewCatalog: &hostcatalogs.HostCatalog{
|
2024-04-02 15:47:49 -04:00
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: new(structpb.Struct),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-10-28 10:48:26 -04:00
|
|
|
expectedErr: "attributes.project_id: missing required value \"project_id\"",
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
{
|
2024-10-28 10:48:26 -04:00
|
|
|
name: "staticCredentialToStaticRotated",
|
|
|
|
|
req: &pb.OnUpdateCatalogRequest{
|
|
|
|
|
CurrentCatalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstClientEmail: structpb.NewStringValue("test@example.com"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
cred.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
NewCatalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
cred.ConstClientEmail: structpb.NewStringValue("test@example.com"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("new-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("new-private-key"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("persisted-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("persisted-private-key"),
|
|
|
|
|
cred.ConstCredsLastRotatedTime: structpb.NewStringValue(time.Time{}.Format(time.RFC3339Nano)),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
catalogOpts: []gcpCatalogPersistedStateOption{
|
|
|
|
|
withTestInstancesAPIFunc(newTestMockInstances(ctx,
|
|
|
|
|
nil,
|
|
|
|
|
testMockInstancesWithListInstancesOutput(&computepb.InstanceList{}),
|
|
|
|
|
testMockInstancesWithListInstancesError(nil),
|
|
|
|
|
)),
|
|
|
|
|
},
|
|
|
|
|
expectedRsp: &pb.OnUpdateCatalogResponse{
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("updated-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("updated-private-key"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "staticRotatedCredentialToStatic",
|
|
|
|
|
req: &pb.OnUpdateCatalogRequest{
|
|
|
|
|
CurrentCatalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstClientEmail: structpb.NewStringValue("test@example.com"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
NewCatalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
cred.ConstClientEmail: structpb.NewStringValue("test@example.com"),
|
|
|
|
|
cred.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("not-rotated-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("not-rotated-private-key"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("persisted-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("persisted-private-key"),
|
|
|
|
|
cred.ConstCredsLastRotatedTime: structpb.NewStringValue(time.Now().Format(time.RFC3339Nano)),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
catalogOpts: []gcpCatalogPersistedStateOption{
|
|
|
|
|
withTestInstancesAPIFunc(newTestMockInstances(ctx,
|
|
|
|
|
nil,
|
|
|
|
|
testMockInstancesWithListInstancesOutput(&computepb.InstanceList{}),
|
|
|
|
|
testMockInstancesWithListInstancesError(nil),
|
|
|
|
|
)),
|
|
|
|
|
},
|
|
|
|
|
expectedRsp: &pb.OnUpdateCatalogResponse{
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("not-rotated-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("not-rotated-private-key"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "staticCredentialToImpersonateCredential",
|
|
|
|
|
req: &pb.OnUpdateCatalogRequest{
|
|
|
|
|
CurrentCatalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstClientEmail: structpb.NewStringValue("test@example.com"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
cred.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
NewCatalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
cred.ConstClientEmail: structpb.NewStringValue("test@example.com"),
|
|
|
|
|
cred.ConstTargetServiceAccountID: structpb.NewStringValue("test-target-service-account-id"),
|
|
|
|
|
cred.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("new-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("new-private-key"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("persisted-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("persisted-private-key"),
|
|
|
|
|
cred.ConstCredsLastRotatedTime: structpb.NewStringValue(time.Time{}.Format(time.RFC3339Nano)),
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
},
|
2024-10-28 10:48:26 -04:00
|
|
|
catalogOpts: []gcpCatalogPersistedStateOption{
|
|
|
|
|
withTestInstancesAPIFunc(newTestMockInstances(ctx,
|
|
|
|
|
nil,
|
|
|
|
|
testMockInstancesWithListInstancesOutput(&computepb.InstanceList{}),
|
|
|
|
|
testMockInstancesWithListInstancesError(nil),
|
|
|
|
|
)),
|
|
|
|
|
},
|
|
|
|
|
expectedRsp: &pb.OnUpdateCatalogResponse{
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("new-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("new-private-key"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
},
|
2024-10-28 10:48:26 -04:00
|
|
|
{
|
|
|
|
|
name: "staticRotatedCredentialToAppDefaultCredential",
|
|
|
|
|
req: &pb.OnUpdateCatalogRequest{
|
|
|
|
|
CurrentCatalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstClientEmail: structpb.NewStringValue("test@example.com"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
NewCatalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("persisted-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("persisted-private-key"),
|
|
|
|
|
cred.ConstCredsLastRotatedTime: structpb.NewStringValue(time.Time{}.Format(time.RFC3339Nano)),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "cannot rotate credentials for non-rotatable credentials",
|
|
|
|
|
},
|
2024-04-02 15:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
|
tc := tc
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
require := require.New(t)
|
2024-10-28 10:48:26 -04:00
|
|
|
|
|
|
|
|
testIAMAdminServer := cred.NewTestIAMAdminServer(nil, nil)
|
|
|
|
|
testResourceServer := cred.NewTestResourceServer(&iampb.TestIamPermissionsResponse{
|
|
|
|
|
Permissions: []string{
|
|
|
|
|
cred.ComputeInstancesListPermission,
|
|
|
|
|
cred.IAMServiceAccountKeysCreatePermission,
|
|
|
|
|
cred.IAMServiceAccountKeysDeletePermission,
|
|
|
|
|
},
|
|
|
|
|
}, nil)
|
|
|
|
|
|
|
|
|
|
gsrv := cred.NewGRPCServer()
|
|
|
|
|
adminpb.RegisterIAMServer(gsrv.Server, testIAMAdminServer)
|
|
|
|
|
resourcemanagerpb.RegisterProjectsServer(gsrv.Server, testResourceServer)
|
|
|
|
|
addr, err := gsrv.Start()
|
|
|
|
|
require.NoError(err)
|
|
|
|
|
|
|
|
|
|
p := &HostPlugin{
|
|
|
|
|
testGCPClientOpts: []option.ClientOption{
|
|
|
|
|
option.WithEndpoint(addr),
|
|
|
|
|
option.WithoutAuthentication(),
|
|
|
|
|
option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
|
|
|
|
|
option.WithTokenSource(nil),
|
|
|
|
|
},
|
|
|
|
|
testCatalogStateOpts: tc.catalogOpts,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
actual, err := p.OnUpdateCatalog(ctx, tc.req)
|
2024-04-02 15:47:49 -04:00
|
|
|
if tc.expectedErr != "" {
|
|
|
|
|
require.Contains(err.Error(), tc.expectedErr)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
require.NoError(err)
|
2024-10-28 10:48:26 -04:00
|
|
|
|
|
|
|
|
delete(actual.GetPersisted().GetSecrets().GetFields(), cred.ConstCredsLastRotatedTime)
|
|
|
|
|
require.Empty(cmp.Diff(tc.expectedRsp, actual, protocmp.Transform()))
|
2024-04-02 15:47:49 -04:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-28 11:00:14 -04:00
|
|
|
func TestDeleteCatalog(t *testing.T) {
|
|
|
|
|
cases := []struct {
|
|
|
|
|
name string
|
|
|
|
|
req *pb.OnDeleteCatalogRequest
|
|
|
|
|
catalogOpts []gcpCatalogPersistedStateOption
|
|
|
|
|
deleteCredError error
|
|
|
|
|
expectedRsp *pb.OnDeleteCatalogResponse
|
|
|
|
|
expectedErr string
|
|
|
|
|
expectedErrCode codes.Code
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "persisted state setup error",
|
|
|
|
|
req: &pb.OnDeleteCatalogRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project-id"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("test-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("test-private-key"),
|
|
|
|
|
cred.ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
catalogOpts: []gcpCatalogPersistedStateOption{
|
|
|
|
|
func(s *gcpCatalogPersistedState) error {
|
|
|
|
|
return fmt.Errorf("error loading persisted state")
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "error loading persisted state",
|
|
|
|
|
expectedErrCode: codes.InvalidArgument,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "delete error",
|
|
|
|
|
req: &pb.OnDeleteCatalogRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstClientEmail: structpb.NewStringValue("test@test.com"),
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project-id"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("test-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("test-private-key"),
|
|
|
|
|
cred.ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
deleteCredError: fmt.Errorf("failed to delete access key"),
|
|
|
|
|
expectedErr: "failed to delete access key",
|
|
|
|
|
expectedErrCode: codes.Unknown,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
require := require.New(t)
|
|
|
|
|
|
|
|
|
|
testIAMAdminServer := cred.NewTestIAMAdminServer(nil, tc.deleteCredError)
|
|
|
|
|
|
|
|
|
|
gsrv := cred.NewGRPCServer()
|
|
|
|
|
adminpb.RegisterIAMServer(gsrv.Server, testIAMAdminServer)
|
|
|
|
|
addr, err := gsrv.Start()
|
|
|
|
|
require.NoError(err)
|
|
|
|
|
|
|
|
|
|
p := &HostPlugin{
|
|
|
|
|
testGCPClientOpts: []option.ClientOption{
|
|
|
|
|
option.WithEndpoint(addr),
|
|
|
|
|
option.WithoutAuthentication(),
|
|
|
|
|
option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
|
|
|
|
|
option.WithTokenSource(nil),
|
|
|
|
|
},
|
|
|
|
|
testCatalogStateOpts: tc.catalogOpts,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = p.OnDeleteCatalog(context.Background(), tc.req)
|
|
|
|
|
require.Error(err)
|
|
|
|
|
require.Contains(err.Error(), tc.expectedErr)
|
|
|
|
|
require.Equal(status.Code(err).String(), tc.expectedErrCode.String())
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-02 15:47:49 -04:00
|
|
|
func TestCreateSet(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
cases := []struct {
|
2024-10-18 10:35:05 -04:00
|
|
|
name string
|
|
|
|
|
req *pb.OnCreateSetRequest
|
|
|
|
|
catalogOpts []gcpCatalogPersistedStateOption
|
|
|
|
|
gcpOptions []option.ClientOption
|
|
|
|
|
expectedErr string
|
|
|
|
|
expectedErrCode codes.Code
|
2024-04-02 15:47:49 -04:00
|
|
|
}{
|
|
|
|
|
{
|
2024-10-18 10:35:05 -04:00
|
|
|
name: "nil host catalog",
|
|
|
|
|
req: &pb.OnCreateSetRequest{},
|
|
|
|
|
expectedErr: "catalog is required",
|
|
|
|
|
expectedErrCode: codes.InvalidArgument,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "nil host catalog attributes",
|
|
|
|
|
req: &pb.OnCreateSetRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{},
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "catalog attributes are required",
|
|
|
|
|
expectedErrCode: codes.InvalidArgument,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "error reading catalog attributes",
|
|
|
|
|
req: &pb.OnCreateSetRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Secrets: new(structpb.Struct),
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: new(structpb.Struct),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "missing required value \"project_id\"",
|
|
|
|
|
expectedErrCode: codes.InvalidArgument,
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "nil set",
|
|
|
|
|
req: &pb.OnCreateSetRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
2024-09-17 10:50:10 -04:00
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-10-18 10:35:05 -04:00
|
|
|
expectedErr: "set is required",
|
|
|
|
|
expectedErrCode: codes.InvalidArgument,
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
{
|
2024-10-18 10:35:05 -04:00
|
|
|
name: "persisted state setup error",
|
|
|
|
|
req: &pb.OnCreateSetRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("test-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("test-private-key"),
|
|
|
|
|
cred.ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
catalogOpts: []gcpCatalogPersistedStateOption{
|
|
|
|
|
func(s *gcpCatalogPersistedState) error {
|
|
|
|
|
return fmt.Errorf("error loading persisted state")
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "error loading persisted state",
|
|
|
|
|
expectedErrCode: codes.InvalidArgument,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "nil attributes in set",
|
|
|
|
|
req: &pb.OnCreateSetRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("test-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("test-private-key"),
|
|
|
|
|
cred.ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Set: &hostsets.HostSet{},
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "set attributes are required",
|
|
|
|
|
expectedErrCode: codes.InvalidArgument,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "set attribute load error",
|
2024-04-02 15:47:49 -04:00
|
|
|
req: &pb.OnCreateSetRequest{
|
2024-10-18 10:35:05 -04:00
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("test-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("test-private-key"),
|
|
|
|
|
cred.ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-04-02 15:47:49 -04:00
|
|
|
Set: &hostsets.HostSet{
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
2024-10-18 10:35:05 -04:00
|
|
|
"foo": structpb.NewBoolValue(true),
|
|
|
|
|
"bar": structpb.NewBoolValue(true),
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-10-18 10:35:05 -04:00
|
|
|
expectedErr: "attributes.bar: unrecognized field, attributes.foo: unrecognized field",
|
|
|
|
|
expectedErrCode: codes.InvalidArgument,
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
{
|
2024-10-18 10:35:05 -04:00
|
|
|
name: "invalid filter",
|
2024-04-02 15:47:49 -04:00
|
|
|
req: &pb.OnCreateSetRequest{
|
2024-10-18 10:35:05 -04:00
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-04-02 15:47:49 -04:00
|
|
|
Set: &hostsets.HostSet{
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
2024-10-18 10:35:05 -04:00
|
|
|
ConstListInstancesFilter: structpb.NewListValue(
|
|
|
|
|
&structpb.ListValue{
|
|
|
|
|
Values: []*structpb.Value{
|
|
|
|
|
structpb.NewStringValue("invalid"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
),
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-10-18 10:35:05 -04:00
|
|
|
expectedErr: "error building filters",
|
|
|
|
|
expectedErrCode: codes.InvalidArgument,
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
{
|
2024-10-18 10:35:05 -04:00
|
|
|
name: "client load error",
|
2024-04-02 15:47:49 -04:00
|
|
|
req: &pb.OnCreateSetRequest{
|
2024-10-18 10:35:05 -04:00
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("test-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("test-private-key"),
|
|
|
|
|
cred.ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-04-02 15:47:49 -04:00
|
|
|
Set: &hostsets.HostSet{
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
2024-10-18 10:35:05 -04:00
|
|
|
ConstListInstancesFilter: structpb.NewListValue(
|
|
|
|
|
&structpb.ListValue{
|
|
|
|
|
Values: []*structpb.Value{
|
|
|
|
|
structpb.NewStringValue("tag-key=foo"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
),
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-10-18 10:35:05 -04:00
|
|
|
gcpOptions: []option.ClientOption{
|
|
|
|
|
option.WithHTTPClient(&http.Client{}),
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "error getting instances client",
|
|
|
|
|
expectedErrCode: codes.InvalidArgument,
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
{
|
2024-10-18 10:35:05 -04:00
|
|
|
name: "List instances error",
|
2024-04-02 15:47:49 -04:00
|
|
|
req: &pb.OnCreateSetRequest{
|
2024-10-18 10:35:05 -04:00
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("test-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("test-private-key"),
|
|
|
|
|
cred.ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-04-02 15:47:49 -04:00
|
|
|
Set: &hostsets.HostSet{
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
2024-10-18 10:35:05 -04:00
|
|
|
ConstListInstancesFilter: structpb.NewListValue(
|
|
|
|
|
&structpb.ListValue{
|
|
|
|
|
Values: []*structpb.Value{
|
|
|
|
|
structpb.NewStringValue("tag-key=foo"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
),
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-10-18 10:35:05 -04:00
|
|
|
catalogOpts: []gcpCatalogPersistedStateOption{
|
|
|
|
|
withTestInstancesAPIFunc(newTestMockInstances(ctx,
|
|
|
|
|
nil,
|
|
|
|
|
testMockInstancesWithListInstancesError(fmt.Errorf("failed to list instances")),
|
|
|
|
|
)),
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "gcp list instances failed",
|
|
|
|
|
expectedErrCode: codes.FailedPrecondition,
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
{
|
2024-10-18 10:35:05 -04:00
|
|
|
name: "success",
|
2024-04-02 15:47:49 -04:00
|
|
|
req: &pb.OnCreateSetRequest{
|
2024-10-18 10:35:05 -04:00
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
|
|
|
Secrets: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
cred.ConstPrivateKeyId: structpb.NewStringValue("test-private-key-id"),
|
|
|
|
|
cred.ConstPrivateKey: structpb.NewStringValue("test-private-key"),
|
|
|
|
|
cred.ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-04-02 15:47:49 -04:00
|
|
|
Set: &hostsets.HostSet{
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
2024-10-18 10:35:05 -04:00
|
|
|
ConstListInstancesFilter: structpb.NewListValue(
|
|
|
|
|
&structpb.ListValue{
|
|
|
|
|
Values: []*structpb.Value{
|
|
|
|
|
structpb.NewStringValue("tag-key=foo"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
),
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-10-18 10:35:05 -04:00
|
|
|
catalogOpts: []gcpCatalogPersistedStateOption{
|
|
|
|
|
withTestInstancesAPIFunc(newTestMockInstances(ctx,
|
|
|
|
|
nil,
|
|
|
|
|
testMockInstancesWithListInstancesOutput(&computepb.InstanceList{}),
|
|
|
|
|
testMockInstancesWithListInstancesError(nil)),
|
|
|
|
|
),
|
|
|
|
|
},
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
|
tc := tc
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
require := require.New(t)
|
|
|
|
|
|
2024-10-18 10:35:05 -04:00
|
|
|
testIAMAdminServer := cred.NewTestIAMAdminServer(nil, nil)
|
|
|
|
|
|
|
|
|
|
gsrv := cred.NewGRPCServer()
|
|
|
|
|
adminpb.RegisterIAMServer(gsrv.Server, testIAMAdminServer)
|
|
|
|
|
addr, err := gsrv.Start()
|
|
|
|
|
require.NoError(err)
|
|
|
|
|
|
|
|
|
|
clientOptions := []option.ClientOption{
|
|
|
|
|
option.WithEndpoint(addr),
|
|
|
|
|
option.WithoutAuthentication(),
|
|
|
|
|
option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
|
|
|
|
|
option.WithTokenSource(nil),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clientOptions = append(clientOptions, tc.gcpOptions...)
|
|
|
|
|
|
|
|
|
|
p := &HostPlugin{
|
|
|
|
|
testGCPClientOpts: clientOptions,
|
|
|
|
|
testCatalogStateOpts: tc.catalogOpts,
|
|
|
|
|
}
|
|
|
|
|
_, err = p.OnCreateSet(ctx, tc.req)
|
2024-04-02 15:47:49 -04:00
|
|
|
if tc.expectedErr != "" {
|
|
|
|
|
require.Contains(err.Error(), tc.expectedErr)
|
2024-10-18 10:35:05 -04:00
|
|
|
require.Equal(status.Code(err).String(), tc.expectedErrCode.String())
|
2024-04-02 15:47:49 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
require.NoError(err)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestUpdateSet(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
2024-09-16 14:28:54 -04:00
|
|
|
p := &HostPlugin{}
|
2024-04-02 15:47:49 -04:00
|
|
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
name string
|
|
|
|
|
req *pb.OnUpdateSetRequest
|
|
|
|
|
expectedErr string
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "nil set",
|
|
|
|
|
req: &pb.OnUpdateSetRequest{},
|
|
|
|
|
expectedErr: "set is nil",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "nil set",
|
|
|
|
|
req: &pb.OnUpdateSetRequest{
|
|
|
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
2024-09-17 10:50:10 -04:00
|
|
|
cred.ConstProjectId: structpb.NewStringValue("test-project"),
|
|
|
|
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "set is nil",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "only allow instance group or filter, not both",
|
|
|
|
|
req: &pb.OnUpdateSetRequest{
|
|
|
|
|
NewSet: &hostsets.HostSet{
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
2024-10-18 10:35:05 -04:00
|
|
|
ConstListInstancesFilter: structpb.NewListValue(
|
|
|
|
|
&structpb.ListValue{
|
|
|
|
|
Values: []*structpb.Value{
|
|
|
|
|
structpb.NewStringValue("status=RUNNING"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
ConstInstanceGroup: structpb.NewStringValue("test"),
|
2024-04-02 15:47:49 -04:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "attributes: must set instance group or filter",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "empty instance group",
|
|
|
|
|
req: &pb.OnUpdateSetRequest{
|
|
|
|
|
NewSet: &hostsets.HostSet{
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstInstanceGroup: structpb.NewStringValue(""),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedErr: "attributes.instance_group: must not be empty",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "good filter",
|
|
|
|
|
req: &pb.OnUpdateSetRequest{
|
|
|
|
|
NewSet: &hostsets.HostSet{
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstInstanceGroup: structpb.NewStringValue("status=RUNNING"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "good instance group",
|
|
|
|
|
req: &pb.OnUpdateSetRequest{
|
|
|
|
|
NewSet: &hostsets.HostSet{
|
|
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
ConstInstanceGroup: structpb.NewStringValue("test"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
|
tc := tc
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
require := require.New(t)
|
|
|
|
|
|
|
|
|
|
_, err := p.OnUpdateSet(ctx, tc.req)
|
|
|
|
|
if tc.expectedErr != "" {
|
|
|
|
|
require.Contains(err.Error(), tc.expectedErr)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
require.NoError(err)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-28 11:02:12 -04:00
|
|
|
|
|
|
|
|
func TestNormalizeSetData(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
p := &HostPlugin{}
|
|
|
|
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
name string
|
|
|
|
|
req *pb.NormalizeSetDataRequest
|
|
|
|
|
expectedRsp *pb.NormalizeSetDataResponse
|
|
|
|
|
expectedErrContains string
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "nil attributes",
|
|
|
|
|
req: &pb.NormalizeSetDataRequest{},
|
|
|
|
|
expectedRsp: &pb.NormalizeSetDataResponse{
|
|
|
|
|
Attributes: nil,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "nil filters",
|
|
|
|
|
req: &pb.NormalizeSetDataRequest{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedRsp: &pb.NormalizeSetDataResponse{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "filters not a string",
|
|
|
|
|
req: &pb.NormalizeSetDataRequest{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
"filters": structpb.NewListValue(&structpb.ListValue{
|
|
|
|
|
Values: []*structpb.Value{
|
|
|
|
|
structpb.NewStringValue("status = RUNNING"),
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedRsp: &pb.NormalizeSetDataResponse{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
"filters": structpb.NewListValue(&structpb.ListValue{
|
|
|
|
|
Values: []*structpb.Value{
|
|
|
|
|
structpb.NewStringValue("status = RUNNING"),
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "filters is a string",
|
|
|
|
|
req: &pb.NormalizeSetDataRequest{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
"filters": structpb.NewStringValue("status = RUNNING"),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedRsp: &pb.NormalizeSetDataResponse{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
"filters": structpb.NewListValue(&structpb.ListValue{
|
|
|
|
|
Values: []*structpb.Value{
|
|
|
|
|
structpb.NewStringValue("status = RUNNING"),
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "filters with multiple values",
|
|
|
|
|
req: &pb.NormalizeSetDataRequest{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
"filters": structpb.NewStringValue(`(cpuPlatform = "Intel Skylake") OR (cpuPlatform = "Intel Broadwell") AND (scheduling.automaticRestart = true)`),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedRsp: &pb.NormalizeSetDataResponse{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
"filters": structpb.NewListValue(&structpb.ListValue{
|
|
|
|
|
Values: []*structpb.Value{
|
|
|
|
|
structpb.NewStringValue(`(cpuPlatform = "Intel Skylake") OR (cpuPlatform = "Intel Broadwell") AND (scheduling.automaticRestart = true)`),
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "filters is an number",
|
|
|
|
|
req: &pb.NormalizeSetDataRequest{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
"filters": structpb.NewNumberValue(1),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedErrContains: "attribute.filters must be a string or list",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "filters is an boolean",
|
|
|
|
|
req: &pb.NormalizeSetDataRequest{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
"filters": structpb.NewBoolValue(true),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedErrContains: "attribute.filters must be a string or list",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "filters is null",
|
|
|
|
|
req: &pb.NormalizeSetDataRequest{
|
|
|
|
|
Attributes: &structpb.Struct{
|
|
|
|
|
Fields: map[string]*structpb.Value{
|
|
|
|
|
"filters": structpb.NewNullValue(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
expectedErrContains: "attribute.filters must be a string or list",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
|
tc := tc
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
require := require.New(t)
|
|
|
|
|
|
|
|
|
|
actual, err := p.NormalizeSetData(ctx, tc.req)
|
|
|
|
|
if tc.expectedErrContains != "" {
|
|
|
|
|
require.ErrorContains(err, tc.expectedErrContains)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
require.NoError(err)
|
|
|
|
|
require.Empty(cmp.Diff(tc.expectedRsp, actual, protocmp.Transform()))
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|