From eb3bb41c6b6e4d77a8a2c83e4e800e5970e32226 Mon Sep 17 00:00:00 2001 From: Christian van der Leeden Date: Thu, 2 Jan 2020 18:24:08 +0100 Subject: [PATCH] kops delete will also delete the DNS entries for GCE --- pkg/resources/gce/BUILD.bazel | 2 + pkg/resources/gce/gce.go | 100 +++++++++++++++++----- upup/pkg/fi/cloudup/gce/BUILD.bazel | 1 + upup/pkg/fi/cloudup/gce/gce_cloud.go | 14 +++ upup/pkg/fi/cloudup/gce/mock_gce_cloud.go | 7 ++ 5 files changed, 103 insertions(+), 21 deletions(-) diff --git a/pkg/resources/gce/BUILD.bazel b/pkg/resources/gce/BUILD.bazel index 1c396b06de..3453df0d18 100644 --- a/pkg/resources/gce/BUILD.bazel +++ b/pkg/resources/gce/BUILD.bazel @@ -9,10 +9,12 @@ go_library( importpath = "k8s.io/kops/pkg/resources/gce", visibility = ["//visibility:public"], deps = [ + "//pkg/dns:go_default_library", "//pkg/resources:go_default_library", "//upup/pkg/fi:go_default_library", "//upup/pkg/fi/cloudup/gce:go_default_library", "//vendor/google.golang.org/api/compute/v0.beta:go_default_library", + "//vendor/google.golang.org/api/dns/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/klog:go_default_library", ], diff --git a/pkg/resources/gce/gce.go b/pkg/resources/gce/gce.go index f402af85bd..f63842f061 100644 --- a/pkg/resources/gce/gce.go +++ b/pkg/resources/gce/gce.go @@ -22,8 +22,10 @@ import ( "strings" compute "google.golang.org/api/compute/v0.beta" + clouddns "google.golang.org/api/dns/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog" + "k8s.io/kops/pkg/dns" "k8s.io/kops/pkg/resources" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/gce" @@ -42,6 +44,7 @@ const ( typeAddress = "Address" typeRoute = "Route" typeSubnet = "Subnet" + typeDNSRecord = "DNSRecord" ) // Maximum number of `-` separated tokens in a name @@ -796,25 +799,80 @@ func (d *clusterDiscoveryGCE) matchesClusterNameMultipart(name string, maxParts return false } -func (d *clusterDiscoveryGCE) listGCEDNSZone() ([]*resources.Resource, error) { - // We never delete the hosted zone, because it is usually shared and we don't create it - return nil, nil - // TODO: When shared resource PR lands, reintroduce - //if dns.IsGossipHostname(d.clusterName) { - // return nil, nil - //} - //zone, err := d.findDNSZone() - //if err != nil { - // return nil, err - //} - // - //return []*resources.Resource{ - // { - // Name: zone.Name(), - // ID: zone.Name(), - // Type: "DNS Zone", - // Deleter: d.deleteDNSZone, - // Obj: zone, - // }, - //}, nil +func (d *clusterDiscoveryGCE) clusterDNSName() string { + return d.clusterName + "." +} + +func (d *clusterDiscoveryGCE) isKopsManagedDNSName(name string) bool { + prefix := []string{`api`, `api.internal`, `bastion`} + for _, p := range prefix { + if name == p+"."+d.clusterDNSName() { + return true + } + } + return false +} + +func (d *clusterDiscoveryGCE) listGCEDNSZone() ([]*resources.Resource, error) { + + if dns.IsGossipHostname(d.clusterName) { + return nil, nil + } + + var resourceTrackers []*resources.Resource + + zoneResponse, err := d.gceCloud.CloudDNS().ManagedZones.List(d.gceCloud.Project()).Do() + if err != nil { + return nil, fmt.Errorf("error getting GCE DNS zones %v", err) + } + + for _, zone := range zoneResponse.ManagedZones { + if !strings.HasSuffix(d.clusterDNSName(), zone.DnsName) { + continue + } + response, err := d.gceCloud.CloudDNS().ResourceRecordSets.List(d.gceCloud.Project(), zone.Name).Do() + if err != nil { + return nil, fmt.Errorf("error getting GCE DNS zone data %v", err) + } + + for _, record := range response.Rrsets { + // adapted from AWS implementation + if record.Type != "A" { + continue + } + + if d.isKopsManagedDNSName(record.Name) { + resource := resources.Resource{ + Name: zone.Name, + ID: record.Name, + Type: typeDNSRecord, + GroupDeleter: deleteDNSRecords, + GroupKey: zone.Name, + Obj: record, + } + resourceTrackers = append(resourceTrackers, &resource) + } + } + } + + return resourceTrackers, nil +} + +func deleteDNSRecords(cloud fi.Cloud, r []*resources.Resource) error { + c := cloud.(gce.GCECloud) + var records []*clouddns.ResourceRecordSet + var zoneName string + + for _, record := range r { + r := record.Obj.(*clouddns.ResourceRecordSet) + zoneName = record.Name + records = append(records, r) + } + + change := clouddns.Change{Deletions: records, Kind: "dns#change", IsServing: true} + _, err := c.CloudDNS().Changes.Create(c.Project(), zoneName, &change).Do() + if err != nil { + return fmt.Errorf("error deleting GCE DNS resource record set %v", err) + } + return nil } diff --git a/upup/pkg/fi/cloudup/gce/BUILD.bazel b/upup/pkg/fi/cloudup/gce/BUILD.bazel index edcefb722c..821b56dbbf 100644 --- a/upup/pkg/fi/cloudup/gce/BUILD.bazel +++ b/upup/pkg/fi/cloudup/gce/BUILD.bazel @@ -27,6 +27,7 @@ go_library( "//vendor/golang.org/x/net/context:go_default_library", "//vendor/golang.org/x/oauth2/google:go_default_library", "//vendor/google.golang.org/api/compute/v0.beta:go_default_library", + "//vendor/google.golang.org/api/dns/v1:go_default_library", "//vendor/google.golang.org/api/googleapi:go_default_library", "//vendor/google.golang.org/api/iam/v1:go_default_library", "//vendor/google.golang.org/api/oauth2/v2:go_default_library", diff --git a/upup/pkg/fi/cloudup/gce/gce_cloud.go b/upup/pkg/fi/cloudup/gce/gce_cloud.go index d1a763e383..3a2f48c5bf 100644 --- a/upup/pkg/fi/cloudup/gce/gce_cloud.go +++ b/upup/pkg/fi/cloudup/gce/gce_cloud.go @@ -26,6 +26,7 @@ import ( "golang.org/x/net/context" "golang.org/x/oauth2/google" compute "google.golang.org/api/compute/v0.beta" + "google.golang.org/api/dns/v1" "google.golang.org/api/iam/v1" oauth2 "google.golang.org/api/oauth2/v2" "google.golang.org/api/storage/v1" @@ -41,6 +42,7 @@ type GCECloud interface { Compute() *compute.Service Storage() *storage.Service IAM() *iam.Service + CloudDNS() *dns.Service Project() string WaitForOp(op *compute.Operation) error @@ -60,6 +62,7 @@ type gceCloudImplementation struct { compute *compute.Service storage *storage.Service iam *iam.Service + dns *dns.Service region string project string @@ -141,6 +144,12 @@ func NewGCECloud(region string, project string, labels map[string]string) (GCECl } c.iam = iamService + dnsService, err := dns.New(client) + if err != nil { + return nil, fmt.Errorf("error building DNS API client: %v", err) + } + c.dns = dnsService + gceCloudInstances[region+"::"+project] = c { @@ -186,6 +195,11 @@ func (c *gceCloudImplementation) IAM() *iam.Service { return c.iam } +// NameService returns the DNS client +func (c *gceCloudImplementation) CloudDNS() *dns.Service { + return c.dns +} + // Region returns private struct element region. func (c *gceCloudImplementation) Region() string { return c.region diff --git a/upup/pkg/fi/cloudup/gce/mock_gce_cloud.go b/upup/pkg/fi/cloudup/gce/mock_gce_cloud.go index d54b70d8a3..c525222aa2 100644 --- a/upup/pkg/fi/cloudup/gce/mock_gce_cloud.go +++ b/upup/pkg/fi/cloudup/gce/mock_gce_cloud.go @@ -20,6 +20,7 @@ import ( "fmt" compute "google.golang.org/api/compute/v0.beta" + "google.golang.org/api/dns/v1" "google.golang.org/api/iam/v1" "google.golang.org/api/storage/v1" v1 "k8s.io/api/core/v1" @@ -105,6 +106,12 @@ func (c *mockGCECloud) IAM() *iam.Service { return nil } +// NameService returns the DNS client +func (c *mockGCECloud) CloudDNS() *dns.Service { + klog.Fatalf("mockGCECloud::CloudDNS not implemented") + return nil +} + // WaitForOp implements GCECloud::WaitForOp func (c *mockGCECloud) WaitForOp(op *compute.Operation) error { return fmt.Errorf("mockGCECloud::WaitForOp not implemented")