external-dns/provider/oci/oci_test.go
Raffaele Di Fazio db4224c705 restructures the providers (build only)
Signed-off-by: Raffaele Di Fazio <difazio.raffaele@gmail.com>
2020-05-08 13:05:17 +02:00

842 lines
26 KiB
Go

/*
Copyright 2018 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 oci
import (
"context"
"sort"
"testing"
"github.com/oracle/oci-go-sdk/common"
"github.com/oracle/oci-go-sdk/dns"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/plan"
)
type mockOCIDNSClient struct{}
func (c *mockOCIDNSClient) ListZones(ctx context.Context, request dns.ListZonesRequest) (response dns.ListZonesResponse, err error) {
if request.Page == nil || *request.Page == "0" {
return dns.ListZonesResponse{
Items: []dns.ZoneSummary{
{
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
Name: common.String("foo.com"),
},
},
OpcNextPage: common.String("1"),
}, nil
}
return dns.ListZonesResponse{
Items: []dns.ZoneSummary{
{
Id: common.String("ocid1.dns-zone.oc1..502aeddba262b92fd13ed7874f6f1404"),
Name: common.String("bar.com"),
},
},
}, nil
}
func (c *mockOCIDNSClient) GetZoneRecords(ctx context.Context, request dns.GetZoneRecordsRequest) (response dns.GetZoneRecordsResponse, err error) {
if request.ZoneNameOrId == nil {
return
}
switch *request.ZoneNameOrId {
case "ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959":
if request.Page == nil || *request.Page == "0" {
response.Items = []dns.Record{{
Domain: common.String("foo.foo.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String(endpoint.RecordTypeA),
Ttl: common.Int(ociRecordTTL),
}, {
Domain: common.String("foo.foo.com"),
Rdata: common.String("heritage=external-dns,external-dns/owner=default,external-dns/resource=service/default/my-svc"),
Rtype: common.String(endpoint.RecordTypeTXT),
Ttl: common.Int(ociRecordTTL),
}}
response.OpcNextPage = common.String("1")
} else {
response.Items = []dns.Record{{Domain: common.String("bar.foo.com"),
Rdata: common.String("bar.com."),
Rtype: common.String(endpoint.RecordTypeCNAME),
Ttl: common.Int(ociRecordTTL),
}}
}
case "ocid1.dns-zone.oc1..502aeddba262b92fd13ed7874f6f1404":
if request.Page == nil || *request.Page == "0" {
response.Items = []dns.Record{{
Domain: common.String("foo.bar.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String(endpoint.RecordTypeA),
Ttl: common.Int(ociRecordTTL),
}}
}
}
return
}
func (c *mockOCIDNSClient) PatchZoneRecords(ctx context.Context, request dns.PatchZoneRecordsRequest) (response dns.PatchZoneRecordsResponse, err error) {
return // Provider does not use the response so nothing to do here.
}
// newOCIProvider creates an OCI provider with API calls mocked out.
func newOCIProvider(client ociDNSClient, domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool) *OCIProvider {
return &OCIProvider{
client: client,
cfg: OCIConfig{
CompartmentID: "ocid1.compartment.oc1..aaaaaaaaujjg4lf3v6uaqeml7xfk7stzvrxeweaeyolhh75exuoqxpqjb4qq",
},
domainFilter: domainFilter,
zoneIDFilter: zoneIDFilter,
dryRun: dryRun,
}
}
func validateOCIZones(t *testing.T, actual, expected map[string]dns.ZoneSummary) {
require.Len(t, actual, len(expected))
for k, a := range actual {
e, ok := expected[k]
require.True(t, ok, "unexpected zone %q (%q)", *a.Name, *a.Id)
require.Equal(t, e, a)
}
}
func TestNewOCIProvider(t *testing.T) {
testCases := map[string]struct {
config OCIConfig
err error
}{
"valid": {
config: OCIConfig{
Auth: OCIAuthConfig{
TenancyID: "ocid1.tenancy.oc1..aaaaaaaaxf3fuazosc6xng7l75rj6uist5jb6ken64t3qltimxnkymddqbma",
UserID: "ocid1.user.oc1..aaaaaaaahx2vpvm4of5nqq3t274ike7ygyk2aexvokk3gyv4eyumzqajcrvq",
Region: "us-ashburn-1",
Fingerprint: "48:ba:d4:21:63:53:db:10:65:20:d4:09:ce:01:f5:97",
PrivateKey: `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAv2JspZyO14kqcO/X4iz3ZdcyAf1GQJqYsBb6wyrlU0PB9Fee
H23/HLtMSqeqo+2KQHmdV1OHFQ/S6tx7zcBaby/+2b+z3/gJO4PGxohe2812AJ/J
W8Fp/4EnwbaRqDhoLN7ms0/e566zE3z40kCSW0NAIzv/F+0nNaka1xrypBqzvaNm
N49dAGvqWRpzFFUb8CbvKmgE6c/H4a2zVNW3G7/K6Og4HQGeEP3NKSVvi0BiQlvd
tVJTg7084kKcrngsS2N3qI3pzsr5wgpzPPefuPHWRKokZ20kpu8tXdFt+mAC2NHh
eWbtY3jsR6JFaXCyZLMXInwDvRgdP0T5+uh8WwIDAQABAoIBAG0rr94omDLKw7L4
naUfEWC+iIAqAdEIXuDTuudpqLb+h7zh3gj/re6tyK8tRWGNNrfgp6gQtZWGGUJv
0w9jEjMqpa2AdRLlYh7Y5KKLV9D6Or3QaAQ3KEffXNZbVmsnAgXWgLL4dKakOPJ8
71LAEryMeCGhL7puRVeOxwi9Dnwc4pcloimdggw/uwVHMK9eY5ylyt5ziiiWfhAo
cnNJNPHRSTqSiCoEhk/8BLZT5gxf1YX0hVSEdQh2WNyxmPmVSC9uuzKOqcEBfHf5
hmLnsUET1REM9IxCLqC9ebW263lIO/KdGiCu+YgIdwIi3wrLhaKXAZQmp4oMvWlE
n5eYlcECgYEA5AhctPWCQBCJhcD39pSWgnSq1O9bt8yQi2P2stqlxKV9ZBepCK49
OT42OYPUgWn7/y//6/LLzsPY58VTDHF3xZN1qu+fU0IM22D3Jqc19pnfVEb6TXSc
0jJIiaYCWTdqRQ4p2DuDcI+EzRB+V1Z7tFWxshZWXwNvtMXNoYPOYaUCgYEA1ttn
R3pCuGYJ5XbBwPzD5J+hvdZ6TQf8oTDraUBPxjtFOr7ea42T6KeYRFvnK2AQDnKL
Mw3I55lNO4I2W9gahUFG28dhxEuxeyvXGqXEJvPCUYePstab/BkUrm7/jkS3CLcJ
dlRXjqOfGwi5+NPUZMoOkZ54ZR4ZpdhIAeEpBf8CgYEAyMyMRlVCowNs9jkcoSfq
+Wme3O8BhvI9/mDCZnCfNHC94Bvtn1U/WF7uBOuPf35Ch05PQAiHa8WOBVn/bZ+l
ZngZT7K+S+SHyc6zFHh9zm9k96Og2f/r8DSTJ5Ll0oY3sCNuuZh+f+oBeUoi1umy
+PPVDAsbd4NhJIBiOO4GGHkCgYA1p4i9Es0Cm4ixItzzwqtwtmR/scXM4se1wS+o
kwTY7gg1yWBl328mVGPz/jdWX6Di2rvkPfcDzwa4a6YDfY3x5QE69Sl3CagCqEoJ
P4giahEGpyG9eVZuuBywCswKzSIgLQVR5XIQDtA2whEfEFcj7EmDF93c8o1ZGw+w
WHgUJQKBgEXr0HgxGG+v8bsXdrJ87Avx/nuA2rrFfECDPa4zuPkEK+cSFibdAq/H
u6OIV+z59AD2s84gxR+KLzEDfQAqBt7cVA5ZH6hrO+bkCtK9ycLL+koOuB+1EV+Y
hKRtDhmSdWBo3tJK12RrAe4t7CUe8gMgTvU7ExlcA3xQkseFPx9K
-----END RSA PRIVATE KEY-----
`,
},
},
},
"invalid": {
config: OCIConfig{
Auth: OCIAuthConfig{
TenancyID: "ocid1.tenancy.oc1..aaaaaaaaxf3fuazosc6xng7l75rj6uist5jb6ken64t3qltimxnkymddqbma",
UserID: "ocid1.user.oc1..aaaaaaaahx2vpvm4of5nqq3t274ike7ygyk2aexvokk3gyv4eyumzqajcrvq",
Region: "us-ashburn-1",
Fingerprint: "48:ba:d4:21:63:53:db:10:65:20:d4:09:ce:01:f5:97",
PrivateKey: `-----BEGIN RSA PRIVATE KEY-----
`,
},
},
err: errors.New("initialising OCI DNS API client: can not create client, bad configuration: PEM data was not found in buffer"),
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
_, err := NewOCIProvider(
tc.config,
endpoint.NewDomainFilter([]string{"com"}),
NewZoneIDFilter([]string{""}),
false,
)
if err == nil {
require.NoError(t, err)
} else {
require.Equal(t, tc.err.Error(), err.Error())
}
})
}
}
func TestOCIZones(t *testing.T) {
testCases := []struct {
name string
domainFilter endpoint.DomainFilter
zoneIDFilter ZoneIDFilter
expected map[string]dns.ZoneSummary
}{
{
name: "DomainFilter_com",
domainFilter: endpoint.NewDomainFilter([]string{"com"}),
zoneIDFilter: NewZoneIDFilter([]string{""}),
expected: map[string]dns.ZoneSummary{
"foo.com": {
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
Name: common.String("foo.com"),
},
"bar.com": {
Id: common.String("ocid1.dns-zone.oc1..502aeddba262b92fd13ed7874f6f1404"),
Name: common.String("bar.com"),
},
},
}, {
name: "DomainFilter_foo.com",
domainFilter: endpoint.NewDomainFilter([]string{"foo.com"}),
zoneIDFilter: NewZoneIDFilter([]string{""}),
expected: map[string]dns.ZoneSummary{
"foo.com": {
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
Name: common.String("foo.com"),
},
},
}, {
name: "ZoneIDFilter_ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959",
domainFilter: endpoint.NewDomainFilter([]string{""}),
zoneIDFilter: NewZoneIDFilter([]string{"ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"}),
expected: map[string]dns.ZoneSummary{
"foo.com": {
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
Name: common.String("foo.com"),
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
provider := newOCIProvider(&mockOCIDNSClient{}, tc.domainFilter, tc.zoneIDFilter, false)
zones, err := provider.zones(context.Background())
require.NoError(t, err)
validateOCIZones(t, zones, tc.expected)
})
}
}
func TestOCIRecords(t *testing.T) {
testCases := []struct {
name string
domainFilter endpoint.DomainFilter
zoneIDFilter ZoneIDFilter
expected []*endpoint.Endpoint
}{
{
name: "unfiltered",
domainFilter: endpoint.NewDomainFilter([]string{""}),
zoneIDFilter: NewZoneIDFilter([]string{""}),
expected: []*endpoint.Endpoint{
endpoint.NewEndpointWithTTL("foo.foo.com", endpoint.RecordTypeA, endpoint.TTL(ociRecordTTL), "127.0.0.1"),
endpoint.NewEndpointWithTTL("foo.foo.com", endpoint.RecordTypeTXT, endpoint.TTL(ociRecordTTL), "heritage=external-dns,external-dns/owner=default,external-dns/resource=service/default/my-svc"),
endpoint.NewEndpointWithTTL("bar.foo.com", endpoint.RecordTypeCNAME, endpoint.TTL(ociRecordTTL), "bar.com."),
endpoint.NewEndpointWithTTL("foo.bar.com", endpoint.RecordTypeA, endpoint.TTL(ociRecordTTL), "127.0.0.1"),
},
}, {
name: "DomainFilter_foo.com",
domainFilter: endpoint.NewDomainFilter([]string{"foo.com"}),
zoneIDFilter: NewZoneIDFilter([]string{""}),
expected: []*endpoint.Endpoint{
endpoint.NewEndpointWithTTL("foo.foo.com", endpoint.RecordTypeA, endpoint.TTL(ociRecordTTL), "127.0.0.1"),
endpoint.NewEndpointWithTTL("foo.foo.com", endpoint.RecordTypeTXT, endpoint.TTL(ociRecordTTL), "heritage=external-dns,external-dns/owner=default,external-dns/resource=service/default/my-svc"),
endpoint.NewEndpointWithTTL("bar.foo.com", endpoint.RecordTypeCNAME, endpoint.TTL(ociRecordTTL), "bar.com."),
},
}, {
name: "ZoneIDFilter_ocid1.dns-zone.oc1..502aeddba262b92fd13ed7874f6f1404",
domainFilter: endpoint.NewDomainFilter([]string{""}),
zoneIDFilter: NewZoneIDFilter([]string{"ocid1.dns-zone.oc1..502aeddba262b92fd13ed7874f6f1404"}),
expected: []*endpoint.Endpoint{
endpoint.NewEndpointWithTTL("foo.bar.com", endpoint.RecordTypeA, endpoint.TTL(ociRecordTTL), "127.0.0.1"),
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
provider := newOCIProvider(&mockOCIDNSClient{}, tc.domainFilter, tc.zoneIDFilter, false)
endpoints, err := provider.Records(context.Background())
require.NoError(t, err)
require.ElementsMatch(t, tc.expected, endpoints)
})
}
}
func TestNewRecordOperation(t *testing.T) {
testCases := []struct {
name string
ep *endpoint.Endpoint
opType dns.RecordOperationOperationEnum
expected dns.RecordOperation
}{
{
name: "A_record",
opType: dns.RecordOperationOperationAdd,
ep: endpoint.NewEndpointWithTTL(
"foo.foo.com",
endpoint.RecordTypeA,
endpoint.TTL(ociRecordTTL),
"127.0.0.1"),
expected: dns.RecordOperation{
Domain: common.String("foo.foo.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String("A"),
Ttl: common.Int(300),
Operation: dns.RecordOperationOperationAdd,
},
}, {
name: "TXT_record",
opType: dns.RecordOperationOperationAdd,
ep: endpoint.NewEndpointWithTTL(
"foo.foo.com",
endpoint.RecordTypeTXT,
endpoint.TTL(ociRecordTTL),
"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/default/my-svc"),
expected: dns.RecordOperation{
Domain: common.String("foo.foo.com"),
Rdata: common.String("heritage=external-dns,external-dns/owner=default,external-dns/resource=service/default/my-svc"),
Rtype: common.String("TXT"),
Ttl: common.Int(300),
Operation: dns.RecordOperationOperationAdd,
},
}, {
name: "CNAME_record",
opType: dns.RecordOperationOperationAdd,
ep: endpoint.NewEndpointWithTTL(
"foo.foo.com",
endpoint.RecordTypeCNAME,
endpoint.TTL(ociRecordTTL),
"bar.com."),
expected: dns.RecordOperation{
Domain: common.String("foo.foo.com"),
Rdata: common.String("bar.com."),
Rtype: common.String("CNAME"),
Ttl: common.Int(300),
Operation: dns.RecordOperationOperationAdd,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
op := newRecordOperation(tc.ep, tc.opType)
require.Equal(t, tc.expected, op)
})
}
}
func TestOperationsByZone(t *testing.T) {
testCases := []struct {
name string
zones map[string]dns.ZoneSummary
ops []dns.RecordOperation
expected map[string][]dns.RecordOperation
}{
{
name: "basic",
zones: map[string]dns.ZoneSummary{
"foo": {
Id: common.String("foo"),
Name: common.String("foo.com"),
},
"bar": {
Id: common.String("bar"),
Name: common.String("bar.com"),
},
},
ops: []dns.RecordOperation{
{
Domain: common.String("foo.foo.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String("A"),
Ttl: common.Int(300),
Operation: dns.RecordOperationOperationAdd,
},
{
Domain: common.String("foo.bar.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String("A"),
Ttl: common.Int(300),
Operation: dns.RecordOperationOperationAdd,
},
},
expected: map[string][]dns.RecordOperation{
"foo": {
{
Domain: common.String("foo.foo.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String("A"),
Ttl: common.Int(300),
Operation: dns.RecordOperationOperationAdd,
},
},
"bar": {
{
Domain: common.String("foo.bar.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String("A"),
Ttl: common.Int(300),
Operation: dns.RecordOperationOperationAdd,
},
},
},
}, {
name: "does_not_include_zones_with_no_changes",
zones: map[string]dns.ZoneSummary{
"foo": {
Id: common.String("foo"),
Name: common.String("foo.com"),
},
"bar": {
Id: common.String("bar"),
Name: common.String("bar.com"),
},
},
ops: []dns.RecordOperation{
{
Domain: common.String("foo.foo.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String("A"),
Ttl: common.Int(300),
Operation: dns.RecordOperationOperationAdd,
},
},
expected: map[string][]dns.RecordOperation{
"foo": {
{
Domain: common.String("foo.foo.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String("A"),
Ttl: common.Int(300),
Operation: dns.RecordOperationOperationAdd,
},
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := operationsByZone(tc.zones, tc.ops)
require.Equal(t, tc.expected, result)
})
}
}
type mutableMockOCIDNSClient struct {
zones map[string]dns.ZoneSummary
records map[string]map[string]dns.Record
}
func newMutableMockOCIDNSClient(zones []dns.ZoneSummary, recordsByZone map[string][]dns.Record) *mutableMockOCIDNSClient {
c := &mutableMockOCIDNSClient{
zones: make(map[string]dns.ZoneSummary),
records: make(map[string]map[string]dns.Record),
}
for _, zone := range zones {
c.zones[*zone.Id] = zone
c.records[*zone.Id] = make(map[string]dns.Record)
}
for zoneID, records := range recordsByZone {
for _, record := range records {
c.records[zoneID][ociRecordKey(*record.Rtype, *record.Domain)] = record
}
}
return c
}
func (c *mutableMockOCIDNSClient) ListZones(ctx context.Context, request dns.ListZonesRequest) (response dns.ListZonesResponse, err error) {
var zones []dns.ZoneSummary
for _, v := range c.zones {
zones = append(zones, v)
}
return dns.ListZonesResponse{Items: zones}, nil
}
func (c *mutableMockOCIDNSClient) GetZoneRecords(ctx context.Context, request dns.GetZoneRecordsRequest) (response dns.GetZoneRecordsResponse, err error) {
if request.ZoneNameOrId == nil {
err = errors.New("no name or id")
return
}
records, ok := c.records[*request.ZoneNameOrId]
if !ok {
err = errors.New("zone not found")
return
}
var items []dns.Record
for _, v := range records {
items = append(items, v)
}
response.Items = items
return
}
func ociRecordKey(rType, domain string) string {
return rType + "/" + domain
}
func (c *mutableMockOCIDNSClient) PatchZoneRecords(ctx context.Context, request dns.PatchZoneRecordsRequest) (response dns.PatchZoneRecordsResponse, err error) {
if request.ZoneNameOrId == nil {
err = errors.New("no name or id")
return
}
records, ok := c.records[*request.ZoneNameOrId]
if !ok {
err = errors.New("zone not found")
return
}
// Ensure that ADD operations occur after REMOVE.
sort.Slice(request.Items, func(i, j int) bool {
return request.Items[i].Operation > request.Items[j].Operation
})
for _, op := range request.Items {
k := ociRecordKey(*op.Rtype, *op.Domain)
switch op.Operation {
case dns.RecordOperationOperationAdd:
records[k] = dns.Record{
Domain: op.Domain,
Rtype: op.Rtype,
Rdata: op.Rdata,
Ttl: op.Ttl,
}
case dns.RecordOperationOperationRemove:
delete(records, k)
default:
err = errors.Errorf("unsupported operation %q", op.Operation)
return
}
}
return
}
// TestMutableMockOCIDNSClient exists because one must always test one's tests
// right...?
func TestMutableMockOCIDNSClient(t *testing.T) {
zones := []dns.ZoneSummary{{
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
Name: common.String("foo.com"),
}}
records := map[string][]dns.Record{
"ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959": {{
Domain: common.String("foo.foo.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String(endpoint.RecordTypeA),
Ttl: common.Int(ociRecordTTL),
}, {
Domain: common.String("foo.foo.com"),
Rdata: common.String("heritage=external-dns,external-dns/owner=default,external-dns/resource=service/default/my-svc"),
Rtype: common.String(endpoint.RecordTypeTXT),
Ttl: common.Int(ociRecordTTL),
}},
}
client := newMutableMockOCIDNSClient(zones, records)
// First ListZones.
zonesResponse, err := client.ListZones(context.Background(), dns.ListZonesRequest{})
require.NoError(t, err)
require.Len(t, zonesResponse.Items, 1)
require.Equal(t, zonesResponse.Items, zones)
// GetZoneRecords for that zone.
recordsResponse, err := client.GetZoneRecords(context.Background(), dns.GetZoneRecordsRequest{
ZoneNameOrId: zones[0].Id,
})
require.NoError(t, err)
require.Len(t, recordsResponse.Items, 2)
require.ElementsMatch(t, recordsResponse.Items, records["ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"])
// Remove the A record.
_, err = client.PatchZoneRecords(context.Background(), dns.PatchZoneRecordsRequest{
ZoneNameOrId: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
PatchZoneRecordsDetails: dns.PatchZoneRecordsDetails{
Items: []dns.RecordOperation{{
Domain: common.String("foo.foo.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String("A"),
Ttl: common.Int(300),
Operation: dns.RecordOperationOperationRemove,
}},
},
})
require.NoError(t, err)
// GetZoneRecords again and check the A record was removed.
recordsResponse, err = client.GetZoneRecords(context.Background(), dns.GetZoneRecordsRequest{
ZoneNameOrId: zones[0].Id,
})
require.NoError(t, err)
require.Len(t, recordsResponse.Items, 1)
require.Equal(t, recordsResponse.Items[0], records["ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"][1])
// Add the A record back.
_, err = client.PatchZoneRecords(context.Background(), dns.PatchZoneRecordsRequest{
ZoneNameOrId: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
PatchZoneRecordsDetails: dns.PatchZoneRecordsDetails{
Items: []dns.RecordOperation{{
Domain: common.String("foo.foo.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String("A"),
Ttl: common.Int(300),
Operation: dns.RecordOperationOperationAdd,
}},
},
})
require.NoError(t, err)
// GetZoneRecords and check we're back in the original state
recordsResponse, err = client.GetZoneRecords(context.Background(), dns.GetZoneRecordsRequest{
ZoneNameOrId: zones[0].Id,
})
require.NoError(t, err)
require.Len(t, recordsResponse.Items, 2)
require.ElementsMatch(t, recordsResponse.Items, records["ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"])
}
func TestOCIApplyChanges(t *testing.T) {
testCases := []struct {
name string
zones []dns.ZoneSummary
records map[string][]dns.Record
changes *plan.Changes
dryRun bool
err error
expectedEndpoints []*endpoint.Endpoint
}{
{
name: "add",
zones: []dns.ZoneSummary{{
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
Name: common.String("foo.com"),
}},
changes: &plan.Changes{
Create: []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(
"foo.foo.com",
endpoint.RecordTypeA,
endpoint.TTL(ociRecordTTL),
"127.0.0.1",
)},
},
expectedEndpoints: []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(
"foo.foo.com",
endpoint.RecordTypeA,
endpoint.TTL(ociRecordTTL),
"127.0.0.1",
)},
}, {
name: "remove",
zones: []dns.ZoneSummary{{
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
Name: common.String("foo.com"),
}},
records: map[string][]dns.Record{
"ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959": {{
Domain: common.String("foo.foo.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String(endpoint.RecordTypeA),
Ttl: common.Int(ociRecordTTL),
}, {
Domain: common.String("foo.foo.com"),
Rdata: common.String("heritage=external-dns,external-dns/owner=default,external-dns/resource=service/default/my-svc"),
Rtype: common.String(endpoint.RecordTypeTXT),
Ttl: common.Int(ociRecordTTL),
}},
},
changes: &plan.Changes{
Delete: []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(
"foo.foo.com",
endpoint.RecordTypeTXT,
endpoint.TTL(ociRecordTTL),
"127.0.0.1",
)},
},
expectedEndpoints: []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(
"foo.foo.com",
endpoint.RecordTypeA,
endpoint.TTL(ociRecordTTL),
"127.0.0.1",
)},
}, {
name: "update",
zones: []dns.ZoneSummary{{
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
Name: common.String("foo.com"),
}},
records: map[string][]dns.Record{
"ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959": {{
Domain: common.String("foo.foo.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String(endpoint.RecordTypeA),
Ttl: common.Int(ociRecordTTL),
}},
},
changes: &plan.Changes{
UpdateOld: []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(
"foo.foo.com",
endpoint.RecordTypeA,
endpoint.TTL(ociRecordTTL),
"127.0.0.1",
)},
UpdateNew: []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(
"foo.foo.com",
endpoint.RecordTypeA,
endpoint.TTL(ociRecordTTL),
"10.0.0.1",
)},
},
expectedEndpoints: []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(
"foo.foo.com",
endpoint.RecordTypeA,
endpoint.TTL(ociRecordTTL),
"10.0.0.1",
)},
}, {
name: "dry_run_no_changes",
zones: []dns.ZoneSummary{{
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
Name: common.String("foo.com"),
}},
records: map[string][]dns.Record{
"ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959": {{
Domain: common.String("foo.foo.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String(endpoint.RecordTypeA),
Ttl: common.Int(ociRecordTTL),
}},
},
changes: &plan.Changes{
Delete: []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(
"foo.foo.com",
endpoint.RecordTypeA,
endpoint.TTL(ociRecordTTL),
"127.0.0.1",
)},
},
dryRun: true,
expectedEndpoints: []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(
"foo.foo.com",
endpoint.RecordTypeA,
endpoint.TTL(ociRecordTTL),
"127.0.0.1",
)},
}, {
name: "add_remove_update",
zones: []dns.ZoneSummary{{
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
Name: common.String("foo.com"),
}},
records: map[string][]dns.Record{
"ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959": {{
Domain: common.String("foo.foo.com"),
Rdata: common.String("127.0.0.1"),
Rtype: common.String(endpoint.RecordTypeA),
Ttl: common.Int(ociRecordTTL),
}, {
Domain: common.String("bar.foo.com"),
Rdata: common.String("bar.com."),
Rtype: common.String(endpoint.RecordTypeCNAME),
Ttl: common.Int(ociRecordTTL),
}},
},
changes: &plan.Changes{
Delete: []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(
"foo.foo.com",
endpoint.RecordTypeA,
endpoint.TTL(ociRecordTTL),
"baz.com.",
)},
UpdateOld: []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(
"bar.foo.com",
endpoint.RecordTypeCNAME,
endpoint.TTL(ociRecordTTL),
"baz.com.",
)},
UpdateNew: []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(
"bar.foo.com",
endpoint.RecordTypeCNAME,
endpoint.TTL(ociRecordTTL),
"foo.bar.com.",
)},
Create: []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(
"baz.foo.com",
endpoint.RecordTypeA,
endpoint.TTL(ociRecordTTL),
"127.0.0.1",
)},
},
expectedEndpoints: []*endpoint.Endpoint{
endpoint.NewEndpointWithTTL(
"bar.foo.com",
endpoint.RecordTypeCNAME,
endpoint.TTL(ociRecordTTL),
"foo.bar.com.",
),
endpoint.NewEndpointWithTTL(
"baz.foo.com",
endpoint.RecordTypeA,
endpoint.TTL(ociRecordTTL),
"127.0.0.1"),
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
client := newMutableMockOCIDNSClient(tc.zones, tc.records)
provider := newOCIProvider(
client,
endpoint.NewDomainFilter([]string{""}),
NewZoneIDFilter([]string{""}),
tc.dryRun,
)
ctx := context.Background()
err := provider.ApplyChanges(ctx, tc.changes)
require.Equal(t, tc.err, err)
endpoints, err := provider.Records(ctx)
require.NoError(t, err)
require.ElementsMatch(t, tc.expectedEndpoints, endpoints)
})
}
}