Repository: gruntwork-io/cloud-nuke
Pull request #664 (2024-04-02 01:04:29)

implement network interface nuke
<!-- Prepend '[WIP]' to the title if this PR is still a work-in-progress. Remove it when it is ready for review! -->

## Description

Partially Fixing https://github.com/gruntwork-io/cloud-nuke/issues/476

<!-- Description of the changes introduced by this PR. -->

## TODOs

Read the [Gruntwork contribution guidelines](https://gruntwork.notion.site/Gruntwork-Coding-Methodology-02fdcd6e4b004e818553684760bf691e).

- [ ] Update the docs.
- [ ] Run the relevant tests successfully, including pre-commit checks.
- [ ] Ensure any 3rd party code adheres with our [license policy](https://www.notion.so/gruntwork/Gruntwork-licenses-and-open-source-usage-policy-f7dece1f780341c7b69c1763f22b1378) or delete this line if its not applicable.
- [ ] Include release notes. If this PR is backward incompatible, include a migration guide.
- [ ] Attention Grunts - if this PR adds support for a new resource, ensure the `nuke_sandbox` and `nuke_phxdevops` jobs in `.circleci/config.yml` have been updated with appropriate exclusions (either directly in the job or via the `.circleci/nuke_config.yml` file) to prevent nuking IAM roles, groups, resources, etc that are important for the test accounts.


## Release Notes (draft)

<!-- One-line description of the PR that can be included in the final release notes. -->
Added / Removed / Updated [X].

### Migration Guide

<!-- Important: If you made any backward incompatible changes, then you must write a migration guide! -->



denis256: Conflicting files
README.md

Patch:
diff --git a/README.md b/README.md
index f65b9de..7d4b6a0 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,7 @@ Cloud-nuke suppports 🔎 inspecting and 🔥💀 deleting the following AWS res
 | EC2                     | Egress only internet gateway					                   |
 | EC2                     | Endpoint                							                   |
 | EC2                     | Security Group           							                   |
+| EC2                     | Network Interface        							                   |
 | Certificate Manager     | ACM Private CA                                           |
 | Direct Connect          | Transit Gateways                                         |
 | Elasticache             | Clusters                                                 |
@@ -581,6 +582,7 @@ of the file that are supported are listed here.
 | macie-member                | MacieMember                  | ❌                                     | ✅ (Creation Time)                   | ❌    |    ❌   |
 | msk-cluster                 | MSKCluster                   | ✅ (Cluster Name)                      | ✅ (Creation Time)                   | ❌    |    ❌   |
 | nat-gateway                 | NatGateway                   | ✅ (EC2 Name Tag)                      | ✅ (Creation Time)                   | ✅    |    ❌   |
+| network-interface           | NetworkInterface             | ✅ (Interface Name Tag)                | ✅ (Creation Time)                   | ✅    |    ❌   |
 | oidcprovider                | OIDCProvider                 | ✅ (Provider URL)                      | ✅ (Creation Time)                   | ❌    |    ❌   |
 | opensearchdomain            | OpenSearchDomain             | ✅ (Domain Name)                       | ✅ (First Seen Tag Time)             | ❌    |    ❌   |
 | redshift                    | Redshift                     | ✅ (Cluster Identifier)                | ✅ (Creation Time)                   | ❌    |    ❌   |
diff --git a/aws/resource_registry.go b/aws/resource_registry.go
index 45bd4f3..231fd36 100644
--- a/aws/resource_registry.go
+++ b/aws/resource_registry.go
@@ -131,6 +131,7 @@ func getRegisteredRegionalResources() []AwsResource {
 		&resources.Route53CidrCollection{},
 		&resources.Route53TrafficPolicy{},
 		&resources.InternetGateway{},
+		&resources.NetworkInterface{},
 		&resources.SecurityGroup{},
 	}
 }
diff --git a/aws/resources/ec2_network_interface.go b/aws/resources/ec2_network_interface.go
new file mode 100644
index 0000000..230f9c0
--- /dev/null
+++ b/aws/resources/ec2_network_interface.go
@@ -0,0 +1,271 @@
+package resources
+
+import (
+	"context"
+	"time"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/ec2"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/cloud-nuke/logging"
+	"github.com/gruntwork-io/cloud-nuke/report"
+	"github.com/gruntwork-io/cloud-nuke/util"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+func (ni *NetworkInterface) setFirstSeenTag(networkInterface ec2.NetworkInterface, value time.Time) error {
+	_, err := ni.Client.CreateTags(&ec2.CreateTagsInput{
+		Resources: []*string{networkInterface.NetworkInterfaceId},
+		Tags: []*ec2.Tag{
+			{
+				Key:   aws.String(util.FirstSeenTagKey),
+				Value: aws.String(util.FormatTimestamp(value)),
+			},
+		},
+	})
+	if err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
+
+func (igw *NetworkInterface) getFirstSeenTag(networkInterface ec2.NetworkInterface) (*time.Time, error) {
+	tags := networkInterface.TagSet
+	for _, tag := range tags {
+		if util.IsFirstSeenTag(tag.Key) {
+			firstSeenTime, err := util.ParseTimestamp(tag.Value)
+			if err != nil {
+				return nil, errors.WithStackTrace(err)
+			}
+
+			return firstSeenTime, nil
+		}
+	}
+
+	return nil, nil
+}
+
+func shouldIncludeNetworkInterface(networkInterface *ec2.NetworkInterface, firstSeenTime *time.Time, configObj config.Config) bool {
+	var interfaceName string
+	// get the tags as map
+	tagMap := util.ConvertEC2TagsToMap(networkInterface.TagSet)
+	if name, ok := tagMap["Name"]; ok {
+		interfaceName = name
+	}
+	return configObj.NetworkInterface.ShouldInclude(config.ResourceValue{
+		Name: &interfaceName,
+		Tags: tagMap,
+		Time: firstSeenTime,
+	})
+}
+
+func (ni *NetworkInterface) getAll(_ context.Context, configObj config.Config) ([]*string, error) {
+	var identifiers []*string
+	resp, err := ni.Client.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{})
+	if err != nil {
+		logging.Debugf("[Internet Gateway] Failed to list internet gateways: %s", err)
+		return nil, err
+	}
+
+	for _, networkInterface := range resp.NetworkInterfaces {
+		// check first seen tag
+		firstSeenTime, err := ni.getFirstSeenTag(*networkInterface)
+		if err != nil {
+			logging.Errorf(
+				"Unable to retrieve tags for Internet gateway: %s, with error: %s", *networkInterface.NetworkInterfaceId, err)
+			continue
+		}
+
+		// if the first seen tag is not there, then create one
+		if firstSeenTime == nil {
+			now := time.Now().UTC()
+			firstSeenTime = &now
+			if err := ni.setFirstSeenTag(*networkInterface, time.Now().UTC()); err != nil {
+				logging.Errorf(
+					"Unable to apply first seen tag Internet gateway: %s, with error: %s", *networkInterface.NetworkInterfaceId, err)
+				continue
+			}
+		}
+
+		if shouldIncludeNetworkInterface(networkInterface, firstSeenTime, configObj) {
+			identifiers = append(identifiers, networkInterface.NetworkInterfaceId)
+		}
+	}
+
+	// Check and verify the list of allowed nuke actions
+	ni.VerifyNukablePermissions(identifiers, func(id *string) error {
+		_, err := ni.Client.DeleteNetworkInterface(&ec2.DeleteNetworkInterfaceInput{
+			NetworkInterfaceId: id,
+			DryRun:             aws.Bool(true),
+		})
+		return err
+	})
+
+	return identifiers, nil
+}
+
+func (ni *NetworkInterface) nuke(id *string) error {
+	// detach network interfaces
+	if err := ni.detachNetworkInterface(id); err != nil {
+		return errors.WithStackTrace(err)
+	}
+	// nuking the network interface
+	if err := ni.nukeNetworkInterface(id); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
+
+func (ni *NetworkInterface) detachNetworkInterface(id *string) error {
+	logging.Debugf("Detaching network interface %s from instances ", aws.StringValue(id))
+
+	output, err := ni.Client.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{
+		NetworkInterfaceIds: []*string{id},
+	})
+	if err != nil {
+		logging.Debugf("[Failed] Error describing network interface %s: %s", aws.StringValue(id), err)
+		return errors.WithStackTrace(err)
+	}
+
+	for _, networkInterface := range output.NetworkInterfaces {
+
+		// check there has some attachments
+		if networkInterface.Attachment == nil {
+			continue
+		}
+
+		// nuking the attached instance
+		// this will also remove the network interface
+		if err := ni.nukeInstance(networkInterface.Attachment.InstanceId); err != nil {
+			logging.Debugf("[Failed] Error nuking the attached instance %s on network interface %s %s", aws.StringValue(networkInterface.Attachment.InstanceId), aws.StringValue(id), err)
+			return errors.WithStackTrace(err)
+		}
+	}
+
+	logging.Debugf("[OK] successfully detached network interface associated on instances")
+
+	return nil
+}
+
+func (ni *NetworkInterface) releaseEIPs(instance *string) error {
+	logging.Debugf("Releasing Elastic IP address(s) associated on instance %s", aws.StringValue(instance))
+	// get the elastic ip's associated with the EC2's
+	output, err := ni.Client.DescribeAddresses(&ec2.DescribeAddressesInput{
+		Filters: []*ec2.Filter{
+			{
+				Name: aws.String("instance-id"),
+				Values: []*string{
+					instance,
+				},
+			},
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	for _, address := range output.Addresses {
+		if _, err := ni.Client.ReleaseAddress(&ec2.ReleaseAddressInput{
+			AllocationId: address.AllocationId,
+		}); err != nil {
+			logging.Debugf("An error happened while releasing the elastic ip address %s, error %v", aws.StringValue(address.AllocationId), err)
+			continue
+		}
+
+		logging.Debugf("Released Elastic IP address %s from instance %s", aws.StringValue(address.AllocationId), aws.StringValue(instance))
+	}
+
+	logging.Debugf("[OK] successfully released Elastic IP address(s) associated on instances")
+
+	return nil
+}
+
+func (ni *NetworkInterface) nukeInstance(id *string) error {
+
+	// Needs to release the elastic ips attached on the instance before nuking
+	if err := ni.releaseEIPs(id); err != nil {
+		logging.Debugf("[Failed EIP release] %s", err)
+		return errors.WithStackTrace(err)
+	}
+
+	// terminating the instance
+	if _, err := ni.Client.TerminateInstances(&ec2.TerminateInstancesInput{
+		InstanceIds: []*string{id},
+	}); err != nil {
+		logging.Debugf("[Failed] Ec2 termination %s", err)
+		return errors.WithStackTrace(err)
+	}
+
+	logging.Debugf("[Instance Termination] waiting to terminate instance %s", aws.StringValue(id))
+
+	// wait until the instance terminated.
+	if err := ni.Client.WaitUntilInstanceTerminated(&ec2.DescribeInstancesInput{
+		InstanceIds: []*string{id},
+	}); err != nil {
+		logging.Debugf("[Instance Termination Waiting] Failed to terminate instance %s : %s", *id, err)
+		return errors.WithStackTrace(err)
+	}
+	logging.Debugf("[OK] successfully nuked instance %v", aws.StringValue(id))
+
+	return nil
+}
+
+func (ni *NetworkInterface) nukeNetworkInterface(id *string) error {
+	logging.Debugf("Deleting network interface %s", aws.StringValue(id))
+
+	// If the network interface was attached to an instance, then when we remove the instance above, the network interface will also be removed.
+	// However, when we attempt to nuke the interface here, we may encounter an error such as InvalidNetworkInterfaceID.NotFound.
+	// In other situations, such as when the network interface hasn't been attached to any instances, we won't encounter this error.
+	//
+	// Note: We are handling the situation here by checking the error response from AWS.
+
+	_, err := ni.Client.DeleteNetworkInterface(&ec2.DeleteNetworkInterfaceInput{
+		NetworkInterfaceId: id,
+	})
+
+	// check the error exists and it is not the interfaceid not found
+	if err != nil && util.TransformAWSError(err) != util.ErrInterfaceIDNotFound {
+		logging.Debugf("[Failed] Error deleting network interface %s: %s", aws.StringValue(id), err)
+		return errors.WithStackTrace(err)
+	}
+	logging.Debugf("[Ok] network interface deleted successfully %s", aws.StringValue(id))
+
+	return nil
+}
+
+func (ni *NetworkInterface) nukeAll(identifiers []*string) error {
+	if len(identifiers) == 0 {
+		logging.Debugf("No network interface identifiers to nuke in region %s", ni.Region)
+		return nil
+	}
+
+	logging.Debugf("Deleting all network interface in region %s", ni.Region)
+	var deleted []*string
+
+	for _, id := range identifiers {
+		if nukable, err := ni.IsNukable(*id); !nukable {
+			logging.Debugf("[Skipping] %s nuke because %v", *id, err)
+			continue
+		}
+
+		err := ni.nuke(id)
+
+		// Record status of this resource
+		e := report.Entry{ // Use the 'r' alias to refer to the package
+			Identifier:   aws.StringValue(id),
+			ResourceType: "Network Interface",
+			Error:        err,
+		}
+		report.Record(e)
+
+		if err == nil {
+			deleted = append(deleted, id)
+		}
+	}
+
+	logging.Debugf("[OK] %d network interface(s) deleted in %s", len(deleted), ni.Region)
+
+	return nil
+}
diff --git a/aws/resources/ec2_network_interface_types.go b/aws/resources/ec2_network_interface_types.go
new file mode 100644
index 0000000..57add90
--- /dev/null
+++ b/aws/resources/ec2_network_interface_types.go
@@ -0,0 +1,54 @@
+package resources
+
+import (
+	"context"
+
+	"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/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+type NetworkInterface struct {
+	BaseAwsResource
+	Client       ec2iface.EC2API
+	Region       string
+	InterfaceIds []string
+}
+
+func (ni *NetworkInterface) Init(session *session.Session) {
+	ni.BaseAwsResource.Init(session)
+	ni.Client = ec2.New(session)
+}
+
+func (ni *NetworkInterface) ResourceName() string {
+	return "network-interface"
+}
+
+func (ni *NetworkInterface) ResourceIdentifiers() []string {
+	return ni.InterfaceIds
+}
+
+func (ni *NetworkInterface) MaxBatchSize() int {
+	return 50
+}
+
+func (ni *NetworkInterface) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
+	identifiers, err := ni.getAll(c, configObj)
+	if err != nil {
+		return nil, err
+	}
+
+	ni.InterfaceIds = aws.StringValueSlice(identifiers)
+	return ni.InterfaceIds, nil
+}
+
+func (ni *NetworkInterface) Nuke(identifiers []string) error {
+	if err := ni.nukeAll(aws.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
diff --git a/config/config.go b/config/config.go
index 0394ef3..efcfa06 100644
--- a/config/config.go
+++ b/config/config.go
@@ -103,6 +103,7 @@ type Config struct {
 	Route53TrafficPolicy            ResourceType               `yaml:"Route53TrafficPolicy"`
 	InternetGateway                 ResourceType               `yaml:"InternetGateway"`
 	SecurityGroup                   ResourceType               `yaml:"SecurityGroup"`
+	NetworkInterface                ResourceType               `yaml:"NetworkInterface"`
 }
 
 func (c *Config) addTimeAfterFilter(timeFilter *time.Time, fieldName string) {
diff --git a/util/error.go b/util/error.go
index 8a41df9..76e78b0 100644
--- a/util/error.go
+++ b/util/error.go
@@ -11,6 +11,7 @@ import (
 var ErrInSufficientPermission = errors.New("error:INSUFFICIENT_PERMISSION")
 var ErrDifferentOwner = errors.New("error:DIFFERENT_OWNER")
 var ErrContextExecutionTimeout = errors.New("error:EXECUTION_TIMEOUT")
+var ErrInterfaceIDNotFound = errors.New("error:InterfaceIdNotFound")
 
 const AWsUnauthorizedError string = "UnauthorizedOperation"
 const AwsDryRunSuccess string = "Request would have succeeded, but DryRun flag is set."
@@ -28,6 +29,10 @@ func TransformAWSError(err error) error {
 		return ErrContextExecutionTimeout
 	}
 
+	if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "InvalidNetworkInterfaceID.NotFound" {
+		return ErrInterfaceIDNotFound
+	}
+
 	if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "DryRunOperation" && awsErr.Message() == AwsDryRunSuccess {
 		return nil
 	}


Test patch:
diff --git a/aws/resources/ec2_network_interface_test.go b/aws/resources/ec2_network_interface_test.go
new file mode 100644
index 0000000..d85823d
--- /dev/null
+++ b/aws/resources/ec2_network_interface_test.go
@@ -0,0 +1,222 @@
+package resources
+
+import (
+	"context"
+	"regexp"
+	"testing"
+	"time"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/ec2"
+	"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/cloud-nuke/telemetry"
+	"github.com/gruntwork-io/cloud-nuke/util"
+	"github.com/stretchr/testify/require"
+)
+
+type mockedNetworkInterface struct {
+	BaseAwsResource
+	ec2iface.EC2API
+	DescribeNetworkInterfacesOutput ec2.DescribeNetworkInterfacesOutput
+	DeleteNetworkInterfaceOutput    ec2.DeleteNetworkInterfaceOutput
+	DescribeAddressesOutput         ec2.DescribeAddressesOutput
+	TerminateInstancesOutput        ec2.TerminateInstancesOutput
+	ReleaseAddressOutput            ec2.ReleaseAddressOutput
+}
+
+func (m mockedNetworkInterface) DescribeNetworkInterfaces(*ec2.DescribeNetworkInterfacesInput) (*ec2.DescribeNetworkInterfacesOutput, error) {
+	return &m.DescribeNetworkInterfacesOutput, nil
+}
+
+func (m mockedNetworkInterface) DeleteNetworkInterface(*ec2.DeleteNetworkInterfaceInput) (*ec2.DeleteNetworkInterfaceOutput, error) {
+	return &m.DeleteNetworkInterfaceOutput, nil
+}
+
+func (m mockedNetworkInterface) DescribeAddresses(*ec2.DescribeAddressesInput) (*ec2.DescribeAddressesOutput, error) {
+	return &m.DescribeAddressesOutput, nil
+}
+
+func (m mockedNetworkInterface) ReleaseAddress(*ec2.ReleaseAddressInput) (*ec2.ReleaseAddressOutput, error) {
+	return &m.ReleaseAddressOutput, nil
+}
+
+func (m mockedNetworkInterface) TerminateInstances(*ec2.TerminateInstancesInput) (*ec2.TerminateInstancesOutput, error) {
+	return &m.TerminateInstancesOutput, nil
+}
+
+func (m mockedNetworkInterface) WaitUntilInstanceTerminated(*ec2.DescribeInstancesInput) error {
+	return nil
+}
+
+func TestNetworkInterface_GetAll(t *testing.T) {
+	telemetry.InitTelemetry("cloud-nuke", "")
+
+	var (
+		now     = time.Now()
+		testId1 = "eni-09e36c45cbdbfb001"
+		testId2 = "eni-09e36c45cbdbfb002"
+
+		testName1 = "cloud-nuke-eni-001"
+		testName2 = "cloud-nuke-eni-002"
+	)
+
+	resourceObject := NetworkInterface{
+		Client: mockedNetworkInterface{
+			DescribeNetworkInterfacesOutput: ec2.DescribeNetworkInterfacesOutput{
+				NetworkInterfaces: []*ec2.NetworkInterface{
+					{
+						NetworkInterfaceId: aws.String(testId1),
+						TagSet: []*ec2.Tag{
+							{
+								Key:   aws.String("Name"),
+								Value: aws.String(testName1),
+							},
+							{
+								Key:   aws.String(util.FirstSeenTagKey),
+								Value: aws.String(util.FormatTimestamp(now)),
+							},
+						},
+					},
+					{
+						NetworkInterfaceId: aws.String(testId2),
+						TagSet: []*ec2.Tag{
+							{
+								Key:   aws.String("Name"),
+								Value: aws.String(testName2),
+							},
+							{
+								Key:   aws.String(util.FirstSeenTagKey),
+								Value: aws.String(util.FormatTimestamp(now.Add(1 * time.Hour))),
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	resourceObject.BaseAwsResource.Init(nil)
+
+	tests := map[string]struct {
+		configObj config.ResourceType
+		expected  []string
+	}{
+		"emptyFilter": {
+			configObj: config.ResourceType{},
+			expected:  []string{testId1, testId2},
+		},
+		"nameExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					NamesRegExp: []config.Expression{{
+						RE: *regexp.MustCompile(testName1),
+					}}},
+			},
+			expected: []string{testId2},
+		},
+		"nameInclusionFilter": {
+			configObj: config.ResourceType{
+				IncludeRule: config.FilterRule{
+					NamesRegExp: []config.Expression{{
+						RE: *regexp.MustCompile(testName1),
+					}}},
+			},
+			expected: []string{testId1},
+		},
+		"timeAfterExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					TimeAfter: aws.Time(now),
+				}},
+			expected: []string{
+				testId1,
+			},
+		},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			t.Parallel()
+			names, err := resourceObject.getAll(context.Background(), config.Config{
+				NetworkInterface: tc.configObj,
+			})
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, aws.StringValueSlice(names))
+		})
+	}
+
+}
+
+func TestNetworkInterface_NukeAll(t *testing.T) {
+	telemetry.InitTelemetry("cloud-nuke", "")
+	t.Parallel()
+
+	var (
+		testId1 = "eni-09e36c45cbdbfb001"
+		testId2 = "eni-09e36c45cbdbfb002"
+
+		testName1 = "cloud-nuke-eni-001"
+		testName2 = "cloud-nuke-eni-002"
+	)
+
+	resourceObject := NetworkInterface{
+		BaseAwsResource: BaseAwsResource{
+			Nukables: map[string]error{
+				testId1: nil,
+				testId2: nil,
+			},
+		},
+		Client: mockedNetworkInterface{
+			DeleteNetworkInterfaceOutput: ec2.DeleteNetworkInterfaceOutput{},
+			DescribeNetworkInterfacesOutput: ec2.DescribeNetworkInterfacesOutput{
+				NetworkInterfaces: []*ec2.NetworkInterface{
+					{
+						NetworkInterfaceId: aws.String(testId1),
+						TagSet: []*ec2.Tag{
+							{
+								Key:   aws.String("Name"),
+								Value: aws.String(testName1),
+							},
+						},
+						Attachment: &ec2.NetworkInterfaceAttachment{
+							AttachmentId: aws.String("network-attachment-09e36c45cbdbfb001"),
+							InstanceId:   aws.String("ec2-instance-09e36c45cbdbfb001"),
+						},
+					},
+					{
+						NetworkInterfaceId: aws.String(testId2),
+						TagSet: []*ec2.Tag{
+							{
+								Key:   aws.String("Name"),
+								Value: aws.String(testName2),
+							},
+						},
+						Attachment: &ec2.NetworkInterfaceAttachment{
+							AttachmentId: aws.String("network-attachment-09e36c45cbdbfb002"),
+							InstanceId:   aws.String("ec2-instance-09e36c45cbdbfb002"),
+						},
+					},
+				},
+			},
+			DescribeAddressesOutput: ec2.DescribeAddressesOutput{
+				Addresses: []*ec2.Address{
+					{
+						AllocationId: aws.String("ec2-addr-alloc-09e36c45cbdbfb001"),
+						InstanceId:   aws.String("ec2-instance-09e36c45cbdbfb001"),
+					},
+					{
+						AllocationId: aws.String("ec2-addr-alloc-09e36c45cbdbfb002"),
+						InstanceId:   aws.String("ec2-instance-09e36c45cbdbfb002"),
+					},
+				},
+			},
+			TerminateInstancesOutput: ec2.TerminateInstancesOutput{},
+			ReleaseAddressOutput:     ec2.ReleaseAddressOutput{},
+		},
+	}
+
+	err := resourceObject.nukeAll([]*string{
+		aws.String(testId1),
+		aws.String(testId2),
+	})
+	require.NoError(t, err)
+}
diff --git a/config/config_test.go b/config/config_test.go
index 3e94be7..c49abb4 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -101,6 +101,7 @@ func emptyConfig() *Config {
 		ResourceType{FilterRule{}, FilterRule{}, ""},
 		ResourceType{FilterRule{}, FilterRule{}, ""},
 		ResourceType{FilterRule{}, FilterRule{}, ""},
+		ResourceType{FilterRule{}, FilterRule{}, ""},
 	}
 }