external-dns/provider/gandi/gandi_test.go
Andy Bursavich 74ffff6c26 gofumpt
2022-09-20 20:48:57 -07:00

759 lines
20 KiB
Go

/*
Copyright 2021 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 gandi
import (
"context"
"fmt"
"os"
"reflect"
"strings"
"testing"
"github.com/go-gandi/go-gandi/domain"
"github.com/go-gandi/go-gandi/livedns"
"github.com/maxatome/go-testdeep/td"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/plan"
)
type MockAction struct {
Name string
FQDN string
Record livedns.DomainRecord
}
type mockGandiClient struct {
Actions []MockAction
FunctionToFail string
RecordsToReturn []livedns.DomainRecord
}
func mockGandiClientNew() *mockGandiClient {
return &mockGandiClient{
RecordsToReturn: testRecords(),
}
}
func mockGandiClientNewWithRecords(recordsToReturn []livedns.DomainRecord) *mockGandiClient {
return &mockGandiClient{
RecordsToReturn: recordsToReturn,
}
}
func mockGandiClientNewWithFailure(functionToFail string) *mockGandiClient {
return &mockGandiClient{
FunctionToFail: functionToFail,
RecordsToReturn: testRecords(),
}
}
const (
domainUriPrefix = "https://api.gandi.net/v5/domain/domains/"
exampleDotComUri = domainUriPrefix + "example.com"
exampleDotNetUri = domainUriPrefix + "example.net"
)
func testRecords() []livedns.DomainRecord {
return []livedns.DomainRecord{
{
RrsetType: endpoint.RecordTypeCNAME,
RrsetTTL: 600,
RrsetName: "@",
RrsetHref: exampleDotComUri + "/records/%40/A",
RrsetValues: []string{"192.168.0.1"},
},
{
RrsetType: endpoint.RecordTypeCNAME,
RrsetTTL: 600,
RrsetName: "www",
RrsetHref: exampleDotComUri + "/records/www/CNAME",
RrsetValues: []string{"lb.example.com"},
},
{
RrsetType: endpoint.RecordTypeA,
RrsetTTL: 600,
RrsetName: "test",
RrsetHref: exampleDotComUri + "/records/test/A",
RrsetValues: []string{"192.168.0.2"},
},
}
}
// Mock all methods
func (m *mockGandiClient) GetDomainRecords(fqdn string) (records []livedns.DomainRecord, err error) {
m.Actions = append(m.Actions, MockAction{
Name: "GetDomainRecords",
FQDN: fqdn,
})
if m.FunctionToFail == "GetDomainRecords" {
return nil, fmt.Errorf("injected error")
}
return m.RecordsToReturn, err
}
func (m *mockGandiClient) CreateDomainRecord(fqdn, name, recordtype string, ttl int, values []string) (response standardResponse, err error) {
m.Actions = append(m.Actions, MockAction{
Name: "CreateDomainRecord",
FQDN: fqdn,
Record: livedns.DomainRecord{
RrsetType: recordtype,
RrsetTTL: ttl,
RrsetName: name,
RrsetValues: values,
},
})
if m.FunctionToFail == "CreateDomainRecord" {
return standardResponse{}, fmt.Errorf("injected error")
}
return standardResponse{}, nil
}
func (m *mockGandiClient) DeleteDomainRecord(fqdn, name, recordtype string) (err error) {
m.Actions = append(m.Actions, MockAction{
Name: "DeleteDomainRecord",
FQDN: fqdn,
Record: livedns.DomainRecord{
RrsetType: recordtype,
RrsetName: name,
},
})
if m.FunctionToFail == "DeleteDomainRecord" {
return fmt.Errorf("injected error")
}
return nil
}
func (m *mockGandiClient) UpdateDomainRecordByNameAndType(fqdn, name, recordtype string, ttl int, values []string) (response standardResponse, err error) {
m.Actions = append(m.Actions, MockAction{
Name: "UpdateDomainRecordByNameAndType",
FQDN: fqdn,
Record: livedns.DomainRecord{
RrsetType: recordtype,
RrsetTTL: ttl,
RrsetName: name,
RrsetValues: values,
},
})
if m.FunctionToFail == "UpdateDomainRecordByNameAndType" {
return standardResponse{}, fmt.Errorf("injected error")
}
return standardResponse{}, nil
}
func (m *mockGandiClient) ListDomains() (domains []domain.ListResponse, err error) {
m.Actions = append(m.Actions, MockAction{
Name: "ListDomains",
})
if m.FunctionToFail == "ListDomains" {
return []domain.ListResponse{}, fmt.Errorf("injected error")
}
return []domain.ListResponse{
{
FQDN: "example.com",
FQDNUnicode: "example.com",
Href: exampleDotComUri,
ID: "b3e9c271-1c29-4441-97d9-bc021a7ac7c3",
NameServer: &domain.NameServerConfig{
Current: gandiLiveDNSProvider,
},
TLD: "com",
},
{
FQDN: "example.net",
FQDNUnicode: "example.net",
Href: exampleDotNetUri,
ID: "dc78c1d8-6143-4edb-93bc-3a20d8bc3570",
NameServer: &domain.NameServerConfig{
Current: "other",
},
TLD: "net",
},
}, nil
}
// Tests
func TestNewGandiProvider(t *testing.T) {
_ = os.Setenv("GANDI_KEY", "myGandiKey")
provider, err := NewGandiProvider(context.Background(), endpoint.NewDomainFilter([]string{"example.com"}), true)
if err != nil {
t.Errorf("failed : %s", err)
}
assert.Equal(t, true, provider.DryRun)
_ = os.Setenv("GANDI_SHARING_ID", "aSharingId")
provider, err = NewGandiProvider(context.Background(), endpoint.NewDomainFilter([]string{"example.com"}), false)
if err != nil {
t.Errorf("failed : %s", err)
}
assert.Equal(t, false, provider.DryRun)
_ = os.Unsetenv("GANDI_KEY")
_, err = NewGandiProvider(context.Background(), endpoint.NewDomainFilter([]string{"example.com"}), true)
if err == nil {
t.Errorf("expected to fail")
}
}
func TestGandiProvider_TestData(t *testing.T) {
mockedClient := mockGandiClientNew()
// Check test zone data is ok
expectedZonesAnswer := []domain.ListResponse{
{
FQDN: "example.com",
FQDNUnicode: "example.com",
Href: exampleDotComUri,
ID: "b3e9c271-1c29-4441-97d9-bc021a7ac7c3",
NameServer: &domain.NameServerConfig{
Current: gandiLiveDNSProvider,
},
TLD: "com",
},
{
FQDN: "example.net",
FQDNUnicode: "example.net",
Href: exampleDotNetUri,
ID: "dc78c1d8-6143-4edb-93bc-3a20d8bc3570",
NameServer: &domain.NameServerConfig{
Current: "other",
},
TLD: "net",
},
}
testingZonesAnswer, err := mockedClient.ListDomains()
if err != nil {
t.Errorf("should not fail, %s", err)
}
if !reflect.DeepEqual(expectedZonesAnswer, testingZonesAnswer) {
t.Errorf("should be equal, %s", err)
}
// Check test record data is ok
expectedRecordsAnswer := []livedns.DomainRecord{
{
RrsetType: endpoint.RecordTypeCNAME,
RrsetTTL: 600,
RrsetName: "@",
RrsetHref: exampleDotComUri + "/records/%40/A",
RrsetValues: []string{"192.168.0.1"},
},
{
RrsetType: endpoint.RecordTypeCNAME,
RrsetTTL: 600,
RrsetName: "www",
RrsetHref: exampleDotComUri + "/records/www/CNAME",
RrsetValues: []string{"lb.example.com"},
},
{
RrsetType: endpoint.RecordTypeA,
RrsetTTL: 600,
RrsetName: "test",
RrsetHref: exampleDotComUri + "/records/test/A",
RrsetValues: []string{"192.168.0.2"},
},
}
testingRecordsAnswer, err := mockedClient.GetDomainRecords("example.com")
if err != nil {
t.Errorf("should not fail, %s", err)
}
if !reflect.DeepEqual(expectedRecordsAnswer, testingRecordsAnswer) {
t.Errorf("should be equal, %s", err)
}
}
func TestGandiProvider_Records(t *testing.T) {
mockedClient := mockGandiClientNew()
mockedProvider := &GandiProvider{
DomainClient: mockedClient,
LiveDNSClient: mockedClient,
}
expectedActions := []MockAction{
{
Name: "ListDomains",
},
{
Name: "GetDomainRecords",
FQDN: "example.com",
},
}
endpoints, err := mockedProvider.Records(context.Background())
if err != nil {
t.Errorf("should not fail, %s", err)
}
assert.Equal(t, 3, len(endpoints))
fmt.Printf("%+v\n", endpoints[0].DNSName)
assert.Equal(t, "example.com", endpoints[0].DNSName)
assert.Equal(t, endpoint.RecordTypeCNAME, endpoints[0].RecordType)
td.Cmp(t, expectedActions, mockedClient.Actions)
}
func TestGandiProvider_RecordsAppliesDomainFilter(t *testing.T) {
mockedClient := mockGandiClientNew()
mockedProvider := &GandiProvider{
DomainClient: mockedClient,
LiveDNSClient: mockedClient,
domainFilter: endpoint.NewDomainFilterWithExclusions([]string{}, []string{"example.com"}),
}
expectedActions := []MockAction{
{
Name: "ListDomains",
},
}
endpoints, err := mockedProvider.Records(context.Background())
if err != nil {
t.Errorf("should not fail, %s", err)
}
assert.Equal(t, 0, len(endpoints))
td.Cmp(t, expectedActions, mockedClient.Actions)
}
func TestGandiProvider_RecordsErrorOnMultipleValues(t *testing.T) {
mockedClient := mockGandiClientNewWithRecords([]livedns.DomainRecord{
{
RrsetValues: []string{"foo", "bar"},
RrsetType: endpoint.RecordTypeCNAME,
},
})
mockedProvider := &GandiProvider{
DomainClient: mockedClient,
LiveDNSClient: mockedClient,
}
expectedActions := []MockAction{
{
Name: "ListDomains",
},
{
Name: "GetDomainRecords",
FQDN: "example.com",
},
}
endpoints, err := mockedProvider.Records(context.Background())
if err == nil {
t.Errorf("expected to fail")
}
assert.Equal(t, 0, len(endpoints))
assert.True(t, strings.HasPrefix(err.Error(), "can't handle multiple values for rrset"))
td.Cmp(t, expectedActions, mockedClient.Actions)
}
func TestGandiProvider_ApplyChangesEmpty(t *testing.T) {
changes := &plan.Changes{}
mockedClient := mockGandiClientNew()
mockedProvider := &GandiProvider{
DomainClient: mockedClient,
LiveDNSClient: mockedClient,
}
err := mockedProvider.ApplyChanges(context.Background(), changes)
if err != nil {
t.Errorf("should not fail, %s", err)
}
if mockedClient.Actions != nil {
t.Error("expected no changes")
}
}
func TestGandiProvider_ApplyChanges(t *testing.T) {
changes := &plan.Changes{}
mockedClient := mockGandiClientNew()
mockedProvider := &GandiProvider{
DomainClient: mockedClient,
LiveDNSClient: mockedClient,
}
changes.Create = []*endpoint.Endpoint{{DNSName: "test2.example.com", Targets: endpoint.Targets{"target"}, RecordType: "A", RecordTTL: 666}}
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "test3.example.com", Targets: endpoint.Targets{"target-new"}, RecordType: "A", RecordTTL: 777}}
changes.Delete = []*endpoint.Endpoint{{DNSName: "test4.example.com", Targets: endpoint.Targets{"target-other"}, RecordType: "A"}}
err := mockedProvider.ApplyChanges(context.Background(), changes)
if err != nil {
t.Errorf("should not fail, %s", err)
}
td.Cmp(t, mockedClient.Actions, []MockAction{
{
Name: "ListDomains",
},
{
Name: "CreateDomainRecord",
FQDN: "example.com",
Record: livedns.DomainRecord{
RrsetType: endpoint.RecordTypeA,
RrsetName: "test2",
RrsetValues: []string{"target"},
RrsetTTL: 666,
},
},
{
Name: "UpdateDomainRecordByNameAndType",
FQDN: "example.com",
Record: livedns.DomainRecord{
RrsetType: endpoint.RecordTypeA,
RrsetName: "test3",
RrsetValues: []string{"target-new"},
RrsetTTL: 777,
},
},
{
Name: "DeleteDomainRecord",
FQDN: "example.com",
Record: livedns.DomainRecord{
RrsetType: endpoint.RecordTypeA,
RrsetName: "test4",
},
},
})
}
func TestGandiProvider_ApplyChangesSkipsNonManaged(t *testing.T) {
changes := &plan.Changes{}
mockedClient := mockGandiClientNew()
mockedProvider := &GandiProvider{
DomainClient: mockedClient,
LiveDNSClient: mockedClient,
}
changes.Create = []*endpoint.Endpoint{{DNSName: "example.net", Targets: endpoint.Targets{"target"}}}
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "test.example.net", Targets: endpoint.Targets{"target-new"}, RecordType: "A", RecordTTL: 777}}
changes.Delete = []*endpoint.Endpoint{{DNSName: "test2.example.net", Targets: endpoint.Targets{"target"}, RecordType: "A"}}
err := mockedProvider.ApplyChanges(context.Background(), changes)
if err != nil {
t.Errorf("should not fail, %s", err)
}
td.Cmp(t, mockedClient.Actions, []MockAction{
{
Name: "ListDomains",
},
})
}
func TestGandiProvider_ApplyChangesCreateUpdateCname(t *testing.T) {
changes := &plan.Changes{}
mockedClient := mockGandiClientNew()
mockedProvider := &GandiProvider{
DomainClient: mockedClient,
LiveDNSClient: mockedClient,
}
changes.Create = []*endpoint.Endpoint{
{DNSName: "test-cname.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "CNAME"},
}
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "test-cname2.example.com", Targets: endpoint.Targets{"target-new"}, RecordType: "CNAME", RecordTTL: 777}}
err := mockedProvider.ApplyChanges(context.Background(), changes)
if err != nil {
t.Errorf("should not fail, %s", err)
}
td.Cmp(t, mockedClient.Actions, []MockAction{
{
Name: "ListDomains",
},
{
Name: "CreateDomainRecord",
FQDN: "example.com",
Record: livedns.DomainRecord{
RrsetType: endpoint.RecordTypeCNAME,
RrsetName: "test-cname",
RrsetValues: []string{"target."},
RrsetTTL: 666,
},
},
{
Name: "UpdateDomainRecordByNameAndType",
FQDN: "example.com",
Record: livedns.DomainRecord{
RrsetType: endpoint.RecordTypeCNAME,
RrsetName: "test-cname2",
RrsetValues: []string{"target-new."},
RrsetTTL: 777,
},
},
})
}
func TestGandiProvider_ApplyChangesCreateEmpty(t *testing.T) {
changes := &plan.Changes{}
mockedClient := mockGandiClientNew()
mockedProvider := &GandiProvider{
DomainClient: mockedClient,
LiveDNSClient: mockedClient,
}
changes.Create = []*endpoint.Endpoint{
{DNSName: "example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
changes.UpdateNew = []*endpoint.Endpoint{}
err := mockedProvider.ApplyChanges(context.Background(), changes)
if err != nil {
t.Errorf("should not fail, %s", err)
}
td.Cmp(t, mockedClient.Actions, []MockAction{
{
Name: "ListDomains",
},
{
Name: "CreateDomainRecord",
FQDN: "example.com",
Record: livedns.DomainRecord{
RrsetType: endpoint.RecordTypeA,
RrsetName: "@",
RrsetValues: []string{"target"},
RrsetTTL: 666,
},
},
})
}
func TestGandiProvider_ApplyChangesRespectsDryRun(t *testing.T) {
changes := &plan.Changes{}
mockedClient := mockGandiClientNew()
mockedProvider := &GandiProvider{
DomainClient: mockedClient,
LiveDNSClient: mockedClient,
DryRun: true,
}
changes.Create = []*endpoint.Endpoint{
{DNSName: "foo.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
changes.UpdateNew = []*endpoint.Endpoint{
{DNSName: "bar.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
changes.Delete = []*endpoint.Endpoint{
{DNSName: "baz.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
err := mockedProvider.ApplyChanges(context.Background(), changes)
if err != nil {
t.Errorf("should not fail, %s", err)
}
td.Cmp(t, mockedClient.Actions, []MockAction{
{
Name: "ListDomains",
},
})
}
func TestGandiProvider_ApplyChangesErrorListDomains(t *testing.T) {
changes := &plan.Changes{}
mockedClient := mockGandiClientNewWithFailure("ListDomains")
mockedProvider := &GandiProvider{
DomainClient: mockedClient,
LiveDNSClient: mockedClient,
}
changes.Create = []*endpoint.Endpoint{
{DNSName: "foo.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
changes.UpdateNew = []*endpoint.Endpoint{
{DNSName: "bar.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
changes.Delete = []*endpoint.Endpoint{
{DNSName: "baz.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
err := mockedProvider.ApplyChanges(context.Background(), changes)
if err == nil {
t.Error("should have failed")
}
td.Cmp(t, mockedClient.Actions, []MockAction{
{
Name: "ListDomains",
},
})
}
func TestGandiProvider_ApplyChangesErrorCreate(t *testing.T) {
changes := &plan.Changes{}
mockedClient := mockGandiClientNewWithFailure("CreateDomainRecord")
mockedProvider := &GandiProvider{
DomainClient: mockedClient,
LiveDNSClient: mockedClient,
}
changes.Create = []*endpoint.Endpoint{
{DNSName: "foo.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
changes.UpdateNew = []*endpoint.Endpoint{
{DNSName: "bar.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
changes.Delete = []*endpoint.Endpoint{
{DNSName: "baz.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
err := mockedProvider.ApplyChanges(context.Background(), changes)
if err == nil {
t.Error("should have failed")
}
td.Cmp(t, mockedClient.Actions, []MockAction{
{
Name: "ListDomains",
},
{
Name: "CreateDomainRecord",
FQDN: "example.com",
Record: livedns.DomainRecord{
RrsetType: endpoint.RecordTypeA,
RrsetName: "foo",
RrsetValues: []string{"target"},
RrsetTTL: 666,
},
},
})
}
func TestGandiProvider_ApplyChangesErrorUpdate(t *testing.T) {
changes := &plan.Changes{}
mockedClient := mockGandiClientNewWithFailure("UpdateDomainRecordByNameAndType")
mockedProvider := &GandiProvider{
DomainClient: mockedClient,
LiveDNSClient: mockedClient,
}
changes.Create = []*endpoint.Endpoint{
{DNSName: "foo.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
changes.UpdateNew = []*endpoint.Endpoint{
{DNSName: "bar.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
changes.Delete = []*endpoint.Endpoint{
{DNSName: "baz.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
err := mockedProvider.ApplyChanges(context.Background(), changes)
if err == nil {
t.Error("should have failed")
}
td.Cmp(t, mockedClient.Actions, []MockAction{
{
Name: "ListDomains",
},
{
Name: "CreateDomainRecord",
FQDN: "example.com",
Record: livedns.DomainRecord{
RrsetType: endpoint.RecordTypeA,
RrsetName: "foo",
RrsetValues: []string{"target"},
RrsetTTL: 666,
},
},
{
Name: "UpdateDomainRecordByNameAndType",
FQDN: "example.com",
Record: livedns.DomainRecord{
RrsetType: endpoint.RecordTypeA,
RrsetName: "bar",
RrsetValues: []string{"target"},
RrsetTTL: 666,
},
},
})
}
func TestGandiProvider_ApplyChangesErrorDelete(t *testing.T) {
changes := &plan.Changes{}
mockedClient := mockGandiClientNewWithFailure("DeleteDomainRecord")
mockedProvider := &GandiProvider{
DomainClient: mockedClient,
LiveDNSClient: mockedClient,
}
changes.Create = []*endpoint.Endpoint{
{DNSName: "foo.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
changes.UpdateNew = []*endpoint.Endpoint{
{DNSName: "bar.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
changes.Delete = []*endpoint.Endpoint{
{DNSName: "baz.example.com", Targets: endpoint.Targets{"target"}, RecordTTL: 666, RecordType: "A"},
}
err := mockedProvider.ApplyChanges(context.Background(), changes)
if err == nil {
t.Error("should have failed")
}
td.Cmp(t, mockedClient.Actions, []MockAction{
{
Name: "ListDomains",
},
{
Name: "CreateDomainRecord",
FQDN: "example.com",
Record: livedns.DomainRecord{
RrsetType: endpoint.RecordTypeA,
RrsetName: "foo",
RrsetValues: []string{"target"},
RrsetTTL: 666,
},
},
{
Name: "UpdateDomainRecordByNameAndType",
FQDN: "example.com",
Record: livedns.DomainRecord{
RrsetType: endpoint.RecordTypeA,
RrsetName: "bar",
RrsetValues: []string{"target"},
RrsetTTL: 666,
},
},
{
Name: "DeleteDomainRecord",
FQDN: "example.com",
Record: livedns.DomainRecord{
RrsetType: endpoint.RecordTypeA,
RrsetName: "baz",
},
},
})
}