Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Google CloudDNS): add routing policy support #4928

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions docs/tutorials/google.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
## Annotations

Annotations which are specific to the Google
[CloudDNS](https://cloud.google.com/dns/docs/overview) provider.

### Routing Policy

The [routing policy](https://cloud.google.com/dns/docs/routing-policies-overview)
for resource record sets managed by ExternalDNS may be specified by applying the
`external-dns.alpha.kubernetes.io/google-routing-policy` annotation on any of the
supported [sources](../sources/about.md).

#### Geolocation routing policies

Specifying a value of `geo` for the `external-dns.alpha.kubernetes.io/google-routing-policy`
annotation will enable geolocation routing for associated resource record sets. The
location attributed to resource record sets may be deduced for instances of ExternalDNS
running within the Google Cloud platform or may be specified via the `--google-location`
command-line argument. Alternatively, a location may be explicitly specified via the
`external-dns.alpha.kubernetes.io/google-location` annotation, where the value is one
of Google Cloud's [locations/regions](https://cloud.google.com/docs/geography-and-regions).

For example:
```yaml
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: geo-example
annotations:
external-dns.alpha.kubernetes.io/google-routing-policy: "geo"
external-dns.alpha.kubernetes.io/google-location: "us-east1"
```

#### Weighted Round Robin routing policies

Specifying a value of `wrr` for the `external-dns.alpha.kubernetes.io/google-routing-policy`
annotation will enable weighted round-robin routing for associated resource record sets.
The weight to be attributed to resource record sets may be specified via the
`external-dns.alpha.kubernetes.io/google-weight` annotation, where the value is a string
representation of a floating-point number. The `external-dns.alpha.kubernetes.io/set-identifier`
annotation must also be applied providing a string value representation of an index into
the list of potential responses.

For example:
```yaml
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: wrr-example
annotations:
external-dns.alpha.kubernetes.io/google-routing-policy: "wrr"
external-dns.alpha.kubernetes.io/google-weight: "100.0"
external-dns.alpha.kubernetes.io/set-identifier: "0"
```
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ func main() {
case "cloudflare":
p, err = cloudflare.NewCloudFlareProvider(domainFilter, zoneIDFilter, cfg.CloudflareProxied, cfg.DryRun, cfg.CloudflareDNSRecordsPerPage, cfg.CloudflareRegionKey)
case "google":
p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.GoogleZoneVisibility, cfg.DryRun)
p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, cfg.GoogleLocation, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.GoogleZoneVisibility, cfg.DryRun)
case "digitalocean":
p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun, cfg.DigitalOceanAPIPageSize)
case "ovh":
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type Config struct {
Provider string
ProviderCacheTime time.Duration
GoogleProject string
GoogleLocation string
GoogleBatchChangeSize int
GoogleBatchChangeInterval time.Duration
GoogleZoneVisibility string
Expand Down Expand Up @@ -234,6 +235,7 @@ var defaultConfig = &Config{
Provider: "",
ProviderCacheTime: 0,
GoogleProject: "",
GoogleLocation: "",
GoogleBatchChangeSize: 1000,
GoogleBatchChangeInterval: time.Second,
GoogleZoneVisibility: "",
Expand Down Expand Up @@ -463,6 +465,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("zone-name-filter", "Filter target zones by zone domain (For now, only AzureDNS provider is using this flag); specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneNameFilter)
app.Flag("zone-id-filter", "Filter target zones by hosted zone id; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneIDFilter)
app.Flag("google-project", "When using the Google provider, current project is auto-detected, when running on GCP. Specify other project with this. Must be specified when running outside GCP.").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject)
app.Flag("google-location", "When using the Google provider, current location is auto-detected, when running on GCP. Specify location with this. May be specified when running outside GCP.").Default(defaultConfig.GoogleLocation).StringVar(&cfg.GoogleLocation)
app.Flag("google-batch-change-size", "When using the Google provider, set the maximum number of changes that will be applied in each batch.").Default(strconv.Itoa(defaultConfig.GoogleBatchChangeSize)).IntVar(&cfg.GoogleBatchChangeSize)
app.Flag("google-batch-change-interval", "When using the Google provider, set the interval between batch changes.").Default(defaultConfig.GoogleBatchChangeInterval.String()).DurationVar(&cfg.GoogleBatchChangeInterval)
app.Flag("google-zone-visibility", "When using the Google provider, filter for zones with this visibility (optional, options: public, private)").Default(defaultConfig.GoogleZoneVisibility).EnumVar(&cfg.GoogleZoneVisibility, "", "public", "private")
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/externaldns/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ var (
Compatibility: "",
Provider: "google",
GoogleProject: "",
GoogleLocation: "",
GoogleBatchChangeSize: 1000,
GoogleBatchChangeInterval: time.Second,
GoogleZoneVisibility: "",
Expand Down Expand Up @@ -142,6 +143,7 @@ var (
Compatibility: "mate",
Provider: "google",
GoogleProject: "project",
GoogleLocation: "location",
GoogleBatchChangeSize: 100,
GoogleBatchChangeInterval: time.Second * 2,
GoogleZoneVisibility: "private",
Expand Down Expand Up @@ -271,6 +273,7 @@ func TestParseFlags(t *testing.T) {
"--compatibility=mate",
"--provider=google",
"--google-project=project",
"--google-location=location",
"--google-batch-change-size=100",
"--google-batch-change-interval=2s",
"--google-zone-visibility=private",
Expand Down Expand Up @@ -391,6 +394,7 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_COMPATIBILITY": "mate",
"EXTERNAL_DNS_PROVIDER": "google",
"EXTERNAL_DNS_GOOGLE_PROJECT": "project",
"EXTERNAL_DNS_GOOGLE_LOCATION": "location",
"EXTERNAL_DNS_GOOGLE_BATCH_CHANGE_SIZE": "100",
"EXTERNAL_DNS_GOOGLE_BATCH_CHANGE_INTERVAL": "2s",
"EXTERNAL_DNS_GOOGLE_ZONE_VISIBILITY": "private",
Expand Down
92 changes: 92 additions & 0 deletions plan/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package plan

import (
"fmt"
"iter"
"maps"
"strings"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -51,6 +53,39 @@ type Plan struct {
OwnerID string
}

// RRName is a canonical name associated with a resource record (i.e. lower-case with trailing period)
type RRName string

// RRType is a type associated with a resource record (e.g., "A", "AAAA", "TXT", etc.)
type RRType string

// RRKey is a key for entries in maps of resource records
type RRKey struct {
Name RRName
Type RRType
}

func newRRKey(ep *endpoint.Endpoint) RRKey {
return RRKey{
Name: RRName(normalizeDNSName(ep.DNSName)),
Type: RRType(ep.RecordType),
}
}

// RRSetChange represents changes to be applied to a single resource record set
//
// | Action | Create | Delete |
// |--------+--------+--------|
// | Create | * | nil |
// | Delete | nil | * |
// | Update | * | * |
type RRSetChange struct {
Name RRName
Type RRType
Create []*endpoint.Endpoint
Delete []*endpoint.Endpoint
}

// Changes holds lists of actions to be executed by dns providers
type Changes struct {
// Records that need to be created
Expand Down Expand Up @@ -154,6 +189,63 @@ func (t *planTable) newPlanKey(e *endpoint.Endpoint) planKey {
return key
}

// Return an iterator of changes to resource records sets
func (c *Changes) All() iter.Seq[*RRSetChange] {
return func(yield func(change *RRSetChange) bool) {
rrSetCreates := map[RRKey]*RRSetChange{}
rrSetDeletes := map[RRKey]*RRSetChange{}
rrSetUpdates := map[RRKey]*RRSetChange{}
for _, action := range []struct {
endpoints *[]*endpoint.Endpoint
rrSetChanges *map[RRKey]*RRSetChange
}{
{endpoints: &c.UpdateNew, rrSetChanges: &rrSetUpdates},
{endpoints: &c.UpdateOld, rrSetChanges: &rrSetUpdates},
{endpoints: &c.Create, rrSetChanges: &rrSetCreates},
{endpoints: &c.Delete, rrSetChanges: &rrSetDeletes},
} {
for _, ep := range *action.endpoints {
rrKey := newRRKey(ep)
change, ok := rrSetUpdates[rrKey]
if !ok && action.rrSetChanges != &rrSetUpdates {
if action.rrSetChanges != &rrSetCreates {
change, ok = rrSetCreates[rrKey]
if ok {
delete(rrSetCreates, rrKey)
rrSetUpdates[rrKey] = change
}
}
if !ok {
change, ok = (*action.rrSetChanges)[rrKey]
}
}
if !ok {
change = &RRSetChange{
Name: rrKey.Name,
Type: rrKey.Type,
}
(*action.rrSetChanges)[rrKey] = change
}
switch action.endpoints {
case &c.Create, &c.UpdateNew:
change.Create = append(change.Create, ep)
case &c.Delete, &c.UpdateOld:
change.Delete = append(change.Delete, ep)
}
}
}
for _, rrSetChanges := range []*map[RRKey]*RRSetChange{
&rrSetDeletes, &rrSetUpdates, &rrSetCreates,
} {
for rrSetChange := range maps.Values(*rrSetChanges) {
if !yield(rrSetChange) {
return
}
}
}
}
}

func (c *Changes) HasChanges() bool {
if len(c.Create) > 0 || len(c.Delete) > 0 {
return true
Expand Down
Loading
Loading