diff --git a/internal/inventory/awsfetcher/fetcher_ec2_instance.go b/internal/inventory/awsfetcher/fetcher_ec2_instance.go index 6d59401cf9..a8d174478a 100644 --- a/internal/inventory/awsfetcher/fetcher_ec2_instance.go +++ b/internal/inventory/awsfetcher/fetcher_ec2_instance.go @@ -213,6 +213,9 @@ func (e *ec2InstanceFetcher) buildDetails(i *ec2.Ec2Instance, tags map[string]st if v := awslib.LookupTag(tags, "costcenter", "cost-center", "cost_center"); v != "" { details["CostCenter"] = v } + if v := awslib.LookupTag(tags, "role"); v != "" { + details["Role"] = v + } return details } diff --git a/internal/inventory/awsfetcher/fetcher_ec2_instance_test.go b/internal/inventory/awsfetcher/fetcher_ec2_instance_test.go index 32d6d2f779..7842b68776 100644 --- a/internal/inventory/awsfetcher/fetcher_ec2_instance_test.go +++ b/internal/inventory/awsfetcher/fetcher_ec2_instance_test.go @@ -52,6 +52,7 @@ func makeTestInstance(launchTime *time.Time) *ec2beat.Ec2Instance { {Key: pointers.Ref("key"), Value: pointers.Ref("value")}, {Key: pointers.Ref("Owner"), Value: pointers.Ref("team-infra")}, {Key: pointers.Ref("CostCenter"), Value: pointers.Ref("cc-1234")}, + {Key: pointers.Ref("Role"), Value: pointers.Ref("sre")}, }, InstanceId: pointers.Ref("234567890"), Architecture: ec2types.ArchitectureValuesX8664, @@ -104,7 +105,7 @@ func TestEC2InstanceFetcher_Fetch(t *testing.T) { "private-dns", inventory.WithRelatedAssetIds([]string{"234567890"}), inventory.WithRawAsset(instance1), - inventory.WithLabels(map[string]string{"Name": "test-server", "key": "value", "Owner": "team-infra", "CostCenter": "cc-1234"}), + inventory.WithLabels(map[string]string{"Name": "test-server", "key": "value", "Owner": "team-infra", "CostCenter": "cc-1234", "Role": "sre"}), inventory.WithCloud(inventory.Cloud{ Provider: inventory.AwsCloudProvider, Region: "us-east", @@ -134,6 +135,7 @@ func TestEC2InstanceFetcher_Fetch(t *testing.T) { "RoleArn": testRoleArn, "Owner": "team-infra", "CostCenter": "cc-1234", + "Role": "sre", }), inventory.WithCreatedAt(&launchTime), inventory.WithUser(inventory.User{ @@ -193,7 +195,7 @@ func TestEC2InstanceFetcher_Fetch_ResolverError(t *testing.T) { "private-dns", inventory.WithRelatedAssetIds([]string{"234567890"}), inventory.WithRawAsset(instance), - inventory.WithLabels(map[string]string{"Name": "test-server", "key": "value", "Owner": "team-infra", "CostCenter": "cc-1234"}), + inventory.WithLabels(map[string]string{"Name": "test-server", "key": "value", "Owner": "team-infra", "CostCenter": "cc-1234", "Role": "sre"}), inventory.WithCloud(inventory.Cloud{ Provider: inventory.AwsCloudProvider, Region: "us-east", @@ -223,6 +225,7 @@ func TestEC2InstanceFetcher_Fetch_ResolverError(t *testing.T) { // RoleArn is absent because the resolver failed. "Owner": "team-infra", "CostCenter": "cc-1234", + "Role": "sre", }), inventory.WithCreatedAt(&launchTime), // WithUser falls back to the profile ARN when role resolution fails. diff --git a/internal/inventory/awsfetcher/fetcher_elb_test.go b/internal/inventory/awsfetcher/fetcher_elb_test.go index 2f4dbc2be1..24606ae17b 100644 --- a/internal/inventory/awsfetcher/fetcher_elb_test.go +++ b/internal/inventory/awsfetcher/fetcher_elb_test.go @@ -37,8 +37,8 @@ import ( ) func TestELBv1Fetcher_Fetch(t *testing.T) { - asset := elb.ElasticLoadBalancerInfo{ - LoadBalancer: types.LoadBalancerDescription{ + asset := elb.NewElasticLoadBalancerInfo( + types.LoadBalancerDescription{ AvailabilityZones: []string{"us-east-1a"}, CanonicalHostedZoneName: pointers.Ref("HZ-NAME"), CanonicalHostedZoneNameID: pointers.Ref("HZ-ID"), @@ -64,7 +64,11 @@ func TestELBv1Fetcher_Fetch(t *testing.T) { Subnets: []string{"subnet-123"}, VPCId: pointers.Ref("vpc-id"), }, - } + "", // awsAccount + "", // region + nil, + []string{"203.0.113.1", "203.0.113.2"}, // DNS-resolved by the provider in real code + ) in := []awslib.AwsResource{asset} expected := []inventory.AssetEvent{ @@ -84,6 +88,8 @@ func TestELBv1Fetcher_Fetch(t *testing.T) { "PubliclyAccessible": false, // scheme is "internal" "AccountID": "123", "LoadBalancerType": "classic", + "State": "active", + "IPAddresses": []string{"203.0.113.1", "203.0.113.2"}, }), inventory.WithCreatedAt(asset.GetCreatedAt()), ), diff --git a/internal/inventory/awsfetcher/fetcher_rds.go b/internal/inventory/awsfetcher/fetcher_rds.go index ad6a81cc01..4aee6eafd1 100644 --- a/internal/inventory/awsfetcher/fetcher_rds.go +++ b/internal/inventory/awsfetcher/fetcher_rds.go @@ -79,6 +79,9 @@ func (s *rdsFetcher) Fetch(ctx context.Context, assetChannel chan<- inventory.As if item.EngineVersion != "" { details["EngineVersion"] = item.EngineVersion } + if item.Status != "" { + details["DBInstanceStatus"] = item.Status + } assetChannel <- inventory.NewAssetEvent( inventory.AssetClassificationAwsRds, diff --git a/internal/inventory/awsfetcher/fetcher_rds_test.go b/internal/inventory/awsfetcher/fetcher_rds_test.go index 89d2deb49a..7fd8a5d8e8 100644 --- a/internal/inventory/awsfetcher/fetcher_rds_test.go +++ b/internal/inventory/awsfetcher/fetcher_rds_test.go @@ -44,6 +44,7 @@ func TestRDSInstanceFetcher_Fetch(t *testing.T) { PubliclyAccessible: false, Engine: "postgres", EngineVersion: "15.4", + Status: "available", CreatedAt: &createdAt, Subnets: []rds.Subnet{ { @@ -68,6 +69,7 @@ func TestRDSInstanceFetcher_Fetch(t *testing.T) { PubliclyAccessible: true, Engine: "mysql", EngineVersion: "8.0.35", + Status: "available", Subnets: []rds.Subnet{ { ID: "subnet-aabbccdd", @@ -107,6 +109,7 @@ func TestRDSInstanceFetcher_Fetch(t *testing.T) { "PubliclyAccessible": false, "Engine": "postgres", "EngineVersion": "15.4", + "DBInstanceStatus": "available", }), inventory.WithCreatedAt(&createdAt), ), @@ -126,6 +129,7 @@ func TestRDSInstanceFetcher_Fetch(t *testing.T) { "PubliclyAccessible": true, "Engine": "mysql", "EngineVersion": "8.0.35", + "DBInstanceStatus": "available", }), ), } diff --git a/internal/resources/providers/awslib/elb/elb.go b/internal/resources/providers/awslib/elb/elb.go index 1ab79536e1..ca108996bd 100644 --- a/internal/resources/providers/awslib/elb/elb.go +++ b/internal/resources/providers/awslib/elb/elb.go @@ -19,6 +19,7 @@ package elb import ( "context" + "net" "github.com/aws/aws-sdk-go-v2/aws" elb "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing" @@ -28,6 +29,12 @@ import ( "github.com/elastic/cloudbeat/internal/resources/providers/awslib" ) +// hostResolver abstracts DNS resolution so that tests can inject a fake without hitting the +// real network. *net.Resolver satisfies this interface. +type hostResolver interface { + LookupHost(ctx context.Context, host string) ([]string, error) +} + type Client interface { elb.DescribeLoadBalancersAPIClient DescribeTags(ctx context.Context, params *elb.DescribeTagsInput, optFns ...func(*elb.Options)) (*elb.DescribeTagsOutput, error) @@ -43,6 +50,7 @@ type Provider struct { client Client clients map[string]Client awsAccountID string + resolver hostResolver } func NewElbProvider(ctx context.Context, log *clog.Logger, awsAccountID string, cfg aws.Config, factory awslib.CrossRegionFactory[Client]) *Provider { @@ -55,5 +63,6 @@ func NewElbProvider(ctx context.Context, log *clog.Logger, awsAccountID string, client: elb.NewFromConfig(cfg), clients: m.GetMultiRegionsClientMap(), awsAccountID: awsAccountID, + resolver: net.DefaultResolver, } } diff --git a/internal/resources/providers/awslib/elb/elb_mock.go b/internal/resources/providers/awslib/elb/elb_mock.go index d2da91ebdf..93ada95477 100644 --- a/internal/resources/providers/awslib/elb/elb_mock.go +++ b/internal/resources/providers/awslib/elb/elb_mock.go @@ -31,6 +31,101 @@ import ( mock "github.com/stretchr/testify/mock" ) +// newMockHostResolver creates a new instance of mockHostResolver. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockHostResolver(t interface { + mock.TestingT + Cleanup(func()) +}) *mockHostResolver { + mock := &mockHostResolver{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// mockHostResolver is an autogenerated mock type for the hostResolver type +type mockHostResolver struct { + mock.Mock +} + +type mockHostResolver_Expecter struct { + mock *mock.Mock +} + +func (_m *mockHostResolver) EXPECT() *mockHostResolver_Expecter { + return &mockHostResolver_Expecter{mock: &_m.Mock} +} + +// LookupHost provides a mock function for the type mockHostResolver +func (_mock *mockHostResolver) LookupHost(ctx context.Context, host string) ([]string, error) { + ret := _mock.Called(ctx, host) + + if len(ret) == 0 { + panic("no return value specified for LookupHost") + } + + var r0 []string + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string) ([]string, error)); ok { + return returnFunc(ctx, host) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string) []string); ok { + r0 = returnFunc(ctx, host) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = returnFunc(ctx, host) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// mockHostResolver_LookupHost_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LookupHost' +type mockHostResolver_LookupHost_Call struct { + *mock.Call +} + +// LookupHost is a helper method to define mock.On call +// - ctx context.Context +// - host string +func (_e *mockHostResolver_Expecter) LookupHost(ctx interface{}, host interface{}) *mockHostResolver_LookupHost_Call { + return &mockHostResolver_LookupHost_Call{Call: _e.mock.On("LookupHost", ctx, host)} +} + +func (_c *mockHostResolver_LookupHost_Call) Run(run func(ctx context.Context, host string)) *mockHostResolver_LookupHost_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *mockHostResolver_LookupHost_Call) Return(strings []string, err error) *mockHostResolver_LookupHost_Call { + _c.Call.Return(strings, err) + return _c +} + +func (_c *mockHostResolver_LookupHost_Call) RunAndReturn(run func(ctx context.Context, host string) ([]string, error)) *mockHostResolver_LookupHost_Call { + _c.Call.Return(run) + return _c +} + // NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockClient(t interface { diff --git a/internal/resources/providers/awslib/elb/load_balancer.go b/internal/resources/providers/awslib/elb/load_balancer.go index 6c43aee676..8875b13215 100644 --- a/internal/resources/providers/awslib/elb/load_balancer.go +++ b/internal/resources/providers/awslib/elb/load_balancer.go @@ -33,6 +33,19 @@ type ElasticLoadBalancerInfo struct { awsAccount string region string tags map[string]string + ipAddresses []string +} + +// NewElasticLoadBalancerInfo constructs a classic load balancer wrapper. ipAddresses are the +// addresses resolved from the load balancer's DNS name (classic ELBs expose no IPs via the API). +func NewElasticLoadBalancerInfo(lb types.LoadBalancerDescription, awsAccount, region string, tags map[string]string, ipAddresses []string) *ElasticLoadBalancerInfo { + return &ElasticLoadBalancerInfo{ + LoadBalancer: lb, + awsAccount: awsAccount, + region: region, + tags: tags, + ipAddresses: ipAddresses, + } } func (v ElasticLoadBalancerInfo) GetResourceArn() string { @@ -73,14 +86,17 @@ func (v ElasticLoadBalancerInfo) GetLoadBalancerType() string { return "classic" } -// GetState is not exposed for classic load balancers by the AWS SDK. +// GetState returns "active" for classic load balancers. The classic ELB API does not expose +// a state field, but any LB returned by DescribeLoadBalancers is live. func (v ElasticLoadBalancerInfo) GetState() string { - return "" + return "active" } -// GetIPAddresses is not exposed for classic load balancers (they are DNS-only). +// GetIPAddresses returns the IP addresses resolved from the load balancer's DNS name. +// Classic ELBs do not expose IPs directly via the AWS API; the addresses are resolved at +// fetch time (see Provider.DescribeAllLoadBalancers) and stored here. func (v ElasticLoadBalancerInfo) GetIPAddresses() []string { - return nil + return v.ipAddresses } // GetOwnerTag returns the value of the "Owner" tag (case-insensitive), if present. diff --git a/internal/resources/providers/awslib/elb/provider.go b/internal/resources/providers/awslib/elb/provider.go index 9ab72a8a36..f1ed2e87fb 100644 --- a/internal/resources/providers/awslib/elb/provider.go +++ b/internal/resources/providers/awslib/elb/provider.go @@ -20,6 +20,7 @@ package elb import ( "context" "fmt" + "sort" elb "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing" "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing/types" @@ -78,12 +79,22 @@ func (p *Provider) DescribeAllLoadBalancers(ctx context.Context) ([]awslib.AwsRe var result []awslib.AwsResource for _, item := range all { - result = append(result, &ElasticLoadBalancerInfo{ - LoadBalancer: item, - awsAccount: p.awsAccountID, - region: region, - tags: tagsByName[pointers.Deref(item.LoadBalancerName)], - }) + var ipAddresses []string + if dnsName := pointers.Deref(item.DNSName); dnsName != "" { + if ips, err := p.resolver.LookupHost(ctx, dnsName); err != nil { + p.log.Debugf("Could not resolve IPs for classic ELB %q: %v", dnsName, err) + } else { + sort.Strings(ips) + ipAddresses = ips + } + } + result = append(result, NewElasticLoadBalancerInfo( + item, + p.awsAccountID, + region, + tagsByName[pointers.Deref(item.LoadBalancerName)], + ipAddresses, + )) } return result, nil }) diff --git a/internal/resources/providers/awslib/elb/provider_test.go b/internal/resources/providers/awslib/elb/provider_test.go index a85bfb71b8..23b7199fc2 100644 --- a/internal/resources/providers/awslib/elb/provider_test.go +++ b/internal/resources/providers/awslib/elb/provider_test.go @@ -119,13 +119,63 @@ func TestProvider_DescribeLoadBalancers(t *testing.T) { } } +// elbV1ClientWithResources builds a mock Client that returns one classic ELB with a DNSName. +func elbV1ClientWithResources() Client { + m := &MockClient{} + m.On("DescribeLoadBalancers", mock.Anything, mock.Anything). + Return(&elasticloadbalancing.DescribeLoadBalancersOutput{ + LoadBalancerDescriptions: []types.LoadBalancerDescription{ + { + AvailabilityZones: []string{"us-east-1a"}, + CanonicalHostedZoneName: pointers.Ref("HZ-NAME"), + CanonicalHostedZoneNameID: pointers.Ref("HZ-ID"), + CreatedTime: pointers.Ref(time.Now()), + DNSName: pointers.Ref("internal-my-elb-v1.us-east-1.elb.amazonaws.com"), + ListenerDescriptions: []types.ListenerDescription{ + { + Listener: &types.Listener{ + Protocol: pointers.Ref("HTTP"), + LoadBalancerPort: 80, + InstanceProtocol: pointers.Ref("HTTP"), + InstancePort: pointers.Ref(int32(80)), + }, + }, + }, + LoadBalancerName: pointers.Ref("my-elb-v1"), + Scheme: pointers.Ref("internal"), + SecurityGroups: []string{"sg-123"}, + SourceSecurityGroup: &types.SourceSecurityGroup{ + OwnerAlias: pointers.Ref("123"), + GroupName: pointers.Ref("default"), + }, + Subnets: []string{"subnet-123"}, + VPCId: pointers.Ref("vpc-id"), + }, + }, + }, nil) + m.On("DescribeTags", mock.Anything, mock.Anything, mock.Anything). + Return(&elasticloadbalancing.DescribeTagsOutput{ + TagDescriptions: []types.TagDescription{ + { + LoadBalancerName: pointers.Ref("my-elb-v1"), + Tags: []types.Tag{ + {Key: pointers.Ref("Owner"), Value: pointers.Ref("team-infra")}, + }, + }, + }, + }, nil) + return m +} + func TestProvider_DescribeAllLoadBalancers(t *testing.T) { tests := []struct { name string client func() Client + resolver func(t *testing.T) hostResolver expectedResults int wantErr bool regions []string + checkResult func(t *testing.T, got []awslib.AwsResource) }{ { name: "with error", @@ -134,59 +184,52 @@ func TestProvider_DescribeAllLoadBalancers(t *testing.T) { m.On("DescribeLoadBalancers", mock.Anything, mock.Anything).Return(nil, errors.New("failed")) return m }, + resolver: func(t *testing.T) hostResolver { + t.Helper() + // LookupHost is never reached: DescribeLoadBalancers fails first. + return newMockHostResolver(t) + }, wantErr: true, regions: onlyDefaultRegion, }, { - name: "with resources", - client: func() Client { - m := &MockClient{} - m.On("DescribeLoadBalancers", mock.Anything, mock.Anything). - Return(&elasticloadbalancing.DescribeLoadBalancersOutput{ - LoadBalancerDescriptions: []types.LoadBalancerDescription{ - { - AvailabilityZones: []string{"us-east-1a"}, - CanonicalHostedZoneName: pointers.Ref("HZ-NAME"), - CanonicalHostedZoneNameID: pointers.Ref("HZ-ID"), - CreatedTime: pointers.Ref(time.Now()), - DNSName: pointers.Ref("internal-my-elb-v1.us-east-1.elb.amazonaws.com"), - ListenerDescriptions: []types.ListenerDescription{ - { - Listener: &types.Listener{ - Protocol: pointers.Ref("HTTP"), - LoadBalancerPort: 80, - InstanceProtocol: pointers.Ref("HTTP"), - InstancePort: pointers.Ref(int32(80)), - }, - }, - }, - LoadBalancerName: pointers.Ref("my-elb-v1"), - Scheme: pointers.Ref("internal"), - SecurityGroups: []string{"sg-123"}, - SourceSecurityGroup: &types.SourceSecurityGroup{ - OwnerAlias: pointers.Ref("123"), - GroupName: pointers.Ref("default"), - }, - Subnets: []string{"subnet-123"}, - VPCId: pointers.Ref("vpc-id"), - }, - }, - }, nil) - m.On("DescribeTags", mock.Anything, mock.Anything, mock.Anything). - Return(&elasticloadbalancing.DescribeTagsOutput{ - TagDescriptions: []types.TagDescription{ - { - LoadBalancerName: pointers.Ref("my-elb-v1"), - Tags: []types.Tag{ - {Key: pointers.Ref("Owner"), Value: pointers.Ref("team-infra")}, - }, - }, - }, - }, nil) + name: "with resources and DNS IPs", + client: elbV1ClientWithResources, + resolver: func(t *testing.T) hostResolver { + t.Helper() + m := newMockHostResolver(t) + // unsorted on purpose: the provider is expected to sort the IPs + m.EXPECT().LookupHost(mock.Anything, mock.Anything).Return([]string{"10.0.0.2", "10.0.0.1"}, nil) return m }, regions: onlyDefaultRegion, expectedResults: 1, + checkResult: func(t *testing.T, got []awslib.AwsResource) { + t.Helper() + lb, ok := got[0].(*ElasticLoadBalancerInfo) + require.True(t, ok) + assert.Equal(t, "team-infra", lb.GetOwnerTag()) + assert.Equal(t, []string{"10.0.0.1", "10.0.0.2"}, lb.GetIPAddresses(), "IPs should be sorted") + }, + }, + { + name: "with resolver error (soft-fail)", + client: elbV1ClientWithResources, + resolver: func(t *testing.T) hostResolver { + t.Helper() + m := newMockHostResolver(t) + m.EXPECT().LookupHost(mock.Anything, mock.Anything).Return(nil, errors.New("dns timeout")) + return m + }, + regions: onlyDefaultRegion, + expectedResults: 1, + checkResult: func(t *testing.T, got []awslib.AwsResource) { + t.Helper() + lb, ok := got[0].(*ElasticLoadBalancerInfo) + require.True(t, ok) + assert.Equal(t, "team-infra", lb.GetOwnerTag()) + assert.Nil(t, lb.GetIPAddresses(), "IPs should be nil on DNS error (soft-fail)") + }, }, } for _, tt := range tests { @@ -198,9 +241,10 @@ func TestProvider_DescribeAllLoadBalancers(t *testing.T) { client = tt.client() } p := &Provider{ - log: testhelper.NewLogger(t), - clients: clients, - client: client, + log: testhelper.NewLogger(t), + clients: clients, + client: client, + resolver: tt.resolver(t), } got, err := p.DescribeAllLoadBalancers(t.Context()) if tt.wantErr { @@ -210,10 +254,8 @@ func TestProvider_DescribeAllLoadBalancers(t *testing.T) { require.NoError(t, err) assert.Len(t, got, tt.expectedResults) - if len(got) > 0 { - lb, ok := got[0].(*ElasticLoadBalancerInfo) - require.True(t, ok) - assert.Equal(t, "team-infra", lb.GetOwnerTag()) + if tt.checkResult != nil && len(got) > 0 { + tt.checkResult(t, got) } }) } diff --git a/internal/resources/providers/awslib/elb_v2/load_balancer_v2.go b/internal/resources/providers/awslib/elb_v2/load_balancer_v2.go index 266f9b375b..e615a64584 100644 --- a/internal/resources/providers/awslib/elb_v2/load_balancer_v2.go +++ b/internal/resources/providers/awslib/elb_v2/load_balancer_v2.go @@ -75,8 +75,9 @@ func (v ElasticLoadBalancerInfo) GetState() string { return string(v.LoadBalancer.State.Code) } -// GetIPAddresses returns the static IP addresses of the load balancer. Only Network Load -// Balancers expose static IPs (via per-AZ addresses); ALB/Gateway return nil. +// GetIPAddresses returns the IP addresses of the load balancer. Network Load Balancers with +// an Elastic IP expose them via IpAddress; internal NLBs use PrivateIPv4Address; IPv6 NLBs +// use IPv6Address. All three are collected so that no NLB address type is missed. func (v ElasticLoadBalancerInfo) GetIPAddresses() []string { var ips []string for _, az := range v.LoadBalancer.AvailabilityZones { @@ -84,6 +85,12 @@ func (v ElasticLoadBalancerInfo) GetIPAddresses() []string { if ip := pointers.Deref(addr.IpAddress); ip != "" { ips = append(ips, ip) } + if ip := pointers.Deref(addr.PrivateIPv4Address); ip != "" { + ips = append(ips, ip) + } + if ip := pointers.Deref(addr.IPv6Address); ip != "" { + ips = append(ips, ip) + } } } return ips diff --git a/internal/resources/providers/awslib/elb_v2/provider_v2_test.go b/internal/resources/providers/awslib/elb_v2/provider_v2_test.go index 84ca305bd5..3c15fcd5a4 100644 --- a/internal/resources/providers/awslib/elb_v2/provider_v2_test.go +++ b/internal/resources/providers/awslib/elb_v2/provider_v2_test.go @@ -90,6 +90,7 @@ func TestProvider_DescribeLoadBalancers(t *testing.T) { { LoadBalancerAddresses: []types.LoadBalancerAddress{ {IpAddress: pointers.Ref("203.0.113.10")}, + {PrivateIPv4Address: pointers.Ref("10.0.1.5")}, }, }, }, @@ -186,7 +187,7 @@ func TestProvider_DescribeLoadBalancers(t *testing.T) { assert.Equal(t, "team-infra", lb.GetOwnerTag()) assert.Equal(t, "network", lb.GetLoadBalancerType()) assert.Equal(t, "active", lb.GetState()) - assert.Equal(t, []string{"203.0.113.10"}, lb.GetIPAddresses()) + assert.Equal(t, []string{"203.0.113.10", "10.0.1.5"}, lb.GetIPAddresses()) } }) } diff --git a/internal/resources/providers/awslib/rds/provider.go b/internal/resources/providers/awslib/rds/provider.go index 5e14b423ed..749f228578 100644 --- a/internal/resources/providers/awslib/rds/provider.go +++ b/internal/resources/providers/awslib/rds/provider.go @@ -74,6 +74,7 @@ func (p Provider) DescribeDBInstances(ctx context.Context) ([]awslib.AwsResource PubliclyAccessible: aws.ToBool(dbInstance.PubliclyAccessible), Engine: aws.ToString(dbInstance.Engine), EngineVersion: aws.ToString(dbInstance.EngineVersion), + Status: aws.ToString(dbInstance.DBInstanceStatus), CreatedAt: dbInstance.InstanceCreateTime, Subnets: subnets, region: region, diff --git a/internal/resources/providers/awslib/rds/provider_test.go b/internal/resources/providers/awslib/rds/provider_test.go index 2e649efdb1..1fc8624390 100644 --- a/internal/resources/providers/awslib/rds/provider_test.go +++ b/internal/resources/providers/awslib/rds/provider_test.go @@ -96,6 +96,7 @@ func (s *ProviderTestSuite) TestProvider_DescribeDBInstances() { StorageEncrypted: aws.Bool(true), AutoMinorVersionUpgrade: aws.Bool(true), PubliclyAccessible: aws.Bool(true), + DBInstanceStatus: aws.String("available"), DBSubnetGroup: &types.DBSubnetGroup{VpcId: &identifier, Subnets: []types.Subnet{ {SubnetIdentifier: &identifier}, {SubnetIdentifier: &identifier2}, @@ -124,7 +125,9 @@ func (s *ProviderTestSuite) TestProvider_DescribeDBInstances() { Arn: arn2, StorageEncrypted: true, AutoMinorVersionUpgrade: true, - PubliclyAccessible: true, Subnets: []Subnet{ + PubliclyAccessible: true, + Status: "available", + Subnets: []Subnet{ {ID: identifier, RouteTable: nil}, {ID: identifier2, RouteTable: &RouteTable{ ID: identifier, diff --git a/internal/resources/providers/awslib/rds/rds.go b/internal/resources/providers/awslib/rds/rds.go index 978e61fdd9..7286e4f5f1 100644 --- a/internal/resources/providers/awslib/rds/rds.go +++ b/internal/resources/providers/awslib/rds/rds.go @@ -36,6 +36,7 @@ type DBInstance struct { PubliclyAccessible bool `json:"publicly_accessible"` Engine string `json:"engine,omitempty"` EngineVersion string `json:"engine_version,omitempty"` + Status string `json:"status,omitempty"` CreatedAt *time.Time `json:"created_at,omitempty"` Subnets []Subnet `json:"subnets"` region string