mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 01:26:59 +02:00
New configuration options created for setting the DNS deployment type, as well as the DNS server to deploy. A DNS server name must be provided and a valid DNS deployment type must be set in order for a deployment to be initiated. Currently, the only supported deployment type is "full deploy", however "quick deploy" and "selective deploy" could be added in the future.
500 lines
16 KiB
Go
500 lines
16 KiB
Go
/*
|
|
Copyright 2020 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 bluecat
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"sigs.k8s.io/external-dns/endpoint"
|
|
"sigs.k8s.io/external-dns/internal/testutils"
|
|
"sigs.k8s.io/external-dns/plan"
|
|
"sigs.k8s.io/external-dns/provider"
|
|
)
|
|
|
|
type mockGatewayClient struct {
|
|
mockBluecatZones *[]BluecatZone
|
|
mockBluecatHosts *[]BluecatHostRecord
|
|
mockBluecatCNAMEs *[]BluecatCNAMERecord
|
|
mockBluecatTXTs *[]BluecatTXTRecord
|
|
}
|
|
|
|
type Changes struct {
|
|
// Records that need to be created
|
|
Create []*endpoint.Endpoint
|
|
// Records that need to be updated (current data)
|
|
UpdateOld []*endpoint.Endpoint
|
|
// Records that need to be updated (desired data)
|
|
UpdateNew []*endpoint.Endpoint
|
|
// Records that need to be deleted
|
|
Delete []*endpoint.Endpoint
|
|
}
|
|
|
|
func (g mockGatewayClient) getBluecatZones(zoneName string) ([]BluecatZone, error) {
|
|
return *g.mockBluecatZones, nil
|
|
}
|
|
func (g mockGatewayClient) getHostRecords(zone string, records *[]BluecatHostRecord) error {
|
|
*records = *g.mockBluecatHosts
|
|
return nil
|
|
}
|
|
func (g mockGatewayClient) getCNAMERecords(zone string, records *[]BluecatCNAMERecord) error {
|
|
*records = *g.mockBluecatCNAMEs
|
|
return nil
|
|
}
|
|
func (g mockGatewayClient) getHostRecord(name string, record *BluecatHostRecord) error {
|
|
for _, currentRecord := range *g.mockBluecatHosts {
|
|
if currentRecord.Name == strings.Split(name, ".")[0] {
|
|
*record = currentRecord
|
|
return nil
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func (g mockGatewayClient) getCNAMERecord(name string, record *BluecatCNAMERecord) error {
|
|
for _, currentRecord := range *g.mockBluecatCNAMEs {
|
|
if currentRecord.Name == strings.Split(name, ".")[0] {
|
|
*record = currentRecord
|
|
return nil
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func (g mockGatewayClient) createHostRecord(zone string, req *bluecatCreateHostRecordRequest) (res interface{}, err error) {
|
|
return nil, nil
|
|
}
|
|
func (g mockGatewayClient) createCNAMERecord(zone string, req *bluecatCreateCNAMERecordRequest) (res interface{}, err error) {
|
|
return nil, nil
|
|
}
|
|
func (g mockGatewayClient) deleteHostRecord(name string, zone string) (err error) {
|
|
*g.mockBluecatHosts = nil
|
|
return nil
|
|
}
|
|
func (g mockGatewayClient) deleteCNAMERecord(name string, zone string) (err error) {
|
|
*g.mockBluecatCNAMEs = nil
|
|
return nil
|
|
}
|
|
func (g mockGatewayClient) getTXTRecords(zone string, records *[]BluecatTXTRecord) error {
|
|
*records = *g.mockBluecatTXTs
|
|
return nil
|
|
}
|
|
func (g mockGatewayClient) getTXTRecord(name string, record *BluecatTXTRecord) error {
|
|
for _, currentRecord := range *g.mockBluecatTXTs {
|
|
if currentRecord.Name == name {
|
|
*record = currentRecord
|
|
return nil
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func (g mockGatewayClient) createTXTRecord(zone string, req *bluecatCreateTXTRecordRequest) (res interface{}, err error) {
|
|
return nil, nil
|
|
}
|
|
func (g mockGatewayClient) deleteTXTRecord(name string, zone string) error {
|
|
*g.mockBluecatTXTs = nil
|
|
return nil
|
|
}
|
|
func (g mockGatewayClient) serverFullDeploy() error {
|
|
return nil
|
|
}
|
|
|
|
func (g mockGatewayClient) buildHTTPRequest(method, url string, body io.Reader) (*http.Request, error) {
|
|
request, _ := http.NewRequest("GET", fmt.Sprintf("%s/users", "http://some.com/api/v1"), nil)
|
|
return request, nil
|
|
}
|
|
|
|
func createMockBluecatZone(fqdn string) BluecatZone {
|
|
props := "absoluteName=" + fqdn
|
|
return BluecatZone{
|
|
Properties: props,
|
|
Name: fqdn,
|
|
ID: 3,
|
|
}
|
|
}
|
|
|
|
func createMockBluecatHostRecord(fqdn, target string, ttl int) BluecatHostRecord {
|
|
props := "absoluteName=" + fqdn + "|addresses=" + target + "|ttl=" + fmt.Sprint(ttl) + "|"
|
|
nameParts := strings.Split(fqdn, ".")
|
|
return BluecatHostRecord{
|
|
Name: nameParts[0],
|
|
Properties: props,
|
|
ID: 3,
|
|
}
|
|
}
|
|
|
|
func createMockBluecatCNAME(alias, target string, ttl int) BluecatCNAMERecord {
|
|
props := "absoluteName=" + alias + "|linkedRecordName=" + target + "|ttl=" + fmt.Sprint(ttl) + "|"
|
|
nameParts := strings.Split(alias, ".")
|
|
return BluecatCNAMERecord{
|
|
Name: nameParts[0],
|
|
Properties: props,
|
|
}
|
|
}
|
|
|
|
func createMockBluecatTXT(fqdn, txt string) BluecatTXTRecord {
|
|
return BluecatTXTRecord{
|
|
Name: fqdn,
|
|
Properties: txt,
|
|
}
|
|
}
|
|
|
|
func newBluecatProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, client GatewayClient) *BluecatProvider {
|
|
return &BluecatProvider{
|
|
domainFilter: domainFilter,
|
|
zoneIDFilter: zoneIDFilter,
|
|
dryRun: dryRun,
|
|
gatewayClient: client,
|
|
}
|
|
}
|
|
|
|
type bluecatTestData []struct {
|
|
TestDescription string
|
|
Endpoints []*endpoint.Endpoint
|
|
}
|
|
|
|
var tests = bluecatTestData{
|
|
{
|
|
"first test case", // TODO: better test description
|
|
[]*endpoint.Endpoint{
|
|
{
|
|
DNSName: "example.com",
|
|
RecordType: endpoint.RecordTypeA,
|
|
Targets: endpoint.Targets{"123.123.123.122"},
|
|
RecordTTL: endpoint.TTL(30),
|
|
},
|
|
{
|
|
DNSName: "nginx.example.com",
|
|
RecordType: endpoint.RecordTypeA,
|
|
Targets: endpoint.Targets{"123.123.123.123"},
|
|
RecordTTL: endpoint.TTL(30),
|
|
},
|
|
{
|
|
DNSName: "whitespace.example.com",
|
|
RecordType: endpoint.RecordTypeA,
|
|
Targets: endpoint.Targets{"123.123.123.124"},
|
|
RecordTTL: endpoint.TTL(30),
|
|
},
|
|
{
|
|
DNSName: "hack.example.com",
|
|
RecordType: endpoint.RecordTypeCNAME,
|
|
Targets: endpoint.Targets{"bluecatnetworks.com"},
|
|
RecordTTL: endpoint.TTL(30),
|
|
},
|
|
{
|
|
DNSName: "wack.example.com",
|
|
RecordType: endpoint.RecordTypeTXT,
|
|
Targets: endpoint.Targets{"hello"},
|
|
Labels: endpoint.Labels{"owner": ""},
|
|
},
|
|
{
|
|
DNSName: "sack.example.com",
|
|
RecordType: endpoint.RecordTypeTXT,
|
|
Targets: endpoint.Targets{""},
|
|
Labels: endpoint.Labels{"owner": ""},
|
|
},
|
|
{
|
|
DNSName: "kdb.example.com",
|
|
RecordType: endpoint.RecordTypeTXT,
|
|
Targets: endpoint.Targets{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
|
|
Labels: endpoint.Labels{"owner": "default"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestBluecatRecords(t *testing.T) {
|
|
client := mockGatewayClient{
|
|
mockBluecatZones: &[]BluecatZone{
|
|
createMockBluecatZone("example.com"),
|
|
},
|
|
mockBluecatTXTs: &[]BluecatTXTRecord{
|
|
createMockBluecatTXT("kdb.example.com", "heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"),
|
|
createMockBluecatTXT("wack.example.com", "hello"),
|
|
createMockBluecatTXT("sack.example.com", ""),
|
|
},
|
|
mockBluecatHosts: &[]BluecatHostRecord{
|
|
createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
|
|
createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
|
|
createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
|
|
},
|
|
mockBluecatCNAMEs: &[]BluecatCNAMERecord{
|
|
createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
|
|
},
|
|
}
|
|
|
|
provider := newBluecatProvider(
|
|
endpoint.NewDomainFilter([]string{"example.com"}),
|
|
provider.NewZoneIDFilter([]string{""}), false, client)
|
|
|
|
for _, ti := range tests {
|
|
actual, err := provider.Records(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
validateEndpoints(t, actual, ti.Endpoints)
|
|
}
|
|
}
|
|
|
|
func TestBluecatApplyChangesCreate(t *testing.T) {
|
|
client := mockGatewayClient{
|
|
mockBluecatZones: &[]BluecatZone{
|
|
createMockBluecatZone("example.com"),
|
|
},
|
|
mockBluecatHosts: &[]BluecatHostRecord{},
|
|
mockBluecatCNAMEs: &[]BluecatCNAMERecord{},
|
|
mockBluecatTXTs: &[]BluecatTXTRecord{},
|
|
}
|
|
|
|
provider := newBluecatProvider(
|
|
endpoint.NewDomainFilter([]string{"example.com"}),
|
|
provider.NewZoneIDFilter([]string{""}), false, client)
|
|
|
|
for _, ti := range tests {
|
|
err := provider.ApplyChanges(context.Background(), &plan.Changes{Create: ti.Endpoints})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
actual, err := provider.Records(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
validateEndpoints(t, actual, []*endpoint.Endpoint{})
|
|
}
|
|
}
|
|
func TestBluecatApplyChangesDelete(t *testing.T) {
|
|
client := mockGatewayClient{
|
|
mockBluecatZones: &[]BluecatZone{
|
|
createMockBluecatZone("example.com"),
|
|
},
|
|
mockBluecatHosts: &[]BluecatHostRecord{
|
|
createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
|
|
createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
|
|
createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
|
|
},
|
|
mockBluecatCNAMEs: &[]BluecatCNAMERecord{
|
|
createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
|
|
},
|
|
mockBluecatTXTs: &[]BluecatTXTRecord{
|
|
createMockBluecatTXT("kdb.example.com", "heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"),
|
|
createMockBluecatTXT("wack.example.com", "hello"),
|
|
createMockBluecatTXT("sack.example.com", ""),
|
|
},
|
|
}
|
|
|
|
provider := newBluecatProvider(
|
|
endpoint.NewDomainFilter([]string{"example.com"}),
|
|
provider.NewZoneIDFilter([]string{""}), false, client)
|
|
|
|
for _, ti := range tests {
|
|
err := provider.ApplyChanges(context.Background(), &plan.Changes{Delete: ti.Endpoints})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
actual, err := provider.Records(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
validateEndpoints(t, actual, []*endpoint.Endpoint{})
|
|
}
|
|
}
|
|
|
|
func TestBluecatApplyChangesDeleteWithOwner(t *testing.T) {
|
|
client := mockGatewayClient{
|
|
mockBluecatZones: &[]BluecatZone{
|
|
createMockBluecatZone("example.com"),
|
|
},
|
|
mockBluecatHosts: &[]BluecatHostRecord{
|
|
createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
|
|
createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
|
|
createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
|
|
},
|
|
mockBluecatCNAMEs: &[]BluecatCNAMERecord{
|
|
createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
|
|
},
|
|
mockBluecatTXTs: &[]BluecatTXTRecord{
|
|
createMockBluecatTXT("kdb.example.com", "heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"),
|
|
createMockBluecatTXT("wack.example.com", "hello"),
|
|
createMockBluecatTXT("sack.example.com", ""),
|
|
},
|
|
}
|
|
|
|
provider := newBluecatProvider(
|
|
endpoint.NewDomainFilter([]string{"example.com"}),
|
|
provider.NewZoneIDFilter([]string{""}), false, client)
|
|
|
|
for _, ti := range tests {
|
|
for _, ep := range ti.Endpoints {
|
|
if strings.Contains(ep.Targets.String(), "external-dns") {
|
|
owner, err := extractOwnerfromTXTRecord(ep.Targets.String())
|
|
if err != nil {
|
|
continue
|
|
}
|
|
t.Logf("Owner %s %s", owner, err)
|
|
}
|
|
}
|
|
err := provider.ApplyChanges(context.Background(), &plan.Changes{Delete: ti.Endpoints})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
actual, err := provider.Records(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
validateEndpoints(t, actual, []*endpoint.Endpoint{})
|
|
}
|
|
|
|
}
|
|
|
|
func TestExpandZones(t *testing.T) {
|
|
mockZones := []string{"example.com", "nginx.example.com", "hack.example.com"}
|
|
expected := []string{"zones/com/zones/example/zones/", "zones/com/zones/example/zones/nginx/zones/", "zones/com/zones/example/zones/hack/zones/"}
|
|
for i := range mockZones {
|
|
if expandZone(mockZones[i]) != expected[i] {
|
|
t.Fatalf("%s", expected[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBluecatNewGatewayClient(t *testing.T) {
|
|
testCookie := http.Cookie{Name: "testCookie", Value: "exampleCookie"}
|
|
testToken := "exampleToken"
|
|
testgateWayHost := "exampleHost"
|
|
testDNSConfiguration := "exampleDNSConfiguration"
|
|
testDNSServer := "exampleServer"
|
|
testView := "testView"
|
|
testZone := "example.com"
|
|
testVerify := true
|
|
|
|
client := NewGatewayClient(testCookie, testToken, testgateWayHost, testDNSConfiguration, testView, testZone, testDNSServer, testVerify)
|
|
|
|
if client.Cookie.Value != testCookie.Value || client.Cookie.Name != testCookie.Name || client.Token != testToken || client.Host != testgateWayHost || client.DNSConfiguration != testDNSConfiguration || client.View != testView || client.RootZone != testZone || client.SkipTLSVerify != testVerify {
|
|
t.Fatal("Client values dont match")
|
|
}
|
|
}
|
|
|
|
// TODO: ensure findZone method is tested
|
|
// TODO: ensure zones method is tested
|
|
// TODO: ensure createRecords method is tested
|
|
// TODO: ensure deleteRecords method is tested
|
|
// TODO: ensure recordSet method is tested
|
|
|
|
// TODO: Figure out why recordSet.res is not being set properly
|
|
func TestBluecatRecordset(t *testing.T) {
|
|
client := mockGatewayClient{
|
|
mockBluecatZones: &[]BluecatZone{
|
|
createMockBluecatZone("example.com"),
|
|
},
|
|
mockBluecatHosts: &[]BluecatHostRecord{
|
|
createMockBluecatHostRecord("example.com", "123.123.123.122", 30),
|
|
createMockBluecatHostRecord("nginx.example.com", "123.123.123.123", 30),
|
|
createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
|
|
},
|
|
mockBluecatCNAMEs: &[]BluecatCNAMERecord{
|
|
createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
|
|
},
|
|
mockBluecatTXTs: &[]BluecatTXTRecord{
|
|
createMockBluecatTXT("abc.example.com", "hello"),
|
|
},
|
|
}
|
|
|
|
provider := newBluecatProvider(
|
|
endpoint.NewDomainFilter([]string{"example.com"}),
|
|
provider.NewZoneIDFilter([]string{""}), false, client)
|
|
|
|
// Test txt records for recordSet function
|
|
testTxtEndpoint := endpoint.NewEndpoint("abc.example.com", endpoint.RecordTypeTXT, "hello")
|
|
txtObj := bluecatCreateTXTRecordRequest{
|
|
AbsoluteName: testTxtEndpoint.DNSName,
|
|
Text: testTxtEndpoint.Targets[0],
|
|
}
|
|
txtRecords := []BluecatTXTRecord{
|
|
createMockBluecatTXT("abc.example.com", "hello"),
|
|
}
|
|
expected := bluecatRecordSet{
|
|
obj: &txtObj,
|
|
res: &txtRecords,
|
|
}
|
|
actual, err := provider.recordSet(testTxtEndpoint, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, actual.obj, expected.obj)
|
|
assert.Equal(t, actual.res, expected.res)
|
|
|
|
// Test a records for recordSet function
|
|
testHostEndpoint := endpoint.NewEndpoint("whitespace.example.com", endpoint.RecordTypeA, "123.123.123.124")
|
|
hostObj := bluecatCreateHostRecordRequest{
|
|
AbsoluteName: testHostEndpoint.DNSName,
|
|
IP4Address: testHostEndpoint.Targets[0],
|
|
}
|
|
hostRecords := []BluecatHostRecord{
|
|
createMockBluecatHostRecord("whitespace.example.com", "123.123.123.124", 30),
|
|
}
|
|
hostExpected := bluecatRecordSet{
|
|
obj: &hostObj,
|
|
res: &hostRecords,
|
|
}
|
|
hostActual, err := provider.recordSet(testHostEndpoint, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, hostActual.obj, hostExpected.obj)
|
|
assert.Equal(t, hostActual.res, hostExpected.res)
|
|
|
|
// Test CName records for recordSet function
|
|
testCnameEndpoint := endpoint.NewEndpoint("hack.example.com", endpoint.RecordTypeCNAME, "bluecatnetworks.com")
|
|
cnameObj := bluecatCreateCNAMERecordRequest{
|
|
AbsoluteName: testCnameEndpoint.DNSName,
|
|
LinkedRecord: testCnameEndpoint.Targets[0],
|
|
}
|
|
cnameRecords := []BluecatCNAMERecord{
|
|
createMockBluecatCNAME("hack.example.com", "bluecatnetworks.com", 30),
|
|
}
|
|
cnameExpected := bluecatRecordSet{
|
|
obj: &cnameObj,
|
|
res: &cnameRecords,
|
|
}
|
|
cnameActual, err := provider.recordSet(testCnameEndpoint, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, cnameActual.obj, cnameExpected.obj)
|
|
assert.Equal(t, cnameActual.res, cnameExpected.res)
|
|
}
|
|
|
|
func TestValidDeployTypes(t *testing.T) {
|
|
validTypes := []string{"no-deploy", "full-deploy"}
|
|
invalidTypes := []string{"anything-else"}
|
|
for _, i := range validTypes {
|
|
if !isValidDNSDeployType(i) {
|
|
t.Fatalf("%s should be a valid deploy type", i)
|
|
}
|
|
}
|
|
for _, i := range invalidTypes {
|
|
if isValidDNSDeployType(i) {
|
|
t.Fatalf("%s should be a invalid deploy type", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func validateEndpoints(t *testing.T, actual, expected []*endpoint.Endpoint) {
|
|
assert.True(t, testutils.SameEndpoints(actual, expected), "actual and expected endpoints don't match. %s:%s", actual, expected)
|
|
}
|