mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-12-08 13:21:00 +01:00
243 lines
7.2 KiB
Go
243 lines
7.2 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 safedns
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
|
|
ansClient "github.com/ans-group/sdk-go/pkg/client"
|
|
ansConnection "github.com/ans-group/sdk-go/pkg/connection"
|
|
"github.com/ans-group/sdk-go/pkg/service/safedns"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"sigs.k8s.io/external-dns/endpoint"
|
|
"sigs.k8s.io/external-dns/plan"
|
|
"sigs.k8s.io/external-dns/provider"
|
|
)
|
|
|
|
// SafeDNS is an interface that is a subset of the SafeDNS service API that are actually used.
|
|
// Signatures must match exactly.
|
|
type SafeDNS interface {
|
|
CreateZoneRecord(zoneName string, req safedns.CreateRecordRequest) (int, error)
|
|
DeleteZoneRecord(zoneName string, recordID int) error
|
|
GetZone(zoneName string) (safedns.Zone, error)
|
|
GetZoneRecord(zoneName string, recordID int) (safedns.Record, error)
|
|
GetZoneRecords(zoneName string, parameters ansConnection.APIRequestParameters) ([]safedns.Record, error)
|
|
GetZones(parameters ansConnection.APIRequestParameters) ([]safedns.Zone, error)
|
|
PatchZoneRecord(zoneName string, recordID int, patch safedns.PatchRecordRequest) (int, error)
|
|
UpdateZoneRecord(zoneName string, record safedns.Record) (int, error)
|
|
}
|
|
|
|
// SafeDNSProvider implements the DNS provider spec for UKFast SafeDNS.
|
|
type SafeDNSProvider struct {
|
|
provider.BaseProvider
|
|
Client SafeDNS
|
|
// Only consider hosted zones managing domains ending in this suffix
|
|
domainFilter endpoint.DomainFilter
|
|
DryRun bool
|
|
APIRequestParams ansConnection.APIRequestParameters
|
|
}
|
|
|
|
// ZoneRecord is a datatype to simplify management of a record in a zone.
|
|
type ZoneRecord struct {
|
|
ID int
|
|
Name string
|
|
Type safedns.RecordType
|
|
TTL safedns.RecordTTL
|
|
Zone string
|
|
Content string
|
|
}
|
|
|
|
func NewSafeDNSProvider(domainFilter endpoint.DomainFilter, dryRun bool) (*SafeDNSProvider, error) {
|
|
token, ok := os.LookupEnv("SAFEDNS_TOKEN")
|
|
if !ok {
|
|
return nil, fmt.Errorf("no SAFEDNS_TOKEN found in environment")
|
|
}
|
|
|
|
ukfAPIConnection := ansConnection.NewAPIKeyCredentialsAPIConnection(token)
|
|
ansClient := ansClient.NewClient(ukfAPIConnection)
|
|
safeDNS := ansClient.SafeDNSService()
|
|
|
|
provider := &SafeDNSProvider{
|
|
Client: safeDNS,
|
|
domainFilter: domainFilter,
|
|
DryRun: dryRun,
|
|
APIRequestParams: *ansConnection.NewAPIRequestParameters(),
|
|
}
|
|
return provider, nil
|
|
}
|
|
|
|
// Zones returns the list of hosted zones in the SafeDNS account
|
|
func (p *SafeDNSProvider) Zones(ctx context.Context) ([]safedns.Zone, error) {
|
|
var zones []safedns.Zone
|
|
|
|
allZones, err := p.Client.GetZones(p.APIRequestParams)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check each found zone to see whether they match the domain filter provided. If they do, append it to the array of
|
|
// zones defined above. If not, continue to the next item in the loop.
|
|
for _, zone := range allZones {
|
|
if p.domainFilter.Match(zone.Name) {
|
|
zones = append(zones, zone)
|
|
} else {
|
|
continue
|
|
}
|
|
}
|
|
return zones, nil
|
|
}
|
|
|
|
func (p *SafeDNSProvider) ZoneRecords(ctx context.Context) ([]ZoneRecord, error) {
|
|
zones, err := p.Zones(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var zoneRecords []ZoneRecord
|
|
for _, zone := range zones {
|
|
// For each zone in the zonelist, get all records of an ExternalDNS supported type.
|
|
records, err := p.Client.GetZoneRecords(zone.Name, p.APIRequestParams)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, r := range records {
|
|
zoneRecord := ZoneRecord{
|
|
ID: r.ID,
|
|
Name: r.Name,
|
|
Type: r.Type,
|
|
TTL: r.TTL,
|
|
Zone: zone.Name,
|
|
Content: r.Content,
|
|
}
|
|
zoneRecords = append(zoneRecords, zoneRecord)
|
|
}
|
|
}
|
|
return zoneRecords, nil
|
|
}
|
|
|
|
// Records returns a list of Endpoint resources created from all records in supported zones.
|
|
func (p *SafeDNSProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
|
var endpoints []*endpoint.Endpoint
|
|
zoneRecords, err := p.ZoneRecords(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, r := range zoneRecords {
|
|
if provider.SupportedRecordType(string(r.Type)) {
|
|
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(r.Name, string(r.Type), endpoint.TTL(r.TTL), r.Content))
|
|
}
|
|
}
|
|
return endpoints, nil
|
|
}
|
|
|
|
// ApplyChanges applies a given set of changes in a given zone.
|
|
func (p *SafeDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
|
// Identify the zone name for each record
|
|
zoneNameIDMapper := provider.ZoneIDName{}
|
|
|
|
zones, err := p.Zones(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, zone := range zones {
|
|
zoneNameIDMapper.Add(zone.Name, zone.Name)
|
|
}
|
|
|
|
zoneRecords, err := p.ZoneRecords(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, endpoint := range changes.Create {
|
|
_, ZoneName := zoneNameIDMapper.FindZone(endpoint.DNSName)
|
|
for _, target := range endpoint.Targets {
|
|
request := safedns.CreateRecordRequest{
|
|
Name: endpoint.DNSName,
|
|
Type: endpoint.RecordType,
|
|
Content: target,
|
|
}
|
|
log.WithFields(log.Fields{
|
|
"zoneID": ZoneName,
|
|
"dnsName": endpoint.DNSName,
|
|
"recordType": endpoint.RecordType,
|
|
"Value": target,
|
|
}).Info("Creating record")
|
|
_, err := p.Client.CreateZoneRecord(ZoneName, request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
for _, endpoint := range changes.UpdateNew {
|
|
// Currently iterates over each zoneRecord in ZoneRecords for each Endpoint
|
|
// in UpdateNew; the same will go for Delete. As it's double-iteration,
|
|
// that's O(n^2), which isn't great. No performance issues have been noted
|
|
// thus far.
|
|
var zoneRecord ZoneRecord
|
|
for _, target := range endpoint.Targets {
|
|
for _, zr := range zoneRecords {
|
|
if zr.Name == endpoint.DNSName && zr.Content == target {
|
|
zoneRecord = zr
|
|
break
|
|
}
|
|
}
|
|
|
|
newTTL := safedns.RecordTTL(int(endpoint.RecordTTL))
|
|
newRecord := safedns.PatchRecordRequest{
|
|
Name: endpoint.DNSName,
|
|
Content: target,
|
|
TTL: &newTTL,
|
|
Type: endpoint.RecordType,
|
|
}
|
|
log.WithFields(log.Fields{
|
|
"zoneID": zoneRecord.Zone,
|
|
"dnsName": newRecord.Name,
|
|
"recordType": newRecord.Type,
|
|
"Value": newRecord.Content,
|
|
"Priority": newRecord.Priority,
|
|
}).Info("Patching record")
|
|
_, err = p.Client.PatchZoneRecord(zoneRecord.Zone, zoneRecord.ID, newRecord)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
for _, endpoint := range changes.Delete {
|
|
// As above, currently iterates in O(n^2). May be a good start for optimisations.
|
|
var zoneRecord ZoneRecord
|
|
for _, zr := range zoneRecords {
|
|
if zr.Name == endpoint.DNSName && string(zr.Type) == endpoint.RecordType {
|
|
zoneRecord = zr
|
|
break
|
|
}
|
|
}
|
|
log.WithFields(log.Fields{
|
|
"zoneID": zoneRecord.Zone,
|
|
"dnsName": zoneRecord.Name,
|
|
"recordType": zoneRecord.Type,
|
|
}).Info("Deleting record")
|
|
err := p.Client.DeleteZoneRecord(zoneRecord.Zone, zoneRecord.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|