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 }