mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-05 17:16:59 +02:00
842 lines
26 KiB
Go
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)
|
|
})
|
|
}
|
|
}
|