chore: host plugin import aws-sdk-go-v2 (#27)

This commit is contained in:
Damian Debkowski authored and GitHub committed 2023-07-04 11:03:24 -07:00
1 parent 7bf9fe2ecf
commit 334732967d
8 files changed
+283 -211

No files matched your search

+6 -5
View File
@@ -4,10 +4,12 @@ go 1.20
require (
github.com/aws/aws-sdk-go v1.44.80
github.com/aws/aws-sdk-go-v2 v1.17.7
github.com/aws/aws-sdk-go-v2 v1.18.0
github.com/aws/aws-sdk-go-v2/config v1.18.19
github.com/aws/aws-sdk-go-v2/credentials v1.13.18
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.31.1
github.com/aws/smithy-go v1.13.5
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.3.0
github.com/hashicorp/boundary/sdk v0.0.33
@@ -22,18 +24,17 @@ require (
require (
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.23 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.26 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.6 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.14.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
+10 -4
View File
@@ -7,8 +7,9 @@ github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.44.80 h1:jEXGecSgPdvM5KnyDsSgFhZSm7WwaTp4h544Im4SfhI=
github.com/aws/aws-sdk-go v1.44.80/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go-v2 v1.17.7 h1:CLSjnhJSTSogvqUGhIC6LqFKATMRexcxLZ0i/Nzk9Eg=
github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2 v1.18.0 h1:882kkTpSFhdgYRKVZ/VCgf7sd0ru57p2JCxz4/oN5RY=
github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno=
github.com/aws/aws-sdk-go-v2/config v1.18.19 h1:AqFK6zFNtq4i1EYu+eC7lcKHYnZagMn6SW171la0bGw=
@@ -17,20 +18,25 @@ github.com/aws/aws-sdk-go-v2/credentials v1.13.18 h1:EQMdtHwz0ILTW1hoP+EwuWhwCG1
github.com/aws/aws-sdk-go-v2/credentials v1.13.18/go.mod h1:vnwlwjIe+3XJPBYKu1et30ZPABG3VaXJYr8ryohpIyM=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 h1:gt57MN3liKiyGopcqgNzJb2+d9MJaKT/q1OksHNXVE4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1/go.mod h1:lfUx8puBRdM5lVVMQlwt2v+ofiG/X6Ms+dy0UkG/kXw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 h1:sJLYcS+eZn5EeNINGHSCRAwUJMFVqklwkH36Vbyai7M=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31/go.mod h1:QT0BqUvX1Bh2ABdTGnjqEjvjzrCfIniM9Sc8zn9Yndo=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 h1:1mnRASEKnkqsntcxHaysxwgVoUUp5dkiB+l3llKnqyg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 h1:kG5eQilShqmJbv11XL1VpyDbaEJzWxd4zRiCG30GSn4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25/go.mod h1:zBHOPwhBc3FlQjQJE/D3IfPWiWaQmT06Vq9aNukDo0k=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 h1:vFQlirhuM8lLlpI7imKOMsjdQLuN9CPi+k44F/OFVsk=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 h1:p5luUImdIqywn6JpQsW3tq5GNOxKmOnEpybzPx+d1lk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32/go.mod h1:XGhIBZDEgfqmFIugclZ6FU7v75nHhBDtzuB4xB/tEi4=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.23 h1:DWYZIsyqagnWL00f8M/SOr9fN063OEQWn9LLTbdYXsk=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.23/go.mod h1:uIiFgURZbACBEQJfqTZPb/jxO7R+9LeoHUFudtIdeQI=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0 h1:NXi4pNJWjAaiI56P1Rl8DC9A4jMNRE00WNBsDua5WRg=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0/go.mod h1:L3ZT0N/vBsw77mOAawXmRnREpEjcHd2v5Hzf7AkIH8M=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.26 h1:CeuSeq/8FnYpPtnuIeLQEEvDv9zUjneuYi8EghMBdwQ=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.26/go.mod h1:2UqAAwMUXKeRkAHIlDJqvMVgOWkUi/AUXPk/YIe+Dg4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 h1:5LHn8JQ0qvjD9L9JhMtylnkcw7j05GDZqM9Oin6hpr0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25/go.mod h1:/95IA+0lMnzW6XzqYJRpjjsAbKEORVeO0anQqjd2CNU=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 h1:0iKliEXAcCa2qVtRs7Ot5hItA2MsufrphbRFlz1Owxo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.0 h1:e2ooMhpYGhDnBfSvIyusvAwX7KexuZaHbQY2Dyei7VU=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.0/go.mod h1:bh2E0CXKZsQN+faiKVqC40vfNMAWheoULBCnEgO9K+8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.31.1 h1:PJH4I+qYjPXclKRbVCW47iYUvtXEh1u6YmDhn5J8VQE=
+59 -86
View File
@@ -9,9 +9,10 @@ import (
"fmt"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
awserr "github.com/aws/smithy-go"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -324,7 +325,11 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest
return nil, err
}
ec2Client, err := catalogState.EC2Client(catalogAttributes.Region)
opts := []ec2Option{}
if catalogAttributes.Region != "" {
opts = append(opts, WithRegion(catalogAttributes.Region))
}
ec2Client, err := catalogState.EC2Client(ctx, opts...)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error getting EC2 client: %s", err)
}
@@ -334,12 +339,12 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest
return nil, status.Errorf(codes.InvalidArgument, "error building DescribeInstances parameters: %s", err)
}
_, err = ec2Client.DescribeInstances(input)
_, err = ec2Client.DescribeInstances(ctx, input)
if err == nil {
return nil, status.Error(codes.FailedPrecondition, "query error: DescribeInstances DryRun should have returned error, but none was found")
}
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "DryRunOperation" {
if awsErr, ok := err.(awserr.APIError); ok && awsErr.ErrorCode() == "DryRunOperation" {
// Success
return &pb.OnCreateSetResponse{}, nil
}
@@ -395,7 +400,11 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest
return nil, err
}
ec2Client, err := catalogState.EC2Client(catalogAttributes.Region)
opts := []ec2Option{}
if catalogAttributes.Region != "" {
opts = append(opts, WithRegion(catalogAttributes.Region))
}
ec2Client, err := catalogState.EC2Client(ctx, opts...)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error getting EC2 client: %s", err)
}
@@ -405,12 +414,12 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest
return nil, status.Errorf(codes.InvalidArgument, "error building DescribeInstances parameters: %s", err)
}
_, err = ec2Client.DescribeInstances(input)
_, err = ec2Client.DescribeInstances(ctx, input)
if err == nil {
return nil, status.Error(codes.FailedPrecondition, "query error: DescribeInstances DryRun should have returned error, but none was found")
}
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "DryRunOperation" {
if awsErr, ok := err.(awserr.APIError); ok && awsErr.ErrorCode() == "DryRunOperation" {
// Success
return &pb.OnUpdateSetResponse{}, nil
}
@@ -494,7 +503,11 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
}
}
ec2Client, err := catalogState.EC2Client(catalogAttributes.Region)
opts := []ec2Option{}
if catalogAttributes.Region != "" {
opts = append(opts, WithRegion(catalogAttributes.Region))
}
ec2Client, err := catalogState.EC2Client(ctx, opts...)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error getting EC2 client: %s", err)
}
@@ -502,7 +515,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
// Run all queries now and assemble output.
var maxLen int
for i, query := range queries {
output, err := ec2Client.DescribeInstances(query.Input)
output, err := ec2Client.DescribeInstances(ctx, query.Input)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error running DescribeInstances for host set id %q: %s", query.Id, err)
}
@@ -556,8 +569,8 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
}, nil
}
func buildFilters(attrs *SetAttributes) ([]*ec2.Filter, error) {
var filters []*ec2.Filter
func buildFilters(attrs *SetAttributes) ([]types.Filter, error) {
var filters []types.Filter
var foundStateFilter bool
for _, filterAttr := range attrs.Filters {
// Each filter arrives in "k=v1,v2" format. First, split on equal sign.
@@ -576,14 +589,9 @@ func buildFilters(attrs *SetAttributes) ([]*ec2.Filter, error) {
foundStateFilter = true
}
var filterValues []*string
for _, val := range strings.Split(filterValue, ",") {
filterValues = append(filterValues, aws.String(val))
}
filters = append(filters, &ec2.Filter{
filters = append(filters, types.Filter{
Name: aws.String(filterKey),
Values: filterValues,
Values: strings.Split(filterValue, ","),
})
}
@@ -592,9 +600,9 @@ func buildFilters(attrs *SetAttributes) ([]*ec2.Filter, error) {
// instance-state-name = ["running"] to the filter set. This
// ensures that we filter on running instances only at the API
// side, saving time when processing results.
filters = append(filters, &ec2.Filter{
filters = append(filters, types.Filter{
Name: aws.String("instance-state-name"),
Values: aws.StringSlice([]string{ec2.InstanceStateNameRunning}),
Values: []string{string(types.InstanceStateNameRunning)},
})
}
@@ -616,97 +624,49 @@ func buildDescribeInstancesInput(attrs *SetAttributes, dryRun bool) (*ec2.Descri
// awsInstanceToHost processes data from an ec2.Instance and returns
// a ListHostsResponseHost with the ID and network addresses
// populated.
func awsInstanceToHost(instance *ec2.Instance) (*pb.ListHostsResponseHost, error) {
// Integrity check: some fields should always be non-nil. Check
// those.
if instance == nil {
return nil, errors.New("response integrity error: missing instance entry")
}
if aws.StringValue(instance.InstanceId) == "" {
func awsInstanceToHost(instance types.Instance) (*pb.ListHostsResponseHost, error) {
if aws.ToString(instance.InstanceId) == "" {
return nil, errors.New("response integrity error: missing instance id")
}
result := new(pb.ListHostsResponseHost)
// External ID is the instance ID.
result.ExternalId = aws.StringValue(instance.InstanceId)
result.ExternalId = aws.ToString(instance.InstanceId)
// External Name is the AWS "Name" tag, if it exists.
for _, t := range instance.Tags {
if aws.StringValue(t.Key) == ConstInstanceNameTagKey {
result.ExternalName = aws.StringValue(t.Value)
if aws.ToString(t.Key) == ConstInstanceNameTagKey {
result.ExternalName = aws.ToString(t.Value)
break
}
}
// First IP address/dns name are always the private fields if they
// are populated
if aws.StringValue(instance.PrivateIpAddress) != "" {
result.IpAddresses = append(result.IpAddresses, aws.StringValue(instance.PrivateIpAddress))
}
if aws.StringValue(instance.PrivateDnsName) != "" {
result.DnsNames = append(result.DnsNames, aws.StringValue(instance.PrivateDnsName))
}
// Public IP address/dns names are next
if aws.StringValue(instance.PublicIpAddress) != "" {
result.IpAddresses = append(result.IpAddresses, aws.StringValue(instance.PublicIpAddress))
}
if aws.StringValue(instance.PublicDnsName) != "" {
result.DnsNames = append(result.DnsNames, aws.StringValue(instance.PublicDnsName))
}
result.IpAddresses = appendDistinct(result.IpAddresses, instance.PrivateIpAddress, instance.PublicIpAddress)
result.DnsNames = appendDistinct(result.DnsNames, instance.PrivateDnsName, instance.PublicDnsName)
// Now go through all of the interfaces and log the IP address of
// every interface.
for _, iface := range instance.NetworkInterfaces {
if iface == nil {
// Probably will never happen, but just in case
return nil, errors.New("response integrity error: interface entry is nil")
}
// Populate default IP addresses/DNS name similar to how we do
// for the entire instance.
if aws.StringValue(iface.PrivateIpAddress) != "" && !stringInSlice(result.IpAddresses, aws.StringValue(iface.PrivateIpAddress)) {
result.IpAddresses = append(result.IpAddresses, aws.StringValue(iface.PrivateIpAddress))
}
if aws.StringValue(iface.PrivateDnsName) != "" && !stringInSlice(result.DnsNames, aws.StringValue(iface.PrivateDnsName)) {
result.DnsNames = append(result.DnsNames, aws.StringValue(iface.PrivateDnsName))
}
result.IpAddresses = appendDistinct(result.IpAddresses, iface.PrivateIpAddress)
result.DnsNames = appendDistinct(result.DnsNames, iface.PrivateDnsName)
// Iterate through the private IP addresses and log the
// information.
for _, addr := range iface.PrivateIpAddresses {
if addr == nil {
return nil, errors.New("response integrity error: interface address entry is nil")
}
// Add private address/dns name if they have not been added yet
if aws.StringValue(addr.PrivateIpAddress) != "" && !stringInSlice(result.IpAddresses, aws.StringValue(addr.PrivateIpAddress)) {
result.IpAddresses = append(result.IpAddresses, aws.StringValue(addr.PrivateIpAddress))
}
if aws.StringValue(addr.PrivateDnsName) != "" && !stringInSlice(result.DnsNames, aws.StringValue(addr.PrivateDnsName)) {
result.DnsNames = append(result.DnsNames, aws.StringValue(addr.PrivateDnsName))
}
// Add public address/dns name if they have not been added yet
if addr.Association != nil && aws.StringValue(addr.Association.PublicIp) != "" && !stringInSlice(result.IpAddresses, aws.StringValue(addr.Association.PublicIp)) {
result.IpAddresses = append(result.IpAddresses, aws.StringValue(addr.Association.PublicIp))
}
if addr.Association != nil && aws.StringValue(addr.Association.PublicDnsName) != "" && !stringInSlice(result.DnsNames, aws.StringValue(addr.Association.PublicDnsName)) {
result.DnsNames = append(result.DnsNames, aws.StringValue(addr.Association.PublicDnsName))
result.IpAddresses = appendDistinct(result.IpAddresses, addr.PrivateIpAddress)
result.DnsNames = appendDistinct(result.DnsNames, addr.PrivateDnsName)
if addr.Association != nil {
result.IpAddresses = appendDistinct(result.IpAddresses, addr.Association.PublicIp)
result.DnsNames = appendDistinct(result.DnsNames, addr.Association.PublicDnsName)
}
}
// Add the IPv6 addresses.
for _, addr := range iface.Ipv6Addresses {
if addr == nil {
continue
}
if addr.Ipv6Address != nil && aws.StringValue(addr.Ipv6Address) != "" {
result.IpAddresses = append(result.IpAddresses, aws.StringValue(addr.Ipv6Address))
}
result.IpAddresses = appendDistinct(result.IpAddresses, addr.Ipv6Address)
}
}
@@ -714,12 +674,25 @@ func awsInstanceToHost(instance *ec2.Instance) (*pb.ListHostsResponseHost, error
return result, nil
}
// appendDistinct will append the elements to the slice
// if an element is not nil, empty, and does not exist in slice.
func appendDistinct(slice []string, elems ...*string) []string {
for _, e := range elems {
value := aws.ToString(e)
if value == "" || stringInSlice(slice, value) {
continue
}
slice = append(slice, value)
}
return slice
}
// stringInSlice returns true if the slice contains the given element.
func stringInSlice(s []string, x string) bool {
for _, y := range s {
if x == y {
return true
}
}
return false
}
+115 -87
View File
@@ -10,8 +10,9 @@ import (
"sort"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
@@ -1566,9 +1567,9 @@ func TestPluginListHostsErr(t *testing.T) {
nil,
testMockEC2WithDescribeInstancesOutput(
&ec2.DescribeInstancesOutput{
Reservations: []*ec2.Reservation{
Reservations: []types.Reservation{
{
Instances: []*ec2.Instance{
Instances: []types.Instance{
{
// Blank so we error out
},
@@ -1603,7 +1604,7 @@ func TestBuildFilters(t *testing.T) {
cases := []struct {
name string
in map[string]any
expected []*ec2.Filter
expected []types.Filter
expectedErrContains string
}{
{
@@ -1611,14 +1612,14 @@ func TestBuildFilters(t *testing.T) {
in: map[string]any{
ConstDescribeInstancesFilters: []any{"foo=bar"},
},
expected: []*ec2.Filter{
expected: []types.Filter{
{
Name: aws.String("foo"),
Values: aws.StringSlice([]string{"bar"}),
Values: []string{"bar"},
},
{
Name: aws.String("instance-state-name"),
Values: aws.StringSlice([]string{ec2.InstanceStateNameRunning}),
Values: []string{string(types.InstanceStateNameRunning)},
},
},
},
@@ -1630,14 +1631,14 @@ func TestBuildFilters(t *testing.T) {
"instance-state-name=static",
},
},
expected: []*ec2.Filter{
expected: []types.Filter{
{
Name: aws.String("foo"),
Values: aws.StringSlice([]string{"bar"}),
Values: []string{"bar"},
},
{
Name: aws.String("instance-state-name"),
Values: aws.StringSlice([]string{"static"}),
Values: []string{"static"},
},
},
},
@@ -1648,14 +1649,14 @@ func TestBuildFilters(t *testing.T) {
"foo=bar,baz",
},
},
expected: []*ec2.Filter{
expected: []types.Filter{
{
Name: aws.String("foo"),
Values: aws.StringSlice([]string{"bar", "baz"}),
Values: []string{"bar", "baz"},
},
{
Name: aws.String("instance-state-name"),
Values: aws.StringSlice([]string{ec2.InstanceStateNameRunning}),
Values: []string{string(types.InstanceStateNameRunning)},
},
},
},
@@ -1664,10 +1665,10 @@ func TestBuildFilters(t *testing.T) {
in: map[string]any{
ConstDescribeInstancesFilters: []any{},
},
expected: []*ec2.Filter{
expected: []types.Filter{
{
Name: aws.String("instance-state-name"),
Values: aws.StringSlice([]string{ec2.InstanceStateNameRunning}),
Values: []string{string(types.InstanceStateNameRunning)},
},
},
},
@@ -1714,14 +1715,14 @@ func TestBuildDescribeInstancesInput(t *testing.T) {
dryRun: true,
expected: &ec2.DescribeInstancesInput{
DryRun: aws.Bool(true),
Filters: []*ec2.Filter{
Filters: []types.Filter{
{
Name: aws.String("foo"),
Values: aws.StringSlice([]string{"bar"}),
Values: []string{"bar"},
},
{
Name: aws.String("instance-state-name"),
Values: aws.StringSlice([]string{ec2.InstanceStateNameRunning}),
Values: []string{string(types.InstanceStateNameRunning)},
},
},
},
@@ -1736,14 +1737,14 @@ func TestBuildDescribeInstancesInput(t *testing.T) {
dryRun: false,
expected: &ec2.DescribeInstancesInput{
DryRun: aws.Bool(false),
Filters: []*ec2.Filter{
Filters: []types.Filter{
{
Name: aws.String("foo"),
Values: aws.StringSlice([]string{"bar"}),
Values: []string{"bar"},
},
{
Name: aws.String("instance-state-name"),
Values: aws.StringSlice([]string{ec2.InstanceStateNameRunning}),
Values: []string{string(types.InstanceStateNameRunning)},
},
},
},
@@ -1775,55 +1776,30 @@ func TestBuildDescribeInstancesInput(t *testing.T) {
func TestAwsInstanceToHost(t *testing.T) {
cases := []struct {
name string
instance *ec2.Instance
instance types.Instance
expected *pb.ListHostsResponseHost
expectedErr string
}{
{
name: "nil instance",
instance: nil,
expectedErr: "response integrity error: missing instance entry",
},
{
name: "missing instance id",
instance: &ec2.Instance{},
instance: types.Instance{},
expectedErr: "response integrity error: missing instance id",
},
{
name: "nil interface entry",
instance: &ec2.Instance{
InstanceId: aws.String("foobar"),
NetworkInterfaces: []*ec2.InstanceNetworkInterface{nil},
},
expectedErr: "response integrity error: interface entry is nil",
},
{
name: "nil interface address entry",
instance: &ec2.Instance{
InstanceId: aws.String("foobar"),
NetworkInterfaces: []*ec2.InstanceNetworkInterface{
{
PrivateIpAddresses: []*ec2.InstancePrivateIpAddress{nil},
},
},
},
expectedErr: "response integrity error: interface address entry is nil",
},
{
name: "good, single IP w/public addr",
instance: &ec2.Instance{
instance: types.Instance{
InstanceId: aws.String("foobar"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PublicIpAddress: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
NetworkInterfaces: []*ec2.InstanceNetworkInterface{
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PrivateIpAddresses: []*ec2.InstancePrivateIpAddress{
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
Association: &ec2.InstanceNetworkInterfaceAssociation{
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
},
@@ -1842,15 +1818,15 @@ func TestAwsInstanceToHost(t *testing.T) {
},
{
name: "good, private",
instance: &ec2.Instance{
instance: types.Instance{
InstanceId: aws.String("foobar"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
NetworkInterfaces: []*ec2.InstanceNetworkInterface{
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PrivateIpAddresses: []*ec2.InstancePrivateIpAddress{
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
@@ -1867,17 +1843,17 @@ func TestAwsInstanceToHost(t *testing.T) {
},
{
name: "good, multiple interfaces",
instance: &ec2.Instance{
instance: types.Instance{
InstanceId: aws.String("foobar"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PublicIpAddress: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
NetworkInterfaces: []*ec2.InstanceNetworkInterface{
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.2"),
PrivateDnsName: aws.String("test2.example.internal"),
PrivateIpAddresses: []*ec2.InstancePrivateIpAddress{
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
PrivateIpAddress: aws.String("10.0.0.2"),
PrivateDnsName: aws.String("test2.example.internal"),
@@ -1887,9 +1863,9 @@ func TestAwsInstanceToHost(t *testing.T) {
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PrivateIpAddresses: []*ec2.InstancePrivateIpAddress{
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
Association: &ec2.InstanceNetworkInterfaceAssociation{
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
},
@@ -1908,19 +1884,19 @@ func TestAwsInstanceToHost(t *testing.T) {
},
{
name: "good, multiple public interfaces",
instance: &ec2.Instance{
instance: types.Instance{
InstanceId: aws.String("foobar"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PublicIpAddress: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
NetworkInterfaces: []*ec2.InstanceNetworkInterface{
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.2"),
PrivateDnsName: aws.String("test2.example.internal"),
PrivateIpAddresses: []*ec2.InstancePrivateIpAddress{
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
Association: &ec2.InstanceNetworkInterfaceAssociation{
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.1.1.2"),
PublicDnsName: aws.String("test2.example.com"),
},
@@ -1932,9 +1908,9 @@ func TestAwsInstanceToHost(t *testing.T) {
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PrivateIpAddresses: []*ec2.InstancePrivateIpAddress{
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
Association: &ec2.InstanceNetworkInterfaceAssociation{
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
},
@@ -1953,19 +1929,19 @@ func TestAwsInstanceToHost(t *testing.T) {
},
{
name: "good, multiple addresses on single interface",
instance: &ec2.Instance{
instance: types.Instance{
InstanceId: aws.String("foobar"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PublicIpAddress: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
NetworkInterfaces: []*ec2.InstanceNetworkInterface{
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PrivateIpAddresses: []*ec2.InstancePrivateIpAddress{
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
Association: &ec2.InstanceNetworkInterfaceAssociation{
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
},
@@ -1988,25 +1964,25 @@ func TestAwsInstanceToHost(t *testing.T) {
},
{
name: "good, single IP w/public addr and IPv6",
instance: &ec2.Instance{
instance: types.Instance{
InstanceId: aws.String("foobar"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PublicIpAddress: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
NetworkInterfaces: []*ec2.InstanceNetworkInterface{
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateIpAddresses: []*ec2.InstancePrivateIpAddress{
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
Association: &ec2.InstanceNetworkInterfaceAssociation{
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.1.1.1"),
},
PrivateIpAddress: aws.String("10.0.0.1"),
},
},
Ipv6Addresses: []*ec2.InstanceIpv6Address{
nil, // Just coverage for nil assertion which is skipped
Ipv6Addresses: []types.InstanceIpv6Address{
{Ipv6Address: nil}, // Just coverage for nil assertion which is skipped
{Ipv6Address: aws.String("some::fake::address")},
},
},
@@ -2020,24 +1996,24 @@ func TestAwsInstanceToHost(t *testing.T) {
},
{
name: "good, single IP w/public addr and external name",
instance: &ec2.Instance{
instance: types.Instance{
InstanceId: aws.String("foobar"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PublicIpAddress: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
Tags: []*ec2.Tag{
Tags: []types.Tag{
{Key: aws.String("Name"), Value: aws.String("test-instance-actual-name")}, // The tag name is "Name", not "name".
{Key: aws.String("name"), Value: aws.String("test-instance-fake-name")},
{Key: aws.String("contains-Name"), Value: aws.String("test-instance-contains-name")},
},
NetworkInterfaces: []*ec2.InstanceNetworkInterface{
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PrivateIpAddresses: []*ec2.InstancePrivateIpAddress{
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
Association: &ec2.InstanceNetworkInterfaceAssociation{
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
},
@@ -2057,24 +2033,24 @@ func TestAwsInstanceToHost(t *testing.T) {
},
{
name: "good, single IP w/public addr and external name",
instance: &ec2.Instance{
instance: types.Instance{
InstanceId: aws.String("foobar"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PublicIpAddress: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
Tags: []*ec2.Tag{
Tags: []types.Tag{
{Key: aws.String("Name"), Value: aws.String("test-instance-actual-name")}, // The tag name is "Name", not "name".
{Key: aws.String("name"), Value: aws.String("test-instance-fake-name")},
{Key: aws.String("contains-Name"), Value: aws.String("test-instance-contains-name")},
},
NetworkInterfaces: []*ec2.InstanceNetworkInterface{
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PrivateIpAddresses: []*ec2.InstancePrivateIpAddress{
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
Association: &ec2.InstanceNetworkInterfaceAssociation{
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
},
@@ -2109,3 +2085,55 @@ func TestAwsInstanceToHost(t *testing.T) {
})
}
}
func TestAppendDistinct(t *testing.T) {
cases := []struct {
name string
slice []string
elems []*string
expected []string
}{
{
name: "empty slice, empty elems",
slice: []string{},
elems: []*string{},
expected: []string{},
},
{
name: "empty elems",
slice: []string{"bar"},
elems: []*string{},
expected: []string{"bar"},
},
{
name: "skip nil elem",
slice: []string{"bar"},
elems: []*string{nil},
expected: []string{"bar"},
},
{
name: "skip empty elem",
slice: []string{"bar"},
elems: []*string{aws.String("")},
expected: []string{"bar"},
},
{
name: "skip duplicate elem",
slice: []string{"bar"},
elems: []*string{
aws.String("bar"),
aws.String("foo"),
aws.String("foo"),
},
expected: []string{"bar", "foo"},
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
actual := appendDistinct(tc.slice, tc.elems...)
require.ElementsMatch(actual, tc.expected)
})
}
}
+74 -16
View File
@@ -4,21 +4,26 @@
package host
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"google.golang.org/protobuf/types/known/structpb"
)
type EC2API interface {
DescribeInstances(context.Context, *ec2.DescribeInstancesInput, ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error)
}
var customClient = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
@@ -30,10 +35,6 @@ var customClient = &http.Client{
},
}
type ec2APIFunc func(*session.Session, ...*aws.Config) (ec2iface.EC2API, error)
type awsCatalogPersistedStateOption func(s *awsCatalogPersistedState) error
type awsCatalogPersistedState struct {
*credential.AwsCredentialPersistedState
@@ -41,6 +42,10 @@ type awsCatalogPersistedState struct {
testEC2APIFunc ec2APIFunc
}
type ec2APIFunc func(...aws.Config) (EC2API, error)
type awsCatalogPersistedStateOption func(s *awsCatalogPersistedState) error
func withTestEC2APIFunc(f ec2APIFunc) awsCatalogPersistedStateOption {
return func(s *awsCatalogPersistedState) error {
if s.testEC2APIFunc != nil {
@@ -83,22 +88,75 @@ func (s *awsCatalogPersistedState) toProto() (*pb.HostCatalogPersisted, error) {
// EC2Client returns a configured EC2 client based on the session
// information stored in the state.
func (s *awsCatalogPersistedState) EC2Client(region string) (ec2iface.EC2API, error) {
func (s *awsCatalogPersistedState) EC2Client(ctx context.Context, opt ...ec2Option) (EC2API, error) {
opts, err := getOpts(opt...)
if err != nil {
return nil, fmt.Errorf("error parsing options when fetching EC2 client: %w", err)
}
awsConfigOpts := []func(*config.LoadOptions) error{}
sess, err := s.GetSession()
if err != nil {
return nil, fmt.Errorf("error getting AWS session when fetching EC2 client: %w", err)
}
if s.testEC2APIFunc != nil {
return s.testEC2APIFunc(sess, aws.NewConfig().WithRegion(region))
creds, err := sess.Config.Credentials.Get()
if err != nil {
return nil, fmt.Errorf("error getting AWS session when fetching EC2 client: %w", err)
}
customCredentials := config.WithCredentialsProvider(
credentials.NewStaticCredentialsProvider(creds.AccessKeyID, creds.SecretAccessKey, creds.SessionToken),
)
awsConfigOpts = append(awsConfigOpts, customCredentials)
if sess.Config.Region != nil {
awsConfigOpts = append(awsConfigOpts, config.WithRegion(*sess.Config.Region))
}
if opts.withRegion != "" {
awsConfigOpts = append(awsConfigOpts, config.WithRegion(opts.withRegion))
}
cfg, err := config.LoadDefaultConfig(ctx, awsConfigOpts...)
if err != nil {
return nil, fmt.Errorf("error loading aws configuration when fetching EC2 client: %w", err)
}
cfg := aws.NewConfig()
if region != "" {
cfg = cfg.WithRegion(region)
if s.testEC2APIFunc != nil {
return s.testEC2APIFunc(cfg)
}
cfg.HTTPClient = customClient
return ec2.New(sess, cfg), nil
return ec2.NewFromConfig(cfg), nil
}
// getOpts iterates the inbound ec2Options and returns a struct
func getOpts(opt ...ec2Option) (ec2Options, error) {
opts := getDefaultEC2Options()
for _, o := range opt {
if o == nil {
continue
}
if err := o(&opts); err != nil {
return ec2Options{}, err
}
}
return opts, nil
}
// ec2Option - how ec2Options are passed as arguments
type ec2Option func(*ec2Options) error
// options = how options are represented
type ec2Options struct {
withRegion string
}
func getDefaultEC2Options() ec2Options {
return ec2Options{}
}
// WithRegion contains the region to use
func WithRegion(with string) ec2Option {
return func(o *ec2Options) error {
o.withRegion = with
return nil
}
}
+2 -1
View File
@@ -4,6 +4,7 @@
package host
import (
"context"
"testing"
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
@@ -25,7 +26,7 @@ func TestAwsCatalogPersistedStateEC2Client(t *testing.T) {
)
require.NoError(err)
clientRaw, err := state.EC2Client("us-west-2")
clientRaw, err := state.EC2Client(context.Background(), WithRegion("us-west-2"))
require.NoError(err)
require.NotNil(clientRaw)
client, ok := clientRaw.(*testMockEC2)
+16 -11
View File
@@ -4,10 +4,11 @@
package host
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
)
const (
@@ -29,10 +30,11 @@ func (s *testMockEC2State) Reset() {
}
type testMockEC2 struct {
ec2iface.EC2API
EC2API
State *testMockEC2State
Region string
State *testMockEC2State
Region string
DescribeInstancesOutput *ec2.DescribeInstancesOutput
DescribeInstancesError error
}
@@ -54,7 +56,7 @@ func testMockEC2WithDescribeInstancesError(e error) testMockEC2Option {
}
func newTestMockEC2(state *testMockEC2State, opts ...testMockEC2Option) ec2APIFunc {
return func(sess *session.Session, cfgs ...*aws.Config) (ec2iface.EC2API, error) {
return func(cfgs ...aws.Config) (EC2API, error) {
m := &testMockEC2{
State: state,
}
@@ -67,14 +69,17 @@ func newTestMockEC2(state *testMockEC2State, opts ...testMockEC2Option) ec2APIFu
for _, cfg := range cfgs {
// Last region takes precedence
m.Region = *cfg.Region
if cfg.Region != "" {
m.Region = cfg.Region
}
}
return m, nil
}
}
func (m *testMockEC2) DescribeInstances(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
func (m *testMockEC2) DescribeInstances(ctx context.Context, input *ec2.DescribeInstancesInput, optFns ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) {
if m.State != nil {
m.State.DescribeInstancesCalled = true
m.State.DescribeInstancesInputParams = input
@@ -87,7 +92,7 @@ func (m *testMockEC2) DescribeInstances(input *ec2.DescribeInstancesInput) (*ec2
return m.DescribeInstancesOutput, nil
}
type ec2FilterSorter []*ec2.Filter
type ec2FilterSorter []types.Filter
func (s ec2FilterSorter) Len() int { return len(s) }
func (s ec2FilterSorter) Less(i, j int) bool { return *s[i].Name < *s[j].Name }
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"context"
"testing"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
"github.com/stretchr/testify/require"
)