diff --git a/go.mod b/go.mod index ec65a1931..fcb5721c0 100644 --- a/go.mod +++ b/go.mod @@ -137,10 +137,12 @@ require ( github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14 // indirect + github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.21 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17 // indirect + github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.13.20 github.com/aws/aws-sdk-go-v2/service/sso v1.11.23 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.6 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.16.19 // indirect diff --git a/go.sum b/go.sum index efabb4325..c4b9f1839 100644 --- a/go.sum +++ b/go.sum @@ -215,6 +215,7 @@ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.16.16/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k= +github.com/aws/aws-sdk-go-v2 v1.17.0/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k= github.com/aws/aws-sdk-go-v2 v1.17.1 h1:02c72fDJr87N8RAC2s3Qu0YuvMRZKNZJ9F+lAehCazk= github.com/aws/aws-sdk-go-v2 v1.17.1/go.mod h1:JLnGeGONAyi2lWXI1p0PCIOIy333JMVK1U7Hf0aRFLw= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 h1:tcFliCWne+zOuUfKNRn8JdFBuWPDuISDH08wD2ULkhk= @@ -226,9 +227,11 @@ github.com/aws/aws-sdk-go-v2/credentials v1.12.21/go.mod h1:O+4XyAt4e+oBAoIwNUYk github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17 h1:r08j4sbZu/RVi+BNxkBJwPMUYY3P8mgSDuKkZ/ZN1lE= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17/go.mod h1:yIkQcCDYNsZfXpd5UX2Cy+sWA1jPgIhGTw9cOBzfVnQ= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23/go.mod h1:2DFxAQ9pfIRy0imBCJv+vZ2X6RKxves6fbnEuSry6b4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.24/go.mod h1:ghMzB/j2wRbPx5/4jPYxJdOtCG2ggrtY01j8K7FMBDA= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.25 h1:nBO/RFxeq/IS5G9Of+ZrgucRciie2qpLy++3UGZ+q2E= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.25/go.mod h1:Zb29PYkf42vVYQY6pvSyJCJcFHlPIiY+YKdPtwnvMkY= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17/go.mod h1:pRwaTYCJemADaqCbUAxltMoHKata7hmB5PjEXeu0kfg= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.18/go.mod h1:fkQKYK/jUhCL/wNS1tOPrlYhr9vqutjCz4zZC1wBE1s= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19 h1:oRHDrwCTVT8ZXi4sr9Ld+EXk7N/KGssOr2ygNeojEhw= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19/go.mod h1:6Q0546uHDp421okhmmGfbxzq2hBqbXFNpi4k+Q1JnQA= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24 h1:wj5Rwc05hvUSvKuOF29IYb9QrCLjU+rHAy/x/o0DK2c= @@ -239,6 +242,8 @@ github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.15.20 h1:yPyXdrZaB4SW+pn2 github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.15.20/go.mod h1:p2i2jyYZzFBJeOOQ5ji2k/Yc6IvlQsG/CuHRwEi8whs= github.com/aws/aws-sdk-go-v2/service/ec2 v1.63.3 h1:+UHyeFhdPddRB+EkgeaKMutWiqwWrj3FIQUif3VnalM= github.com/aws/aws-sdk-go-v2/service/ec2 v1.63.3/go.mod h1:zul71QqzR4D1a90/5FloZiAnZ1CtuIjVH7R9MP997+A= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.21 h1:bKnKpB8pRBGczC8GqKdZuXK1+fa+Cz3jZOWfp/R4bGc= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.21/go.mod h1:HU+wcatcklGhNeBDAYmBI3IujHu1d7A9fWX+juhdxxg= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 h1:Lh1AShsuIJTwMkoxVCAYPJgNG5H+eN6SmoUn8nOZ5wE= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9/go.mod h1:a9j48l6yL5XINLHLcOKInjdvknN+vWqPBxqeIDw7ktw= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18 h1:BBYoNQt2kUZUUK4bIPsKrCcjVPUMNsgQpNAwhznK/zo= @@ -250,6 +255,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17 h1:HfVVR1vItaG6l github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17/go.mod h1:YqMdV+gEKCQ59NrB7rzrJdALeBIsYiVi8Inj3+KcqHI= github.com/aws/aws-sdk-go-v2/service/kms v1.18.13 h1:/qZYGhQ18P1DAjXzmDuBN6yxeWaj45RRpiemB7lircc= github.com/aws/aws-sdk-go-v2/service/kms v1.18.13/go.mod h1:DZtboupHLNr0p6qHw9r3kR8MUnN/rc4AAVmNpe2ocuU= +github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.13.20 h1:eOsXUswNWvA1VQ7niGYjEqCcf5uAQBGx+SnZK216P14= +github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.13.20/go.mod h1:+yOwSpDnUvb71cl23SK9+6lrvX/a94K7NDtgxip0UCo= github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11 h1:3/gm/JTX9bX8CpzTgIlrtYpB3EVBDxyg/GY/QdcIEZw= github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11/go.mod h1:fmgDANqTUCxciViKl9hb/zD5LFbvPINFRgWhDbR+vZo= github.com/aws/aws-sdk-go-v2/service/sso v1.11.23 h1:pwvCchFUEnlceKIgPUouBJwK81aCkQ8UDMORfeFtW10= diff --git a/hack/go.mod b/hack/go.mod index ddb71714f..c29d29400 100644 --- a/hack/go.mod +++ b/hack/go.mod @@ -82,6 +82,7 @@ require ( github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14 // indirect + github.com/aws/aws-sdk-go-v2/service/ec2 v1.63.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19 // indirect @@ -153,6 +154,7 @@ require ( github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect diff --git a/hack/go.sum b/hack/go.sum index 58147b1de..d7da68970 100644 --- a/hack/go.sum +++ b/hack/go.sum @@ -220,6 +220,8 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24 h1:wj5Rwc05hvUSvKuOF29IYb9QrCL github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24/go.mod h1:jULHjqqjDlbyTa7pfM7WICATnOv+iOhjletM3N0Xbu8= github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14 h1:ZSIPAkAsCCjYrhqfw2+lNzWDzxzHXEckFkTePL5RSWQ= github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14/go.mod h1:AyGgqiKv9ECM6IZeNQtdT8NnMvUb3/2wokeq2Fgryto= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.63.3 h1:+UHyeFhdPddRB+EkgeaKMutWiqwWrj3FIQUif3VnalM= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.63.3/go.mod h1:zul71QqzR4D1a90/5FloZiAnZ1CtuIjVH7R9MP997+A= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 h1:Lh1AShsuIJTwMkoxVCAYPJgNG5H+eN6SmoUn8nOZ5wE= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9/go.mod h1:a9j48l6yL5XINLHLcOKInjdvknN+vWqPBxqeIDw7ktw= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18 h1:BBYoNQt2kUZUUK4bIPsKrCcjVPUMNsgQpNAwhznK/zo= @@ -761,7 +763,9 @@ github.com/jhump/protoreflect v1.8.2/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8 github.com/jhump/protoreflect v1.9.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548 h1:dYTbLf4m0a5u0KLmPfB6mgxbcV7588bOCx79hxa5Sr4= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= diff --git a/internal/cloud/aws/metadata.go b/internal/cloud/aws/metadata.go index daf539f43..4beaac367 100644 --- a/internal/cloud/aws/metadata.go +++ b/internal/cloud/aws/metadata.go @@ -16,7 +16,10 @@ import ( "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/aws/aws-sdk-go-v2/service/ec2" - "github.com/aws/aws-sdk-go-v2/service/ec2/types" + ec2Types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" + "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi" + tagType "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi/types" "github.com/edgelesssys/constellation/v2/internal/cloud" "github.com/edgelesssys/constellation/v2/internal/cloud/metadata" "github.com/edgelesssys/constellation/v2/internal/role" @@ -26,6 +29,15 @@ const ( tagName = "Name" ) +type resourceAPI interface { + GetResources(context.Context, *resourcegroupstaggingapi.GetResourcesInput, ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error) +} + +type loadbalancerAPI interface { + DescribeLoadBalancers(ctx context.Context, params *elasticloadbalancingv2.DescribeLoadBalancersInput, + optFns ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeLoadBalancersOutput, error) +} + type ec2API interface { DescribeInstances(context.Context, *ec2.DescribeInstancesInput, ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) } @@ -37,8 +49,10 @@ type imdsAPI interface { // Metadata implements core.ProviderMetadata interface for AWS. type Metadata struct { - ec2 ec2API - imds imdsAPI + ec2 ec2API + imds imdsAPI + loadbalancer loadbalancerAPI + resourceapiClient resourceAPI } // New initializes a new AWS Metadata client using instance default credentials. @@ -49,8 +63,10 @@ func New(ctx context.Context) (*Metadata, error) { return nil, err } return &Metadata{ - ec2: ec2.NewFromConfig(cfg), - imds: imds.New(imds.Options{}), + ec2: ec2.NewFromConfig(cfg), + imds: imds.New(imds.Options{}), + loadbalancer: elasticloadbalancingv2.NewFromConfig(cfg), + resourceapiClient: resourcegroupstaggingapi.NewFromConfig(cfg), }, nil } @@ -132,23 +148,81 @@ func (m *Metadata) UID(ctx context.Context) (string, error) { // SupportsLoadBalancer returns true if the cloud provider supports load balancers. func (m *Metadata) SupportsLoadBalancer() bool { - return false + return true } // GetLoadBalancerEndpoint returns the endpoint of the load balancer. func (m *Metadata) GetLoadBalancerEndpoint(ctx context.Context) (string, error) { - panic("function *Metadata.GetLoadBalancerEndpoint not implemented") + uid, err := readInstanceTag(ctx, m.imds, cloud.TagUID) + if err != nil { + return "", fmt.Errorf("retrieving uid tag: %w", err) + } + arns, err := m.getARNsByTag(ctx, uid, "elasticloadbalancing:loadbalancer") + if err != nil { + return "", fmt.Errorf("retrieving load balancer ARNs: %w", err) + } + if len(arns) != 1 { + return "", fmt.Errorf("%d load balancers found", len(arns)) + } + + output, err := m.loadbalancer.DescribeLoadBalancers(ctx, &elasticloadbalancingv2.DescribeLoadBalancersInput{ + LoadBalancerArns: arns, + }) + if err != nil { + return "", fmt.Errorf("retrieving load balancer: %w", err) + } + if len(output.LoadBalancers) != 1 { + return "", fmt.Errorf("%d load balancers found; expected 1", len(output.LoadBalancers)) + } + + if len(output.LoadBalancers[0].AvailabilityZones) != 1 { + return "", fmt.Errorf("%d availability zones found; expected 1", len(output.LoadBalancers[0].AvailabilityZones)) + } + if len(output.LoadBalancers[0].AvailabilityZones[0].LoadBalancerAddresses) != 1 { + return "", fmt.Errorf("%d load balancer addresses found; expected 1", len(output.LoadBalancers[0].AvailabilityZones[0].LoadBalancerAddresses)) + } + if output.LoadBalancers[0].AvailabilityZones[0].LoadBalancerAddresses[0].IpAddress == nil { + return "", errors.New("load balancer address is nil") + } + + return *output.LoadBalancers[0].AvailabilityZones[0].LoadBalancerAddresses[0].IpAddress, nil } -// GetSubnetworkCIDR retrieves the subnetwork CIDR from cloud provider metadata. -func (m *Metadata) GetSubnetworkCIDR(ctx context.Context) (string, error) { - panic("function *Metadata.GetSubnetworkCIDR not implemented") +// getARNsByTag returns a list of ARNs that have the given tag. +func (m *Metadata) getARNsByTag(ctx context.Context, uid, resourceType string) ([]string, error) { + var ARNs []string + resourcesReq := &resourcegroupstaggingapi.GetResourcesInput{ + TagFilters: []tagType.TagFilter{ + { + Key: aws.String(cloud.TagUID), + Values: []string{uid}, + }, + }, + ResourceTypeFilters: []string{resourceType}, + } + + for out, err := m.resourceapiClient.GetResources(ctx, resourcesReq); ; out, err = m.resourceapiClient.GetResources(ctx, resourcesReq) { + if err != nil { + return nil, fmt.Errorf("retrieving resources: %w", err) + } + + for _, resource := range out.ResourceTagMappingList { + if resource.ResourceARN != nil { + ARNs = append(ARNs, *resource.ResourceARN) + } + } + + if out.PaginationToken == nil || *out.PaginationToken == "" { + return ARNs, nil + } + resourcesReq.PaginationToken = out.PaginationToken + } } -func (m *Metadata) getAllInstancesInGroup(ctx context.Context, uid string) ([]types.Instance, error) { - var instances []types.Instance +func (m *Metadata) getAllInstancesInGroup(ctx context.Context, uid string) ([]ec2Types.Instance, error) { + var instances []ec2Types.Instance instanceReq := &ec2.DescribeInstancesInput{ - Filters: []types.Filter{ + Filters: []ec2Types.Filter{ { Name: aws.String("tag:" + cloud.TagUID), Values: []string{uid}, @@ -172,11 +246,11 @@ func (m *Metadata) getAllInstancesInGroup(ctx context.Context, uid string) ([]ty } } -func (m *Metadata) convertToMetadataInstance(ec2Instances []types.Instance) ([]metadata.InstanceMetadata, error) { +func (m *Metadata) convertToMetadataInstance(ec2Instances []ec2Types.Instance) ([]metadata.InstanceMetadata, error) { var instances []metadata.InstanceMetadata for _, ec2Instance := range ec2Instances { // ignore not running instances - if ec2Instance.State == nil || ec2Instance.State.Name != types.InstanceStateNameRunning { + if ec2Instance.State == nil || ec2Instance.State.Name != ec2Types.InstanceStateNameRunning { continue } @@ -231,7 +305,7 @@ func readInstanceTag(ctx context.Context, api imdsAPI, tag string) (string, erro return string(instanceTag), err } -func findTag(tags []types.Tag, wantKey string) (string, error) { +func findTag(tags []ec2Types.Tag, wantKey string) (string, error) { for _, tag := range tags { if tag.Key == nil || tag.Value == nil { continue diff --git a/internal/cloud/aws/metadata_test.go b/internal/cloud/aws/metadata_test.go index 162a769ba..7890fbe18 100644 --- a/internal/cloud/aws/metadata_test.go +++ b/internal/cloud/aws/metadata_test.go @@ -16,7 +16,12 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/aws/aws-sdk-go-v2/service/ec2" - "github.com/aws/aws-sdk-go-v2/service/ec2/types" + ec2Types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" + elbTypes "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + tagTypes "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi/types" + + "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi" "github.com/edgelesssys/constellation/v2/internal/cloud" "github.com/edgelesssys/constellation/v2/internal/cloud/metadata" "github.com/edgelesssys/constellation/v2/internal/role" @@ -150,17 +155,17 @@ func TestList(t *testing.T) { someErr := errors.New("failed") successfulResp := &ec2.DescribeInstancesOutput{ - Reservations: []types.Reservation{ + Reservations: []ec2Types.Reservation{ { - Instances: []types.Instance{ + Instances: []ec2Types.Instance{ { - State: &types.InstanceState{Name: types.InstanceStateNameRunning}, + State: &ec2Types.InstanceState{Name: ec2Types.InstanceStateNameRunning}, InstanceId: aws.String("id-1"), PrivateIpAddress: aws.String("192.0.2.1"), - Placement: &types.Placement{ + Placement: &ec2Types.Placement{ AvailabilityZone: aws.String("test-zone"), }, - Tags: []types.Tag{ + Tags: []ec2Types.Tag{ { Key: aws.String(tagName), Value: aws.String("name-1"), @@ -176,13 +181,13 @@ func TestList(t *testing.T) { }, }, { - State: &types.InstanceState{Name: types.InstanceStateNameRunning}, + State: &ec2Types.InstanceState{Name: ec2Types.InstanceStateNameRunning}, InstanceId: aws.String("id-2"), PrivateIpAddress: aws.String("192.0.2.2"), - Placement: &types.Placement{ + Placement: &ec2Types.Placement{ AvailabilityZone: aws.String("test-zone"), }, - Tags: []types.Tag{ + Tags: []ec2Types.Tag{ { Key: aws.String(tagName), Value: aws.String("name-2"), @@ -240,17 +245,17 @@ func TestList(t *testing.T) { }, ec2: &stubEC2{ describeInstancesResp1: &ec2.DescribeInstancesOutput{ - Reservations: []types.Reservation{ + Reservations: []ec2Types.Reservation{ { - Instances: []types.Instance{ + Instances: []ec2Types.Instance{ { - State: &types.InstanceState{Name: types.InstanceStateNameRunning}, + State: &ec2Types.InstanceState{Name: ec2Types.InstanceStateNameRunning}, InstanceId: aws.String("id-3"), PrivateIpAddress: aws.String("192.0.2.3"), - Placement: &types.Placement{ + Placement: &ec2Types.Placement{ AvailabilityZone: aws.String("test-zone-2"), }, - Tags: []types.Tag{ + Tags: []ec2Types.Tag{ { Key: aws.String(tagName), Value: aws.String("name-3"), @@ -330,22 +335,223 @@ func TestList(t *testing.T) { } } +func TestGetLoadBalancerEndpoint(t *testing.T) { + lbAddr := "192.0.2.1" + someErr := errors.New("some error") + + testCases := map[string]struct { + imds *stubIMDS + loadbalancer *stubLoadbalancer + resourceapi *stubResourceGroupTagging + wantAddr string + wantErr bool + }{ + "success retrieving loadbalancer endpoint": { + imds: &stubIMDS{ + tags: map[string]string{ + cloud.TagUID: "uid", + }, + }, + loadbalancer: &stubLoadbalancer{ + describeLoadBalancersOut: &elasticloadbalancingv2.DescribeLoadBalancersOutput{ + LoadBalancers: []elbTypes.LoadBalancer{ + { + AvailabilityZones: []elbTypes.AvailabilityZone{ + { + LoadBalancerAddresses: []elbTypes.LoadBalancerAddress{ + { + IpAddress: aws.String(lbAddr), + }, + }, + }, + }, + }, + }, + }, + }, + resourceapi: &stubResourceGroupTagging{ + getResourcesOut1: &resourcegroupstaggingapi.GetResourcesOutput{ + ResourceTagMappingList: []tagTypes.ResourceTagMapping{ + { + ResourceARN: aws.String("arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/test-loadbalancer/50dc6c495c0c9188"), + }, + }, + }, + }, + wantAddr: lbAddr, + }, + "too many ARNs": { + imds: &stubIMDS{ + tags: map[string]string{ + cloud.TagUID: "uid", + }, + }, + loadbalancer: &stubLoadbalancer{ + describeLoadBalancersOut: &elasticloadbalancingv2.DescribeLoadBalancersOutput{ + LoadBalancers: []elbTypes.LoadBalancer{ + { + AvailabilityZones: []elbTypes.AvailabilityZone{ + { + LoadBalancerAddresses: []elbTypes.LoadBalancerAddress{ + { + IpAddress: aws.String(lbAddr), + }, + }, + }, + }, + }, + }, + }, + }, + resourceapi: &stubResourceGroupTagging{ + getResourcesOut1: &resourcegroupstaggingapi.GetResourcesOutput{ + ResourceTagMappingList: []tagTypes.ResourceTagMapping{ + { + ResourceARN: aws.String("arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/test-loadbalancer/50dc6c495c0c9188"), + }, + { + ResourceARN: aws.String("arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/test-loadbalancer/50dc6c495c0c9188"), + }, + }, + }, + }, + wantErr: true, + }, + "too many ARNs (paged)": { + imds: &stubIMDS{ + tags: map[string]string{ + cloud.TagUID: "uid", + }, + }, + loadbalancer: &stubLoadbalancer{ + describeLoadBalancersOut: &elasticloadbalancingv2.DescribeLoadBalancersOutput{ + LoadBalancers: []elbTypes.LoadBalancer{ + { + AvailabilityZones: []elbTypes.AvailabilityZone{ + { + LoadBalancerAddresses: []elbTypes.LoadBalancerAddress{ + { + IpAddress: aws.String(lbAddr), + }, + }, + }, + }, + }, + }, + }, + }, + resourceapi: &stubResourceGroupTagging{ + getResourcesOut1: &resourcegroupstaggingapi.GetResourcesOutput{ + ResourceTagMappingList: []tagTypes.ResourceTagMapping{ + { + ResourceARN: aws.String("arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/test-loadbalancer/50dc6c495c0c9188"), + }, + }, + PaginationToken: aws.String("token"), + }, + getResourcesOut2: &resourcegroupstaggingapi.GetResourcesOutput{ + ResourceTagMappingList: []tagTypes.ResourceTagMapping{ + { + ResourceARN: aws.String("arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/test-loadbalancer/50dc6c495c0c9188"), + }, + }, + }, + }, + wantErr: true, + }, + "loadbalancer has no availability zones": { + imds: &stubIMDS{ + tags: map[string]string{ + cloud.TagUID: "uid", + }, + }, + loadbalancer: &stubLoadbalancer{ + describeLoadBalancersOut: &elasticloadbalancingv2.DescribeLoadBalancersOutput{ + LoadBalancers: []elbTypes.LoadBalancer{ + { + AvailabilityZones: []elbTypes.AvailabilityZone{}, + }, + }, + }, + }, + resourceapi: &stubResourceGroupTagging{ + getResourcesOut1: &resourcegroupstaggingapi.GetResourcesOutput{ + ResourceTagMappingList: []tagTypes.ResourceTagMapping{ + { + ResourceARN: aws.String("arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/test-loadbalancer/50dc6c495c0c9188"), + }, + }, + }, + }, + wantErr: true, + }, + "failure to get resources by tag": { + imds: &stubIMDS{ + tags: map[string]string{ + cloud.TagUID: "uid", + }, + }, + loadbalancer: &stubLoadbalancer{ + describeLoadBalancersOut: &elasticloadbalancingv2.DescribeLoadBalancersOutput{ + LoadBalancers: []elbTypes.LoadBalancer{ + { + AvailabilityZones: []elbTypes.AvailabilityZone{ + { + LoadBalancerAddresses: []elbTypes.LoadBalancerAddress{ + { + IpAddress: aws.String(lbAddr), + }, + }, + }, + }, + }, + }, + }, + }, + resourceapi: &stubResourceGroupTagging{ + getResourcesErr: someErr, + }, + wantErr: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + m := &Metadata{ + imds: tc.imds, + loadbalancer: tc.loadbalancer, + resourceapiClient: tc.resourceapi, + } + + endpoint, err := m.GetLoadBalancerEndpoint(context.Background()) + if tc.wantErr { + assert.Error(err) + return + } + + assert.NoError(err) + assert.Equal(tc.wantAddr, endpoint) + }) + } +} + func TestConvertToMetadataInstance(t *testing.T) { testCases := map[string]struct { - in []types.Instance + in []ec2Types.Instance wantInstances []metadata.InstanceMetadata wantErr bool }{ "success": { - in: []types.Instance{ + in: []ec2Types.Instance{ { - State: &types.InstanceState{Name: types.InstanceStateNameRunning}, + State: &ec2Types.InstanceState{Name: ec2Types.InstanceStateNameRunning}, InstanceId: aws.String("id-1"), PrivateIpAddress: aws.String("192.0.2.1"), - Placement: &types.Placement{ + Placement: &ec2Types.Placement{ AvailabilityZone: aws.String("test-zone"), }, - Tags: []types.Tag{ + Tags: []ec2Types.Tag{ { Key: aws.String(tagName), Value: aws.String("name-1"), @@ -367,12 +573,12 @@ func TestConvertToMetadataInstance(t *testing.T) { }, }, "fallback to instance ID": { - in: []types.Instance{ + in: []ec2Types.Instance{ { - State: &types.InstanceState{Name: types.InstanceStateNameRunning}, + State: &ec2Types.InstanceState{Name: ec2Types.InstanceStateNameRunning}, InstanceId: aws.String("id-1"), PrivateIpAddress: aws.String("192.0.2.1"), - Tags: []types.Tag{ + Tags: []ec2Types.Tag{ { Key: aws.String(tagName), Value: aws.String("name-1"), @@ -395,24 +601,24 @@ func TestConvertToMetadataInstance(t *testing.T) { }, }, "non running instances are ignored": { - in: []types.Instance{ + in: []ec2Types.Instance{ { - State: &types.InstanceState{Name: types.InstanceStateNameStopped}, + State: &ec2Types.InstanceState{Name: ec2Types.InstanceStateNameStopped}, }, { - State: &types.InstanceState{Name: types.InstanceStateNameTerminated}, + State: &ec2Types.InstanceState{Name: ec2Types.InstanceStateNameTerminated}, }, }, }, "no instance ID": { - in: []types.Instance{ + in: []ec2Types.Instance{ { - State: &types.InstanceState{Name: types.InstanceStateNameRunning}, + State: &ec2Types.InstanceState{Name: ec2Types.InstanceStateNameRunning}, PrivateIpAddress: aws.String("192.0.2.1"), - Placement: &types.Placement{ + Placement: &ec2Types.Placement{ AvailabilityZone: aws.String("test-zone"), }, - Tags: []types.Tag{ + Tags: []ec2Types.Tag{ { Key: aws.String(tagName), Value: aws.String("name-1"), @@ -427,14 +633,14 @@ func TestConvertToMetadataInstance(t *testing.T) { wantErr: true, }, "no private IP": { - in: []types.Instance{ + in: []ec2Types.Instance{ { - State: &types.InstanceState{Name: types.InstanceStateNameRunning}, + State: &ec2Types.InstanceState{Name: ec2Types.InstanceStateNameRunning}, InstanceId: aws.String("id-1"), - Placement: &types.Placement{ + Placement: &ec2Types.Placement{ AvailabilityZone: aws.String("test-zone"), }, - Tags: []types.Tag{ + Tags: []ec2Types.Tag{ { Key: aws.String(tagName), Value: aws.String("name-1"), @@ -449,15 +655,15 @@ func TestConvertToMetadataInstance(t *testing.T) { wantErr: true, }, "missing name tag": { - in: []types.Instance{ + in: []ec2Types.Instance{ { - State: &types.InstanceState{Name: types.InstanceStateNameRunning}, + State: &ec2Types.InstanceState{Name: ec2Types.InstanceStateNameRunning}, InstanceId: aws.String("id-1"), PrivateIpAddress: aws.String("192.0.2.1"), - Placement: &types.Placement{ + Placement: &ec2Types.Placement{ AvailabilityZone: aws.String("test-zone"), }, - Tags: []types.Tag{ + Tags: []ec2Types.Tag{ { Key: aws.String(cloud.TagRole), Value: aws.String("controlplane"), @@ -468,15 +674,15 @@ func TestConvertToMetadataInstance(t *testing.T) { wantErr: true, }, "missing role tag": { - in: []types.Instance{ + in: []ec2Types.Instance{ { - State: &types.InstanceState{Name: types.InstanceStateNameRunning}, + State: &ec2Types.InstanceState{Name: ec2Types.InstanceStateNameRunning}, InstanceId: aws.String("id-1"), PrivateIpAddress: aws.String("192.0.2.1"), - Placement: &types.Placement{ + Placement: &ec2Types.Placement{ AvailabilityZone: aws.String("test-zone"), }, - Tags: []types.Tag{ + Tags: []ec2Types.Tag{ { Key: aws.String(tagName), Value: aws.String("name-1"), @@ -542,3 +748,33 @@ func (s *stubEC2) DescribeInstances(_ context.Context, in *ec2.DescribeInstances } return s.describeInstancesResp2, s.describeInstancesErr } + +type stubLoadbalancer struct { + describeLoadBalancersErr error + describeLoadBalancersOut *elasticloadbalancingv2.DescribeLoadBalancersOutput +} + +func (s *stubLoadbalancer) DescribeLoadBalancers(_ context.Context, + in *elasticloadbalancingv2.DescribeLoadBalancersInput, + _ ...func(*elasticloadbalancingv2.Options)) ( + *elasticloadbalancingv2.DescribeLoadBalancersOutput, error, +) { + return s.describeLoadBalancersOut, s.describeLoadBalancersErr +} + +type stubResourceGroupTagging struct { + getResourcesErr error + getResourcesOut1 *resourcegroupstaggingapi.GetResourcesOutput + getResourcesOut2 *resourcegroupstaggingapi.GetResourcesOutput +} + +func (s *stubResourceGroupTagging) GetResources(_ context.Context, + in *resourcegroupstaggingapi.GetResourcesInput, + _ ...func(*resourcegroupstaggingapi.Options)) ( + *resourcegroupstaggingapi.GetResourcesOutput, error, +) { + if in.PaginationToken == nil { + return s.getResourcesOut1, s.getResourcesErr + } + return s.getResourcesOut2, s.getResourcesErr +}