diff --git a/upup/cmd/upup/delete_cluster.go b/upup/cmd/upup/delete_cluster.go index da58bff635..5b45599c43 100644 --- a/upup/cmd/upup/delete_cluster.go +++ b/upup/cmd/upup/delete_cluster.go @@ -6,7 +6,9 @@ import ( "bytes" "github.com/golang/glog" "github.com/spf13/cobra" + "k8s.io/kube-deploy/upup/pkg/api" "k8s.io/kube-deploy/upup/pkg/fi" + "k8s.io/kube-deploy/upup/pkg/fi/cloudup" "k8s.io/kube-deploy/upup/pkg/fi/cloudup/awsup" "k8s.io/kube-deploy/upup/pkg/kutil" "os" @@ -15,8 +17,9 @@ import ( ) type DeleteClusterCmd struct { - Yes bool - Region string + Yes bool + Region string + External bool } var deleteCluster DeleteClusterCmd @@ -38,30 +41,59 @@ func init() { cmd.Flags().BoolVar(&deleteCluster.Yes, "yes", false, "Delete without confirmation") + cmd.Flags().BoolVar(&deleteCluster.External, "external", false, "Delete an external cluster") + cmd.Flags().StringVar(&deleteCluster.Region, "region", "", "region") } type getter func(o interface{}) interface{} func (c *DeleteClusterCmd) Run() error { - if c.Region == "" { - return fmt.Errorf("--region is required") - } - clusterName := rootCommand.clusterName - if clusterName == "" { - return fmt.Errorf("--name is required") - } + var stateStore fi.StateStore + var err error - tags := map[string]string{"KubernetesCluster": clusterName} - cloud, err := awsup.NewAWSCloud(c.Region, tags) - if err != nil { - return fmt.Errorf("error initializing AWS client: %v", err) + var cloud fi.Cloud + clusterName := "" + region := "" + if c.External { + region = c.Region + if region == "" { + return fmt.Errorf("--region is required") + } + clusterName := rootCommand.clusterName + if clusterName == "" { + return fmt.Errorf("--name is required (when --external)") + } + + tags := map[string]string{"KubernetesCluster": clusterName} + cloud, err = awsup.NewAWSCloud(c.Region, tags) + if err != nil { + return fmt.Errorf("error initializing AWS client: %v", err) + } + } else { + stateStore, err = rootCommand.StateStore() + if err != nil { + return err + } + + cluster, _, err := api.ReadConfig(stateStore) + if err != nil { + return err + } + + if rootCommand.clusterName != cluster.Name { + return fmt.Errorf("sanity check failed: cluster name mismatch") + } + + cloud, err = cloudup.BuildCloud(cluster) + if err != nil { + return err + } } d := &kutil.DeleteCluster{} - d.ClusterName = clusterName - d.Region = c.Region + d.Region = region d.Cloud = cloud resources, err := d.ListResources() @@ -71,68 +103,84 @@ func (c *DeleteClusterCmd) Run() error { if len(resources) == 0 { fmt.Printf("Nothing to delete\n") - return nil - } + } else { + columns := []string{"TYPE", "ID", "NAME"} + fields := []string{"Type", "ID", "Name"} - columns := []string{"TYPE", "ID", "NAME"} - fields := []string{"Type", "ID", "Name"} + var b bytes.Buffer + w := new(tabwriter.Writer) - var b bytes.Buffer - w := new(tabwriter.Writer) + // Format in tab-separated columns with a tab stop of 8. + w.Init(os.Stdout, 0, 8, 0, '\t', tabwriter.StripEscape) - // Format in tab-separated columns with a tab stop of 8. - w.Init(os.Stdout, 0, 8, 0, '\t', tabwriter.StripEscape) - - writeHeader := true - if writeHeader { - for i, c := range columns { - if i != 0 { - b.WriteByte('\t') + writeHeader := true + if writeHeader { + for i, c := range columns { + if i != 0 { + b.WriteByte('\t') + } + b.WriteByte(tabwriter.Escape) + b.WriteString(c) + b.WriteByte(tabwriter.Escape) } - b.WriteByte(tabwriter.Escape) - b.WriteString(c) - b.WriteByte(tabwriter.Escape) - } - b.WriteByte('\n') + b.WriteByte('\n') - _, err := w.Write(b.Bytes()) + _, err := w.Write(b.Bytes()) + if err != nil { + return fmt.Errorf("error writing to output: %v", err) + } + b.Reset() + } + + for _, t := range resources { + for i := range columns { + if i != 0 { + b.WriteByte('\t') + } + + v := reflect.ValueOf(t) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + fv := v.FieldByName(fields[i]) + + s := fi.ValueAsString(fv) + + b.WriteByte(tabwriter.Escape) + b.WriteString(s) + b.WriteByte(tabwriter.Escape) + } + b.WriteByte('\n') + + _, err := w.Write(b.Bytes()) + if err != nil { + return fmt.Errorf("error writing to output: %v", err) + } + b.Reset() + } + w.Flush() + + if !c.Yes { + return fmt.Errorf("Must specify --yes to delete") + } + + err := d.DeleteResources(resources) if err != nil { - return fmt.Errorf("error writing to output: %v", err) + return err } - b.Reset() } - for _, t := range resources { - for i := range columns { - if i != 0 { - b.WriteByte('\t') - } - - v := reflect.ValueOf(t) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - fv := v.FieldByName(fields[i]) - - s := fi.ValueAsString(fv) - - b.WriteByte(tabwriter.Escape) - b.WriteString(s) - b.WriteByte(tabwriter.Escape) + if stateStore != nil { + if !c.Yes { + return fmt.Errorf("Must specify --yes to delete") } - b.WriteByte('\n') - - _, err := w.Write(b.Bytes()) + err := api.DeleteConfig(stateStore) if err != nil { - return fmt.Errorf("error writing to output: %v", err) + return fmt.Errorf("error removing cluster from state store: %v", err) } - b.Reset() - } - w.Flush() - - if !c.Yes { - return fmt.Errorf("Must specify --yes to delete") } - return d.DeleteResources(resources) + fmt.Printf("\nCluster deleted\n") + + return nil } diff --git a/upup/pkg/kutil/delete_cluster.go b/upup/pkg/kutil/delete_cluster.go index 46cf1cebbc..299568ab14 100644 --- a/upup/pkg/kutil/delete_cluster.go +++ b/upup/pkg/kutil/delete_cluster.go @@ -96,20 +96,17 @@ func (c *DeleteCluster) ListResources() (map[string]*ResourceTracker, error) { } } - // TODO: Move to ListUntaggedInternetGateways? { // 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) - glog.V(2).Infof("Listing all Internet Gateways") - request := &ec2.DescribeInternetGatewaysInput{} - response, err := cloud.EC2.DescribeInternetGateways(request) + gateways, err := DescribeInternetGatewaysIgnoreTags(cloud) if err != nil { - return nil, fmt.Errorf("error listing InternetGateways: %v", err) + return nil, err } - for _, igw := range response.InternetGateways { + for _, igw := range gateways { for _, attachment := range igw.Attachments { vpcID := aws.StringValue(attachment.VpcId) igwID := aws.StringValue(igw.InternetGatewayId) @@ -336,7 +333,8 @@ func ListInstances(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, erro continue case "running": - // Fine + case "stopped": + // We need to delete glog.V(4).Infof("instance %q has state=%q", id, stateName) default: @@ -847,20 +845,14 @@ func DeleteInternetGateway(cloud fi.Cloud, r *ResourceTracker) error { } func ListInternetGateways(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) { - c := cloud.(*awsup.AWSCloud) - - glog.V(2).Infof("Listing EC2 InternetGateways") - request := &ec2.DescribeInternetGatewaysInput{ - Filters: buildEC2Filters(cloud), - } - response, err := c.EC2.DescribeInternetGateways(request) + gateways, err := DescribeInternetGateways(cloud) if err != nil { - return nil, fmt.Errorf("error listing InternetGateway: %v", err) + return nil, err } var trackers []*ResourceTracker - for _, o := range response.InternetGateways { + for _, o := range gateways { tracker := &ResourceTracker{ Name: FindName(o.Tags), ID: aws.StringValue(o.InternetGatewayId), @@ -882,6 +874,48 @@ func ListInternetGateways(cloud fi.Cloud, clusterName string) ([]*ResourceTracke 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) @@ -1082,15 +1116,49 @@ func DeleteELB(cloud fi.Cloud, r *ResourceTracker) error { } func ListELBs(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) { - c := cloud.(*awsup.AWSCloud) - tags := c.Tags() + 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{} + 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 { @@ -1120,37 +1188,21 @@ func ListELBs(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error) { continue } - tracker := &ResourceTracker{ - Name: FindELBName(t.Tags), - ID: elbName, - Type: "load-balancer", - deleter: DeleteELB, - } + elbTags[elbName] = t.Tags elb := nameToELB[elbName] - 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) + elbs = append(elbs, elb) } return true }) if err != nil { - return nil, fmt.Errorf("error describing LoadBalances: %v", err) + return nil, nil, fmt.Errorf("error describing LoadBalancers: %v", err) } if innerError != nil { - return nil, fmt.Errorf("error describing LoadBalancers: %v", innerError) + return nil, nil, fmt.Errorf("error describing LoadBalancers: %v", innerError) } - return trackers, nil + return elbs, elbTags, nil } func DeleteElasticIP(cloud fi.Cloud, t *ResourceTracker) error {