github.com/openshift/installer@v1.4.17/pkg/destroy/aws/ec2helpers.go (about)

     1  package aws
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/aws/aws-sdk-go/aws"
     9  	"github.com/aws/aws-sdk-go/aws/arn"
    10  	"github.com/aws/aws-sdk-go/aws/awserr"
    11  	"github.com/aws/aws-sdk-go/aws/endpoints"
    12  	"github.com/aws/aws-sdk-go/aws/session"
    13  	"github.com/aws/aws-sdk-go/service/ec2"
    14  	"github.com/aws/aws-sdk-go/service/elb"
    15  	"github.com/aws/aws-sdk-go/service/elbv2"
    16  	"github.com/aws/aws-sdk-go/service/iam"
    17  	"github.com/pkg/errors"
    18  	"github.com/sirupsen/logrus"
    19  	"k8s.io/apimachinery/pkg/util/sets"
    20  	"k8s.io/apimachinery/pkg/util/wait"
    21  )
    22  
    23  // findEC2Instances returns the EC2 instances with tags that satisfy the filters.
    24  // returns two lists, first one is the list of all resources that are not terminated and are not in shutdown
    25  // stage and the second list is the list of resources that are not terminated.
    26  //
    27  //	deleted - the resources that have already been deleted. Any resources specified in this set will be ignored.
    28  func findEC2Instances(ctx context.Context, ec2Client *ec2.EC2, deleted sets.Set[string], filters []Filter, logger logrus.FieldLogger) ([]string, []string, error) {
    29  	if ec2Client.Config.Region == nil {
    30  		return nil, nil, errors.New("EC2 client does not have region configured")
    31  	}
    32  
    33  	partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), *ec2Client.Config.Region)
    34  	if !ok {
    35  		return nil, nil, errors.Errorf("no partition found for region %q", *ec2Client.Config.Region)
    36  	}
    37  
    38  	var resourcesRunning []string
    39  	var resourcesNotTerminated []string
    40  	for _, filter := range filters {
    41  		logger.Debugf("search for instances by tag matching %#+v", filter)
    42  		instanceFilters := make([]*ec2.Filter, 0, len(filter))
    43  		for key, value := range filter {
    44  			instanceFilters = append(instanceFilters, &ec2.Filter{
    45  				Name:   aws.String("tag:" + key),
    46  				Values: []*string{aws.String(value)},
    47  			})
    48  		}
    49  		err := ec2Client.DescribeInstancesPagesWithContext(
    50  			ctx,
    51  			&ec2.DescribeInstancesInput{Filters: instanceFilters},
    52  			func(results *ec2.DescribeInstancesOutput, lastPage bool) bool {
    53  				for _, reservation := range results.Reservations {
    54  					if reservation.OwnerId == nil {
    55  						continue
    56  					}
    57  
    58  					for _, instance := range reservation.Instances {
    59  						if instance.InstanceId == nil || instance.State == nil {
    60  							continue
    61  						}
    62  
    63  						instanceLogger := logger.WithField("instance", *instance.InstanceId)
    64  						arn := fmt.Sprintf("arn:%s:ec2:%s:%s:instance/%s", partition.ID(), *ec2Client.Config.Region, *reservation.OwnerId, *instance.InstanceId)
    65  						if *instance.State.Name == "terminated" {
    66  							if !deleted.Has(arn) {
    67  								instanceLogger.Info("Terminated")
    68  								deleted.Insert(arn)
    69  							}
    70  							continue
    71  						}
    72  						if *instance.State.Name != "shutting-down" {
    73  							resourcesRunning = append(resourcesRunning, arn)
    74  						}
    75  						resourcesNotTerminated = append(resourcesNotTerminated, arn)
    76  					}
    77  				}
    78  				return !lastPage
    79  			},
    80  		)
    81  		if err != nil {
    82  			err = errors.Wrap(err, "get ec2 instances")
    83  			logger.Info(err)
    84  			return resourcesRunning, resourcesNotTerminated, err
    85  		}
    86  	}
    87  	return resourcesRunning, resourcesNotTerminated, nil
    88  }
    89  
    90  // DeleteEC2Instances terminates all EC2 instances found.
    91  func DeleteEC2Instances(ctx context.Context, logger logrus.FieldLogger, awsSession *session.Session, filters []Filter, toDelete sets.Set[string], deleted sets.Set[string], tracker *ErrorTracker) error {
    92  	ec2Client := ec2.New(awsSession)
    93  	lastTerminateTime := time.Now()
    94  	err := wait.PollUntilContextCancel(
    95  		ctx,
    96  		time.Second*10,
    97  		true,
    98  		func(ctx context.Context) (bool, error) {
    99  			instancesRunning, instancesNotTerminated, err := findEC2Instances(ctx, ec2Client, deleted, filters, logger)
   100  			if err != nil {
   101  				logger.WithError(err).Info("error while finding EC2 instances to delete")
   102  				return false, nil
   103  			}
   104  			if len(instancesNotTerminated) == 0 && len(instancesRunning) == 0 {
   105  				return true, nil
   106  			}
   107  			instancesToDelete := instancesRunning
   108  			if time.Since(lastTerminateTime) > 10*time.Minute {
   109  				instancesToDelete = instancesNotTerminated
   110  				lastTerminateTime = time.Now()
   111  			}
   112  			newlyDeleted, err := DeleteResources(ctx, logger, awsSession, instancesToDelete, tracker)
   113  			// Delete from the resources-to-delete set so that the current state of the resources to delete can be
   114  			// returned if the context is completed.
   115  			toDelete = toDelete.Difference(newlyDeleted)
   116  			deleted = deleted.Union(newlyDeleted)
   117  			if err != nil {
   118  				logger.WithError(err).Info("error while deleting EC2 instances")
   119  			}
   120  			return false, nil
   121  		},
   122  	)
   123  	return err
   124  }
   125  
   126  func deleteEC2(ctx context.Context, session *session.Session, arn arn.ARN, logger logrus.FieldLogger) error {
   127  	client := ec2.New(session)
   128  
   129  	resourceType, id, err := splitSlash("resource", arn.Resource)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	logger = logger.WithField("id", id).WithField("resourceType", resourceType)
   134  
   135  	switch resourceType {
   136  	case "dhcp-options":
   137  		return deleteEC2DHCPOptions(ctx, client, id, logger)
   138  	case "elastic-ip":
   139  		return deleteEC2ElasticIP(ctx, client, id, logger)
   140  	case "image":
   141  		return deleteEC2Image(ctx, client, id, logger)
   142  	case "instance":
   143  		return terminateEC2Instance(ctx, client, iam.New(session), id, logger)
   144  	case "internet-gateway":
   145  		return deleteEC2InternetGateway(ctx, client, id, logger)
   146  	case "carrier-gateway":
   147  		return deleteEC2CarrierGateway(ctx, client, id, logger)
   148  	case "natgateway":
   149  		return deleteEC2NATGateway(ctx, client, id, logger)
   150  	case "placement-group":
   151  		return deleteEC2PlacementGroup(ctx, client, id, logger)
   152  	case "route-table":
   153  		return deleteEC2RouteTable(ctx, client, id, logger)
   154  	case "security-group":
   155  		return deleteEC2SecurityGroup(ctx, client, id, logger)
   156  	case "snapshot":
   157  		return deleteEC2Snapshot(ctx, client, id, logger)
   158  	case "network-interface":
   159  		return deleteEC2NetworkInterface(ctx, client, id, logger)
   160  	case "subnet":
   161  		return deleteEC2Subnet(ctx, client, id, logger)
   162  	case "volume":
   163  		return deleteEC2Volume(ctx, client, id, logger)
   164  	case "vpc":
   165  		return deleteEC2VPC(ctx, client, elb.New(session), elbv2.New(session), id, logger)
   166  	case "vpc-endpoint":
   167  		return deleteEC2VPCEndpoint(ctx, client, id, logger)
   168  	case "vpc-peering-connection":
   169  		return deleteEC2VPCPeeringConnection(ctx, client, id, logger)
   170  	case "vpc-endpoint-service":
   171  		return deleteEC2VPCEndpointService(ctx, client, id, logger)
   172  	default:
   173  		return errors.Errorf("unrecognized EC2 resource type %s", resourceType)
   174  	}
   175  }
   176  
   177  func deleteEC2DHCPOptions(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   178  	_, err := client.DeleteDhcpOptionsWithContext(ctx, &ec2.DeleteDhcpOptionsInput{
   179  		DhcpOptionsId: &id,
   180  	})
   181  	if err != nil {
   182  		if err.(awserr.Error).Code() == "InvalidDhcpOptionsID.NotFound" {
   183  			return nil
   184  		}
   185  		return err
   186  	}
   187  
   188  	logger.Info("Deleted")
   189  	return nil
   190  }
   191  
   192  func deleteEC2Image(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   193  	// tag the snapshots used by the AMI so that the snapshots are matched
   194  	// by the filter and deleted
   195  	response, err := client.DescribeImagesWithContext(ctx, &ec2.DescribeImagesInput{
   196  		ImageIds: []*string{&id},
   197  	})
   198  	if err != nil {
   199  		if err.(awserr.Error).Code() == "InvalidAMIID.NotFound" {
   200  			return nil
   201  		}
   202  		return err
   203  	}
   204  	for _, image := range response.Images {
   205  		var snapshots []*string
   206  		for _, bdm := range image.BlockDeviceMappings {
   207  			if bdm.Ebs != nil && bdm.Ebs.SnapshotId != nil {
   208  				snapshots = append(snapshots, bdm.Ebs.SnapshotId)
   209  			}
   210  		}
   211  		if len(snapshots) != 0 {
   212  			_, err = client.CreateTagsWithContext(ctx, &ec2.CreateTagsInput{
   213  				Resources: snapshots,
   214  				Tags:      image.Tags,
   215  			})
   216  			if err != nil {
   217  				return errors.Wrapf(err, "tagging snapshots for %s", id)
   218  			}
   219  		}
   220  	}
   221  
   222  	_, err = client.DeregisterImageWithContext(ctx, &ec2.DeregisterImageInput{
   223  		ImageId: &id,
   224  	})
   225  	if err != nil {
   226  		if err.(awserr.Error).Code() == "InvalidAMIID.NotFound" {
   227  			return nil
   228  		}
   229  		return err
   230  	}
   231  
   232  	logger.Info("Deleted")
   233  	return nil
   234  }
   235  
   236  func deleteEC2ElasticIP(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   237  	_, err := client.ReleaseAddressWithContext(ctx, &ec2.ReleaseAddressInput{
   238  		AllocationId: aws.String(id),
   239  	})
   240  	if err != nil {
   241  		if err.(awserr.Error).Code() == "InvalidAllocationID.NotFound" {
   242  			return nil
   243  		}
   244  		return err
   245  	}
   246  
   247  	logger.Info("Released")
   248  	return nil
   249  }
   250  
   251  func terminateEC2Instance(ctx context.Context, ec2Client *ec2.EC2, iamClient *iam.IAM, id string, logger logrus.FieldLogger) error {
   252  	response, err := ec2Client.DescribeInstancesWithContext(ctx, &ec2.DescribeInstancesInput{
   253  		InstanceIds: []*string{aws.String(id)},
   254  	})
   255  	if err != nil {
   256  		if err.(awserr.Error).Code() == "InvalidInstanceID.NotFound" {
   257  			return nil
   258  		}
   259  		return err
   260  	}
   261  
   262  	for _, reservation := range response.Reservations {
   263  		for _, instance := range reservation.Instances {
   264  			err = terminateEC2InstanceByInstance(ctx, ec2Client, iamClient, instance, logger)
   265  			if err != nil {
   266  				return err
   267  			}
   268  		}
   269  	}
   270  	return nil
   271  }
   272  
   273  func terminateEC2InstanceByInstance(ctx context.Context, ec2Client *ec2.EC2, iamClient *iam.IAM, instance *ec2.Instance, logger logrus.FieldLogger) error {
   274  	// Ignore instances that are already terminated
   275  	if instance.State == nil || *instance.State.Name == "terminated" {
   276  		return nil
   277  	}
   278  
   279  	_, err := ec2Client.TerminateInstancesWithContext(ctx, &ec2.TerminateInstancesInput{
   280  		InstanceIds: []*string{instance.InstanceId},
   281  	})
   282  	if err != nil {
   283  		return err
   284  	}
   285  
   286  	logger.Debug("Terminating")
   287  	return nil
   288  }
   289  
   290  func deleteEC2InternetGateway(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   291  	response, err := client.DescribeInternetGatewaysWithContext(ctx, &ec2.DescribeInternetGatewaysInput{
   292  		InternetGatewayIds: []*string{aws.String(id)},
   293  	})
   294  	if err != nil {
   295  		return err
   296  	}
   297  
   298  	for _, gateway := range response.InternetGateways {
   299  		for _, vpc := range gateway.Attachments {
   300  			if vpc.VpcId == nil {
   301  				logger.Warn("gateway does not have a VPC ID")
   302  				continue
   303  			}
   304  			_, err := client.DetachInternetGatewayWithContext(ctx, &ec2.DetachInternetGatewayInput{
   305  				InternetGatewayId: gateway.InternetGatewayId,
   306  				VpcId:             vpc.VpcId,
   307  			})
   308  			if err == nil {
   309  				logger.WithField("vpc", *vpc.VpcId).Debug("Detached")
   310  			} else if err.(awserr.Error).Code() != "Gateway.NotAttached" {
   311  				return errors.Wrapf(err, "detaching from %s", *vpc.VpcId)
   312  			}
   313  		}
   314  	}
   315  
   316  	_, err = client.DeleteInternetGatewayWithContext(ctx, &ec2.DeleteInternetGatewayInput{
   317  		InternetGatewayId: &id,
   318  	})
   319  	if err != nil {
   320  		return err
   321  	}
   322  
   323  	logger.Info("Deleted")
   324  	return nil
   325  }
   326  
   327  func deleteEC2CarrierGateway(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   328  	_, err := client.DeleteCarrierGatewayWithContext(ctx, &ec2.DeleteCarrierGatewayInput{
   329  		CarrierGatewayId: &id,
   330  	})
   331  	if err != nil {
   332  		var awsErr awserr.Error
   333  		if errors.As(err, &awsErr) && awsErr.Code() == "InvalidCarrierGatewayID.NotFound" {
   334  			return nil
   335  		}
   336  		return err
   337  	}
   338  
   339  	logger.Info("Deleted")
   340  	return nil
   341  }
   342  
   343  func deleteEC2NATGateway(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   344  	_, err := client.DeleteNatGatewayWithContext(ctx, &ec2.DeleteNatGatewayInput{
   345  		NatGatewayId: aws.String(id),
   346  	})
   347  	if err != nil {
   348  		if err.(awserr.Error).Code() == "NatGatewayNotFound" {
   349  			return nil
   350  		}
   351  		return err
   352  	}
   353  
   354  	logger.Info("Deleted")
   355  	return nil
   356  }
   357  
   358  func deleteEC2NATGatewaysByVPC(ctx context.Context, client *ec2.EC2, vpc string, failFast bool, logger logrus.FieldLogger) error {
   359  	var lastError error
   360  	err := client.DescribeNatGatewaysPagesWithContext(
   361  		ctx,
   362  		&ec2.DescribeNatGatewaysInput{
   363  			Filter: []*ec2.Filter{
   364  				{
   365  					Name:   aws.String("vpc-id"),
   366  					Values: []*string{&vpc},
   367  				},
   368  			},
   369  		},
   370  		func(results *ec2.DescribeNatGatewaysOutput, lastPage bool) bool {
   371  			for _, gateway := range results.NatGateways {
   372  				err := deleteEC2NATGateway(ctx, client, *gateway.NatGatewayId, logger.WithField("NAT gateway", *gateway.NatGatewayId))
   373  				if err != nil {
   374  					if lastError != nil {
   375  						logger.Debug(err)
   376  					}
   377  					lastError = errors.Wrapf(err, "deleting EC2 NAT gateway %s", *gateway.NatGatewayId)
   378  					if failFast {
   379  						return false
   380  					}
   381  				}
   382  			}
   383  
   384  			return !lastPage
   385  		},
   386  	)
   387  
   388  	if lastError != nil {
   389  		return lastError
   390  	}
   391  	return err
   392  }
   393  
   394  func deleteEC2PlacementGroup(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   395  	response, err := client.DescribePlacementGroupsWithContext(ctx, &ec2.DescribePlacementGroupsInput{
   396  		GroupIds: []*string{aws.String(id)},
   397  	})
   398  	if err != nil {
   399  		if err.(awserr.Error).Code() == "InvalidPlacementGroup.Unknown" {
   400  			return nil
   401  		}
   402  		return err
   403  	}
   404  
   405  	for _, placementGroup := range response.PlacementGroups {
   406  		if _, err := client.DeletePlacementGroupWithContext(ctx, &ec2.DeletePlacementGroupInput{
   407  			GroupName: placementGroup.GroupName,
   408  		}); err != nil {
   409  			return err
   410  		}
   411  	}
   412  
   413  	logger.Info("Deleted")
   414  	return nil
   415  }
   416  
   417  func deleteEC2RouteTable(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   418  	response, err := client.DescribeRouteTablesWithContext(ctx, &ec2.DescribeRouteTablesInput{
   419  		RouteTableIds: []*string{aws.String(id)},
   420  	})
   421  	if err != nil {
   422  		if err.(awserr.Error).Code() == "InvalidRouteTableID.NotFound" {
   423  			return nil
   424  		}
   425  		return err
   426  	}
   427  
   428  	for _, table := range response.RouteTables {
   429  		err = deleteEC2RouteTableObject(ctx, client, table, logger)
   430  		if err != nil {
   431  			return err
   432  		}
   433  	}
   434  
   435  	return nil
   436  }
   437  
   438  func deleteEC2RouteTableObject(ctx context.Context, client *ec2.EC2, table *ec2.RouteTable, logger logrus.FieldLogger) error {
   439  	hasMain := false
   440  	for _, association := range table.Associations {
   441  		if *association.Main {
   442  			// can't remove the 'Main' association
   443  			hasMain = true
   444  			continue
   445  		}
   446  		_, err := client.DisassociateRouteTableWithContext(ctx, &ec2.DisassociateRouteTableInput{
   447  			AssociationId: association.RouteTableAssociationId,
   448  		})
   449  		if err != nil {
   450  			return errors.Wrapf(err, "dissociating %s", *association.RouteTableAssociationId)
   451  		}
   452  		logger.WithField("id", *association.RouteTableAssociationId).Info("Disassociated")
   453  	}
   454  
   455  	if hasMain {
   456  		// can't delete route table with the 'Main' association
   457  		// it will get cleaned up as part of deleting the VPC
   458  		return nil
   459  	}
   460  
   461  	_, err := client.DeleteRouteTableWithContext(ctx, &ec2.DeleteRouteTableInput{
   462  		RouteTableId: table.RouteTableId,
   463  	})
   464  	if err != nil {
   465  		return err
   466  	}
   467  
   468  	logger.Info("Deleted")
   469  	return nil
   470  }
   471  
   472  func deleteEC2RouteTablesByVPC(ctx context.Context, client *ec2.EC2, vpc string, failFast bool, logger logrus.FieldLogger) error {
   473  	var lastError error
   474  	err := client.DescribeRouteTablesPagesWithContext(
   475  		ctx,
   476  		&ec2.DescribeRouteTablesInput{
   477  			Filters: []*ec2.Filter{
   478  				{
   479  					Name:   aws.String("vpc-id"),
   480  					Values: []*string{&vpc},
   481  				},
   482  			},
   483  		},
   484  		func(results *ec2.DescribeRouteTablesOutput, lastPage bool) bool {
   485  			for _, table := range results.RouteTables {
   486  				err := deleteEC2RouteTableObject(ctx, client, table, logger.WithField("table", *table.RouteTableId))
   487  				if err != nil {
   488  					if lastError != nil {
   489  						logger.Debug(err)
   490  					}
   491  					lastError = errors.Wrapf(err, "deleting EC2 route table %s", *table.RouteTableId)
   492  					if failFast {
   493  						return false
   494  					}
   495  				}
   496  			}
   497  
   498  			return !lastPage
   499  		},
   500  	)
   501  
   502  	if lastError != nil {
   503  		return lastError
   504  	}
   505  	return err
   506  }
   507  
   508  func deleteEC2SecurityGroup(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   509  	response, err := client.DescribeSecurityGroupsWithContext(ctx, &ec2.DescribeSecurityGroupsInput{
   510  		GroupIds: []*string{aws.String(id)},
   511  	})
   512  	if err != nil {
   513  		if err.(awserr.Error).Code() == "InvalidGroup.NotFound" {
   514  			return nil
   515  		}
   516  		return err
   517  	}
   518  
   519  	for _, group := range response.SecurityGroups {
   520  		err = deleteEC2SecurityGroupObject(ctx, client, group, logger)
   521  		if err != nil {
   522  			return err
   523  		}
   524  	}
   525  
   526  	return nil
   527  }
   528  
   529  func deleteEC2SecurityGroupObject(ctx context.Context, client *ec2.EC2, group *ec2.SecurityGroup, logger logrus.FieldLogger) error {
   530  	if len(group.IpPermissions) > 0 {
   531  		_, err := client.RevokeSecurityGroupIngressWithContext(ctx, &ec2.RevokeSecurityGroupIngressInput{
   532  			GroupId:       group.GroupId,
   533  			IpPermissions: group.IpPermissions,
   534  		})
   535  		if err != nil {
   536  			return errors.Wrap(err, "revoking ingress permissions")
   537  		}
   538  		logger.Debug("Revoked ingress permissions")
   539  	}
   540  
   541  	if len(group.IpPermissionsEgress) > 0 {
   542  		_, err := client.RevokeSecurityGroupEgressWithContext(ctx, &ec2.RevokeSecurityGroupEgressInput{
   543  			GroupId:       group.GroupId,
   544  			IpPermissions: group.IpPermissionsEgress,
   545  		})
   546  		if err != nil {
   547  			return errors.Wrap(err, "revoking egress permissions")
   548  		}
   549  		logger.Debug("Revoked egress permissions")
   550  	}
   551  
   552  	if group.GroupName != nil && *group.GroupName == "default" {
   553  		logger.Debug("Skipping default security group")
   554  		return nil
   555  	}
   556  
   557  	_, err := client.DeleteSecurityGroupWithContext(ctx, &ec2.DeleteSecurityGroupInput{
   558  		GroupId: group.GroupId,
   559  	})
   560  	if err != nil {
   561  		if err.(awserr.Error).Code() == "InvalidGroup.NotFound" {
   562  			return nil
   563  		}
   564  		return err
   565  	}
   566  
   567  	logger.Info("Deleted")
   568  	return nil
   569  }
   570  
   571  func deleteEC2SecurityGroupsByVPC(ctx context.Context, client *ec2.EC2, vpc string, failFast bool, logger logrus.FieldLogger) error {
   572  	var lastError error
   573  	err := client.DescribeSecurityGroupsPagesWithContext(
   574  		ctx,
   575  		&ec2.DescribeSecurityGroupsInput{
   576  			Filters: []*ec2.Filter{
   577  				{
   578  					Name:   aws.String("vpc-id"),
   579  					Values: []*string{&vpc},
   580  				},
   581  			},
   582  		},
   583  		func(results *ec2.DescribeSecurityGroupsOutput, lastPage bool) bool {
   584  			for _, group := range results.SecurityGroups {
   585  				err := deleteEC2SecurityGroupObject(ctx, client, group, logger.WithField("security group", *group.GroupId))
   586  				if err != nil {
   587  					if lastError != nil {
   588  						logger.Debug(err)
   589  					}
   590  					lastError = errors.Wrapf(err, "deleting EC2 security group %s", *group.GroupId)
   591  					if failFast {
   592  						return false
   593  					}
   594  				}
   595  			}
   596  
   597  			return !lastPage
   598  		},
   599  	)
   600  
   601  	if lastError != nil {
   602  		return lastError
   603  	}
   604  	return err
   605  }
   606  
   607  func deleteEC2Snapshot(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   608  	_, err := client.DeleteSnapshotWithContext(ctx, &ec2.DeleteSnapshotInput{
   609  		SnapshotId: &id,
   610  	})
   611  	if err != nil {
   612  		if err.(awserr.Error).Code() == "InvalidSnapshot.NotFound" {
   613  			return nil
   614  		}
   615  		return err
   616  	}
   617  
   618  	logger.Info("Deleted")
   619  	return nil
   620  }
   621  
   622  func deleteEC2NetworkInterface(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   623  	_, err := client.DeleteNetworkInterfaceWithContext(ctx, &ec2.DeleteNetworkInterfaceInput{
   624  		NetworkInterfaceId: aws.String(id),
   625  	})
   626  	if err != nil {
   627  		if err.(awserr.Error).Code() == "InvalidNetworkInterfaceID.NotFound" {
   628  			return nil
   629  		}
   630  		return err
   631  	}
   632  
   633  	logger.Info("Deleted")
   634  	return nil
   635  }
   636  
   637  func deleteEC2NetworkInterfaceByVPC(ctx context.Context, client *ec2.EC2, vpc string, failFast bool, logger logrus.FieldLogger) error {
   638  	var lastError error
   639  	err := client.DescribeNetworkInterfacesPagesWithContext(
   640  		ctx,
   641  		&ec2.DescribeNetworkInterfacesInput{
   642  			Filters: []*ec2.Filter{
   643  				{
   644  					Name:   aws.String("vpc-id"),
   645  					Values: []*string{&vpc},
   646  				},
   647  			},
   648  		},
   649  		func(results *ec2.DescribeNetworkInterfacesOutput, lastPage bool) bool {
   650  			for _, networkInterface := range results.NetworkInterfaces {
   651  				err := deleteEC2NetworkInterface(ctx, client, *networkInterface.NetworkInterfaceId, logger.WithField("network interface", *networkInterface.NetworkInterfaceId))
   652  				if err != nil {
   653  					if lastError != nil {
   654  						logger.Debug(lastError)
   655  					}
   656  					lastError = errors.Wrapf(err, "deleting EC2 network interface %s", *networkInterface.NetworkInterfaceId)
   657  					if failFast {
   658  						return false
   659  					}
   660  				}
   661  			}
   662  
   663  			return !lastPage
   664  		},
   665  	)
   666  
   667  	if lastError != nil {
   668  		return lastError
   669  	}
   670  	return err
   671  }
   672  
   673  func deleteEC2Subnet(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   674  	_, err := client.DeleteSubnetWithContext(ctx, &ec2.DeleteSubnetInput{
   675  		SubnetId: aws.String(id),
   676  	})
   677  	if err != nil {
   678  		if err.(awserr.Error).Code() == "InvalidSubnetID.NotFound" {
   679  			return nil
   680  		}
   681  		return err
   682  	}
   683  
   684  	logger.Info("Deleted")
   685  	return nil
   686  }
   687  
   688  func deleteEC2SubnetsByVPC(ctx context.Context, client *ec2.EC2, vpc string, failFast bool, logger logrus.FieldLogger) error {
   689  	var lastError error
   690  	err := client.DescribeSubnetsPagesWithContext(
   691  		ctx,
   692  		&ec2.DescribeSubnetsInput{
   693  			Filters: []*ec2.Filter{
   694  				{
   695  					Name:   aws.String("vpc-id"),
   696  					Values: []*string{&vpc},
   697  				},
   698  			},
   699  		},
   700  		func(results *ec2.DescribeSubnetsOutput, lastPage bool) bool {
   701  			for _, subnet := range results.Subnets {
   702  				err := deleteEC2Subnet(ctx, client, *subnet.SubnetId, logger.WithField("subnet", *subnet.SubnetId))
   703  				if err != nil {
   704  					err = errors.Wrapf(err, "deleting EC2 subnet %s", *subnet.SubnetId)
   705  					if lastError != nil {
   706  						logger.Debug(lastError)
   707  					}
   708  					lastError = err
   709  					if failFast {
   710  						return false
   711  					}
   712  				}
   713  			}
   714  			return !lastPage
   715  		},
   716  	)
   717  	if err != nil {
   718  		return err
   719  	}
   720  
   721  	return lastError
   722  }
   723  
   724  func deleteEC2Volume(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   725  	_, err := client.DeleteVolumeWithContext(ctx, &ec2.DeleteVolumeInput{
   726  		VolumeId: aws.String(id),
   727  	})
   728  	if err != nil {
   729  		if err.(awserr.Error).Code() == "InvalidVolume.NotFound" {
   730  			return nil
   731  		}
   732  		return err
   733  	}
   734  
   735  	logger.Info("Deleted")
   736  	return nil
   737  }
   738  
   739  func deleteEC2VPC(ctx context.Context, ec2Client *ec2.EC2, elbClient *elb.ELB, elbv2Client *elbv2.ELBV2, id string, logger logrus.FieldLogger) error {
   740  	// first delete any Load Balancers under this VPC (not all of them are tagged)
   741  	v1lbError := deleteElasticLoadBalancerClassicByVPC(ctx, elbClient, id, logger)
   742  	v2lbError := deleteElasticLoadBalancerV2ByVPC(ctx, elbv2Client, id, logger)
   743  	if v1lbError != nil {
   744  		if v2lbError != nil {
   745  			logger.Info(v2lbError)
   746  		}
   747  		return v1lbError
   748  	} else if v2lbError != nil {
   749  		return v2lbError
   750  	}
   751  
   752  	for _, child := range []struct {
   753  		helper   func(ctx context.Context, client *ec2.EC2, vpc string, failFast bool, logger logrus.FieldLogger) error
   754  		failFast bool
   755  	}{
   756  		{helper: deleteEC2NATGatewaysByVPC, failFast: true},      // not always tagged
   757  		{helper: deleteEC2NetworkInterfaceByVPC, failFast: true}, // not always tagged
   758  		{helper: deleteEC2RouteTablesByVPC, failFast: true},      // not always tagged
   759  		{helper: deleteEC2SecurityGroupsByVPC, failFast: false},  // not always tagged
   760  		{helper: deleteEC2SubnetsByVPC, failFast: true},          // not always tagged
   761  		{helper: deleteEC2VPCEndpointsByVPC, failFast: true},     // not taggable
   762  	} {
   763  		err := child.helper(ctx, ec2Client, id, child.failFast, logger)
   764  		if err != nil {
   765  			return err
   766  		}
   767  	}
   768  
   769  	_, err := ec2Client.DeleteVpcWithContext(ctx, &ec2.DeleteVpcInput{
   770  		VpcId: aws.String(id),
   771  	})
   772  	if err != nil {
   773  		return err
   774  	}
   775  
   776  	logger.Info("Deleted")
   777  	return nil
   778  }
   779  
   780  func deleteEC2VPCEndpoint(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   781  	_, err := client.DeleteVpcEndpointsWithContext(ctx, &ec2.DeleteVpcEndpointsInput{
   782  		VpcEndpointIds: []*string{aws.String(id)},
   783  	})
   784  	if err != nil {
   785  		return errors.Wrapf(err, "cannot delete VPC endpoint %s", id)
   786  	}
   787  
   788  	logger.Info("Deleted")
   789  	return nil
   790  }
   791  
   792  func deleteEC2VPCEndpointsByVPC(ctx context.Context, client *ec2.EC2, vpc string, failFast bool, logger logrus.FieldLogger) error {
   793  	response, err := client.DescribeVpcEndpointsWithContext(ctx, &ec2.DescribeVpcEndpointsInput{
   794  		Filters: []*ec2.Filter{
   795  			{
   796  				Name:   aws.String("vpc-id"),
   797  				Values: []*string{aws.String(vpc)},
   798  			},
   799  		},
   800  	})
   801  	if err != nil {
   802  		return err
   803  	}
   804  
   805  	for _, endpoint := range response.VpcEndpoints {
   806  		err := deleteEC2VPCEndpoint(ctx, client, *endpoint.VpcEndpointId, logger.WithField("VPC endpoint", *endpoint.VpcEndpointId))
   807  		if err != nil {
   808  			if err.(awserr.Error).Code() == "InvalidVpcID.NotFound" {
   809  				return nil
   810  			}
   811  			return err
   812  		}
   813  	}
   814  
   815  	return nil
   816  }
   817  
   818  func deleteEC2VPCPeeringConnection(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   819  	_, err := client.DeleteVpcPeeringConnectionWithContext(ctx, &ec2.DeleteVpcPeeringConnectionInput{
   820  		VpcPeeringConnectionId: &id,
   821  	})
   822  	if err != nil {
   823  		if err.(awserr.Error).Code() == "InvalidVpcPeeringConnectionID.NotFound" {
   824  			return nil
   825  		}
   826  		return errors.Wrapf(err, "cannot delete VPC Peering Connection %s", id)
   827  	}
   828  	logger.Info("Deleted")
   829  	return nil
   830  }
   831  
   832  func deleteEC2VPCEndpointService(ctx context.Context, client *ec2.EC2, id string, logger logrus.FieldLogger) error {
   833  	output, err := client.DescribeVpcEndpointConnectionsWithContext(ctx, &ec2.DescribeVpcEndpointConnectionsInput{
   834  		Filters: []*ec2.Filter{
   835  			{
   836  				Name:   aws.String("service-id"),
   837  				Values: aws.StringSlice([]string{id}),
   838  			},
   839  		},
   840  	})
   841  
   842  	if err != nil {
   843  		logger.Warn("Unable to get the list of VPC endpoint connections connected to service: ", err)
   844  		logger.Warn("Attempting to delete the VPC Endpoint Service")
   845  	} else {
   846  		endpointList := make([]*string, len(output.VpcEndpointConnections))
   847  		for _, endpoint := range output.VpcEndpointConnections {
   848  			if aws.StringValue(endpoint.VpcEndpointState) != "rejected" {
   849  				endpointList = append(endpointList, endpoint.VpcEndpointId)
   850  			}
   851  		}
   852  
   853  		_, err = client.RejectVpcEndpointConnectionsWithContext(ctx, &ec2.RejectVpcEndpointConnectionsInput{
   854  			ServiceId:      &id,
   855  			VpcEndpointIds: endpointList,
   856  		})
   857  
   858  		if err != nil {
   859  			logger.Warn("Unable to reject VPC endpoint connections for service: ", err)
   860  			logger.Warn("Attempting to delete the VPC Endpoint Service")
   861  		} else {
   862  			logger.WithField("resourceType", "VPC Endpoint Connection").Info("Rejected")
   863  		}
   864  	}
   865  
   866  	_, err = client.DeleteVpcEndpointServiceConfigurationsWithContext(ctx, &ec2.DeleteVpcEndpointServiceConfigurationsInput{
   867  		ServiceIds: aws.StringSlice([]string{id}),
   868  	})
   869  	if err != nil {
   870  		if err.(awserr.Error).Code() == "InvalidVpcEndpointService.NotFound" {
   871  			return nil
   872  		}
   873  		return errors.Wrapf(err, "cannot delete VPC Endpoint Service %s", id)
   874  	}
   875  	logger.Info("Deleted")
   876  	return nil
   877  }