mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 01:26:59 +02:00
add multi-zone capability to google provider (take 2) (#163)
* feat(google): auto-detect and multiple zone support * chore: run gofmt with the simplified command * fix: pass desired domain to google provider * feat(google): correctly auto-detect records for sub-zones * chore: update changelog with support for multiple zones in google * fix(google): don't append traling dot to TXT records * ref(provider): extract hostname sanitization to general provider
This commit is contained in:
parent
5e3f2b7773
commit
e5f21ad32a
@ -1,7 +1,7 @@
|
||||
Features:
|
||||
- Improved logging
|
||||
- Generate DNS Name from template for services/ingress if annotation is missing but `--fqdn-template` is specified
|
||||
- Route 53: Support creation of records in multiple hosted zones.
|
||||
- Route 53, Google CloudDNS: Support creation of records in multiple hosted zones.
|
||||
- Route 53: Support creation of ALIAS records when endpoint target is a ELB/ALB.
|
||||
- Ownership via TXT records
|
||||
1. Create TXT records to mark the records managed by External DNS
|
||||
|
2
main.go
2
main.go
@ -105,7 +105,7 @@ func main() {
|
||||
var p provider.Provider
|
||||
switch cfg.Provider {
|
||||
case "google":
|
||||
p, err = provider.NewGoogleProvider(cfg.GoogleProject, cfg.DryRun)
|
||||
p, err = provider.NewGoogleProvider(cfg.GoogleProject, cfg.Domain, cfg.DryRun)
|
||||
case "aws":
|
||||
p, err = provider.NewAWSProvider(cfg.Domain, cfg.DryRun)
|
||||
default:
|
||||
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||
package provider
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
@ -327,12 +326,3 @@ func canonicalHostedZone(hostname string) string {
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// ensureTrailingDot ensures that the hostname receives a trailing dot if it hasn't already.
|
||||
func ensureTrailingDot(hostname string) string {
|
||||
if net.ParseIP(hostname) != nil {
|
||||
return hostname
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(hostname, ".") + "."
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
||||
package provider
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
@ -33,17 +35,12 @@ type managedZonesCreateCallInterface interface {
|
||||
Do(opts ...googleapi.CallOption) (*dns.ManagedZone, error)
|
||||
}
|
||||
|
||||
type managedZonesDeleteCallInterface interface {
|
||||
Do(opts ...googleapi.CallOption) error
|
||||
}
|
||||
|
||||
type managedZonesListCallInterface interface {
|
||||
Pages(ctx context.Context, f func(*dns.ManagedZonesListResponse) error) error
|
||||
}
|
||||
|
||||
type managedZonesServiceInterface interface {
|
||||
Create(project string, managedzone *dns.ManagedZone) managedZonesCreateCallInterface
|
||||
Delete(project string, managedZone string) managedZonesDeleteCallInterface
|
||||
List(project string) managedZonesListCallInterface
|
||||
}
|
||||
|
||||
@ -79,10 +76,6 @@ func (m managedZonesService) Create(project string, managedzone *dns.ManagedZone
|
||||
return m.service.Create(project, managedzone)
|
||||
}
|
||||
|
||||
func (m managedZonesService) Delete(project string, managedZone string) managedZonesDeleteCallInterface {
|
||||
return m.service.Delete(project, managedZone)
|
||||
}
|
||||
|
||||
func (m managedZonesService) List(project string) managedZonesListCallInterface {
|
||||
return m.service.List(project)
|
||||
}
|
||||
@ -101,6 +94,8 @@ type googleProvider struct {
|
||||
project string
|
||||
// Enabled dry-run will print any modifying actions rather than execute them.
|
||||
dryRun bool
|
||||
// only consider hosted zones managing domains ending in this suffix
|
||||
domain string
|
||||
// A client for managing resource record sets
|
||||
resourceRecordSetsClient resourceRecordSetsClientInterface
|
||||
// A client for managing hosted zones
|
||||
@ -110,7 +105,7 @@ type googleProvider struct {
|
||||
}
|
||||
|
||||
// NewGoogleProvider initializes a new Google CloudDNS based Provider.
|
||||
func NewGoogleProvider(project string, dryRun bool) (Provider, error) {
|
||||
func NewGoogleProvider(project string, domain string, dryRun bool) (Provider, error) {
|
||||
gcloud, err := google.DefaultClient(context.TODO(), dns.NdevClouddnsReadwriteScope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -123,6 +118,7 @@ func NewGoogleProvider(project string, dryRun bool) (Provider, error) {
|
||||
|
||||
provider := &googleProvider{
|
||||
project: project,
|
||||
domain: domain,
|
||||
dryRun: dryRun,
|
||||
resourceRecordSetsClient: resourceRecordSetsService{dnsClient.ResourceRecordSets},
|
||||
managedZonesClient: managedZonesService{dnsClient.ManagedZones},
|
||||
@ -133,49 +129,33 @@ func NewGoogleProvider(project string, dryRun bool) (Provider, error) {
|
||||
}
|
||||
|
||||
// Zones returns the list of hosted zones.
|
||||
func (p *googleProvider) Zones() (zones []*dns.ManagedZone, _ error) {
|
||||
func (p *googleProvider) Zones() (map[string]*dns.ManagedZone, error) {
|
||||
zones := make(map[string]*dns.ManagedZone)
|
||||
|
||||
f := func(resp *dns.ManagedZonesListResponse) error {
|
||||
// each page is processed sequentially, no need for a mutex here.
|
||||
zones = append(zones, resp.ManagedZones...)
|
||||
for _, zone := range resp.ManagedZones {
|
||||
if strings.HasSuffix(zone.DnsName, p.domain) {
|
||||
zones[zone.Name] = zone
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := p.managedZonesClient.List(p.project).Pages(context.TODO(), f)
|
||||
if err != nil {
|
||||
if err := p.managedZonesClient.List(p.project).Pages(context.TODO(), f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
// CreateZone creates a hosted zone given a name.
|
||||
func (p *googleProvider) CreateZone(name, domain string) error {
|
||||
zone := &dns.ManagedZone{
|
||||
Name: name,
|
||||
DnsName: domain,
|
||||
Description: "Automatically managed zone by kubernetes.io/external-dns",
|
||||
}
|
||||
|
||||
_, err := p.managedZonesClient.Create(p.project, zone).Do()
|
||||
// Records returns the list of records in all relevant zones.
|
||||
func (p *googleProvider) Records(_ string) (endpoints []*endpoint.Endpoint, _ error) {
|
||||
zones, err := p.Zones()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteZone deletes a hosted zone given a name.
|
||||
func (p *googleProvider) DeleteZone(name string) error {
|
||||
err := p.managedZonesClient.Delete(p.project, name).Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Records returns the list of A records in a given hosted zone.
|
||||
func (p *googleProvider) Records(zone string) (endpoints []*endpoint.Endpoint, _ error) {
|
||||
f := func(resp *dns.ResourceRecordSetsListResponse) error {
|
||||
for _, r := range resp.Rrsets {
|
||||
// TODO(linki, ownership): Remove once ownership system is in place.
|
||||
@ -196,44 +176,45 @@ func (p *googleProvider) Records(zone string) (endpoints []*endpoint.Endpoint, _
|
||||
return nil
|
||||
}
|
||||
|
||||
err := p.resourceRecordSetsClient.List(p.project, zone).Pages(context.TODO(), f)
|
||||
if err != nil {
|
||||
for _, z := range zones {
|
||||
if err := p.resourceRecordSetsClient.List(p.project, z.Name).Pages(context.TODO(), f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// CreateRecords creates a given set of DNS records in the given hosted zone.
|
||||
func (p *googleProvider) CreateRecords(zone string, endpoints []*endpoint.Endpoint) error {
|
||||
func (p *googleProvider) CreateRecords(endpoints []*endpoint.Endpoint) error {
|
||||
change := &dns.Change{}
|
||||
|
||||
change.Additions = append(change.Additions, newRecords(endpoints)...)
|
||||
|
||||
return p.submitChange(zone, change)
|
||||
return p.submitChange(change)
|
||||
}
|
||||
|
||||
// UpdateRecords updates a given set of old records to a new set of records in a given hosted zone.
|
||||
func (p *googleProvider) UpdateRecords(zone string, records, oldRecords []*endpoint.Endpoint) error {
|
||||
func (p *googleProvider) UpdateRecords(records, oldRecords []*endpoint.Endpoint) error {
|
||||
change := &dns.Change{}
|
||||
|
||||
change.Additions = append(change.Additions, newRecords(records)...)
|
||||
change.Deletions = append(change.Deletions, newRecords(oldRecords)...)
|
||||
|
||||
return p.submitChange(zone, change)
|
||||
return p.submitChange(change)
|
||||
}
|
||||
|
||||
// DeleteRecords deletes a given set of DNS records in a given zone.
|
||||
func (p *googleProvider) DeleteRecords(zone string, endpoints []*endpoint.Endpoint) error {
|
||||
func (p *googleProvider) DeleteRecords(endpoints []*endpoint.Endpoint) error {
|
||||
change := &dns.Change{}
|
||||
|
||||
change.Deletions = append(change.Deletions, newRecords(endpoints)...)
|
||||
|
||||
return p.submitChange(zone, change)
|
||||
return p.submitChange(change)
|
||||
}
|
||||
|
||||
// ApplyChanges applies a given set of changes in a given zone.
|
||||
func (p *googleProvider) ApplyChanges(zone string, changes *plan.Changes) error {
|
||||
func (p *googleProvider) ApplyChanges(_ string, changes *plan.Changes) error {
|
||||
change := &dns.Change{}
|
||||
|
||||
change.Additions = append(change.Additions, newRecords(changes.Create)...)
|
||||
@ -243,12 +224,11 @@ func (p *googleProvider) ApplyChanges(zone string, changes *plan.Changes) error
|
||||
|
||||
change.Deletions = append(change.Deletions, newRecords(changes.Delete)...)
|
||||
|
||||
return p.submitChange(zone, change)
|
||||
return p.submitChange(change)
|
||||
}
|
||||
|
||||
// submitChange takes a zone and a Change and sends it to Google.
|
||||
func (p *googleProvider) submitChange(zone string, change *dns.Change) error {
|
||||
|
||||
func (p *googleProvider) submitChange(change *dns.Change) error {
|
||||
if len(change.Additions) == 0 && len(change.Deletions) == 0 {
|
||||
log.Infoln("Received empty list of records for creation and deletion")
|
||||
return nil
|
||||
@ -261,16 +241,75 @@ func (p *googleProvider) submitChange(zone string, change *dns.Change) error {
|
||||
log.Infof("Add records: %s %s %s", add.Name, add.Type, add.Rrdatas)
|
||||
}
|
||||
|
||||
if !p.dryRun {
|
||||
_, err := p.changesClient.Create(p.project, zone, change).Do()
|
||||
if p.dryRun {
|
||||
return nil
|
||||
}
|
||||
|
||||
zones, err := p.Zones()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// separate into per-zone change sets to be passed to the API.
|
||||
changes := separateChange(zones, change)
|
||||
|
||||
for z, c := range changes {
|
||||
if _, err := p.changesClient.Create(p.project, z, c).Do(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// separateChange separates a multi-zone change into a single change per zone.
|
||||
func separateChange(zones map[string]*dns.ManagedZone, change *dns.Change) map[string]*dns.Change {
|
||||
changes := make(map[string]*dns.Change)
|
||||
|
||||
for _, z := range zones {
|
||||
changes[z.Name] = &dns.Change{
|
||||
Additions: []*dns.ResourceRecordSet{},
|
||||
Deletions: []*dns.ResourceRecordSet{},
|
||||
}
|
||||
}
|
||||
|
||||
for _, a := range change.Additions {
|
||||
if zone := suitableManagedZone(ensureTrailingDot(a.Name), zones); zone != nil {
|
||||
changes[zone.Name].Additions = append(changes[zone.Name].Additions, a)
|
||||
}
|
||||
}
|
||||
|
||||
for _, d := range change.Deletions {
|
||||
if zone := suitableManagedZone(ensureTrailingDot(d.Name), zones); zone != nil {
|
||||
changes[zone.Name].Deletions = append(changes[zone.Name].Deletions, d)
|
||||
}
|
||||
}
|
||||
|
||||
// separating a change could lead to empty sub changes, remove them here.
|
||||
for zone, change := range changes {
|
||||
if len(change.Additions) == 0 && len(change.Deletions) == 0 {
|
||||
delete(changes, zone)
|
||||
}
|
||||
}
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
// suitableManagedZone returns the most suitable zone for a given hostname and a set of zones.
|
||||
func suitableManagedZone(hostname string, zones map[string]*dns.ManagedZone) *dns.ManagedZone {
|
||||
var zone *dns.ManagedZone
|
||||
|
||||
for _, z := range zones {
|
||||
if strings.HasSuffix(hostname, z.DnsName) {
|
||||
if zone == nil || len(z.DnsName) > len(zone.DnsName) {
|
||||
zone = z
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return zone
|
||||
}
|
||||
|
||||
// newRecords returns a collection of RecordSets based on the given endpoints.
|
||||
func newRecords(endpoints []*endpoint.Endpoint) []*dns.ResourceRecordSet {
|
||||
records := make([]*dns.ResourceRecordSet, len(endpoints))
|
||||
@ -284,9 +323,17 @@ func newRecords(endpoints []*endpoint.Endpoint) []*dns.ResourceRecordSet {
|
||||
|
||||
// newRecord returns a RecordSet based on the given endpoint.
|
||||
func newRecord(endpoint *endpoint.Endpoint) *dns.ResourceRecordSet {
|
||||
// TODO(linki): works around appending a trailing dot to TXT records. I think
|
||||
// we should go back to storing DNS names with a trailing dot internally. This
|
||||
// way we can use it has is here and trim it off if it exists when necessary.
|
||||
target := endpoint.Target
|
||||
if suitableType(endpoint) == "CNAME" {
|
||||
target = ensureTrailingDot(target)
|
||||
}
|
||||
|
||||
return &dns.ResourceRecordSet{
|
||||
Name: ensureTrailingDot(endpoint.DNSName),
|
||||
Rrdatas: []string{ensureTrailingDot(endpoint.Target)},
|
||||
Rrdatas: []string{target},
|
||||
Ttl: 300,
|
||||
Type: suitableType(endpoint),
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
@ -25,320 +27,603 @@ import (
|
||||
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||
"github.com/kubernetes-incubator/external-dns/plan"
|
||||
|
||||
dns "google.golang.org/api/dns/v1"
|
||||
googleapi "google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/dns/v1"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
var (
|
||||
expectedZones = []*dns.ManagedZone{{Name: "expected"}}
|
||||
expectedRecordSets = []*dns.ResourceRecordSet{
|
||||
{
|
||||
Type: "A",
|
||||
Name: "expected-1",
|
||||
Rrdatas: []string{"8.8.8.8"},
|
||||
},
|
||||
{
|
||||
Type: "CNAME",
|
||||
Name: "expected-2",
|
||||
Rrdatas: []string{"target.com"},
|
||||
},
|
||||
{
|
||||
Type: "NS",
|
||||
Name: "unexpected",
|
||||
Rrdatas: []string{"target"},
|
||||
},
|
||||
}
|
||||
testZones = map[string]*dns.ManagedZone{}
|
||||
testRecords = map[string]map[string]*dns.ResourceRecordSet{}
|
||||
)
|
||||
|
||||
type mockManagedZonesCreateCall struct{}
|
||||
type mockManagedZonesCreateCall struct {
|
||||
project string
|
||||
managedZone *dns.ManagedZone
|
||||
}
|
||||
|
||||
func (m *mockManagedZonesCreateCall) Do(opts ...googleapi.CallOption) (*dns.ManagedZone, error) {
|
||||
return nil, nil
|
||||
zoneKey := zoneKey(m.project, m.managedZone.Name)
|
||||
|
||||
if _, ok := testZones[zoneKey]; ok {
|
||||
return nil, &googleapi.Error{Code: http.StatusConflict}
|
||||
}
|
||||
|
||||
testZones[zoneKey] = m.managedZone
|
||||
|
||||
return m.managedZone, nil
|
||||
}
|
||||
|
||||
type mockErrManagedZonesCreateCall struct{}
|
||||
|
||||
func (m *mockErrManagedZonesCreateCall) Do(opts ...googleapi.CallOption) (*dns.ManagedZone, error) {
|
||||
return nil, fmt.Errorf("failed")
|
||||
type mockManagedZonesListCall struct {
|
||||
project string
|
||||
}
|
||||
|
||||
type mockManagedZonesDeleteCall struct{}
|
||||
|
||||
func (m *mockManagedZonesDeleteCall) Do(opts ...googleapi.CallOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockErrManagedZonesDeleteCall struct{}
|
||||
|
||||
func (m *mockErrManagedZonesDeleteCall) Do(opts ...googleapi.CallOption) error {
|
||||
return fmt.Errorf("failed")
|
||||
}
|
||||
|
||||
type mockManagedZonesListCall struct{}
|
||||
|
||||
func (m *mockManagedZonesListCall) Pages(ctx context.Context, f func(*dns.ManagedZonesListResponse) error) error {
|
||||
return f(&dns.ManagedZonesListResponse{ManagedZones: expectedZones})
|
||||
}
|
||||
zones := []*dns.ManagedZone{}
|
||||
|
||||
type mockErrManagedZonesListCall struct{}
|
||||
for k, v := range testZones {
|
||||
if strings.HasPrefix(k, m.project+"/") {
|
||||
zones = append(zones, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mockErrManagedZonesListCall) Pages(ctx context.Context, f func(*dns.ManagedZonesListResponse) error) error {
|
||||
return fmt.Errorf("failed")
|
||||
return f(&dns.ManagedZonesListResponse{ManagedZones: zones})
|
||||
}
|
||||
|
||||
type mockManagedZonesClient struct{}
|
||||
|
||||
func (m *mockManagedZonesClient) Create(project string, managedzone *dns.ManagedZone) managedZonesCreateCallInterface {
|
||||
return &mockManagedZonesCreateCall{}
|
||||
}
|
||||
|
||||
func (m *mockManagedZonesClient) Delete(project string, managedZone string) managedZonesDeleteCallInterface {
|
||||
return &mockManagedZonesDeleteCall{}
|
||||
func (m *mockManagedZonesClient) Create(project string, managedZone *dns.ManagedZone) managedZonesCreateCallInterface {
|
||||
return &mockManagedZonesCreateCall{project: project, managedZone: managedZone}
|
||||
}
|
||||
|
||||
func (m *mockManagedZonesClient) List(project string) managedZonesListCallInterface {
|
||||
return &mockManagedZonesListCall{}
|
||||
return &mockManagedZonesListCall{project: project}
|
||||
}
|
||||
|
||||
type mockErrManagedZonesClient struct{}
|
||||
|
||||
func (m *mockErrManagedZonesClient) Create(project string, managedzone *dns.ManagedZone) managedZonesCreateCallInterface {
|
||||
return &mockErrManagedZonesCreateCall{}
|
||||
type mockResourceRecordSetsListCall struct {
|
||||
project string
|
||||
managedZone string
|
||||
}
|
||||
|
||||
func (m *mockErrManagedZonesClient) Delete(project string, managedZone string) managedZonesDeleteCallInterface {
|
||||
return &mockErrManagedZonesDeleteCall{}
|
||||
}
|
||||
|
||||
func (m *mockErrManagedZonesClient) List(project string) managedZonesListCallInterface {
|
||||
return &mockErrManagedZonesListCall{}
|
||||
}
|
||||
|
||||
type mockResourceRecordSetsListCall struct{}
|
||||
|
||||
func (m *mockResourceRecordSetsListCall) Pages(ctx context.Context, f func(*dns.ResourceRecordSetsListResponse) error) error {
|
||||
return f(&dns.ResourceRecordSetsListResponse{Rrsets: expectedRecordSets})
|
||||
}
|
||||
zoneKey := zoneKey(m.project, m.managedZone)
|
||||
|
||||
type mockErrResourceRecordSetsListCall struct{}
|
||||
if _, ok := testZones[zoneKey]; !ok {
|
||||
return &googleapi.Error{Code: http.StatusNotFound}
|
||||
}
|
||||
|
||||
func (m *mockErrResourceRecordSetsListCall) Pages(ctx context.Context, f func(*dns.ResourceRecordSetsListResponse) error) error {
|
||||
return fmt.Errorf("failed")
|
||||
resp := []*dns.ResourceRecordSet{}
|
||||
|
||||
for _, v := range testRecords[zoneKey] {
|
||||
resp = append(resp, v)
|
||||
}
|
||||
|
||||
return f(&dns.ResourceRecordSetsListResponse{Rrsets: resp})
|
||||
}
|
||||
|
||||
type mockResourceRecordSetsClient struct{}
|
||||
|
||||
func (m *mockResourceRecordSetsClient) List(project string, managedZone string) resourceRecordSetsListCallInterface {
|
||||
return &mockResourceRecordSetsListCall{}
|
||||
return &mockResourceRecordSetsListCall{project: project, managedZone: managedZone}
|
||||
}
|
||||
|
||||
type mockErrResourceRecordSetsClient struct{}
|
||||
|
||||
func (m *mockErrResourceRecordSetsClient) List(project string, managedZone string) resourceRecordSetsListCallInterface {
|
||||
return &mockErrResourceRecordSetsListCall{}
|
||||
type mockChangesCreateCall struct {
|
||||
project string
|
||||
managedZone string
|
||||
change *dns.Change
|
||||
}
|
||||
|
||||
type mockChangesCreateCall struct{}
|
||||
|
||||
func (m *mockChangesCreateCall) Do(opts ...googleapi.CallOption) (*dns.Change, error) {
|
||||
return nil, nil
|
||||
}
|
||||
zoneKey := zoneKey(m.project, m.managedZone)
|
||||
|
||||
type mockErrChangesCreateCall struct{}
|
||||
if _, ok := testZones[zoneKey]; !ok {
|
||||
return nil, &googleapi.Error{Code: http.StatusNotFound}
|
||||
}
|
||||
|
||||
func (m *mockErrChangesCreateCall) Do(opts ...googleapi.CallOption) (*dns.Change, error) {
|
||||
return nil, fmt.Errorf("failed")
|
||||
if _, ok := testRecords[zoneKey]; !ok {
|
||||
testRecords[zoneKey] = make(map[string]*dns.ResourceRecordSet)
|
||||
}
|
||||
|
||||
for _, c := range append(m.change.Additions, m.change.Deletions...) {
|
||||
if !isValidRecordSet(c) {
|
||||
return nil, &googleapi.Error{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: fmt.Sprintf("invalid record: %v", c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, del := range m.change.Deletions {
|
||||
recordKey := recordKey(del.Type, del.Name)
|
||||
delete(testRecords[zoneKey], recordKey)
|
||||
}
|
||||
|
||||
for _, add := range m.change.Additions {
|
||||
recordKey := recordKey(add.Type, add.Name)
|
||||
testRecords[zoneKey][recordKey] = add
|
||||
}
|
||||
|
||||
return m.change, nil
|
||||
}
|
||||
|
||||
type mockChangesClient struct{}
|
||||
|
||||
func (m *mockChangesClient) Create(project string, managedZone string, change *dns.Change) changesCreateCallInterface {
|
||||
return &mockChangesCreateCall{}
|
||||
return &mockChangesCreateCall{project: project, managedZone: managedZone, change: change}
|
||||
}
|
||||
|
||||
type mockErrChangesClient struct{}
|
||||
func zoneKey(project, zoneName string) string {
|
||||
return project + "/" + zoneName
|
||||
}
|
||||
|
||||
func (m *mockErrChangesClient) Create(project string, managedZone string, change *dns.Change) changesCreateCallInterface {
|
||||
return &mockErrChangesCreateCall{}
|
||||
func recordKey(recordType, recordName string) string {
|
||||
return recordType + "/" + recordName
|
||||
}
|
||||
|
||||
func isValidRecordSet(recordSet *dns.ResourceRecordSet) bool {
|
||||
if !hasTrailingDot(recordSet.Name) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch recordSet.Type {
|
||||
case "CNAME":
|
||||
for _, rrd := range recordSet.Rrdatas {
|
||||
if !hasTrailingDot(rrd) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
case "A", "TXT":
|
||||
for _, rrd := range recordSet.Rrdatas {
|
||||
if hasTrailingDot(rrd) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic("unhandled record type")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func hasTrailingDot(target string) bool {
|
||||
return strings.HasSuffix(target, ".")
|
||||
}
|
||||
|
||||
func TestGoogleZones(t *testing.T) {
|
||||
provider := &googleProvider{
|
||||
project: "project",
|
||||
managedZonesClient: &mockManagedZonesClient{},
|
||||
}
|
||||
provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", false, []*endpoint.Endpoint{})
|
||||
|
||||
zones, err := provider.Zones()
|
||||
if err != nil {
|
||||
t.Errorf("should not fail: %s", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(zones) != len(expectedZones) {
|
||||
t.Errorf("expected %d zones, got %d", len(expectedZones), len(zones))
|
||||
}
|
||||
|
||||
provider.managedZonesClient = &mockErrManagedZonesClient{}
|
||||
|
||||
_, err = provider.Zones()
|
||||
if err == nil {
|
||||
t.Errorf("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoogleCreateZone(t *testing.T) {
|
||||
provider := &googleProvider{
|
||||
project: "project",
|
||||
managedZonesClient: &mockManagedZonesClient{},
|
||||
}
|
||||
|
||||
err := provider.CreateZone("name", "domain")
|
||||
if err != nil {
|
||||
t.Errorf("should not fail: %s", err)
|
||||
}
|
||||
|
||||
provider.managedZonesClient = &mockErrManagedZonesClient{}
|
||||
|
||||
err = provider.CreateZone("name", "domain")
|
||||
if err == nil {
|
||||
t.Errorf("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoogleDeleteZone(t *testing.T) {
|
||||
provider := &googleProvider{
|
||||
project: "project",
|
||||
managedZonesClient: &mockManagedZonesClient{},
|
||||
}
|
||||
|
||||
err := provider.DeleteZone("name")
|
||||
if err != nil {
|
||||
t.Errorf("should not fail: %s", err)
|
||||
}
|
||||
|
||||
provider.managedZonesClient = &mockErrManagedZonesClient{}
|
||||
|
||||
err = provider.DeleteZone("name")
|
||||
if err == nil {
|
||||
t.Errorf("expected error")
|
||||
}
|
||||
validateZones(t, zones, map[string]*dns.ManagedZone{
|
||||
"zone-1-ext-dns-test-2-gcp-zalan-do": {Name: "zone-1-ext-dns-test-2-gcp-zalan-do", DnsName: "zone-1.ext-dns-test-2.gcp.zalan.do."},
|
||||
"zone-2-ext-dns-test-2-gcp-zalan-do": {Name: "zone-2-ext-dns-test-2-gcp-zalan-do", DnsName: "zone-2.ext-dns-test-2.gcp.zalan.do."},
|
||||
"zone-3-ext-dns-test-2-gcp-zalan-do": {Name: "zone-3-ext-dns-test-2-gcp-zalan-do", DnsName: "zone-3.ext-dns-test-2.gcp.zalan.do."},
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoogleRecords(t *testing.T) {
|
||||
provider := &googleProvider{
|
||||
project: "project",
|
||||
resourceRecordSetsClient: &mockResourceRecordSetsClient{},
|
||||
originalEndpoints := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("list-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", "A"),
|
||||
endpoint.NewEndpoint("list-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("list-test-alias.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", "CNAME"),
|
||||
}
|
||||
|
||||
endpoints, err := provider.Records("zone")
|
||||
provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", false, originalEndpoints)
|
||||
|
||||
records, err := provider.Records("_")
|
||||
if err != nil {
|
||||
t.Errorf("should not fail: %s", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(endpoints) != len(expectedRecordSets)-1 {
|
||||
t.Errorf("expected %d endpoints, got %d", len(expectedRecordSets)-1, len(endpoints))
|
||||
}
|
||||
|
||||
provider.resourceRecordSetsClient = &mockErrResourceRecordSetsClient{}
|
||||
|
||||
_, err = provider.Records("zone")
|
||||
if err == nil {
|
||||
t.Errorf("expected error")
|
||||
}
|
||||
validateEndpoints(t, records, originalEndpoints)
|
||||
}
|
||||
|
||||
func TestGoogleCreateRecords(t *testing.T) {
|
||||
provider := &googleProvider{
|
||||
project: "project",
|
||||
changesClient: &mockChangesClient{},
|
||||
provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", false, []*endpoint.Endpoint{})
|
||||
|
||||
records := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", ""),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", ""),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", ""),
|
||||
}
|
||||
|
||||
endpoints := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "dns-name",
|
||||
Target: "target",
|
||||
},
|
||||
if err := provider.CreateRecords(records); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err := provider.CreateRecords("zone", endpoints)
|
||||
records, err := provider.Records("_")
|
||||
if err != nil {
|
||||
t.Errorf("should not fail: %s", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
provider.changesClient = &mockErrChangesClient{}
|
||||
|
||||
err = provider.CreateRecords("zone", endpoints)
|
||||
if err == nil {
|
||||
t.Errorf("expected error")
|
||||
}
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", "A"),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", "CNAME"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoogleUpdateRecords(t *testing.T) {
|
||||
provider := &googleProvider{
|
||||
project: "project",
|
||||
changesClient: &mockChangesClient{},
|
||||
provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", false, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", "CNAME"),
|
||||
})
|
||||
|
||||
currentRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", "CNAME"),
|
||||
}
|
||||
updatedRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", "A"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "4.3.2.1", "A"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "bar.elb.amazonaws.com", "CNAME"),
|
||||
}
|
||||
|
||||
records := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "dns-name",
|
||||
Target: "target",
|
||||
},
|
||||
if err := provider.UpdateRecords(updatedRecords, currentRecords); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
oldRecords := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "dns-name",
|
||||
Target: "target",
|
||||
},
|
||||
}
|
||||
|
||||
err := provider.UpdateRecords("zone", records, oldRecords)
|
||||
records, err := provider.Records("_")
|
||||
if err != nil {
|
||||
t.Errorf("should not fail: %s", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = provider.UpdateRecords("zone", nil, nil)
|
||||
if err != nil {
|
||||
t.Errorf("should not fail: %s", err)
|
||||
}
|
||||
|
||||
provider.dryRun = true
|
||||
|
||||
err = provider.UpdateRecords("zone", records, oldRecords)
|
||||
if err != nil {
|
||||
t.Errorf("should not fail: %s", err)
|
||||
}
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", "A"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "4.3.2.1", "A"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "bar.elb.amazonaws.com", "CNAME"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoogleDeleteRecords(t *testing.T) {
|
||||
provider := &googleProvider{
|
||||
project: "project",
|
||||
changesClient: &mockChangesClient{},
|
||||
originalEndpoints := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", "A"),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "baz.elb.amazonaws.com", "CNAME"),
|
||||
}
|
||||
|
||||
endpoints := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "dns-name",
|
||||
Target: "target",
|
||||
},
|
||||
provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", false, originalEndpoints)
|
||||
|
||||
if err := provider.DeleteRecords(originalEndpoints); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err := provider.DeleteRecords("zone", endpoints)
|
||||
records, err := provider.Records("_")
|
||||
if err != nil {
|
||||
t.Errorf("should not fail: %s", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{})
|
||||
}
|
||||
|
||||
func TestGoogleApplyChanges(t *testing.T) {
|
||||
provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", false, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "bar.elb.amazonaws.com", "CNAME"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "qux.elb.amazonaws.com", "CNAME"),
|
||||
})
|
||||
|
||||
createRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", "CNAME"),
|
||||
}
|
||||
|
||||
currentRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "bar.elb.amazonaws.com", "CNAME"),
|
||||
}
|
||||
updatedRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", "A"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "4.3.2.1", "A"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "baz.elb.amazonaws.com", "CNAME"),
|
||||
}
|
||||
|
||||
deleteRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "qux.elb.amazonaws.com", "CNAME"),
|
||||
}
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: createRecords,
|
||||
UpdateNew: updatedRecords,
|
||||
UpdateOld: currentRecords,
|
||||
Delete: deleteRecords,
|
||||
}
|
||||
|
||||
if err := provider.ApplyChanges("_", changes); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
records, err := provider.Records("_")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", "A"),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "4.3.2.1", "A"),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", "CNAME"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "baz.elb.amazonaws.com", "CNAME"),
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoogleApplyChangesDryRun(t *testing.T) {
|
||||
originalEndpoints := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "bar.elb.amazonaws.com", "CNAME"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "qux.elb.amazonaws.com", "CNAME"),
|
||||
}
|
||||
|
||||
provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", true, originalEndpoints)
|
||||
|
||||
createRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("create-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"),
|
||||
endpoint.NewEndpoint("create-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", "CNAME"),
|
||||
}
|
||||
|
||||
currentRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "bar.elb.amazonaws.com", "CNAME"),
|
||||
}
|
||||
updatedRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", "A"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "4.3.2.1", "A"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "baz.elb.amazonaws.com", "CNAME"),
|
||||
}
|
||||
|
||||
deleteRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"),
|
||||
endpoint.NewEndpoint("delete-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"),
|
||||
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "qux.elb.amazonaws.com", "CNAME"),
|
||||
}
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: createRecords,
|
||||
UpdateNew: updatedRecords,
|
||||
UpdateOld: currentRecords,
|
||||
Delete: deleteRecords,
|
||||
}
|
||||
|
||||
if err := provider.ApplyChanges("_", changes); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
records, err := provider.Records("_")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validateEndpoints(t, records, originalEndpoints)
|
||||
}
|
||||
|
||||
func TestGoogleApplyChangesEmpty(t *testing.T) {
|
||||
provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", false, []*endpoint.Endpoint{})
|
||||
|
||||
if err := provider.ApplyChanges("_", &plan.Changes{}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeparateChanges(t *testing.T) {
|
||||
change := &dns.Change{
|
||||
Additions: []*dns.ResourceRecordSet{
|
||||
{Name: "qux.foo.example.org.", Ttl: 1},
|
||||
{Name: "qux.bar.example.org.", Ttl: 2},
|
||||
},
|
||||
Deletions: []*dns.ResourceRecordSet{
|
||||
{Name: "wambo.foo.example.org.", Ttl: 10},
|
||||
{Name: "wambo.bar.example.org.", Ttl: 20},
|
||||
},
|
||||
}
|
||||
|
||||
zones := map[string]*dns.ManagedZone{
|
||||
"foo-example-org": {
|
||||
Name: "foo-example-org",
|
||||
DnsName: "foo.example.org.",
|
||||
},
|
||||
"bar-example-org": {
|
||||
Name: "bar-example-org",
|
||||
DnsName: "bar.example.org.",
|
||||
},
|
||||
"baz-example-org": {
|
||||
Name: "baz-example-org",
|
||||
DnsName: "baz.example.org.",
|
||||
},
|
||||
}
|
||||
|
||||
changes := separateChange(zones, change)
|
||||
|
||||
if len(changes) != 2 {
|
||||
t.Fatalf("expected %d change(s), got %d", 2, len(changes))
|
||||
}
|
||||
|
||||
validateChange(t, changes["foo-example-org"], &dns.Change{
|
||||
Additions: []*dns.ResourceRecordSet{
|
||||
{Name: "qux.foo.example.org.", Ttl: 1},
|
||||
},
|
||||
Deletions: []*dns.ResourceRecordSet{
|
||||
{Name: "wambo.foo.example.org.", Ttl: 10},
|
||||
},
|
||||
})
|
||||
|
||||
validateChange(t, changes["bar-example-org"], &dns.Change{
|
||||
Additions: []*dns.ResourceRecordSet{
|
||||
{Name: "qux.bar.example.org.", Ttl: 2},
|
||||
},
|
||||
Deletions: []*dns.ResourceRecordSet{
|
||||
{Name: "wambo.bar.example.org.", Ttl: 20},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoogleSuitableZone(t *testing.T) {
|
||||
zones := map[string]*dns.ManagedZone{
|
||||
"example-org": {Name: "example-org", DnsName: "example.org."},
|
||||
"bar-example-org": {Name: "bar-example-org", DnsName: "bar.example.org."},
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
hostname string
|
||||
expected *dns.ManagedZone
|
||||
}{
|
||||
{"foo.bar.example.org.", zones["bar-example-org"]},
|
||||
{"foo.example.org.", zones["example-org"]},
|
||||
{"foo.kubernetes.io.", nil},
|
||||
} {
|
||||
suitableZone := suitableManagedZone(tc.hostname, zones)
|
||||
|
||||
if suitableZone != tc.expected {
|
||||
t.Errorf("expected %v, got %v", tc.expected, suitableZone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateZones(t *testing.T, zones map[string]*dns.ManagedZone, expected map[string]*dns.ManagedZone) {
|
||||
if len(zones) != len(expected) {
|
||||
t.Fatalf("expected %d zone(s), got %d", len(expected), len(zones))
|
||||
}
|
||||
|
||||
for i, zone := range zones {
|
||||
validateZone(t, zone, expected[i])
|
||||
}
|
||||
}
|
||||
|
||||
func validateZone(t *testing.T, zone *dns.ManagedZone, expected *dns.ManagedZone) {
|
||||
if zone.Name != expected.Name {
|
||||
t.Errorf("expected %s, got %s", expected.Name, zone.Name)
|
||||
}
|
||||
|
||||
if zone.DnsName != expected.DnsName {
|
||||
t.Errorf("expected %s, got %s", expected.DnsName, zone.DnsName)
|
||||
}
|
||||
}
|
||||
|
||||
func validateChange(t *testing.T, change *dns.Change, expected *dns.Change) {
|
||||
validateChangeRecords(t, change.Additions, expected.Additions)
|
||||
validateChangeRecords(t, change.Deletions, expected.Deletions)
|
||||
}
|
||||
|
||||
func validateChangeRecords(t *testing.T, records []*dns.ResourceRecordSet, expected []*dns.ResourceRecordSet) {
|
||||
if len(records) != len(expected) {
|
||||
t.Fatalf("expected %d change(s), got %d", len(expected), len(records))
|
||||
}
|
||||
|
||||
for i := range records {
|
||||
validateChangeRecord(t, records[i], expected[i])
|
||||
}
|
||||
}
|
||||
|
||||
func validateChangeRecord(t *testing.T, record *dns.ResourceRecordSet, expected *dns.ResourceRecordSet) {
|
||||
if record.Name != expected.Name {
|
||||
t.Errorf("expected %s, got %s", expected.Name, record.Name)
|
||||
}
|
||||
|
||||
if record.Ttl != expected.Ttl {
|
||||
t.Errorf("expected %d, got %d", expected.Ttl, record.Ttl)
|
||||
}
|
||||
}
|
||||
|
||||
func newGoogleProvider(t *testing.T, domain string, dryRun bool, records []*endpoint.Endpoint) *googleProvider {
|
||||
provider := &googleProvider{
|
||||
project: "project",
|
||||
project: "zalando-external-dns-test",
|
||||
domain: domain,
|
||||
dryRun: false,
|
||||
resourceRecordSetsClient: &mockResourceRecordSetsClient{},
|
||||
managedZonesClient: &mockManagedZonesClient{},
|
||||
changesClient: &mockChangesClient{},
|
||||
}
|
||||
|
||||
changes := &plan.Changes{}
|
||||
createZone(t, provider, &dns.ManagedZone{
|
||||
Name: "zone-1-ext-dns-test-2-gcp-zalan-do",
|
||||
DnsName: "zone-1.ext-dns-test-2.gcp.zalan.do.",
|
||||
})
|
||||
|
||||
err := provider.ApplyChanges("zone", changes)
|
||||
if err != nil {
|
||||
t.Errorf("should not fail: %s", err)
|
||||
createZone(t, provider, &dns.ManagedZone{
|
||||
Name: "zone-2-ext-dns-test-2-gcp-zalan-do",
|
||||
DnsName: "zone-2.ext-dns-test-2.gcp.zalan.do.",
|
||||
})
|
||||
|
||||
createZone(t, provider, &dns.ManagedZone{
|
||||
Name: "zone-3-ext-dns-test-2-gcp-zalan-do",
|
||||
DnsName: "zone-3.ext-dns-test-2.gcp.zalan.do.",
|
||||
})
|
||||
|
||||
setupGoogleRecords(t, provider, records)
|
||||
|
||||
provider.dryRun = dryRun
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
func createZone(t *testing.T, provider *googleProvider, zone *dns.ManagedZone) {
|
||||
zone.Description = "Testing zone for kubernetes.io/external-dns"
|
||||
|
||||
if _, err := provider.managedZonesClient.Create("zalando-external-dns-test", zone).Do(); err != nil {
|
||||
if err, ok := err.(*googleapi.Error); !ok || err.Code != http.StatusConflict {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setupGoogleRecords(t *testing.T, provider *googleProvider, endpoints []*endpoint.Endpoint) {
|
||||
clearGoogleRecords(t, provider, "zone-1-ext-dns-test-2-gcp-zalan-do")
|
||||
clearGoogleRecords(t, provider, "zone-2-ext-dns-test-2-gcp-zalan-do")
|
||||
|
||||
records, err := provider.Records("_")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{})
|
||||
|
||||
if err = provider.CreateRecords(endpoints); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
records, err = provider.Records("_")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validateEndpoints(t, records, endpoints)
|
||||
}
|
||||
|
||||
func clearGoogleRecords(t *testing.T, provider *googleProvider, zone string) {
|
||||
recordSets := []*dns.ResourceRecordSet{}
|
||||
if err := provider.resourceRecordSetsClient.List(provider.project, zone).Pages(context.TODO(), func(resp *dns.ResourceRecordSetsListResponse) error {
|
||||
for _, r := range resp.Rrsets {
|
||||
switch r.Type {
|
||||
case "A", "CNAME":
|
||||
recordSets = append(recordSets, r)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(recordSets) != 0 {
|
||||
if _, err := provider.changesClient.Create(provider.project, zone, &dns.Change{
|
||||
Deletions: recordSets,
|
||||
}).Do(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package provider
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||
"github.com/kubernetes-incubator/external-dns/plan"
|
||||
@ -40,3 +41,12 @@ func suitableType(ep *endpoint.Endpoint) string {
|
||||
}
|
||||
return "CNAME"
|
||||
}
|
||||
|
||||
// ensureTrailingDot ensures that the hostname receives a trailing dot if it hasn't already.
|
||||
func ensureTrailingDot(hostname string) string {
|
||||
if net.ParseIP(hostname) != nil {
|
||||
return hostname
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(hostname, ".") + "."
|
||||
}
|
||||
|
@ -43,3 +43,19 @@ func TestSuitableType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureTrailingDot(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
input, expected string
|
||||
}{
|
||||
{"example.org", "example.org."},
|
||||
{"example.org.", "example.org."},
|
||||
{"8.8.8.8", "8.8.8.8"},
|
||||
} {
|
||||
output := ensureTrailingDot(tc.input)
|
||||
|
||||
if output != tc.expected {
|
||||
t.Errorf("expected %s, got %s", tc.expected, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user