kops/upup/pkg/kutil/delete_cluster.go

1895 lines
46 KiB
Go

/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kutil
import (
"bufio"
"bytes"
"compress/gzip"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/elb"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/golang/glog"
"io"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kubernetes/pkg/util/sets"
"strings"
"sync"
"time"
)
const (
TypeAutoscalingLaunchConfig = "autoscaling-config"
)
// DeleteCluster implements deletion of cluster cloud resources
// The algorithm is pretty simple: it discovers all the resources it can (primary using tags),
// and then it repeatedly attempts to delete them all until they are all deleted.
// There are a few tweaks to that approach, like choosing a default ordering, but it is not much
// smarter. Cluster deletion is a fairly rare operation anyway, and also some dependencies are invisible
// (e.g. ELB dependencies).
type DeleteCluster struct {
ClusterName string
Cloud fi.Cloud
}
type ResourceTracker struct {
Name string
Type string
ID string
blocks []string
blocked []string
done bool
deleter func(cloud fi.Cloud, tracker *ResourceTracker) error
groupKey string
groupDeleter func(cloud fi.Cloud, trackers []*ResourceTracker) error
obj interface{}
}
type listFn func(fi.Cloud, string) ([]*ResourceTracker, error)
func gunzipBytes(d []byte) ([]byte, error) {
var out bytes.Buffer
in := bytes.NewReader(d)
r, err := gzip.NewReader(in)
if err != nil {
return nil, fmt.Errorf("error building gunzip reader: %v", err)
}
defer r.Close()
_, err = io.Copy(&out, r)
if err != nil {
return nil, fmt.Errorf("error decompressing data: %v", err)
}
return out.Bytes(), nil
}
func buildEC2Filters(cloud fi.Cloud) []*ec2.Filter {
awsCloud := cloud.(awsup.AWSCloud)
tags := awsCloud.Tags()
var filters []*ec2.Filter
for k, v := range tags {
filter := awsup.NewEC2Filter("tag:"+k, v)
filters = append(filters, filter)
}
return filters
}
func (c *DeleteCluster) ListResources() (map[string]*ResourceTracker, error) {
cloud := c.Cloud.(awsup.AWSCloud)
resources := make(map[string]*ResourceTracker)
listFunctions := []listFn{
// EC2
ListInstances,
ListKeypairs,
ListSecurityGroups,
ListVolumes,
// EC2 VPC
ListDhcpOptions,
ListInternetGateways,
ListRouteTables,
ListSubnets,
ListVPCs,
// ELBs
ListELBs,
// ASG
ListAutoScalingGroups,
// Route 53
ListRoute53Records,
// IAM
ListIAMInstanceProfiles,
ListIAMRoles,
}
for _, fn := range listFunctions {
trackers, err := fn(cloud, c.ClusterName)
if err != nil {
return nil, err
}
for _, t := range trackers {
resources[t.Type+":"+t.ID] = t
}
}
{
// Gateways weren't tagged in kube-up
// If we are deleting the VPC, we should delete the attached gateway
// (no real reason not to; easy to recreate; no real state etc)
gateways, err := DescribeInternetGatewaysIgnoreTags(cloud)
if err != nil {
return nil, err
}
for _, igw := range gateways {
for _, attachment := range igw.Attachments {
vpcID := aws.StringValue(attachment.VpcId)
igwID := aws.StringValue(igw.InternetGatewayId)
if vpcID == "" || igwID == "" {
continue
}
if resources["vpc:"+vpcID] != nil && resources["internet-gateway:"+igwID] == nil {
resources["internet-gateway:"+igwID] = &ResourceTracker{
Name: FindName(igw.Tags),
ID: igwID,
Type: "internet-gateway",
deleter: DeleteInternetGateway,
}
}
}
}
}
{
// We delete a launch configuration if it is bound to one of the tagged security groups
securityGroups := sets.NewString()
for k := range resources {
if !strings.HasPrefix(k, "security-group:") {
continue
}
id := strings.TrimPrefix(k, "security-group:")
securityGroups.Insert(id)
}
lcs, err := FindAutoScalingLaunchConfigurations(cloud, securityGroups)
if err != nil {
return nil, err
}
for _, t := range lcs {
resources[t.Type+":"+t.ID] = t
}
}
if err := addUntaggedRouteTables(cloud, c.ClusterName, resources); err != nil {
return nil, err
}
for k, t := range resources {
if t.done {
delete(resources, k)
}
}
return resources, nil
}
func addUntaggedRouteTables(cloud awsup.AWSCloud, clusterName string, resources map[string]*ResourceTracker) error {
// We sometimes have trouble tagging the route table (eventual consistency, e.g. #597)
// If we are deleting the VPC, we should delete the route table
// (no real reason not to; easy to recreate; no real state etc)
routeTables, err := DescribeRouteTablesIgnoreTags(cloud)
if err != nil {
return err
}
for _, rt := range routeTables {
rtID := aws.StringValue(rt.RouteTableId)
vpcID := aws.StringValue(rt.VpcId)
if vpcID == "" || rtID == "" {
continue
}
if resources["vpc:"+vpcID] == nil {
// Not deleting this VPC; ignore
continue
}
clusterTag, _ := awsup.FindEC2Tag(rt.Tags, awsup.TagClusterName)
if clusterTag != "" && clusterTag != clusterName {
glog.Infof("Skipping route table in VPC, but with wrong cluster tag (%q)", clusterTag)
continue
}
isMain := false
for _, a := range rt.Associations {
if aws.BoolValue(a.Main) == true {
isMain = true
}
}
if isMain {
glog.V(4).Infof("ignoring main routetable %q", rtID)
continue
}
t := buildTrackerForRouteTable(rt)
if resources[t.Type+":"+t.ID] == nil {
resources[t.Type+":"+t.ID] = t
}
}
return nil
}
func (c *DeleteCluster) DeleteResources(resources map[string]*ResourceTracker) error {
depMap := make(map[string][]string)
done := make(map[string]*ResourceTracker)
var mutex sync.Mutex
for k, t := range resources {
for _, block := range t.blocks {
depMap[block] = append(depMap[block], k)
}
for _, blocked := range t.blocked {
depMap[k] = append(depMap[k], blocked)
}
if t.done {
done[k] = t
}
}
glog.V(2).Infof("Dependencies")
for k, v := range depMap {
glog.V(2).Infof("\t%s\t%v", k, v)
}
iterationsWithNoProgress := 0
for {
// TODO: Some form of default ordering based on types?
failed := make(map[string]*ResourceTracker)
for {
phase := make(map[string]*ResourceTracker)
for k, r := range resources {
if _, d := done[k]; d {
continue
}
if _, d := failed[k]; d {
// Only attempt each resource once per pass
continue
}
ready := true
for _, dep := range depMap[k] {
if _, d := done[dep]; !d {
glog.V(4).Infof("dependency %q of %q not deleted; skipping", dep, k)
ready = false
}
}
if !ready {
continue
}
phase[k] = r
}
if len(phase) == 0 {
break
}
groups := make(map[string][]*ResourceTracker)
for k, t := range phase {
groupKey := t.groupKey
if groupKey == "" {
groupKey = "_" + k
}
groups[groupKey] = append(groups[groupKey], t)
}
var wg sync.WaitGroup
for _, trackers := range groups {
wg.Add(1)
go func(trackers []*ResourceTracker) {
mutex.Lock()
for _, t := range trackers {
k := t.Type + ":" + t.ID
failed[k] = t
}
mutex.Unlock()
defer wg.Done()
human := trackers[0].Type + ":" + trackers[0].ID
var err error
if trackers[0].groupDeleter != nil {
err = trackers[0].groupDeleter(c.Cloud, trackers)
} else {
if len(trackers) != 1 {
glog.Fatalf("found group without groupKey")
}
err = trackers[0].deleter(c.Cloud, trackers[0])
}
if err != nil {
mutex.Lock()
if IsDependencyViolation(err) {
fmt.Printf("%s\tstill has dependencies, will retry\n", human)
glog.V(4).Infof("API call made when had dependency: %s", human)
} else {
fmt.Printf("%s\terror deleting resource, will retry: %v\n", human, err)
}
for _, t := range trackers {
k := t.Type + ":" + t.ID
failed[k] = t
}
mutex.Unlock()
} else {
mutex.Lock()
fmt.Printf("%s\tok\n", human)
iterationsWithNoProgress = 0
for _, t := range trackers {
k := t.Type + ":" + t.ID
delete(failed, k)
done[k] = t
}
mutex.Unlock()
}
}(trackers)
}
wg.Wait()
}
if len(resources) == len(done) {
return nil
}
fmt.Printf("Not all resources deleted; waiting before reattempting deletion\n")
for k := range resources {
if _, d := done[k]; d {
continue
}
fmt.Printf("\t%s\n", k)
}
iterationsWithNoProgress++
if iterationsWithNoProgress > 42 {
return fmt.Errorf("Not making progress deleting resources; giving up")
}
time.Sleep(10 * time.Second)
}
}
func matchesAsgTags(tags map[string]string, actual []*autoscaling.TagDescription) bool {
for k, v := range tags {
found := false
for _, a := range actual {
if aws.StringValue(a.Key) == k {
if aws.StringValue(a.Value) == v {
found = true
break
}
}
}
if !found {
return false
}
}
return true
}
func matchesElbTags(tags map[string]string, actual []*elb.Tag) bool {
for k, v := range tags {
found := false
for _, a := range actual {
if aws.StringValue(a.Key) == k {
if aws.StringValue(a.Value) == v {
found = true
break
}
}
}
if !found {
return false
}
}
return true
}
//type DeletableResource interface {
// Delete(cloud fi.Cloud) error
//}
func DeleteInstance(cloud fi.Cloud, t *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
id := t.ID
glog.V(2).Infof("Deleting EC2 instance %q", id)
request := &ec2.TerminateInstancesInput{
InstanceIds: []*string{&id},
}
_, err := c.EC2().TerminateInstances(request)
if err != nil {
if awsup.AWSErrorCode(err) == "InvalidInstanceID.NotFound" {
glog.V(2).Infof("Got InvalidInstanceID.NotFound error deleting instance %q; will treat as already-deleted", id)
} else {
return fmt.Errorf("error deleting Instance %q: %v", id, err)
}
}
return nil
}
func ListInstances(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) {
c := cloud.(awsup.AWSCloud)
glog.V(2).Infof("Querying EC2 instances")
request := &ec2.DescribeInstancesInput{
Filters: buildEC2Filters(cloud),
}
var trackers []*ResourceTracker
err := c.EC2().DescribeInstancesPages(request, func(p *ec2.DescribeInstancesOutput, lastPage bool) bool {
for _, reservation := range p.Reservations {
for _, instance := range reservation.Instances {
id := aws.StringValue(instance.InstanceId)
if instance.State != nil {
stateName := aws.StringValue(instance.State.Name)
switch stateName {
case "terminated", "shutting-down":
continue
case "running", "stopped":
// We need to delete
glog.V(4).Infof("instance %q has state=%q", id, stateName)
default:
glog.Infof("unknown instance state for %q: %q", id, stateName)
}
}
tracker := &ResourceTracker{
Name: FindName(instance.Tags),
ID: id,
Type: "instance",
deleter: DeleteInstance,
}
var blocks []string
blocks = append(blocks, "vpc:"+aws.StringValue(instance.VpcId))
for _, volume := range instance.BlockDeviceMappings {
if volume.Ebs == nil {
continue
}
blocks = append(blocks, "volume:"+aws.StringValue(volume.Ebs.VolumeId))
}
for _, sg := range instance.SecurityGroups {
blocks = append(blocks, "security-group:"+aws.StringValue(sg.GroupId))
}
blocks = append(blocks, "subnet:"+aws.StringValue(instance.SubnetId))
blocks = append(blocks, "vpc:"+aws.StringValue(instance.VpcId))
tracker.blocks = blocks
trackers = append(trackers, tracker)
}
}
return true
})
if err != nil {
return nil, fmt.Errorf("error describing instances: %v", err)
}
return trackers, nil
}
func DeleteSecurityGroup(cloud fi.Cloud, t *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
id := t.ID
// First clear all inter-dependent rules
// TODO: Move to a "pre-execute" phase?
{
request := &ec2.DescribeSecurityGroupsInput{
GroupIds: []*string{&id},
}
response, err := c.EC2().DescribeSecurityGroups(request)
if err != nil {
return fmt.Errorf("error describing SecurityGroup %q: %v", id, err)
}
if len(response.SecurityGroups) == 0 {
return nil
}
if len(response.SecurityGroups) != 1 {
return fmt.Errorf("found mutiple SecurityGroups with ID %q", id)
}
sg := response.SecurityGroups[0]
if len(sg.IpPermissions) != 0 {
revoke := &ec2.RevokeSecurityGroupIngressInput{
GroupId: &id,
IpPermissions: sg.IpPermissions,
}
_, err = c.EC2().RevokeSecurityGroupIngress(revoke)
if err != nil {
return fmt.Errorf("cannot revoke ingress for ID %q: %v", id, err)
}
}
}
{
glog.V(2).Infof("Deleting EC2 SecurityGroup %q", id)
request := &ec2.DeleteSecurityGroupInput{
GroupId: &id,
}
_, err := c.EC2().DeleteSecurityGroup(request)
if err != nil {
if IsDependencyViolation(err) {
return err
}
return fmt.Errorf("error deleting SecurityGroup %q: %v", id, err)
}
}
return nil
}
func ListSecurityGroups(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) {
groups, err := DescribeSecurityGroups(cloud)
if err != nil {
return nil, err
}
var trackers []*ResourceTracker
for _, sg := range groups {
tracker := &ResourceTracker{
Name: FindName(sg.Tags),
ID: aws.StringValue(sg.GroupId),
Type: "security-group",
deleter: DeleteSecurityGroup,
}
var blocks []string
blocks = append(blocks, "vpc:"+aws.StringValue(sg.VpcId))
tracker.blocks = blocks
trackers = append(trackers, tracker)
}
return trackers, nil
}
func DescribeSecurityGroups(cloud fi.Cloud) ([]*ec2.SecurityGroup, error) {
c := cloud.(awsup.AWSCloud)
glog.V(2).Infof("Listing EC2 SecurityGroups")
request := &ec2.DescribeSecurityGroupsInput{
Filters: buildEC2Filters(cloud),
}
response, err := c.EC2().DescribeSecurityGroups(request)
if err != nil {
return nil, fmt.Errorf("error listing SecurityGroups: %v", err)
}
return response.SecurityGroups, nil
}
func DeleteVolume(cloud fi.Cloud, r *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
id := r.ID
glog.V(2).Infof("Deleting EC2 Volume %q", id)
request := &ec2.DeleteVolumeInput{
VolumeId: &id,
}
_, err := c.EC2().DeleteVolume(request)
if err != nil {
if IsDependencyViolation(err) {
return err
}
if awsup.AWSErrorCode(err) == "InvalidVolume.NotFound" {
// Concurrently deleted
return nil
}
return fmt.Errorf("error deleting Volume %q: %v", id, err)
}
return nil
}
func ListVolumes(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) {
c := cloud.(awsup.AWSCloud)
volumes, err := DescribeVolumes(cloud)
if err != nil {
return nil, err
}
var trackers []*ResourceTracker
elasticIPs := make(map[string]bool)
for _, volume := range volumes {
id := aws.StringValue(volume.VolumeId)
tracker := &ResourceTracker{
Name: FindName(volume.Tags),
ID: id,
Type: "volume",
deleter: DeleteVolume,
}
var blocks []string
//blocks = append(blocks, "vpc:" + aws.StringValue(rt.VpcId))
tracker.blocks = blocks
trackers = append(trackers, tracker)
// Check for an elastic IP tag
for _, tag := range volume.Tags {
name := aws.StringValue(tag.Key)
ip := ""
if name == "kubernetes.io/master-ip" {
ip = aws.StringValue(tag.Value)
}
if ip != "" {
elasticIPs[ip] = true
}
}
}
if len(elasticIPs) != 0 {
glog.V(2).Infof("Querying EC2 Elastic IPs")
request := &ec2.DescribeAddressesInput{}
response, err := c.EC2().DescribeAddresses(request)
if err != nil {
return nil, fmt.Errorf("error describing addresses: %v", err)
}
for _, address := range response.Addresses {
ip := aws.StringValue(address.PublicIp)
if !elasticIPs[ip] {
continue
}
tracker := &ResourceTracker{
Name: ip,
ID: aws.StringValue(address.AllocationId),
Type: "elastic-ip",
deleter: DeleteElasticIP,
}
trackers = append(trackers, tracker)
}
}
return trackers, nil
}
func DescribeVolumes(cloud fi.Cloud) ([]*ec2.Volume, error) {
c := cloud.(awsup.AWSCloud)
var volumes []*ec2.Volume
glog.V(2).Infof("Listing EC2 Volumes")
request := &ec2.DescribeVolumesInput{
Filters: buildEC2Filters(c),
}
err := c.EC2().DescribeVolumesPages(request, func(p *ec2.DescribeVolumesOutput, lastPage bool) bool {
for _, volume := range p.Volumes {
volumes = append(volumes, volume)
}
return true
})
if err != nil {
return nil, fmt.Errorf("error describing volumes: %v", err)
}
return volumes, nil
}
func DeleteKeypair(cloud fi.Cloud, r *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
name := r.Name
glog.V(2).Infof("Deleting EC2 Keypair %q", name)
request := &ec2.DeleteKeyPairInput{
KeyName: &name,
}
_, err := c.EC2().DeleteKeyPair(request)
if err != nil {
return fmt.Errorf("error deleting KeyPair %q: %v", name, err)
}
return nil
}
func ListKeypairs(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) {
if !strings.Contains(clusterName, ".") {
glog.Infof("cluster %q is legacy (kube-up) cluster; won't delete keypairs", clusterName)
return nil, nil
}
c := cloud.(awsup.AWSCloud)
keypairName := "kubernetes." + clusterName
glog.V(2).Infof("Listing EC2 Keypairs")
request := &ec2.DescribeKeyPairsInput{
// We need to match both the name and a prefix
//Filters: []*ec2.Filter{awsup.NewEC2Filter("key-name", keypairName)},
}
response, err := c.EC2().DescribeKeyPairs(request)
if err != nil {
return nil, fmt.Errorf("error listing KeyPairs: %v", err)
}
var trackers []*ResourceTracker
for _, keypair := range response.KeyPairs {
name := aws.StringValue(keypair.KeyName)
if name != keypairName && !strings.HasPrefix(name, keypairName+"-") {
continue
}
tracker := &ResourceTracker{
Name: name,
ID: name,
Type: "keypair",
deleter: DeleteKeypair,
}
trackers = append(trackers, tracker)
}
return trackers, nil
}
func IsDependencyViolation(err error) bool {
code := awsup.AWSErrorCode(err)
switch code {
case "":
return false
case "DependencyViolation", "VolumeInUse", "InvalidIPAddress.InUse":
return true
default:
glog.Infof("unexpected aws error code: %q", code)
return false
}
}
func DeleteSubnet(cloud fi.Cloud, tracker *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
id := tracker.ID
glog.V(2).Infof("Deleting EC2 Subnet %q", id)
request := &ec2.DeleteSubnetInput{
SubnetId: &id,
}
_, err := c.EC2().DeleteSubnet(request)
if err != nil {
if IsDependencyViolation(err) {
return err
}
return fmt.Errorf("error deleting Subnet %q: %v", id, err)
}
return nil
}
func ListSubnets(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) {
c := cloud.(awsup.AWSCloud)
subnets, err := DescribeSubnets(cloud)
if err != nil {
return nil, fmt.Errorf("error listing subnets: %v", err)
}
var trackers []*ResourceTracker
elasticIPs := make(map[string]bool)
ngws := make(map[string]bool)
for _, subnet := range subnets {
tracker := &ResourceTracker{
Name: FindName(subnet.Tags),
ID: aws.StringValue(subnet.SubnetId),
Type: "subnet",
deleter: DeleteSubnet,
}
// Get tags and append with EIPs/NGWs as needed
for _, tag := range subnet.Tags {
name := aws.StringValue(tag.Key)
ip := ""
if name == "AssociatedElasticIp" {
ip = aws.StringValue(tag.Value)
}
if ip != "" {
elasticIPs[ip] = true
}
id := ""
if name == "AssociatedNatgateway" {
id = aws.StringValue(tag.Value)
}
if id != "" {
ngws[id] = true
}
}
var blocks []string
blocks = append(blocks, "vpc:"+aws.StringValue(subnet.VpcId))
tracker.blocks = blocks
trackers = append(trackers, tracker)
// Associated Elastic IPs
if len(elasticIPs) != 0 {
glog.V(2).Infof("Querying EC2 Elastic IPs")
request := &ec2.DescribeAddressesInput{}
response, err := c.EC2().DescribeAddresses(request)
if err != nil {
return nil, fmt.Errorf("error describing addresses: %v", err)
}
for _, address := range response.Addresses {
ip := aws.StringValue(address.PublicIp)
if !elasticIPs[ip] {
continue
}
tracker := &ResourceTracker{
Name: ip,
ID: aws.StringValue(address.AllocationId),
Type: "elastic-ip",
deleter: DeleteElasticIP,
}
trackers = append(trackers, tracker)
}
}
// Associated Nat Gateways
if len(ngws) != 0 {
glog.V(2).Infof("Querying Nat Gateways")
request := &ec2.DescribeNatGatewaysInput{}
response, err := c.EC2().DescribeNatGateways(request)
if err != nil {
return nil, fmt.Errorf("error describing nat gateways: %v", err)
}
for _, ngw := range response.NatGateways {
id := aws.StringValue(ngw.NatGatewayId)
if !ngws[id] {
continue
}
tracker := &ResourceTracker{
Name: id,
ID: aws.StringValue(ngw.NatGatewayId),
Type: "natgateway",
deleter: DeleteNGW,
}
trackers = append(trackers, tracker)
}
}
}
return trackers, nil
}
func DescribeSubnets(cloud fi.Cloud) ([]*ec2.Subnet, error) {
c := cloud.(awsup.AWSCloud)
glog.V(2).Infof("Listing EC2 subnets")
request := &ec2.DescribeSubnetsInput{
Filters: buildEC2Filters(cloud),
}
response, err := c.EC2().DescribeSubnets(request)
if err != nil {
return nil, fmt.Errorf("error listing subnets: %v", err)
}
return response.Subnets, nil
}
func DeleteRouteTable(cloud fi.Cloud, r *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
id := r.ID
glog.V(2).Infof("Deleting EC2 RouteTable %q", id)
request := &ec2.DeleteRouteTableInput{
RouteTableId: &id,
}
_, err := c.EC2().DeleteRouteTable(request)
if err != nil {
if IsDependencyViolation(err) {
return err
}
return fmt.Errorf("error deleting RouteTable %q: %v", id, err)
}
return nil
}
// DescribeRouteTablesIgnoreTags returns all ec2.RouteTable, ignoring tags
func DescribeRouteTablesIgnoreTags(cloud fi.Cloud) ([]*ec2.RouteTable, error) {
c := cloud.(awsup.AWSCloud)
glog.V(2).Infof("Listing all RouteTables")
request := &ec2.DescribeRouteTablesInput{}
response, err := c.EC2().DescribeRouteTables(request)
if err != nil {
return nil, fmt.Errorf("error listing RouteTables: %v", err)
}
return response.RouteTables, nil
}
// DescribeRouteTables lists route-tables tagged for the cloud
func DescribeRouteTables(cloud fi.Cloud) ([]*ec2.RouteTable, error) {
c := cloud.(awsup.AWSCloud)
glog.V(2).Infof("Listing EC2 RouteTables")
request := &ec2.DescribeRouteTablesInput{
Filters: buildEC2Filters(cloud),
}
response, err := c.EC2().DescribeRouteTables(request)
if err != nil {
return nil, fmt.Errorf("error listing RouteTables: %v", err)
}
return response.RouteTables, nil
}
func ListRouteTables(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) {
routeTables, err := DescribeRouteTables(cloud)
if err != nil {
return nil, err
}
var trackers []*ResourceTracker
for _, rt := range routeTables {
tracker := buildTrackerForRouteTable(rt)
trackers = append(trackers, tracker)
}
return trackers, nil
}
func buildTrackerForRouteTable(rt *ec2.RouteTable) *ResourceTracker {
tracker := &ResourceTracker{
Name: FindName(rt.Tags),
ID: aws.StringValue(rt.RouteTableId),
Type: "route-table",
deleter: DeleteRouteTable,
}
var blocks []string
var blocked []string
blocks = append(blocks, "vpc:"+aws.StringValue(rt.VpcId))
for _, a := range rt.Associations {
blocked = append(blocked, "subnet:"+aws.StringValue(a.SubnetId))
}
tracker.blocks = blocks
tracker.blocked = blocked
return tracker
}
func DeleteDhcpOptions(cloud fi.Cloud, r *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
id := r.ID
glog.V(2).Infof("Deleting EC2 DhcpOptions %q", id)
request := &ec2.DeleteDhcpOptionsInput{
DhcpOptionsId: &id,
}
_, err := c.EC2().DeleteDhcpOptions(request)
if err != nil {
if IsDependencyViolation(err) {
return err
}
return fmt.Errorf("error deleting DhcpOptions %q: %v", id, err)
}
return nil
}
func ListDhcpOptions(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) {
dhcpOptions, err := DescribeDhcpOptions(cloud)
if err != nil {
return nil, err
}
var trackers []*ResourceTracker
for _, o := range dhcpOptions {
tracker := &ResourceTracker{
Name: FindName(o.Tags),
ID: aws.StringValue(o.DhcpOptionsId),
Type: "dhcp-options",
deleter: DeleteDhcpOptions,
}
var blocks []string
tracker.blocks = blocks
trackers = append(trackers, tracker)
}
return trackers, nil
}
func DescribeDhcpOptions(cloud fi.Cloud) ([]*ec2.DhcpOptions, error) {
c := cloud.(awsup.AWSCloud)
glog.V(2).Infof("Listing EC2 DhcpOptions")
request := &ec2.DescribeDhcpOptionsInput{
Filters: buildEC2Filters(cloud),
}
response, err := c.EC2().DescribeDhcpOptions(request)
if err != nil {
return nil, fmt.Errorf("error listing DhcpOptions: %v", err)
}
return response.DhcpOptions, nil
}
func DeleteInternetGateway(cloud fi.Cloud, r *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
id := r.ID
var igw *ec2.InternetGateway
{
request := &ec2.DescribeInternetGatewaysInput{
InternetGatewayIds: []*string{&id},
}
response, err := c.EC2().DescribeInternetGateways(request)
if err != nil {
if awsup.AWSErrorCode(err) == "InvalidInternetGatewayID.NotFound" {
glog.Infof("Internet gateway %q not found; assuming already deleted", id)
return nil
}
return fmt.Errorf("error describing InternetGateway %q: %v", id, err)
}
if response == nil || len(response.InternetGateways) == 0 {
return nil
}
if len(response.InternetGateways) != 1 {
return fmt.Errorf("found multiple InternetGateways with id %q", id)
}
igw = response.InternetGateways[0]
}
for _, a := range igw.Attachments {
glog.V(2).Infof("Detaching EC2 InternetGateway %q", id)
request := &ec2.DetachInternetGatewayInput{
InternetGatewayId: &id,
VpcId: a.VpcId,
}
_, err := c.EC2().DetachInternetGateway(request)
if err != nil {
if IsDependencyViolation(err) {
return err
}
return fmt.Errorf("error detaching InternetGateway %q: %v", id, err)
}
}
{
glog.V(2).Infof("Deleting EC2 InternetGateway %q", id)
request := &ec2.DeleteInternetGatewayInput{
InternetGatewayId: &id,
}
_, err := c.EC2().DeleteInternetGateway(request)
if err != nil {
if IsDependencyViolation(err) {
return err
}
if awsup.AWSErrorCode(err) == "InvalidInternetGatewayID.NotFound" {
glog.Infof("Internet gateway %q not found; assuming already deleted", id)
return nil
}
return fmt.Errorf("error deleting InternetGateway %q: %v", id, err)
}
}
return nil
}
func ListInternetGateways(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) {
gateways, err := DescribeInternetGateways(cloud)
if err != nil {
return nil, err
}
var trackers []*ResourceTracker
for _, o := range gateways {
tracker := &ResourceTracker{
Name: FindName(o.Tags),
ID: aws.StringValue(o.InternetGatewayId),
Type: "internet-gateway",
deleter: DeleteInternetGateway,
}
var blocks []string
for _, a := range o.Attachments {
if aws.StringValue(a.VpcId) != "" {
blocks = append(blocks, "vpc:"+aws.StringValue(a.VpcId))
}
}
tracker.blocks = blocks
trackers = append(trackers, tracker)
}
return trackers, nil
}
func DescribeInternetGateways(cloud fi.Cloud) ([]*ec2.InternetGateway, error) {
c := cloud.(awsup.AWSCloud)
glog.V(2).Infof("Listing EC2 InternetGateways")
request := &ec2.DescribeInternetGatewaysInput{
Filters: buildEC2Filters(cloud),
}
response, err := c.EC2().DescribeInternetGateways(request)
if err != nil {
return nil, fmt.Errorf("error listing InternetGateway: %v", err)
}
var gateways []*ec2.InternetGateway
for _, o := range response.InternetGateways {
gateways = append(gateways, o)
}
return gateways, nil
}
// DescribeInternetGatewaysIgnoreTags returns all ec2.InternetGateways, ignoring tags
// (gateways were not always tagged in kube-up)
func DescribeInternetGatewaysIgnoreTags(cloud fi.Cloud) ([]*ec2.InternetGateway, error) {
c := cloud.(awsup.AWSCloud)
glog.V(2).Infof("Listing all Internet Gateways")
request := &ec2.DescribeInternetGatewaysInput{}
response, err := c.EC2().DescribeInternetGateways(request)
if err != nil {
return nil, fmt.Errorf("error listing (all) InternetGateways: %v", err)
}
var gateways []*ec2.InternetGateway
for _, igw := range response.InternetGateways {
gateways = append(gateways, igw)
}
return gateways, nil
}
func DeleteVPC(cloud fi.Cloud, r *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
id := r.ID
glog.V(2).Infof("Deleting EC2 VPC %q", id)
request := &ec2.DeleteVpcInput{
VpcId: &id,
}
_, err := c.EC2().DeleteVpc(request)
if err != nil {
if IsDependencyViolation(err) {
return err
}
return fmt.Errorf("error deleting VPC %q: %v", id, err)
}
return nil
}
func DescribeVPCs(cloud fi.Cloud) ([]*ec2.Vpc, error) {
c := cloud.(awsup.AWSCloud)
glog.V(2).Infof("Listing EC2 VPC")
request := &ec2.DescribeVpcsInput{
Filters: buildEC2Filters(cloud),
}
response, err := c.EC2().DescribeVpcs(request)
if err != nil {
return nil, fmt.Errorf("error listing VPCs: %v", err)
}
return response.Vpcs, nil
}
func ListVPCs(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) {
vpcs, err := DescribeVPCs(cloud)
if err != nil {
return nil, err
}
var trackers []*ResourceTracker
for _, v := range vpcs {
tracker := &ResourceTracker{
Name: FindName(v.Tags),
ID: aws.StringValue(v.VpcId),
Type: "vpc",
deleter: DeleteVPC,
}
var blocks []string
blocks = append(blocks, "dhcp-options:"+aws.StringValue(v.DhcpOptionsId))
tracker.blocks = blocks
trackers = append(trackers, tracker)
}
return trackers, nil
}
func DeleteAutoScalingGroup(cloud fi.Cloud, r *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
id := r.ID
glog.V(2).Infof("Deleting autoscaling group %q", id)
request := &autoscaling.DeleteAutoScalingGroupInput{
AutoScalingGroupName: &id,
ForceDelete: aws.Bool(true),
}
_, err := c.Autoscaling().DeleteAutoScalingGroup(request)
if err != nil {
if IsDependencyViolation(err) {
return err
}
return fmt.Errorf("error deleting autoscaling group %q: %v", id, err)
}
return nil
}
func ListAutoScalingGroups(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) {
c := cloud.(awsup.AWSCloud)
tags := c.Tags()
asgs, err := findAutoscalingGroups(c, tags)
if err != nil {
return nil, err
}
var trackers []*ResourceTracker
for _, asg := range asgs {
tracker := &ResourceTracker{
Name: FindASGName(asg.Tags),
ID: aws.StringValue(asg.AutoScalingGroupName),
Type: "autoscaling-group",
deleter: DeleteAutoScalingGroup,
}
var blocks []string
subnets := aws.StringValue(asg.VPCZoneIdentifier)
for _, subnet := range strings.Split(subnets, ",") {
if subnet == "" {
continue
}
blocks = append(blocks, "subnet:"+subnet)
}
blocks = append(blocks, TypeAutoscalingLaunchConfig+":"+aws.StringValue(asg.LaunchConfigurationName))
tracker.blocks = blocks
trackers = append(trackers, tracker)
}
return trackers, nil
}
func FindAutoScalingLaunchConfigurations(cloud fi.Cloud, securityGroups sets.String) ([]*ResourceTracker, error) {
c := cloud.(awsup.AWSCloud)
glog.V(2).Infof("Finding all Autoscaling LaunchConfigurations by security group")
var trackers []*ResourceTracker
request := &autoscaling.DescribeLaunchConfigurationsInput{}
err := c.Autoscaling().DescribeLaunchConfigurationsPages(request, func(p *autoscaling.DescribeLaunchConfigurationsOutput, lastPage bool) bool {
for _, t := range p.LaunchConfigurations {
found := false
for _, sg := range t.SecurityGroups {
if securityGroups.Has(aws.StringValue(sg)) {
found = true
break
}
}
if !found {
continue
}
tracker := &ResourceTracker{
Name: aws.StringValue(t.LaunchConfigurationName),
ID: aws.StringValue(t.LaunchConfigurationName),
Type: TypeAutoscalingLaunchConfig,
deleter: DeleteAutoscalingLaunchConfiguration,
}
var blocks []string
//blocks = append(blocks, TypeAutoscalingLaunchConfig + ":" + aws.StringValue(asg.LaunchConfigurationName))
tracker.blocks = blocks
trackers = append(trackers, tracker)
}
return true
})
if err != nil {
return nil, fmt.Errorf("error listing autoscaling LaunchConfigurations: %v", err)
}
return trackers, nil
}
// extractClusterName performs string-matching / parsing to determine the ClusterName in some instance-data
// It returns "" if it could not be (uniquely) determined
func extractClusterName(userData string) string {
clusterName := ""
scanner := bufio.NewScanner(bytes.NewReader([]byte(userData)))
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "INSTANCE_PREFIX:") {
// kube-up
// Match:
// INSTANCE_PREFIX: 'clustername'
// INSTANCE_PREFIX: "clustername"
// INSTANCE_PREFIX: clustername
line = strings.TrimPrefix(line, "INSTANCE_PREFIX:")
} else if strings.HasPrefix(line, "ClusterName:") {
// kops
// Match:
// ClusterName: 'clustername'
// ClusterName: "clustername"
// ClusterName: clustername
line = strings.TrimPrefix(line, "ClusterName:")
} else {
continue
}
line = strings.TrimSpace(line)
line = strings.Trim(line, "'\"")
if clusterName != "" && clusterName != line {
glog.Warning("cannot uniquely determine cluster-name, found %q and %q", line, clusterName)
return ""
}
clusterName = line
}
if err := scanner.Err(); err != nil {
glog.Warning("error scanning UserData: %v", err)
return ""
}
return clusterName
}
func DeleteAutoscalingLaunchConfiguration(cloud fi.Cloud, r *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
id := r.ID
glog.V(2).Infof("Deleting autoscaling LaunchConfiguration %q", id)
request := &autoscaling.DeleteLaunchConfigurationInput{
LaunchConfigurationName: &id,
}
_, err := c.Autoscaling().DeleteLaunchConfiguration(request)
if err != nil {
return fmt.Errorf("error deleting autoscaling LaunchConfiguration %q: %v", id, err)
}
return nil
}
func DeleteELB(cloud fi.Cloud, r *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
id := r.ID
glog.V(2).Infof("Deleting ELB %q", id)
request := &elb.DeleteLoadBalancerInput{
LoadBalancerName: &id,
}
_, err := c.ELB().DeleteLoadBalancer(request)
if err != nil {
if IsDependencyViolation(err) {
return err
}
return fmt.Errorf("error deleting LoadBalancer %q: %v", id, err)
}
return nil
}
func ListELBs(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) {
elbs, elbTags, err := DescribeELBs(cloud)
if err != nil {
return nil, err
}
var trackers []*ResourceTracker
for _, elb := range elbs {
id := aws.StringValue(elb.LoadBalancerName)
tracker := &ResourceTracker{
Name: FindELBName(elbTags[id]),
ID: id,
Type: "load-balancer",
deleter: DeleteELB,
}
var blocks []string
for _, sg := range elb.SecurityGroups {
blocks = append(blocks, "security-group:"+aws.StringValue(sg))
}
for _, s := range elb.Subnets {
blocks = append(blocks, "subnet:"+aws.StringValue(s))
}
blocks = append(blocks, "vpc:"+aws.StringValue(elb.VPCId))
tracker.blocks = blocks
trackers = append(trackers, tracker)
}
return trackers, nil
}
func DescribeELBs(cloud fi.Cloud) ([]*elb.LoadBalancerDescription, map[string][]*elb.Tag, error) {
c := cloud.(awsup.AWSCloud)
tags := c.Tags()
glog.V(2).Infof("Listing all ELBs")
request := &elb.DescribeLoadBalancersInput{}
// ELB DescribeTags has a limit of 20 names, so we set the page size here to 20 also
request.PageSize = aws.Int64(20)
var elbs []*elb.LoadBalancerDescription
elbTags := make(map[string][]*elb.Tag)
var innerError error
err := c.ELB().DescribeLoadBalancersPages(request, func(p *elb.DescribeLoadBalancersOutput, lastPage bool) bool {
if len(p.LoadBalancerDescriptions) == 0 {
return true
}
tagRequest := &elb.DescribeTagsInput{}
nameToELB := make(map[string]*elb.LoadBalancerDescription)
for _, elb := range p.LoadBalancerDescriptions {
name := aws.StringValue(elb.LoadBalancerName)
nameToELB[name] = elb
tagRequest.LoadBalancerNames = append(tagRequest.LoadBalancerNames, elb.LoadBalancerName)
}
tagResponse, err := c.ELB().DescribeTags(tagRequest)
if err != nil {
innerError = fmt.Errorf("error listing elb Tags: %v", err)
return false
}
for _, t := range tagResponse.TagDescriptions {
elbName := aws.StringValue(t.LoadBalancerName)
if !matchesElbTags(tags, t.Tags) {
continue
}
elbTags[elbName] = t.Tags
elb := nameToELB[elbName]
elbs = append(elbs, elb)
}
return true
})
if err != nil {
return nil, nil, fmt.Errorf("error describing LoadBalancers: %v", err)
}
if innerError != nil {
return nil, nil, fmt.Errorf("error describing LoadBalancers: %v", innerError)
}
return elbs, elbTags, nil
}
func DeleteElasticIP(cloud fi.Cloud, t *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
id := t.ID
glog.V(2).Infof("Releasing IP %s", t.Name)
request := &ec2.ReleaseAddressInput{
AllocationId: &id,
}
_, err := c.EC2().ReleaseAddress(request)
if err != nil {
if IsDependencyViolation(err) {
return err
}
return fmt.Errorf("error deleting elastic ip %q: %v", t.Name, err)
}
return nil
}
func DeleteNGW(cloud fi.Cloud, t *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
id := t.ID
glog.V(2).Infof("Removing NGW %s", t.Name)
request := &ec2.DeleteNatGatewayInput{
NatGatewayId: &id,
}
_, err := c.EC2().DeleteNatGateway(request)
if err != nil {
if IsDependencyViolation(err) {
return err
}
return fmt.Errorf("error deleting ngw %q: %v", t.Name, err)
}
return nil
}
func deleteRoute53Records(cloud fi.Cloud, zone *route53.HostedZone, trackers []*ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
var changes []*route53.Change
var names []string
for _, tracker := range trackers {
names = append(names, tracker.Name)
changes = append(changes, &route53.Change{
Action: aws.String("DELETE"),
ResourceRecordSet: tracker.obj.(*route53.ResourceRecordSet),
})
}
human := strings.Join(names, ", ")
glog.V(2).Infof("Deleting route53 records %q", human)
changeBatch := &route53.ChangeBatch{
Changes: changes,
}
request := &route53.ChangeResourceRecordSetsInput{
HostedZoneId: zone.Id,
ChangeBatch: changeBatch,
}
_, err := c.Route53().ChangeResourceRecordSets(request)
if err != nil {
return fmt.Errorf("error deleting route53 record %q: %v", human, err)
}
return nil
}
func ListRoute53Records(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) {
c := cloud.(awsup.AWSCloud)
// Normalize cluster name, with leading "."
clusterName = "." + strings.TrimSuffix(clusterName, ".")
// TODO: If we have the zone id in the cluster spec, use it!
var zones []*route53.HostedZone
{
glog.V(2).Infof("Querying for all route53 zones")
request := &route53.ListHostedZonesInput{}
err := c.Route53().ListHostedZonesPages(request, func(p *route53.ListHostedZonesOutput, lastPage bool) bool {
for _, zone := range p.HostedZones {
zoneName := aws.StringValue(zone.Name)
zoneName = "." + strings.TrimSuffix(zoneName, ".")
if strings.HasSuffix(clusterName, zoneName) {
zones = append(zones, zone)
}
}
return true
})
if err != nil {
return nil, fmt.Errorf("error querying for route53 zones: %v", err)
}
}
var trackers []*ResourceTracker
for i := range zones {
// Be super careful because we close over this later (in groupDeleter)
zone := zones[i]
hostedZoneID := strings.TrimPrefix(aws.StringValue(zone.Id), "/hostedzone/")
glog.V(2).Infof("Querying for records in zone: %q", aws.StringValue(zone.Name))
request := &route53.ListResourceRecordSetsInput{
HostedZoneId: zone.Id,
}
err := c.Route53().ListResourceRecordSetsPages(request, func(p *route53.ListResourceRecordSetsOutput, lastPage bool) bool {
for _, rrs := range p.ResourceRecordSets {
if aws.StringValue(rrs.Type) != "A" {
continue
}
name := aws.StringValue(rrs.Name)
name = "." + strings.TrimSuffix(name, ".")
if !strings.HasSuffix(name, clusterName) {
continue
}
prefix := strings.TrimSuffix(name, clusterName)
remove := false
// TODO: Compute the actual set of names?
if prefix == ".api" || prefix == ".api.internal" {
remove = true
} else if strings.HasPrefix(prefix, ".etcd-") {
remove = true
}
if !remove {
continue
}
tracker := &ResourceTracker{
Name: aws.StringValue(rrs.Name),
ID: hostedZoneID + "/" + aws.StringValue(rrs.Name),
Type: "route53-record",
groupKey: hostedZoneID,
groupDeleter: func(cloud fi.Cloud, trackers []*ResourceTracker) error {
return deleteRoute53Records(cloud, zone, trackers)
},
obj: rrs,
}
trackers = append(trackers, tracker)
}
return true
})
if err != nil {
return nil, fmt.Errorf("error querying for route53 records for zone %q: %v", aws.StringValue(zone.Name), err)
}
}
return trackers, nil
}
func DeleteIAMRole(cloud fi.Cloud, r *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
roleName := r.Name
var policyNames []string
{
request := &iam.ListRolePoliciesInput{
RoleName: aws.String(roleName),
}
err := c.IAM().ListRolePoliciesPages(request, func(page *iam.ListRolePoliciesOutput, lastPage bool) bool {
for _, policy := range page.PolicyNames {
policyNames = append(policyNames, aws.StringValue(policy))
}
return true
})
if err != nil {
return fmt.Errorf("error listing IAM role policies for %q: %v", roleName, err)
}
}
for _, policyName := range policyNames {
glog.V(2).Infof("Deleting IAM role policy %q %q", roleName, policyName)
request := &iam.DeleteRolePolicyInput{
RoleName: aws.String(r.Name),
PolicyName: aws.String(policyName),
}
_, err := c.IAM().DeleteRolePolicy(request)
if err != nil {
return fmt.Errorf("error deleting IAM role policy %q %q: %v", roleName, policyName, err)
}
}
{
glog.V(2).Infof("Deleting IAM role %q", r.Name)
request := &iam.DeleteRoleInput{
RoleName: aws.String(r.Name),
}
_, err := c.IAM().DeleteRole(request)
if err != nil {
return fmt.Errorf("error deleting IAM role %q: %v", r.Name, err)
}
}
return nil
}
func ListIAMRoles(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) {
c := cloud.(awsup.AWSCloud)
remove := make(map[string]bool)
remove["masters."+clusterName] = true
remove["nodes."+clusterName] = true
var roles []*iam.Role
// Find roles matching remove map
{
request := &iam.ListRolesInput{}
err := c.IAM().ListRolesPages(request, func(p *iam.ListRolesOutput, lastPage bool) bool {
for _, r := range p.Roles {
name := aws.StringValue(r.RoleName)
if remove[name] {
roles = append(roles, r)
}
}
return true
})
if err != nil {
return nil, fmt.Errorf("error listing IAM roles: %v", err)
}
}
var trackers []*ResourceTracker
for _, role := range roles {
name := aws.StringValue(role.RoleName)
tracker := &ResourceTracker{
Name: name,
ID: name,
Type: "iam-role",
deleter: DeleteIAMRole,
}
trackers = append(trackers, tracker)
}
return trackers, nil
}
func DeleteIAMInstanceProfile(cloud fi.Cloud, r *ResourceTracker) error {
c := cloud.(awsup.AWSCloud)
profile := r.obj.(*iam.InstanceProfile)
name := aws.StringValue(profile.InstanceProfileName)
// Remove roles
{
for _, role := range profile.Roles {
glog.V(2).Infof("Removing role %q from IAM instance profile %q", aws.StringValue(role.RoleName), name)
request := &iam.RemoveRoleFromInstanceProfileInput{
InstanceProfileName: profile.InstanceProfileName,
RoleName: role.RoleName,
}
_, err := c.IAM().RemoveRoleFromInstanceProfile(request)
if err != nil {
return fmt.Errorf("error removing role %q from IAM instance profile %q: %v", aws.StringValue(role.RoleName), name, err)
}
}
}
// Delete the instance profile
{
glog.V(2).Infof("Deleting IAM instance profile %q", name)
request := &iam.DeleteInstanceProfileInput{
InstanceProfileName: profile.InstanceProfileName,
}
_, err := c.IAM().DeleteInstanceProfile(request)
if err != nil {
return fmt.Errorf("error deleting IAM instance profile %q: %v", name, err)
}
}
return nil
}
func ListIAMInstanceProfiles(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) {
c := cloud.(awsup.AWSCloud)
remove := make(map[string]bool)
remove["masters."+clusterName] = true
remove["nodes."+clusterName] = true
var profiles []*iam.InstanceProfile
request := &iam.ListInstanceProfilesInput{}
err := c.IAM().ListInstanceProfilesPages(request, func(p *iam.ListInstanceProfilesOutput, lastPage bool) bool {
for _, p := range p.InstanceProfiles {
name := aws.StringValue(p.InstanceProfileName)
if remove[name] {
profiles = append(profiles, p)
}
}
return true
})
if err != nil {
return nil, fmt.Errorf("error listing IAM instance profiles: %v", err)
}
var trackers []*ResourceTracker
for _, profile := range profiles {
name := aws.StringValue(profile.InstanceProfileName)
tracker := &ResourceTracker{
Name: name,
ID: name,
Type: "iam-instance-profile",
deleter: DeleteIAMInstanceProfile,
obj: profile,
}
trackers = append(trackers, tracker)
}
return trackers, nil
}
func FindName(tags []*ec2.Tag) string {
if name, found := awsup.FindEC2Tag(tags, "Name"); found {
return name
}
return ""
}
func FindASGName(tags []*autoscaling.TagDescription) string {
if name, found := awsup.FindASGTag(tags, "Name"); found {
return name
}
return ""
}
func FindELBName(tags []*elb.Tag) string {
if name, found := awsup.FindELBTag(tags, "Name"); found {
return name
}
return ""
}