* test: add e2e tests Spin up GCP resources for testing dynamic host catalogs. The resources are tested with tests that focus on credential changes and rotation for host catalog and host set creation and updates using labels. - The tests all depend on each other so they need to run in order. - The test can also only be executed locally for now. - The resources are cleaned up after the test as part of the test.
590 lines
18 KiB
Go
590 lines
18 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package testing
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/boundary-plugin-gcp/internal/credential"
|
|
"github.com/hashicorp/boundary-plugin-gcp/plugin/service/host"
|
|
"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/stretchr/testify/require"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
)
|
|
|
|
const (
|
|
expectedInstanceCount = 3
|
|
expectedTagsCount = 3
|
|
expectedLabelsCount = 3
|
|
)
|
|
|
|
// All the tests in this test depend on each other
|
|
// to run in order. This is because the tests are
|
|
// testing the same plugin instance and the same
|
|
// persisted state. The tests are designed to run
|
|
// in order to test the plugin's ability to rotate
|
|
// credentials and manage the state of the plugin
|
|
// instance.
|
|
// To run the tests you need the following
|
|
// environment variables set:
|
|
// GOOGLE_APPLICATION_CREDENTIALS: This should be
|
|
// the path to the service account key. This can also
|
|
// be automatically set after authenticating with gcloud
|
|
// using `gcloud auth application-default login`.
|
|
//
|
|
// TF_VAR_project_id: The project_id of the project.
|
|
//
|
|
// TF_VAR_region: The region the resources should be
|
|
// deployed in.
|
|
//
|
|
// TF_VAR_zone: The zone the resources should be
|
|
// deployed in.
|
|
func TestHostPlugin(t *testing.T) {
|
|
creds := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
|
|
if creds == "" {
|
|
t.Skip("set GOOGLE_APPLICATION_CREDENTIALS to use this test")
|
|
}
|
|
projectId := os.Getenv("TF_VAR_project_id")
|
|
if projectId == "" {
|
|
t.Skip("set TF_VAR_project_id to use this test")
|
|
}
|
|
region := os.Getenv("TF_VAR_region")
|
|
if region == "" {
|
|
t.Skip("set TF_VAR_region to use this test")
|
|
}
|
|
zone := os.Getenv("TF_VAR_zone")
|
|
if zone == "" {
|
|
t.Skip("set TF_VAR_zone to use this test")
|
|
}
|
|
|
|
require := require.New(t)
|
|
tf, err := NewTestTerraformer("testdata/host")
|
|
require.NoError(err)
|
|
require.NotNil(tf)
|
|
|
|
t.Log("===== deploying test Terraform workspace =====")
|
|
err = tf.Deploy()
|
|
require.NoError(err)
|
|
|
|
defer func() {
|
|
t.Log("===== destroying test Terraform workspace =====")
|
|
if err := tf.Destroy(); err != nil {
|
|
t.Logf("WARNING: could not run Terraform destroy: %s", err)
|
|
}
|
|
}()
|
|
|
|
clientEmail, err := tf.GetOutput("client_email")
|
|
require.NoError(err)
|
|
require.NotNil(clientEmail)
|
|
|
|
instanceIds, err := tf.GetOutputSlice("instance_ids")
|
|
require.NoError(err)
|
|
require.Len(instanceIds, expectedInstanceCount)
|
|
|
|
instanceTags, err := tf.GetOutputMap("instance_tags")
|
|
require.NoError(err)
|
|
require.Len(instanceTags, expectedInstanceCount)
|
|
|
|
instanceLabels, err := tf.GetOutputMap("instance_labels")
|
|
require.NoError(err)
|
|
require.Len(instanceLabels, expectedLabelsCount)
|
|
|
|
privateKeys, err := tf.GetOutputSlice("private_keys")
|
|
require.NoError(err)
|
|
require.Len(privateKeys, expectedServiceAccountKeys)
|
|
|
|
privateKeyIds, err := tf.GetOutputSlice("private_key_ids")
|
|
require.NoError(err)
|
|
require.Len(privateKeyIds, expectedServiceAccountKeys)
|
|
|
|
p := new(host.HostPlugin)
|
|
ctx := context.Background()
|
|
|
|
var keyId, secret string
|
|
|
|
t.Run("OnCreateCatalog", func(t *testing.T) {
|
|
t.Run("Test non-rotation", func(t *testing.T) {
|
|
keyId, secret = testPluginOnCreateCatalog(
|
|
ctx,
|
|
t,
|
|
p,
|
|
zone,
|
|
projectId,
|
|
clientEmail.(string),
|
|
privateKeyIds[0].(string),
|
|
privateKeys[0].(string),
|
|
false,
|
|
)
|
|
require.NotNil(keyId)
|
|
require.NotNil(secret)
|
|
})
|
|
|
|
t.Run("Test rotation", func(t *testing.T) {
|
|
keyId, secret = testPluginOnCreateCatalog(
|
|
ctx,
|
|
t,
|
|
p,
|
|
zone,
|
|
projectId,
|
|
clientEmail.(string),
|
|
keyId,
|
|
secret,
|
|
true,
|
|
)
|
|
require.NotNil(keyId)
|
|
require.NotNil(secret)
|
|
})
|
|
})
|
|
|
|
t.Run("OnUpdateCatalog", func(t *testing.T) {
|
|
t.Run("Test no-op non-rotation", func(t *testing.T) {
|
|
keyId, secret = testPluginOnUpdateCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, "", "", false, false)
|
|
require.NotNil(keyId)
|
|
require.NotNil(secret)
|
|
})
|
|
|
|
t.Run("Switch to rotation", func(t *testing.T) {
|
|
keyId, secret = testPluginOnUpdateCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, "", "", false, true)
|
|
require.NotNil(keyId)
|
|
require.NotNil(secret)
|
|
})
|
|
|
|
t.Run("Test no-op with rotation", func(t *testing.T) {
|
|
keyId, secret = testPluginOnUpdateCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, "", "", true, true)
|
|
require.NotNil(keyId)
|
|
require.NotNil(secret)
|
|
})
|
|
|
|
t.Run("Switch credentials to next service account key. Don't rotate", func(t *testing.T) {
|
|
keyId, secret = testPluginOnUpdateCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, privateKeyIds[1].(string), privateKeys[1].(string), true, false)
|
|
require.NotNil(keyId)
|
|
require.NotNil(secret)
|
|
})
|
|
|
|
t.Run("Switch credentials to next service account key. Add rotation", func(t *testing.T) {
|
|
keyId, secret = testPluginOnUpdateCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, privateKeyIds[2].(string), privateKeys[2].(string), false, true)
|
|
require.NotNil(keyId)
|
|
require.NotNil(secret)
|
|
})
|
|
|
|
t.Run("Switch to next service account key, with rotation disabled", func(t *testing.T) {
|
|
keyId, secret = testPluginOnUpdateCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, privateKeyIds[3].(string), privateKeys[3].(string), true, false)
|
|
require.NotNil(keyId)
|
|
require.NotNil(secret)
|
|
})
|
|
|
|
t.Run("Switch to another service account key and keep rotation off", func(t *testing.T) {
|
|
keyId, secret = testPluginOnUpdateCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, privateKeyIds[4].(string), privateKeys[4].(string), false, false)
|
|
require.NotNil(keyId)
|
|
require.NotNil(secret)
|
|
})
|
|
})
|
|
|
|
t.Run("OnDeleteCatalog", func(t *testing.T) {
|
|
t.Run("Test non-rotated", func(t *testing.T) {
|
|
testPluginOnDeleteCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, false)
|
|
})
|
|
|
|
t.Run("Test as if we had rotated these credentials.", func(t *testing.T) {
|
|
testPluginOnDeleteCatalog(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, true)
|
|
})
|
|
})
|
|
|
|
t.Run("HostSet", func(t *testing.T) {
|
|
keyId, secret := privateKeyIds[5].(string), privateKeys[5].(string)
|
|
expectedLabelInstancesMap := make(map[string][]string)
|
|
for instanceId, instanceLabel := range instanceLabels {
|
|
for tagKey := range instanceLabel.(map[string]any) {
|
|
for _, expectedTag := range instanceLabels {
|
|
if tagKey == expectedTag {
|
|
expectedLabelInstancesMap[tagKey] = append(expectedLabelInstancesMap[tagKey], instanceId)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cases := map[string][]string{
|
|
"label-1": {"foo"},
|
|
"label-2": {"bar"},
|
|
"label-3": {"baz"},
|
|
"label-4": {"foo", "bar"},
|
|
"label-5": {"foo", "baz"},
|
|
"label-6": {"bar", "baz"},
|
|
"label-7": {"foo", "bar", "baz"},
|
|
}
|
|
|
|
for key, tc := range cases {
|
|
t.Run(fmt.Sprintf("Label=%s", key), func(t *testing.T) {
|
|
labels := make([]string, 0, len(tc))
|
|
for _, label := range tc {
|
|
labels = append(labels, fmt.Sprintf("labels.%s=%s", key, label))
|
|
}
|
|
testPluginOnCreateUpdateSet(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, labels)
|
|
testPluginListHosts(ctx, t, p, zone, projectId, clientEmail.(string), keyId, secret, labels, expectedLabelInstancesMap)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func testPluginOnCreateCatalog(
|
|
ctx context.Context,
|
|
t *testing.T,
|
|
p *host.HostPlugin,
|
|
zone string,
|
|
projectId string,
|
|
clientEmail string,
|
|
privateKeyId string,
|
|
privateKey string,
|
|
rotate bool) (string, string) {
|
|
t.Helper()
|
|
t.Logf("testing OnCreateCatalog (zone=%s, rotate=%t)", zone, rotate)
|
|
require := require.New(t)
|
|
|
|
reqAttrs, err := structpb.NewStruct(map[string]any{
|
|
credential.ConstClientEmail: clientEmail,
|
|
credential.ConstProjectId: projectId,
|
|
credential.ConstZone: zone,
|
|
credential.ConstDisableCredentialRotation: !rotate,
|
|
})
|
|
require.NoError(err)
|
|
reqSecrets, err := structpb.NewStruct(map[string]any{
|
|
credential.ConstPrivateKeyId: privateKeyId,
|
|
credential.ConstPrivateKey: privateKey,
|
|
})
|
|
require.NoError(err)
|
|
request := &pb.OnCreateCatalogRequest{
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
Attributes: reqAttrs,
|
|
},
|
|
Secrets: reqSecrets,
|
|
},
|
|
}
|
|
response, err := p.OnCreateCatalog(ctx, request)
|
|
require.NoError(err)
|
|
require.NotNil(response)
|
|
persisted := response.GetPersisted()
|
|
require.NotNil(persisted)
|
|
return validatePersistedSecrets(t, persisted.GetSecrets(), projectId, clientEmail, privateKeyId, privateKey, rotate)
|
|
}
|
|
|
|
func testPluginOnUpdateCatalog(
|
|
ctx context.Context,
|
|
t *testing.T,
|
|
p *host.HostPlugin,
|
|
zone string,
|
|
projectId string,
|
|
clientEmail string,
|
|
currentPrivateKeyId string,
|
|
currentPrivateKey string,
|
|
newPrivateKeyId string,
|
|
newPrivateKey string,
|
|
rotated bool,
|
|
rotate bool,
|
|
) (string, string) {
|
|
t.Helper()
|
|
t.Logf("testing OnUpdateCatalog (zone=%s, newcreds=%t, rotated=%t, rotate=%t)", zone, newPrivateKeyId != "" && newPrivateKey != "", rotated, rotate)
|
|
require := require.New(t)
|
|
|
|
// Take a timestamp of the current time to get a point in time to
|
|
// reference, ensuring that we are updating credential rotation
|
|
// timestamps.
|
|
currentCredsLastRotatedTime := time.Now()
|
|
|
|
reqCurrentAttrs, err := structpb.NewStruct(map[string]any{
|
|
credential.ConstProjectId: projectId,
|
|
credential.ConstClientEmail: clientEmail,
|
|
credential.ConstZone: zone,
|
|
credential.ConstDisableCredentialRotation: !rotated,
|
|
})
|
|
require.NoError(err)
|
|
reqNewAttrs, err := structpb.NewStruct(map[string]any{
|
|
credential.ConstProjectId: projectId,
|
|
credential.ConstClientEmail: clientEmail,
|
|
credential.ConstZone: zone,
|
|
credential.ConstDisableCredentialRotation: !rotate,
|
|
})
|
|
require.NoError(err)
|
|
var reqSecrets *structpb.Struct
|
|
if newPrivateKeyId != "" && newPrivateKey != "" {
|
|
reqSecrets, err = structpb.NewStruct(map[string]any{
|
|
credential.ConstPrivateKeyId: newPrivateKeyId,
|
|
credential.ConstPrivateKey: newPrivateKey,
|
|
})
|
|
require.NoError(err)
|
|
}
|
|
reqPersistedSecrets, err := structpb.NewStruct(map[string]any{
|
|
credential.ConstPrivateKeyId: currentPrivateKeyId,
|
|
credential.ConstPrivateKey: currentPrivateKey,
|
|
credential.ConstCredsLastRotatedTime: func() string {
|
|
if rotated {
|
|
return currentCredsLastRotatedTime.Format(time.RFC3339Nano)
|
|
}
|
|
|
|
return (time.Time{}).Format(time.RFC3339Nano)
|
|
}(),
|
|
})
|
|
require.NoError(err)
|
|
require.NotNil(reqPersistedSecrets)
|
|
request := &pb.OnUpdateCatalogRequest{
|
|
CurrentCatalog: &hostcatalogs.HostCatalog{
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
Attributes: reqCurrentAttrs,
|
|
},
|
|
},
|
|
NewCatalog: &hostcatalogs.HostCatalog{
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
Attributes: reqNewAttrs,
|
|
},
|
|
Secrets: reqSecrets,
|
|
},
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
Secrets: reqPersistedSecrets,
|
|
},
|
|
}
|
|
response, err := p.OnUpdateCatalog(ctx, request)
|
|
require.NoError(err)
|
|
require.NotNil(response)
|
|
persisted := response.GetPersisted()
|
|
require.NotNil(persisted)
|
|
return validateUpdateSecrets(
|
|
t,
|
|
projectId,
|
|
clientEmail,
|
|
persisted.GetSecrets(),
|
|
currentCredsLastRotatedTime,
|
|
currentPrivateKeyId,
|
|
currentPrivateKey,
|
|
newPrivateKeyId,
|
|
newPrivateKey,
|
|
rotated,
|
|
rotate,
|
|
)
|
|
}
|
|
|
|
func testPluginOnDeleteCatalog(
|
|
ctx context.Context,
|
|
t *testing.T,
|
|
p *host.HostPlugin,
|
|
zone string,
|
|
projectId string,
|
|
clientEmail string,
|
|
privateKeyId string,
|
|
privateKey string,
|
|
rotated bool) {
|
|
t.Helper()
|
|
t.Logf("testing OnDeleteCatalog (zone=%s, rotated=%t)", zone, rotated)
|
|
require := require.New(t)
|
|
|
|
reqAttrs, err := structpb.NewStruct(map[string]any{
|
|
credential.ConstProjectId: projectId,
|
|
credential.ConstClientEmail: clientEmail,
|
|
credential.ConstZone: zone,
|
|
credential.ConstDisableCredentialRotation: !rotated,
|
|
})
|
|
require.NoError(err)
|
|
reqSecrets, err := structpb.NewStruct(map[string]any{
|
|
credential.ConstPrivateKeyId: privateKeyId,
|
|
credential.ConstPrivateKey: privateKey,
|
|
})
|
|
require.NoError(err)
|
|
reqPersistedSecrets, err := structpb.NewStruct(map[string]any{
|
|
credential.ConstPrivateKeyId: privateKeyId,
|
|
credential.ConstPrivateKey: privateKey,
|
|
credential.ConstCredsLastRotatedTime: func() string {
|
|
if rotated {
|
|
return time.Now().Format(time.RFC3339Nano)
|
|
}
|
|
|
|
return (time.Time{}).Format(time.RFC3339Nano)
|
|
}(),
|
|
})
|
|
require.NoError(err)
|
|
request := &pb.OnDeleteCatalogRequest{
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
Attributes: reqAttrs,
|
|
},
|
|
Secrets: reqSecrets,
|
|
},
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
Secrets: reqPersistedSecrets,
|
|
},
|
|
}
|
|
response, err := p.OnDeleteCatalog(ctx, request)
|
|
require.NoError(err)
|
|
require.NotNil(response)
|
|
|
|
// We want to test the validity of the credentials post-deletion.
|
|
if rotated {
|
|
// The credentials should no longer be valid.
|
|
requireCredentialsInvalid(t, projectId, clientEmail, privateKeyId, privateKey)
|
|
} else {
|
|
// The credentials should still be valid. Sleep 10s first just to
|
|
// be sure, since we're not rotating.
|
|
time.Sleep(time.Second * 10)
|
|
requireCredentialsValid(t, projectId, clientEmail, privateKeyId, privateKey)
|
|
}
|
|
}
|
|
|
|
func testPluginOnCreateUpdateSet(
|
|
ctx context.Context,
|
|
t *testing.T,
|
|
p *host.HostPlugin,
|
|
zone string,
|
|
projectId string,
|
|
clientEmail string,
|
|
privateKeyId string,
|
|
privateKey string,
|
|
labels []string) {
|
|
t.Helper()
|
|
t.Logf("testing OnCreateSet (zone=%s, labels=%v)", zone, labels)
|
|
require := require.New(t)
|
|
catalogAttrs, err := structpb.NewStruct(map[string]any{
|
|
credential.ConstProjectId: projectId,
|
|
credential.ConstClientEmail: clientEmail,
|
|
credential.ConstZone: zone,
|
|
credential.ConstDisableCredentialRotation: true, // Note that this does nothing in sets, but just noting for tests
|
|
})
|
|
require.NoError(err)
|
|
setAttrs, err := structpb.NewStruct(map[string]any{
|
|
host.ConstListInstancesFilter: []any{strings.Join(labels, " AND ")},
|
|
})
|
|
require.NoError(err)
|
|
reqPersistedSecrets, err := structpb.NewStruct(map[string]any{
|
|
credential.ConstPrivateKeyId: privateKeyId,
|
|
credential.ConstPrivateKey: privateKey,
|
|
credential.ConstCredsLastRotatedTime: (time.Time{}).Format(time.RFC3339Nano),
|
|
})
|
|
require.NoError(err)
|
|
createRequest := &pb.OnCreateSetRequest{
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
Attributes: catalogAttrs,
|
|
},
|
|
},
|
|
Set: &hostsets.HostSet{
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
Attributes: setAttrs,
|
|
},
|
|
},
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
Secrets: reqPersistedSecrets,
|
|
},
|
|
}
|
|
createResponse, err := p.OnCreateSet(ctx, createRequest)
|
|
require.NoError(err)
|
|
require.NotNil(createResponse)
|
|
|
|
// Do an update test in the same function, as it's pretty much the
|
|
// same function right now.
|
|
t.Logf("testing OnUpdateSet (zone=%s, labels=%v)", zone, labels)
|
|
updateRequest := &pb.OnUpdateSetRequest{
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
Attributes: catalogAttrs,
|
|
},
|
|
},
|
|
CurrentSet: &hostsets.HostSet{
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
Attributes: setAttrs,
|
|
},
|
|
},
|
|
NewSet: &hostsets.HostSet{
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
Attributes: setAttrs,
|
|
},
|
|
},
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
Secrets: reqPersistedSecrets,
|
|
},
|
|
}
|
|
updateResponse, err := p.OnUpdateSet(ctx, updateRequest)
|
|
require.NoError(err)
|
|
require.NotNil(updateResponse)
|
|
}
|
|
|
|
func testPluginListHosts(
|
|
ctx context.Context,
|
|
t *testing.T,
|
|
p *host.HostPlugin,
|
|
zone string,
|
|
projectId string,
|
|
clientEmail string,
|
|
privateKeyId string,
|
|
privateKey string,
|
|
labels []string,
|
|
expected map[string][]string) {
|
|
t.Helper()
|
|
t.Logf("testing ListHosts (zone=%s, labels=%v)", zone, labels)
|
|
require := require.New(t)
|
|
catalogAttrs, err := structpb.NewStruct(map[string]any{
|
|
credential.ConstProjectId: projectId,
|
|
credential.ConstClientEmail: clientEmail,
|
|
credential.ConstZone: zone,
|
|
credential.ConstDisableCredentialRotation: true, // Note that this does nothing in sets, but just noting for tests
|
|
})
|
|
require.NoError(err)
|
|
sets := make([]*hostsets.HostSet, len(labels))
|
|
for i, label := range labels {
|
|
setAttrs, err := structpb.NewStruct(map[string]any{
|
|
host.ConstListInstancesFilter: []any{label},
|
|
})
|
|
require.NoError(err)
|
|
sets[i] = &hostsets.HostSet{
|
|
Id: fmt.Sprintf("hostset-%d", i),
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
Attributes: setAttrs,
|
|
},
|
|
}
|
|
}
|
|
reqPersistedSecrets, err := structpb.NewStruct(map[string]any{
|
|
credential.ConstPrivateKeyId: privateKeyId,
|
|
credential.ConstPrivateKey: privateKey,
|
|
credential.ConstCredsLastRotatedTime: (time.Time{}).Format(time.RFC3339Nano),
|
|
})
|
|
require.NoError(err)
|
|
request := &pb.ListHostsRequest{
|
|
Catalog: &hostcatalogs.HostCatalog{
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
Attributes: catalogAttrs,
|
|
},
|
|
},
|
|
Sets: sets,
|
|
Persisted: &pb.HostCatalogPersisted{
|
|
Secrets: reqPersistedSecrets,
|
|
},
|
|
}
|
|
response, err := p.ListHosts(ctx, request)
|
|
require.NoError(err)
|
|
require.NotNil(response)
|
|
|
|
// Validate the returned instances by ID. Assemble the instance
|
|
// details from the expected set.
|
|
expectedInstances := make(map[string][]string)
|
|
for i, label := range labels {
|
|
for _, instanceId := range expected[label] {
|
|
expectedInstances[instanceId] = append(expectedInstances[instanceId], fmt.Sprintf("hostset-%d", i))
|
|
}
|
|
}
|
|
|
|
// Take the returned hosts by ID and create the same kind of map.
|
|
actualInstances := make(map[string][]string)
|
|
for _, host := range response.GetHosts() {
|
|
actualInstances[host.ExternalId] = host.SetIds
|
|
}
|
|
|
|
// Compare
|
|
require.Equal(expectedInstances, actualInstances)
|
|
// Success
|
|
t.Logf("testing ListHosts: success (region=%s, tags=%v, expected/actual=(len=%d, ids=%s))", zone, labels, len(actualInstances), actualInstances)
|
|
}
|