diff --git a/spinifex/handlers/ec2/natgw/service_impl.go b/spinifex/handlers/ec2/natgw/service_impl.go index b8333bb39..ef106c83b 100644 --- a/spinifex/handlers/ec2/natgw/service_impl.go +++ b/spinifex/handlers/ec2/natgw/service_impl.go @@ -311,8 +311,10 @@ func mustJS(nc *nats.Conn) nats.JetStreamContext { } var describeNatGatewaysValidFilters = map[string]bool{ - "vpc-id": true, - "state": true, + "nat-gateway-id": true, + "subnet-id": true, + "vpc-id": true, + "state": true, } // DescribeNatGateways lists NAT Gateways, optionally filtered @@ -402,6 +404,14 @@ func natgwMatchesFilters(record *NatGatewayRecord, filters map[string][]string) continue } switch name { + case "nat-gateway-id": + if !filterutil.MatchesAny(values, record.NatGatewayId) { + return false + } + case "subnet-id": + if !filterutil.MatchesAny(values, record.SubnetId) { + return false + } case "vpc-id": if !filterutil.MatchesAny(values, record.VpcId) { return false diff --git a/spinifex/handlers/ec2/natgw/service_impl_test.go b/spinifex/handlers/ec2/natgw/service_impl_test.go index 5e47ba3a4..16689b7e0 100644 --- a/spinifex/handlers/ec2/natgw/service_impl_test.go +++ b/spinifex/handlers/ec2/natgw/service_impl_test.go @@ -268,6 +268,61 @@ func TestDescribeNatGateways_DeletedGatewayVisible(t *testing.T) { assert.EqualError(t, err, awserrors.ErrorInvalidNatGatewayIDNotFound) } +func TestDescribeNatGateways_FilterByNatGatewayId(t *testing.T) { + svc := setupTestService(t) + createOut, err := svc.CreateNatGateway(&ec2.CreateNatGatewayInput{ + SubnetId: aws.String("subnet-pub1"), + AllocationId: aws.String("eipalloc-test1"), + }, testAccountID) + require.NoError(t, err) + natgwID := *createOut.NatGateway.NatGatewayId + + // Exact match + out, err := svc.DescribeNatGateways(&ec2.DescribeNatGatewaysInput{ + Filter: []*ec2.Filter{{Name: aws.String("nat-gateway-id"), Values: []*string{aws.String(natgwID)}}}, + }, testAccountID) + require.NoError(t, err) + require.Len(t, out.NatGateways, 1) + assert.Equal(t, natgwID, *out.NatGateways[0].NatGatewayId) + + // Non-match + out, err = svc.DescribeNatGateways(&ec2.DescribeNatGatewaysInput{ + Filter: []*ec2.Filter{{Name: aws.String("nat-gateway-id"), Values: []*string{aws.String("nat-000000")}}}, + }, testAccountID) + require.NoError(t, err) + assert.Empty(t, out.NatGateways) + + // Wildcard + out, err = svc.DescribeNatGateways(&ec2.DescribeNatGatewaysInput{ + Filter: []*ec2.Filter{{Name: aws.String("nat-gateway-id"), Values: []*string{aws.String("nat-*")}}}, + }, testAccountID) + require.NoError(t, err) + assert.Len(t, out.NatGateways, 1) +} + +func TestDescribeNatGateways_FilterBySubnetId(t *testing.T) { + svc := setupTestService(t) + _, err := svc.CreateNatGateway(&ec2.CreateNatGatewayInput{ + SubnetId: aws.String("subnet-pub1"), + AllocationId: aws.String("eipalloc-test1"), + }, testAccountID) + require.NoError(t, err) + + // Exact match + out, err := svc.DescribeNatGateways(&ec2.DescribeNatGatewaysInput{ + Filter: []*ec2.Filter{{Name: aws.String("subnet-id"), Values: []*string{aws.String("subnet-pub1")}}}, + }, testAccountID) + require.NoError(t, err) + assert.Len(t, out.NatGateways, 1) + + // Non-match + out, err = svc.DescribeNatGateways(&ec2.DescribeNatGatewaysInput{ + Filter: []*ec2.Filter{{Name: aws.String("subnet-id"), Values: []*string{aws.String("subnet-000000")}}}, + }, testAccountID) + require.NoError(t, err) + assert.Empty(t, out.NatGateways) +} + func TestDescribeNatGateways_FilterByVpcId(t *testing.T) { svc := setupTestService(t) _, err := svc.CreateNatGateway(&ec2.CreateNatGatewayInput{ diff --git a/spinifex/handlers/ec2/placementgroup/service_impl.go b/spinifex/handlers/ec2/placementgroup/service_impl.go index 29f0edeb6..9f3647486 100644 --- a/spinifex/handlers/ec2/placementgroup/service_impl.go +++ b/spinifex/handlers/ec2/placementgroup/service_impl.go @@ -158,6 +158,7 @@ func (s *PlacementGroupServiceImpl) DeletePlacementGroup(input *ec2.DeletePlacem } var describePlacementGroupsValidFilters = map[string]bool{ + "group-id": true, "strategy": true, "state": true, "spread-level": true, @@ -254,6 +255,10 @@ func (s *PlacementGroupServiceImpl) DescribePlacementGroups(input *ec2.DescribeP func pgMatchesFilters(record *PlacementGroupRecord, filters map[string][]string) bool { for name, values := range filters { switch name { + case "group-id": + if !filterutil.MatchesAny(values, record.GroupId) { + return false + } case "strategy": if !filterutil.MatchesAny(values, record.Strategy) { return false diff --git a/spinifex/handlers/ec2/placementgroup/service_impl_test.go b/spinifex/handlers/ec2/placementgroup/service_impl_test.go index 3ea8d8718..7a5799970 100644 --- a/spinifex/handlers/ec2/placementgroup/service_impl_test.go +++ b/spinifex/handlers/ec2/placementgroup/service_impl_test.go @@ -226,6 +226,40 @@ func TestDescribePlacementGroups_FilterByGroupId(t *testing.T) { assert.Equal(t, *pg.GroupId, *out.PlacementGroups[0].GroupId) } +func TestDescribePlacementGroups_FilterByGroupIdFilter(t *testing.T) { + svc := setupTestService(t) + pg := createTestGroup(t, svc, "gid-filter", "spread") + createTestGroup(t, svc, "other-group", "cluster") + + // Exact match + out, err := svc.DescribePlacementGroups(&ec2.DescribePlacementGroupsInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("group-id"), Values: []*string{pg.GroupId}}, + }, + }, testAccountID) + require.NoError(t, err) + require.Len(t, out.PlacementGroups, 1) + assert.Equal(t, *pg.GroupId, *out.PlacementGroups[0].GroupId) + + // Non-match + out, err = svc.DescribePlacementGroups(&ec2.DescribePlacementGroupsInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("group-id"), Values: []*string{aws.String("pg-000000")}}, + }, + }, testAccountID) + require.NoError(t, err) + assert.Empty(t, out.PlacementGroups) + + // Wildcard + out, err = svc.DescribePlacementGroups(&ec2.DescribePlacementGroupsInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("group-id"), Values: []*string{aws.String("pg-*")}}, + }, + }, testAccountID) + require.NoError(t, err) + assert.Len(t, out.PlacementGroups, 2) +} + func TestDescribePlacementGroups_FilterByStrategy(t *testing.T) { svc := setupTestService(t) createTestGroup(t, svc, "spread1", "spread") diff --git a/spinifex/handlers/ec2/routetable/service_impl.go b/spinifex/handlers/ec2/routetable/service_impl.go index 0e5cbd670..3a054892d 100644 --- a/spinifex/handlers/ec2/routetable/service_impl.go +++ b/spinifex/handlers/ec2/routetable/service_impl.go @@ -265,6 +265,10 @@ var describeRouteTablesValidFilters = map[string]bool{ "association.subnet-id": true, "route.destination-cidr-block": true, "route.gateway-id": true, + "route.nat-gateway-id": true, + "route.state": true, + "route.origin": true, + "owner-id": true, } // DescribeRouteTables lists route tables, optionally filtered @@ -831,6 +835,43 @@ func rtbMatchesFilters(record *RouteTableRecord, filters map[string][]string) bo if !found { return false } + case "route.nat-gateway-id": + found := false + for _, r := range record.Routes { + if filterutil.MatchesAny(values, r.NatGatewayId) { + found = true + break + } + } + if !found { + return false + } + case "route.state": + found := false + for _, r := range record.Routes { + if filterutil.MatchesAny(values, r.State) { + found = true + break + } + } + if !found { + return false + } + case "route.origin": + found := false + for _, r := range record.Routes { + if filterutil.MatchesAny(values, r.Origin) { + found = true + break + } + } + if !found { + return false + } + case "owner-id": + if !filterutil.MatchesAny(values, record.AccountID) { + return false + } default: return false } diff --git a/spinifex/handlers/ec2/routetable/service_impl_test.go b/spinifex/handlers/ec2/routetable/service_impl_test.go index 84981adde..0c1b66170 100644 --- a/spinifex/handlers/ec2/routetable/service_impl_test.go +++ b/spinifex/handlers/ec2/routetable/service_impl_test.go @@ -194,6 +194,87 @@ func TestDescribeRouteTables_FilterByMain(t *testing.T) { assert.True(t, *out.RouteTables[0].Associations[0].Main) } +func TestDescribeRouteTables_FilterByRouteState(t *testing.T) { + svc := setupTestService(t) + createTestRtb(t, svc) // has local route with state=active + + // Exact match + out, err := svc.DescribeRouteTables(&ec2.DescribeRouteTablesInput{ + Filters: []*ec2.Filter{{Name: aws.String("route.state"), Values: []*string{aws.String("active")}}}, + }, testAccountID) + require.NoError(t, err) + assert.Len(t, out.RouteTables, 1) + + // Non-match + out, err = svc.DescribeRouteTables(&ec2.DescribeRouteTablesInput{ + Filters: []*ec2.Filter{{Name: aws.String("route.state"), Values: []*string{aws.String("blackhole")}}}, + }, testAccountID) + require.NoError(t, err) + assert.Empty(t, out.RouteTables) +} + +func TestDescribeRouteTables_FilterByRouteOrigin(t *testing.T) { + svc := setupTestService(t) + createTestRtb(t, svc) // local route has origin=CreateRouteTable + + out, err := svc.DescribeRouteTables(&ec2.DescribeRouteTablesInput{ + Filters: []*ec2.Filter{{Name: aws.String("route.origin"), Values: []*string{aws.String("CreateRouteTable")}}}, + }, testAccountID) + require.NoError(t, err) + assert.Len(t, out.RouteTables, 1) + + out, err = svc.DescribeRouteTables(&ec2.DescribeRouteTablesInput{ + Filters: []*ec2.Filter{{Name: aws.String("route.origin"), Values: []*string{aws.String("CreateRoute")}}}, + }, testAccountID) + require.NoError(t, err) + assert.Empty(t, out.RouteTables) +} + +func TestDescribeRouteTables_FilterByRouteNatGatewayId(t *testing.T) { + svc := setupTestService(t) + createTestRtb(t, svc) // no NAT GW route + + // No match — no routes have a nat-gateway-id + out, err := svc.DescribeRouteTables(&ec2.DescribeRouteTablesInput{ + Filters: []*ec2.Filter{{Name: aws.String("route.nat-gateway-id"), Values: []*string{aws.String("nat-000000")}}}, + }, testAccountID) + require.NoError(t, err) + assert.Empty(t, out.RouteTables) + + // Wildcard on empty field should not match + out, err = svc.DescribeRouteTables(&ec2.DescribeRouteTablesInput{ + Filters: []*ec2.Filter{{Name: aws.String("route.nat-gateway-id"), Values: []*string{aws.String("nat-*")}}}, + }, testAccountID) + require.NoError(t, err) + assert.Empty(t, out.RouteTables) +} + +func TestDescribeRouteTables_FilterByOwnerId(t *testing.T) { + svc := setupTestService(t) + createTestRtb(t, svc) + + // Exact match + out, err := svc.DescribeRouteTables(&ec2.DescribeRouteTablesInput{ + Filters: []*ec2.Filter{{Name: aws.String("owner-id"), Values: []*string{aws.String(testAccountID)}}}, + }, testAccountID) + require.NoError(t, err) + assert.Len(t, out.RouteTables, 1) + + // Non-match + out, err = svc.DescribeRouteTables(&ec2.DescribeRouteTablesInput{ + Filters: []*ec2.Filter{{Name: aws.String("owner-id"), Values: []*string{aws.String("999999999999")}}}, + }, testAccountID) + require.NoError(t, err) + assert.Empty(t, out.RouteTables) + + // Wildcard + out, err = svc.DescribeRouteTables(&ec2.DescribeRouteTablesInput{ + Filters: []*ec2.Filter{{Name: aws.String("owner-id"), Values: []*string{aws.String("1234*")}}}, + }, testAccountID) + require.NoError(t, err) + assert.Len(t, out.RouteTables, 1) +} + func TestCreateRoute(t *testing.T) { svc := setupTestService(t) rtbID := createTestRtb(t, svc) diff --git a/spinifex/handlers/ec2/vpc/eni.go b/spinifex/handlers/ec2/vpc/eni.go index d2f3257dc..fed1cc05c 100644 --- a/spinifex/handlers/ec2/vpc/eni.go +++ b/spinifex/handlers/ec2/vpc/eni.go @@ -227,10 +227,18 @@ func (s *VPCServiceImpl) ModifyNetworkInterfaceAttribute(input *ec2.ModifyNetwor } var describeNetworkInterfacesValidFilters = map[string]bool{ - "subnet-id": true, - "vpc-id": true, - "description": true, - "attachment.instance-id": true, + "network-interface-id": true, + "subnet-id": true, + "vpc-id": true, + "status": true, + "private-ip-address": true, + "availability-zone": true, + "group-id": true, + "mac-address": true, + "description": true, + "attachment.attachment-id": true, + "attachment.instance-id": true, + "attachment.status": true, } // DescribeNetworkInterfaces lists ENIs with optional filters @@ -315,6 +323,10 @@ func eniMatchesFilters(record *ENIRecord, filters map[string][]string) bool { continue } switch name { + case "network-interface-id": + if !filterutil.MatchesAny(values, record.NetworkInterfaceId) { + return false + } case "subnet-id": if !filterutil.MatchesAny(values, record.SubnetId) { return false @@ -323,14 +335,53 @@ func eniMatchesFilters(record *ENIRecord, filters map[string][]string) bool { if !filterutil.MatchesAny(values, record.VpcId) { return false } + case "status": + if !filterutil.MatchesAny(values, record.Status) { + return false + } + case "private-ip-address": + if !filterutil.MatchesAny(values, record.PrivateIpAddress) { + return false + } + case "availability-zone": + if !filterutil.MatchesAny(values, record.AvailabilityZone) { + return false + } + case "group-id": + found := false + for _, sgId := range record.SecurityGroupIds { + if filterutil.MatchesAny(values, sgId) { + found = true + break + } + } + if !found { + return false + } + case "mac-address": + if !filterutil.MatchesAny(values, record.MacAddress) { + return false + } case "description": if !filterutil.MatchesAny(values, record.Description) { return false } + case "attachment.attachment-id": + if !filterutil.MatchesAny(values, record.AttachmentId) { + return false + } case "attachment.instance-id": if !filterutil.MatchesAny(values, record.InstanceId) { return false } + case "attachment.status": + status := "detached" + if record.InstanceId != "" { + status = "attached" + } + if !filterutil.MatchesAny(values, status) { + return false + } default: return false } diff --git a/spinifex/handlers/ec2/vpc/eni_test.go b/spinifex/handlers/ec2/vpc/eni_test.go index 365099dd9..72fdedf4e 100644 --- a/spinifex/handlers/ec2/vpc/eni_test.go +++ b/spinifex/handlers/ec2/vpc/eni_test.go @@ -524,3 +524,242 @@ func TestCreateNetworkInterface_WithSecurityGroups(t *testing.T) { assert.Equal(t, "sg-aaa", *out.NetworkInterface.Groups[0].GroupId) assert.Equal(t, "sg-bbb", *out.NetworkInterface.Groups[1].GroupId) } + +func TestDescribeNetworkInterfaces_FilterByNetworkInterfaceId(t *testing.T) { + svc := setupTestVPCService(t) + vpcId := createTestVPC(t, svc, "10.0.0.0/16") + subnetId := createTestSubnet(t, svc, vpcId, "10.0.1.0/24") + + eniId := createTestENI(t, svc, subnetId) + createTestENI(t, svc, subnetId) + + out, err := svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("network-interface-id"), Values: []*string{aws.String(eniId)}}, + }, + }, testAccountID) + require.NoError(t, err) + require.Len(t, out.NetworkInterfaces, 1) + assert.Equal(t, eniId, *out.NetworkInterfaces[0].NetworkInterfaceId) + + // Wildcard + out, err = svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("network-interface-id"), Values: []*string{aws.String("eni-*")}}, + }, + }, testAccountID) + require.NoError(t, err) + assert.Len(t, out.NetworkInterfaces, 2) +} + +func TestDescribeNetworkInterfaces_FilterByStatus(t *testing.T) { + svc := setupTestVPCService(t) + vpcId := createTestVPC(t, svc, "10.0.0.0/16") + subnetId := createTestSubnet(t, svc, vpcId, "10.0.1.0/24") + + eni1 := createTestENI(t, svc, subnetId) + createTestENI(t, svc, subnetId) // stays available + + _, err := svc.AttachENI(testAccountID, eni1, "i-test", 0) + require.NoError(t, err) + + out, err := svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("status"), Values: []*string{aws.String("in-use")}}, + }, + }, testAccountID) + require.NoError(t, err) + require.Len(t, out.NetworkInterfaces, 1) + assert.Equal(t, eni1, *out.NetworkInterfaces[0].NetworkInterfaceId) + + out, err = svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("status"), Values: []*string{aws.String("available")}}, + }, + }, testAccountID) + require.NoError(t, err) + assert.Len(t, out.NetworkInterfaces, 1) +} + +func TestDescribeNetworkInterfaces_FilterByPrivateIpAddress(t *testing.T) { + svc := setupTestVPCService(t) + vpcId := createTestVPC(t, svc, "10.0.0.0/16") + subnetId := createTestSubnet(t, svc, vpcId, "10.0.1.0/24") + + createTestENI(t, svc, subnetId) // 10.0.1.4 + createTestENI(t, svc, subnetId) // 10.0.1.5 + + out, err := svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("private-ip-address"), Values: []*string{aws.String("10.0.1.4")}}, + }, + }, testAccountID) + require.NoError(t, err) + require.Len(t, out.NetworkInterfaces, 1) + assert.Equal(t, "10.0.1.4", *out.NetworkInterfaces[0].PrivateIpAddress) + + // Wildcard + out, err = svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("private-ip-address"), Values: []*string{aws.String("10.0.1.*")}}, + }, + }, testAccountID) + require.NoError(t, err) + assert.Len(t, out.NetworkInterfaces, 2) + + // Non-match + out, err = svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("private-ip-address"), Values: []*string{aws.String("192.168.0.1")}}, + }, + }, testAccountID) + require.NoError(t, err) + assert.Empty(t, out.NetworkInterfaces) +} + +func TestDescribeNetworkInterfaces_FilterByAvailabilityZone(t *testing.T) { + svc := setupTestVPCService(t) + vpcId := createTestVPC(t, svc, "10.0.0.0/16") + // Create subnet with explicit AZ + subnetOut, err := svc.CreateSubnet(&ec2.CreateSubnetInput{ + VpcId: aws.String(vpcId), + CidrBlock: aws.String("10.0.1.0/24"), + AvailabilityZone: aws.String("ap-southeast-2a"), + }, testAccountID) + require.NoError(t, err) + subnetId := *subnetOut.Subnet.SubnetId + + createTestENI(t, svc, subnetId) + + out, err := svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("availability-zone"), Values: []*string{aws.String("ap-southeast-2a")}}, + }, + }, testAccountID) + require.NoError(t, err) + assert.Len(t, out.NetworkInterfaces, 1) + + out, err = svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("availability-zone"), Values: []*string{aws.String("us-east-1a")}}, + }, + }, testAccountID) + require.NoError(t, err) + assert.Empty(t, out.NetworkInterfaces) +} + +func TestDescribeNetworkInterfaces_FilterByGroupId(t *testing.T) { + svc := setupTestVPCService(t) + vpcId := createTestVPC(t, svc, "10.0.0.0/16") + subnetId := createTestSubnet(t, svc, vpcId, "10.0.1.0/24") + + // Create ENI with security groups + out, err := svc.CreateNetworkInterface(&ec2.CreateNetworkInterfaceInput{ + SubnetId: aws.String(subnetId), + Groups: []*string{aws.String("sg-aaa"), aws.String("sg-bbb")}, + }, testAccountID) + require.NoError(t, err) + eniId := *out.NetworkInterface.NetworkInterfaceId + + createTestENI(t, svc, subnetId) // no SGs + + // Match one of the SGs + desc, err := svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("group-id"), Values: []*string{aws.String("sg-bbb")}}, + }, + }, testAccountID) + require.NoError(t, err) + require.Len(t, desc.NetworkInterfaces, 1) + assert.Equal(t, eniId, *desc.NetworkInterfaces[0].NetworkInterfaceId) + + // Non-match + desc, err = svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("group-id"), Values: []*string{aws.String("sg-zzz")}}, + }, + }, testAccountID) + require.NoError(t, err) + assert.Empty(t, desc.NetworkInterfaces) +} + +func TestDescribeNetworkInterfaces_FilterByMacAddress(t *testing.T) { + svc := setupTestVPCService(t) + vpcId := createTestVPC(t, svc, "10.0.0.0/16") + subnetId := createTestSubnet(t, svc, vpcId, "10.0.1.0/24") + + outCreate, err := svc.CreateNetworkInterface(&ec2.CreateNetworkInterfaceInput{ + SubnetId: aws.String(subnetId), + }, testAccountID) + require.NoError(t, err) + mac := *outCreate.NetworkInterface.MacAddress + + out, err := svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("mac-address"), Values: []*string{aws.String(mac)}}, + }, + }, testAccountID) + require.NoError(t, err) + assert.Len(t, out.NetworkInterfaces, 1) + + out, err = svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("mac-address"), Values: []*string{aws.String("ff:ff:ff:ff:ff:ff")}}, + }, + }, testAccountID) + require.NoError(t, err) + assert.Empty(t, out.NetworkInterfaces) +} + +func TestDescribeNetworkInterfaces_FilterByAttachmentId(t *testing.T) { + svc := setupTestVPCService(t) + vpcId := createTestVPC(t, svc, "10.0.0.0/16") + subnetId := createTestSubnet(t, svc, vpcId, "10.0.1.0/24") + + eniId := createTestENI(t, svc, subnetId) + attachId, err := svc.AttachENI(testAccountID, eniId, "i-test", 0) + require.NoError(t, err) + + createTestENI(t, svc, subnetId) // not attached + + out, err := svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("attachment.attachment-id"), Values: []*string{aws.String(attachId)}}, + }, + }, testAccountID) + require.NoError(t, err) + require.Len(t, out.NetworkInterfaces, 1) + assert.Equal(t, eniId, *out.NetworkInterfaces[0].NetworkInterfaceId) +} + +func TestDescribeNetworkInterfaces_FilterByAttachmentStatus(t *testing.T) { + svc := setupTestVPCService(t) + vpcId := createTestVPC(t, svc, "10.0.0.0/16") + subnetId := createTestSubnet(t, svc, vpcId, "10.0.1.0/24") + + eni1 := createTestENI(t, svc, subnetId) + createTestENI(t, svc, subnetId) // stays detached + + _, err := svc.AttachENI(testAccountID, eni1, "i-test", 0) + require.NoError(t, err) + + // Filter attached + out, err := svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("attachment.status"), Values: []*string{aws.String("attached")}}, + }, + }, testAccountID) + require.NoError(t, err) + require.Len(t, out.NetworkInterfaces, 1) + assert.Equal(t, eni1, *out.NetworkInterfaces[0].NetworkInterfaceId) + + // Filter detached + out, err = svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ + Filters: []*ec2.Filter{ + {Name: aws.String("attachment.status"), Values: []*string{aws.String("detached")}}, + }, + }, testAccountID) + require.NoError(t, err) + assert.Len(t, out.NetworkInterfaces, 1) + assert.NotEqual(t, eni1, *out.NetworkInterfaces[0].NetworkInterfaceId) +}