Files
Elim Tsiagbey 779d383a24 feat: OnUpdateSet (#13) & ListHosts (#14)
* feat: OnUpdateSet

Add support for OnUpdateSet with validation. OnUpdateSet will validate he attributes passed to it and run a dryRunValidation with the new set

* feat: ListHosts (#14)

- Update list hosts to only support listing instances
- Update host client to support Instances API
- Add reading IPV6 Address by `Ipv6AccessConfigs`
- Add internal DNS which is the hostname
2024-10-28 13:10:44 -04:00

238 lines
5.9 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package host
import (
"context"
"errors"
"strconv"
"testing"
"cloud.google.com/go/compute/apiv1/computepb"
"github.com/hashicorp/boundary-plugin-gcp/internal/credential"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/stretchr/testify/require"
)
func TestInstanceToHost(t *testing.T) {
exampleId := uint64(123456789)
examplePrivateIp := "10.0.0.1"
examplePrivateIp2 := "10.0.0.2"
examplePublicIp := "1.1.1.1"
examplePublicIp2 := "1.1.1.2"
exampleIPv6 := "some::fake::address"
exampleName := "test-instance"
cases := []struct {
name string
instance *computepb.Instance
expected *pb.ListHostsResponseHost
expectedErr string
}{
{
name: "missing instance id",
instance: &computepb.Instance{
Name: &exampleName,
},
expectedErr: "response integrity error: missing instance id",
},
{
name: "missing instance name",
instance: &computepb.Instance{
Id: &exampleId,
},
expectedErr: "response integrity error: missing instance name",
},
{
name: "good, single private IP with public IP address",
instance: &computepb.Instance{
Id: &exampleId,
Name: &exampleName,
NetworkInterfaces: []*computepb.NetworkInterface{
{
NetworkIP: &examplePrivateIp,
AccessConfigs: []*computepb.AccessConfig{
{
NatIP: &examplePublicIp,
},
},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: strconv.FormatUint(exampleId, 10),
ExternalName: exampleName,
IpAddresses: []string{examplePrivateIp, examplePublicIp},
},
},
{
name: "good, single private IP address",
instance: &computepb.Instance{
Id: &exampleId,
Name: &exampleName,
NetworkInterfaces: []*computepb.NetworkInterface{
{
NetworkIP: &examplePrivateIp,
AccessConfigs: []*computepb.AccessConfig{},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: strconv.FormatUint(exampleId, 10),
ExternalName: exampleName,
IpAddresses: []string{examplePrivateIp},
},
},
{
name: "good, multiple interfaces",
instance: &computepb.Instance{
Id: &exampleId,
Name: &exampleName,
NetworkInterfaces: []*computepb.NetworkInterface{
{
NetworkIP: &examplePrivateIp,
AccessConfigs: []*computepb.AccessConfig{
{
NatIP: &examplePublicIp,
},
},
},
{
NetworkIP: &examplePrivateIp2,
AccessConfigs: []*computepb.AccessConfig{
{
NatIP: &examplePublicIp2,
},
},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: strconv.FormatUint(exampleId, 10),
ExternalName: exampleName,
IpAddresses: []string{examplePrivateIp, examplePublicIp, examplePrivateIp2, examplePublicIp2},
},
},
{
name: "good, single private IP address with IPv6",
instance: &computepb.Instance{
Id: &exampleId,
Name: &exampleName,
NetworkInterfaces: []*computepb.NetworkInterface{
{
NetworkIP: &examplePrivateIp,
Ipv6Address: &exampleIPv6,
AccessConfigs: []*computepb.AccessConfig{},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: strconv.FormatUint(exampleId, 10),
ExternalName: exampleName,
IpAddresses: []string{examplePrivateIp, exampleIPv6},
},
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
actual, err := instanceToHost(tc.instance)
if tc.expectedErr != "" {
require.EqualError(err, tc.expectedErr)
return
}
require.NoError(err)
require.Equal(tc.expected, actual)
})
}
}
func TestGetInstances(t *testing.T) {
ctx := context.Background()
exampleId := uint64(123456789)
exampleName := "test-instance"
exampleInstance := &computepb.Instance{
Id: &exampleId,
Name: &exampleName,
}
cases := []struct {
name string
instancesClient InstancesAPI
request *computepb.ListInstancesRequest
expected []*computepb.Instance
expectedErr string
}{
{
name: "successful retrieval",
instancesClient: func() InstancesAPI {
fn := newTestMockInstances(ctx,
nil,
testMockInstancesWithListInstancesOutput(&computepb.InstanceList{
Items: []*computepb.Instance{exampleInstance},
}),
testMockInstancesWithListInstancesError(nil),
)
client, err := fn(&credential.Config{})
require.NoError(t, err)
return client
}(),
request: &computepb.ListInstancesRequest{},
expected: []*computepb.Instance{exampleInstance},
},
{
name: "error during listing",
instancesClient: func() InstancesAPI {
fn := newTestMockInstances(ctx,
nil,
testMockInstancesWithListInstancesOutput(&computepb.InstanceList{}),
testMockInstancesWithListInstancesError(errors.New("internal error")),
)
client, err := fn(&credential.Config{})
require.NoError(t, err)
return client
}(),
request: &computepb.ListInstancesRequest{},
expectedErr: "error listing instances",
},
{
name: "no instances",
instancesClient: func() InstancesAPI {
fn := newTestMockInstances(ctx,
nil,
testMockInstancesWithListInstancesOutput(&computepb.InstanceList{}),
testMockInstancesWithListInstancesError(nil),
)
client, err := fn(&credential.Config{})
require.NoError(t, err)
return client
}(),
request: &computepb.ListInstancesRequest{},
expected: []*computepb.Instance{},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
actual, err := getInstances(ctx, tc.instancesClient, tc.request)
if tc.expectedErr != "" {
require.ErrorContains(err, tc.expectedErr)
return
}
require.NoError(err)
require.Len(actual, len(tc.expected))
if len(actual) > 0 {
require.Equal(tc.expected[0].Name, actual[0].Name)
require.Equal(tc.expected[0].Id, actual[0].Id)
}
})
}
}