Refactor and clean up akamai provider

refactor: remove dns api logic and use dns api library
enhancement: add additional args for auth credential retieval
cleanup: simplify, organize processing logic
test: update automation and validate
This commit is contained in:
Edward Lynes 2020-11-23 14:34:44 -05:00
parent 65087c4e02
commit 75429cc504
8 changed files with 632 additions and 414 deletions

2
go.mod
View File

@ -10,7 +10,7 @@ require (
github.com/Azure/go-autorest/autorest/adal v0.9.5
github.com/Azure/go-autorest/autorest/azure/auth v0.5.3
github.com/Azure/go-autorest/autorest/to v0.4.0
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.11
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect
github.com/alecthomas/colour v0.1.0 // indirect
github.com/alecthomas/kingpin v2.2.5+incompatible

2
go.sum
View File

@ -60,6 +60,8 @@ github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4Rq
github.com/ahmetb/gen-crd-api-reference-docs v0.1.5/go.mod h1:P/XzJ+c2+khJKNKABcm2biRwk2QAuwbLf8DlXuaL7WM=
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.11 h1:QGjNHMwoPYxE5NpOAc8kpd2KTY293/oFk5BWdjkza+k=
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.11/go.mod h1:L+HB2uBoDgi3+r1pJEJcbGwyyHhd2QXaGsKLbDwtm8Q=
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0 h1:FJF58TWBaQnGqTIcziIP5/z3TTqWUn8fh26z03oZE2c=
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk=

View File

@ -151,7 +151,7 @@ func main() {
var p provider.Provider
switch cfg.Provider {
case "akamai":
p = akamai.NewAkamaiProvider(
p, err = akamai.NewAkamaiProvider(
akamai.AkamaiConfig{
DomainFilter: domainFilter,
ZoneIDFilter: zoneIDFilter,
@ -159,9 +159,10 @@ func main() {
ClientToken: cfg.AkamaiClientToken,
ClientSecret: cfg.AkamaiClientSecret,
AccessToken: cfg.AkamaiAccessToken,
EdgercPath: cfg.AkamaiEdgercPath,
EdgercSection: cfg.AkamaiEdgercSection,
DryRun: cfg.DryRun,
},
)
}, nil)
case "alibabacloud":
p, err = alibabacloud.NewAlibabaCloudProvider(cfg.AlibabaCloudConfigFile, domainFilter, zoneIDFilter, cfg.AlibabaCloudZoneType, cfg.DryRun)
case "aws":

View File

@ -88,6 +88,8 @@ type Config struct {
AkamaiClientToken string
AkamaiClientSecret string
AkamaiAccessToken string
AkamaiEdgercPath string
AkamaiEdgercSection string
InfobloxGridHost string
InfobloxWapiPort int
InfobloxWapiUsername string
@ -194,6 +196,8 @@ var defaultConfig = &Config{
AkamaiClientToken: "",
AkamaiClientSecret: "",
AkamaiAccessToken: "",
AkamaiEdgercSection: "",
AkamaiEdgercPath: "",
InfobloxGridHost: "",
InfobloxWapiPort: 443,
InfobloxWapiUsername: "admin",
@ -353,10 +357,12 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("cloudflare-proxied", "When using the Cloudflare provider, specify if the proxy mode must be enabled (default: disabled)").BoolVar(&cfg.CloudflareProxied)
app.Flag("cloudflare-zones-per-page", "When using the Cloudflare provider, specify how many zones per page listed, max. possible 50 (default: 50)").Default(strconv.Itoa(defaultConfig.CloudflareZonesPerPage)).IntVar(&cfg.CloudflareZonesPerPage)
app.Flag("coredns-prefix", "When using the CoreDNS provider, specify the prefix name").Default(defaultConfig.CoreDNSPrefix).StringVar(&cfg.CoreDNSPrefix)
app.Flag("akamai-serviceconsumerdomain", "When using the Akamai provider, specify the base URL (required when --provider=akamai)").Default(defaultConfig.AkamaiServiceConsumerDomain).StringVar(&cfg.AkamaiServiceConsumerDomain)
app.Flag("akamai-client-token", "When using the Akamai provider, specify the client token (required when --provider=akamai)").Default(defaultConfig.AkamaiClientToken).StringVar(&cfg.AkamaiClientToken)
app.Flag("akamai-client-secret", "When using the Akamai provider, specify the client secret (required when --provider=akamai)").Default(defaultConfig.AkamaiClientSecret).StringVar(&cfg.AkamaiClientSecret)
app.Flag("akamai-access-token", "When using the Akamai provider, specify the access token (required when --provider=akamai)").Default(defaultConfig.AkamaiAccessToken).StringVar(&cfg.AkamaiAccessToken)
app.Flag("akamai-serviceconsumerdomain", "When using the Akamai provider, specify the base URL (required when --provider=akamai and edgerc-path not specified)").Default(defaultConfig.AkamaiServiceConsumerDomain).StringVar(&cfg.AkamaiServiceConsumerDomain)
app.Flag("akamai-client-token", "When using the Akamai provider, specify the client token (required when --provider=akamai and edgerc-path not specified)").Default(defaultConfig.AkamaiClientToken).StringVar(&cfg.AkamaiClientToken)
app.Flag("akamai-client-secret", "When using the Akamai provider, specify the client secret (required when --provider=akamai and edgerc-path not specified)").Default(defaultConfig.AkamaiClientSecret).StringVar(&cfg.AkamaiClientSecret)
app.Flag("akamai-access-token", "When using the Akamai provider, specify the access token (required when --provider=akamai and edgerc-path not specified)").Default(defaultConfig.AkamaiAccessToken).StringVar(&cfg.AkamaiAccessToken)
app.Flag("akamai-edgerc-path", "When using the Akamai provider, specify the .edgerc file path. Path must be reachable form invocation environment. (required when --provider=akamai and *-token, secret serviceconsumerdomain not specified)").Default(defaultConfig.AkamaiEdgercPath).StringVar(&cfg.AkamaiEdgercPath)
app.Flag("akamai-edgerc-section", "When using the Akamai provider, specify the .edgerc file path (Optional when edgerc-path is specified)").Default(defaultConfig.AkamaiEdgercSection).StringVar(&cfg.AkamaiEdgercSection)
app.Flag("infoblox-grid-host", "When using the Infoblox provider, specify the Grid Manager host (required when --provider=infoblox)").Default(defaultConfig.InfobloxGridHost).StringVar(&cfg.InfobloxGridHost)
app.Flag("infoblox-wapi-port", "When using the Infoblox provider, specify the WAPI port (default: 443)").Default(strconv.Itoa(defaultConfig.InfobloxWapiPort)).IntVar(&cfg.InfobloxWapiPort)
app.Flag("infoblox-wapi-username", "When using the Infoblox provider, specify the WAPI username (default: admin)").Default(defaultConfig.InfobloxWapiUsername).StringVar(&cfg.InfobloxWapiUsername)

View File

@ -66,6 +66,8 @@ var (
AkamaiClientToken: "",
AkamaiClientSecret: "",
AkamaiAccessToken: "",
AkamaiEdgercPath: "",
AkamaiEdgercSection: "",
InfobloxGridHost: "",
InfobloxWapiPort: 443,
InfobloxWapiUsername: "admin",
@ -144,6 +146,8 @@ var (
AkamaiClientToken: "o184671d5307a388180fbf7f11dbdf46",
AkamaiClientSecret: "o184671d5307a388180fbf7f11dbdf46",
AkamaiAccessToken: "o184671d5307a388180fbf7f11dbdf46",
AkamaiEdgercPath: "/home/test/.edgerc",
AkamaiEdgercSection: "default",
InfobloxGridHost: "127.0.0.1",
InfobloxWapiPort: 8443,
InfobloxWapiUsername: "infoblox",
@ -235,6 +239,8 @@ func TestParseFlags(t *testing.T) {
"--akamai-client-token=o184671d5307a388180fbf7f11dbdf46",
"--akamai-client-secret=o184671d5307a388180fbf7f11dbdf46",
"--akamai-access-token=o184671d5307a388180fbf7f11dbdf46",
"--akamai-edgerc-path=/home/test/.edgerc",
"--akamai-edgerc-section=default",
"--infoblox-grid-host=127.0.0.1",
"--infoblox-wapi-port=8443",
"--infoblox-wapi-username=infoblox",
@ -328,6 +334,8 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_AKAMAI_CLIENT_TOKEN": "o184671d5307a388180fbf7f11dbdf46",
"EXTERNAL_DNS_AKAMAI_CLIENT_SECRET": "o184671d5307a388180fbf7f11dbdf46",
"EXTERNAL_DNS_AKAMAI_ACCESS_TOKEN": "o184671d5307a388180fbf7f11dbdf46",
"EXTERNAL_DNS_AKAMAI_EDGERC_PATH": "/home/test/.edgerc",
"EXTERNAL_DNS_AKAMAI_EDGERC_SECTION": "default",
"EXTERNAL_DNS_INFOBLOX_GRID_HOST": "127.0.0.1",
"EXTERNAL_DNS_INFOBLOX_WAPI_PORT": "8443",
"EXTERNAL_DNS_INFOBLOX_WAPI_USERNAME": "infoblox",

View File

@ -45,16 +45,20 @@ func ValidateConfig(cfg *externaldns.Config) error {
// Akamai provider specific validations
if cfg.Provider == "akamai" {
if cfg.AkamaiServiceConsumerDomain == "" {
edgerc := false
if cfg.AkamaiEdgercPath != "" {
edgerc = true
}
if cfg.AkamaiServiceConsumerDomain == "" && !edgerc {
return errors.New("no Akamai ServiceConsumerDomain specified")
}
if cfg.AkamaiClientToken == "" {
if cfg.AkamaiClientToken == "" && !edgerc {
return errors.New("no Akamai client token specified")
}
if cfg.AkamaiClientSecret == "" {
if cfg.AkamaiClientSecret == "" && !edgerc {
return errors.New("no Akamai client secret specified")
}
if cfg.AkamaiAccessToken == "" {
if cfg.AkamaiAccessToken == "" && !edgerc {
return errors.New("no Akamai access token specified")
}
}

View File

@ -17,15 +17,13 @@ limitations under the License.
package akamai
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
c "github.com/akamai/AkamaiOPEN-edgegrid-golang/client-v1"
dns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2"
"github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid"
log "github.com/sirupsen/logrus"
@ -34,22 +32,23 @@ import (
"sigs.k8s.io/external-dns/provider"
)
type akamaiClient interface {
NewRequest(config edgegrid.Config, method, path string, body io.Reader) (*http.Request, error)
Do(config edgegrid.Config, req *http.Request) (*http.Response, error)
const (
// Default Record TTL
edgeDNSRecordTTL = 600
maxUint = ^uint(0)
maxInt = int(maxUint >> 1)
)
// edgeDNSClient is a proxy interface of the Akamai edgegrid configdns-v2 package that can be stubbed for testing.
type AkamaiDNSService interface {
ListZones(queryArgs dns.ZoneListQueryArgs) (*dns.ZoneListResponse, error)
GetRecordsets(zone string, queryArgs dns.RecordsetQueryArgs) (*dns.RecordSetResponse, error)
GetRecord(zone string, name string, recordtype string) (*dns.RecordBody, error)
DeleteRecord(record *dns.RecordBody, zone string, recLock bool) error
UpdateRecord(record *dns.RecordBody, zone string, recLock bool) error
CreateRecordsets(recordsets *dns.Recordsets, zone string, recLock bool) error
}
type akamaiOpenClient struct{}
func (*akamaiOpenClient) NewRequest(config edgegrid.Config, method, path string, body io.Reader) (*http.Request, error) {
return c.NewRequest(config, method, path, body)
}
func (*akamaiOpenClient) Do(config edgegrid.Config, req *http.Request) (*http.Response, error) {
return c.Do(config, req)
}
// AkamaiConfig clarifies the method signature
type AkamaiConfig struct {
DomainFilter endpoint.DomainFilter
ZoneIDFilter provider.ZoneIDFilter
@ -57,17 +56,25 @@ type AkamaiConfig struct {
ClientToken string
ClientSecret string
AccessToken string
EdgercPath string
EdgercSection string
MaxBody int
AccountKey string
DryRun bool
}
// AkamaiProvider implements the DNS provider for Akamai.
type AkamaiProvider struct {
provider.BaseProvider
// Edgedns zones to filter on
domainFilter endpoint.DomainFilter
// Contract Ids to filter on
zoneIDFilter provider.ZoneIDFilter
config edgegrid.Config
dryRun bool
client akamaiClient
// Edgegrid library configuration
config *edgegrid.Config
dryRun bool
// Defines client. Allows for mocking.
client AkamaiDNSService
}
type akamaiZones struct {
@ -79,84 +86,126 @@ type akamaiZone struct {
Zone string `json:"zone"`
}
type akamaiRecordsets struct {
Recordsets []akamaiRecord `json:"recordsets"`
}
type akamaiRecord struct {
Name string `json:"name"`
Type string `json:"type"`
TTL int64 `json:"ttl"`
Rdata []interface{} `json:"rdata"`
}
// NewAkamaiProvider initializes a new Akamai DNS based Provider.
func NewAkamaiProvider(akamaiConfig AkamaiConfig) *AkamaiProvider {
edgeGridConfig := edgegrid.Config{
Host: akamaiConfig.ServiceConsumerDomain,
ClientToken: akamaiConfig.ClientToken,
ClientSecret: akamaiConfig.ClientSecret,
AccessToken: akamaiConfig.AccessToken,
MaxBody: 1024,
HeaderToSign: []string{
"X-External-DNS",
},
Debug: false,
func NewAkamaiProvider(akamaiConfig AkamaiConfig, akaService AkamaiDNSService) (provider.Provider, error) {
var edgeGridConfig edgegrid.Config
/*
log.Debugf("Host: %s", akamaiConfig.ServiceConsumerDomain)
log.Debugf("ClientToken: %s", akamaiConfig.ClientToken)
log.Debugf("ClientSecret: %s", akamaiConfig.ClientSecret)
log.Debugf("AccessToken: %s", akamaiConfig.AccessToken)
log.Debugf("EdgePath: %s", akamaiConfig.EdgercPath)
log.Debugf("EdgeSection: %s", akamaiConfig.EdgercSection)
*/
// environment overrides edgerc file but config needs to be complete
if akamaiConfig.ServiceConsumerDomain == "" || akamaiConfig.ClientToken == "" || akamaiConfig.ClientSecret == "" || akamaiConfig.AccessToken == "" {
// Kubernetes config incomplete or non existent. Can't mix and match.
// Look for Akamai environment or .edgerd creds
var err error
edgeGridConfig, err = edgegrid.Init(akamaiConfig.EdgercPath, akamaiConfig.EdgercSection) // use default .edgerc location and section
if err != nil {
log.Errorf("Edgegrid Init Failed")
return &AkamaiProvider{}, err // return empty provider for backward compatibility
}
edgeGridConfig.HeaderToSign = append(edgeGridConfig.HeaderToSign, "X-External-DNS")
} else {
// Use external-dns config
edgeGridConfig = edgegrid.Config{
Host: akamaiConfig.ServiceConsumerDomain,
ClientToken: akamaiConfig.ClientToken,
ClientSecret: akamaiConfig.ClientSecret,
AccessToken: akamaiConfig.AccessToken,
MaxBody: 131072, // same default val as used by Edgegrid
HeaderToSign: []string{
"X-External-DNS",
},
Debug: false,
}
// Check for edgegrid overrides
if envval, ok := os.LookupEnv("AKAMAI_MAX_BODY"); ok {
if i, err := strconv.Atoi(envval); err == nil {
edgeGridConfig.MaxBody = i
log.Debugf("Edgegrid maxbody set to %s", envval)
}
}
if envval, ok := os.LookupEnv("AKAMAI_ACCOUNT_KEY"); ok {
edgeGridConfig.AccountKey = envval
log.Debugf("Edgegrid applying account key %s", envval)
}
if envval, ok := os.LookupEnv("AKAMAI_DEBUG"); ok {
if dbgval, err := strconv.ParseBool(envval); err == nil {
edgeGridConfig.Debug = dbgval
log.Debugf("Edgegrid debug set to %s", envval)
}
}
}
provider := &AkamaiProvider{
domainFilter: akamaiConfig.DomainFilter,
zoneIDFilter: akamaiConfig.ZoneIDFilter,
config: edgeGridConfig,
config: &edgeGridConfig,
dryRun: akamaiConfig.DryRun,
client: &akamaiOpenClient{},
}
return provider
if akaService != nil {
log.Debugf("Using STUB")
provider.client = akaService
} else {
provider.client = provider
}
// Init library for direct endpoint calls
dns.Init(edgeGridConfig)
return provider, nil
}
func (p *AkamaiProvider) request(method, path string, body io.Reader) (*http.Response, error) {
req, err := p.client.NewRequest(p.config, method, fmt.Sprintf("https://%s/%s", p.config.Host, path), body)
if err != nil {
log.Errorf("Akamai client failed to prepare the request")
return nil, err
}
resp, err := p.client.Do(p.config, req)
if err != nil {
log.Errorf("Akamai client failed to do the request")
return nil, err
}
if !c.IsSuccess(resp) {
return nil, c.NewAPIError(resp)
}
return resp, err
func (p AkamaiProvider) ListZones(queryArgs dns.ZoneListQueryArgs) (*dns.ZoneListResponse, error) {
return dns.ListZones(queryArgs)
}
//Look here for endpoint documentation -> https://developer.akamai.com/api/web_performance/fast_dns_zone_management/v2.html#getzones
func (p *AkamaiProvider) fetchZones() (zones akamaiZones, err error) {
log.Debugf("Trying to fetch zones from Akamai")
resp, err := p.request("GET", "config-dns/v2/zones?showAll=true&types=primary%2Csecondary", nil)
func (p AkamaiProvider) GetRecordsets(zone string, queryArgs dns.RecordsetQueryArgs) (*dns.RecordSetResponse, error) {
return dns.GetRecordsets(zone, queryArgs)
}
func (p AkamaiProvider) CreateRecordsets(recordsets *dns.Recordsets, zone string, reclock bool) error {
return recordsets.Save(zone, reclock)
}
func (p AkamaiProvider) GetRecord(zone string, name string, recordtype string) (*dns.RecordBody, error) {
return dns.GetRecord(zone, name, recordtype)
}
func (p AkamaiProvider) DeleteRecord(record *dns.RecordBody, zone string, recLock bool) error {
return record.Delete(zone, recLock)
}
func (p AkamaiProvider) UpdateRecord(record *dns.RecordBody, zone string, recLock bool) error {
return record.Update(zone, recLock)
}
// Fetch zones using Edgegrid DNS v2 API
func (p AkamaiProvider) fetchZones() (akamaiZones, error) {
log.Debugf("Fetching Akamai Edge DNS zones")
filteredZones := akamaiZones{Zones: make([]akamaiZone, 0)}
queryArgs := dns.ZoneListQueryArgs{Types: "primary", ShowAll: true}
// filter based on contractIds
if len(p.zoneIDFilter.ZoneIDs) > 0 {
queryArgs.ContractIds = strings.Join(p.zoneIDFilter.ZoneIDs, ",")
}
resp, err := p.client.ListZones(queryArgs) // don't worry about paged results
if err != nil {
log.Errorf("Failed to fetch zones from Akamai")
return zones, err
return filteredZones, err
}
err = json.NewDecoder(resp.Body).Decode(&zones)
if err != nil {
log.Errorf("Could not decode json response from Akamai on zone request")
return zones, err
}
defer resp.Body.Close()
filteredZones := akamaiZones{}
for _, zone := range zones.Zones {
if !p.zoneIDFilter.Match(zone.ContractID) {
log.Debugf("Skipping zone: '%s' with ZoneID: '%s', it does not match against ZoneID filters", zone.Zone, zone.ContractID)
continue
for _, zone := range resp.Zones {
//log.Debugf("Evaluating zone: %s", zone.Zone)
if p.domainFilter.Match(zone.Zone) || !p.domainFilter.IsConfigured() {
filteredZones.Zones = append(filteredZones.Zones, akamaiZone{ContractID: zone.ContractId, Zone: zone.Zone})
log.Debugf("Fetched zone: '%s' (ZoneID: %s)", zone.Zone, zone.ContractId)
}
filteredZones.Zones = append(filteredZones.Zones, akamaiZone{ContractID: zone.ContractID, Zone: zone.Zone})
log.Debugf("Fetched zone: '%s' (ZoneID: %s)", zone.Zone, zone.ContractID)
}
lenFilteredZones := len(filteredZones.Zones)
if lenFilteredZones == 0 {
@ -168,53 +217,45 @@ func (p *AkamaiProvider) fetchZones() (zones akamaiZones, err error) {
return filteredZones, nil
}
//Look here for endpoint documentation -> https://developer.akamai.com/api/web_performance/fast_dns_zone_management/v2.html#getzonerecordsets
func (p *AkamaiProvider) fetchRecordSet(zone string) (recordSet akamaiRecordsets, err error) {
log.Debugf("Trying to fetch endpoints for zone: '%s' from Akamai", zone)
resp, err := p.request("GET", "config-dns/v2/zones/"+zone+"/recordsets?showAll=true&types=A%2CTXT%2CCNAME", nil)
if err != nil {
log.Errorf("Failed to fetch records from Akamai for zone: '%s'", zone)
return recordSet, err
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&recordSet)
if err != nil {
log.Errorf("Could not decode json response from Akamai for zone: '%s' on request", zone)
return recordSet, err
}
return recordSet, nil
}
//Records returns the list of records in a given zone.
func (p *AkamaiProvider) Records(context.Context) (endpoints []*endpoint.Endpoint, err error) {
zones, err := p.fetchZones()
func (p AkamaiProvider) Records(context.Context) (endpoints []*endpoint.Endpoint, err error) {
log.Debugf("Entering Records function")
if p.config == nil {
log.Errorf("Akamai provider failed initialization!")
return endpoints, fmt.Errorf("edge dns provider is not initialized")
}
zones, err := p.fetchZones() // returns a filtered set of zones
if err != nil {
log.Warnf("No zones to fetch endpoints from!")
log.Warnf("Failed to identify target zones! Error: %s", err.Error())
return endpoints, err
}
for _, zone := range zones.Zones {
records, err := p.fetchRecordSet(zone.Zone)
recordsets, err := p.client.GetRecordsets(zone.Zone, dns.RecordsetQueryArgs{})
if err != nil {
log.Warnf("No recordsets could be fetched for zone: '%s'!", zone.Zone)
log.Errorf("Recordsets retrieval for zone: '%s' failed! %s", zone.Zone, err.Error())
continue
}
if len(recordsets.Recordsets) == 0 {
log.Warnf("Zone %s contains no recordsets", zone.Zone)
}
for _, record := range records.Recordsets {
rdata := make([]string, len(record.Rdata))
for i, v := range record.Rdata {
rdata[i] = v.(string)
}
if !p.domainFilter.Match(record.Name) {
log.Debugf("Skipping endpoint DNSName: '%s' RecordType: '%s', it does not match against Domain filters", record.Name, record.Type)
for _, recordset := range recordsets.Recordsets {
if !provider.SupportedRecordType(recordset.Type) {
log.Debugf("Skipping endpoint DNSName: '%s' RecordType: '%s'. Record type not supported.", recordset.Name, recordset.Type)
continue
}
endpoints = append(endpoints, endpoint.NewEndpoint(record.Name, record.Type, rdata...))
log.Debugf("Fetched endpoint DNSName: '%s' RecordType: '%s' Rdata: '%s')", record.Name, record.Type, rdata)
if !p.domainFilter.Match(recordset.Name) {
log.Debugf("Skipping endpoint. Record name %s doesn't match containing zone %s.", recordset.Name, zone)
continue
}
var temp interface{} = int64(recordset.TTL)
var ttl endpoint.TTL = endpoint.TTL(temp.(int64)) //endpoint.TTL)
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(recordset.Name,
recordset.Type,
ttl,
trimTxtRdata(recordset.Rdata, recordset.Type)...))
log.Debugf("Fetched endpoint DNSName: '%s' RecordType: '%s' Rdata: '%s')", recordset.Name, recordset.Type, recordset.Rdata)
}
}
lenEndpoints := len(endpoints)
@ -222,161 +263,247 @@ func (p *AkamaiProvider) Records(context.Context) (endpoints []*endpoint.Endpoin
log.Warnf("No endpoints could be fetched")
} else {
log.Debugf("Fetched '%d' endpoints from Akamai", lenEndpoints)
log.Debugf("Endpoints [%v]", endpoints)
}
return endpoints, nil
}
// ApplyChanges applies a given set of changes in a given zone.
func (p *AkamaiProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
func (p AkamaiProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
log.Debugf("Entering ApplyChanges")
if p.config == nil {
log.Errorf("Akamai provider failed initialization!")
return fmt.Errorf("edge dns provider failed initialization")
}
zoneNameIDMapper := provider.ZoneIDName{}
zones, err := p.fetchZones()
if err != nil {
log.Warnf("No zones to fetch endpoints from!")
return nil
log.Errorf("Failed to fetch zones from Akamai")
return err
}
for _, z := range zones.Zones {
zoneNameIDMapper[z.Zone] = z.Zone
}
log.Debugf("Processing zones: [%v]", zoneNameIDMapper)
_, cf := p.createRecords(zoneNameIDMapper, changes.Create)
if !p.dryRun {
if len(cf) > 0 {
log.Warnf("Not all desired endpoints could be created, retrying next iteration")
for _, f := range cf {
log.Warnf("Not created was DNSName: '%s' RecordType: '%s'", f.DNSName, f.RecordType)
// Create recodsets
log.Debugf("Create Changes requested [%v]", changes.Create)
if err := p.createRecordsets(zoneNameIDMapper, changes.Create); err != nil {
return err
}
// Delete recordsets
log.Debugf("Delete Changes requested [%v]", changes.Delete)
if err := p.deleteRecordsets(zoneNameIDMapper, changes.Delete); err != nil {
return err
}
// Update recordsets
log.Debugf("Update Changes requested [%v]", changes.UpdateNew)
if err := p.updateNewRecordsets(zoneNameIDMapper, changes.UpdateNew); err != nil {
return err
}
// Check that all old endpoints were accounted for
revRecs := changes.Delete
revRecs = append(revRecs, changes.UpdateNew...)
for _, rec := range changes.UpdateOld {
found := false
for _, r := range revRecs {
if rec.DNSName == r.DNSName {
found = true
break
}
}
}
_, df := p.deleteRecords(zoneNameIDMapper, changes.Delete)
if !p.dryRun {
if len(df) > 0 {
log.Warnf("Not all endpoints that require deletion could be deleted, retrying next iteration")
for _, f := range df {
log.Warnf("Not deleted was DNSName: '%s' RecordType: '%s'", f.DNSName, f.RecordType)
}
}
}
_, uf := p.updateNewRecords(zoneNameIDMapper, changes.UpdateNew)
if !p.dryRun {
if len(uf) > 0 {
log.Warnf("Not all endpoints that require updating could be updated, retrying next iteration")
for _, f := range uf {
log.Warnf("Not updated was DNSName: '%s' RecordType: '%s'", f.DNSName, f.RecordType)
}
}
}
for _, uold := range changes.UpdateOld {
if !p.dryRun {
log.Debugf("UpdateOld (ignored) for DNSName: '%s' RecordType: '%s'", uold.DNSName, uold.RecordType)
if !found {
log.Warnf("UpdateOld endpoint '%s' is not accounted for in UpdateNew|Delete endpoint list", rec.DNSName)
}
}
return nil
}
func (p *AkamaiProvider) newAkamaiRecord(dnsName, recordType string, targets ...string) *akamaiRecord {
cleanTargets := make([]interface{}, len(targets))
for idx, target := range targets {
cleanTargets[idx] = strings.TrimSuffix(target, ".")
}
return &akamaiRecord{
// Create DNS Recordset
func newAkamaiRecordset(dnsName, recordType string, ttl int, targets []string) dns.Recordset {
return dns.Recordset{
Name: strings.TrimSuffix(dnsName, "."),
Rdata: cleanTargets,
Rdata: targets,
Type: recordType,
TTL: 300,
TTL: ttl,
}
}
func (p *AkamaiProvider) createRecords(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) (created []*endpoint.Endpoint, failed []*endpoint.Endpoint) {
for _, endpoint := range endpoints {
if !p.domainFilter.Match(endpoint.DNSName) {
log.Debugf("Skipping creation at Akamai of endpoint DNSName: '%s' RecordType: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType)
// cleanTargets preps recordset rdata if necessary for EdgeDNS
func cleanTargets(rtype string, targets ...string) []string {
log.Debugf("Targets to clean: [%v]", targets)
//var targets []string = tgts
if rtype == "CNAME" || rtype == "SRV" {
for idx, target := range targets {
targets[idx] = strings.TrimSuffix(target, ".")
}
} else if rtype == "TXT" {
for idx, target := range targets {
log.Debugf("TXT data to clean: [%s]", target)
// need to embed text data in quotes. Make sure not piling on
target = strings.Trim(target, "\"")
// bug in DNS API with embedded quotes.
if strings.Contains(target, "owner") && strings.Contains(target, "\"") {
target = strings.ReplaceAll(target, "\"", "`")
}
targets[idx] = "\"" + target + "\""
}
}
log.Debugf("Clean targets: [%v]", targets)
return targets
}
// trimTxtRdata removes surrounding quotes for received TXT rdata
func trimTxtRdata(rdata []string, rtype string) []string {
if rtype == "TXT" {
for idx, d := range rdata {
//rdata[idx] = strings.Trim(d, "\"")
if strings.Contains(d, "`") {
rdata[idx] = strings.ReplaceAll(d, "`", "\"")
}
}
}
log.Debugf("Trimmed data: [%v]", rdata)
return rdata
}
func ttlAsInt(src endpoint.TTL) int {
var temp interface{} = int64(src)
var temp64 = temp.(int64)
var ttl int = edgeDNSRecordTTL // int
if temp64 > 0 && temp64 <= int64(maxInt) {
ttl = int(temp64)
}
return ttl
}
// Create Endpoint Recordsets
func (p AkamaiProvider) createRecordsets(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) error {
if len(endpoints) == 0 {
log.Info("No endpoints to create")
return nil
}
endpointsByZone := edgeChangesByZone(zoneNameIDMapper, endpoints)
// create all recordsets by zone
for zone, endpoints := range endpointsByZone {
recordsets := &dns.Recordsets{Recordsets: make([]dns.Recordset, 0)}
for _, endpoint := range endpoints {
newrec := newAkamaiRecordset(endpoint.DNSName,
endpoint.RecordType,
ttlAsInt(endpoint.RecordTTL),
cleanTargets(endpoint.RecordType, endpoint.Targets...))
logfields := log.Fields{
"record": newrec.Name,
"type": newrec.Type,
"ttl": newrec.TTL,
"target": fmt.Sprintf("%v", newrec.Rdata),
"zone": zone,
}
log.WithFields(logfields).Info("Creating recordsets")
recordsets.Recordsets = append(recordsets.Recordsets, newrec)
}
if p.dryRun {
continue
}
if zoneName, _ := zoneNameIDMapper.FindZone(endpoint.DNSName); zoneName != "" {
akamaiRecord := p.newAkamaiRecord(endpoint.DNSName, endpoint.RecordType, endpoint.Targets...)
body, _ := json.MarshalIndent(akamaiRecord, "", " ")
log.Infof("Create new Endpoint at Akamai FastDNS - Zone: '%s', DNSName: '%s', RecordType: '%s', Targets: '%+v'", zoneName, endpoint.DNSName, endpoint.RecordType, endpoint.Targets)
if p.dryRun {
continue
}
_, err := p.request("POST", "config-dns/v2/zones/"+zoneName+"/names/"+endpoint.DNSName+"/types/"+endpoint.RecordType, bytes.NewReader(body))
if err != nil {
log.Errorf("Failed to create Akamai endpoint DNSName: '%s' RecordType: '%s' for zone: '%s'", endpoint.DNSName, endpoint.RecordType, zoneName)
failed = append(failed, endpoint)
continue
}
created = append(created, endpoint)
} else {
log.Warnf("No matching zone for endpoint addition DNSName: '%s' RecordType: '%s'", endpoint.DNSName, endpoint.RecordType)
failed = append(failed, endpoint)
// Create recordsets all at once
err := p.client.CreateRecordsets(recordsets, zone, true)
if err != nil {
log.Errorf("Failed to create endpoints for DNS zone %s. Error: %s", zone, err.Error())
return err
}
}
return created, failed
return nil
}
func (p *AkamaiProvider) deleteRecords(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) (deleted []*endpoint.Endpoint, failed []*endpoint.Endpoint) {
func (p AkamaiProvider) deleteRecordsets(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) error {
for _, endpoint := range endpoints {
if !p.domainFilter.Match(endpoint.DNSName) {
log.Debugf("Skipping deletion at Akamai of endpoint: '%s' type: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType)
zoneName, _ := zoneNameIDMapper.FindZone(endpoint.DNSName)
if zoneName == "" {
log.Debugf("Skipping Akamai Edge DNS endpoint deletion: '%s' type: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType)
continue
}
if zoneName, _ := zoneNameIDMapper.FindZone(endpoint.DNSName); zoneName != "" {
log.Infof("Deletion at Akamai FastDNS - Zone: '%s', DNSName: '%s', RecordType: '%s', Targets: '%+v'", zoneName, endpoint.DNSName, endpoint.RecordType, endpoint.Targets)
log.Infof("Akamai Edge DNS recordset deletion- Zone: '%s', DNSName: '%s', RecordType: '%s', Targets: '%+v'", zoneName, endpoint.DNSName, endpoint.RecordType, endpoint.Targets)
if p.dryRun {
continue
}
_, err := p.request("DELETE", "config-dns/v2/zones/"+zoneName+"/names/"+endpoint.DNSName+"/types/"+endpoint.RecordType, nil)
if err != nil {
log.Errorf("Failed to delete Akamai endpoint DNSName: '%s' for zone: '%s'", endpoint.DNSName, zoneName)
failed = append(failed, endpoint)
continue
}
deleted = append(deleted, endpoint)
} else {
log.Warnf("No matching zone for endpoint deletion DNSName: '%s' RecordType: '%s'", endpoint.DNSName, endpoint.RecordType)
failed = append(failed, endpoint)
}
}
return deleted, failed
}
func (p *AkamaiProvider) updateNewRecords(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) (updated []*endpoint.Endpoint, failed []*endpoint.Endpoint) {
for _, endpoint := range endpoints {
if !p.domainFilter.Match(endpoint.DNSName) {
log.Debugf("Skipping update at Akamai of endpoint DNSName: '%s' RecordType: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType)
if p.dryRun {
continue
}
if zoneName, _ := zoneNameIDMapper.FindZone(endpoint.DNSName); zoneName != "" {
akamaiRecord := p.newAkamaiRecord(endpoint.DNSName, endpoint.RecordType, endpoint.Targets...)
body, _ := json.MarshalIndent(akamaiRecord, "", " ")
log.Infof("Updating endpoint at Akamai FastDNS - Zone: '%s', DNSName: '%s', RecordType: '%s', Targets: '%+v'", zoneName, endpoint.DNSName, endpoint.RecordType, endpoint.Targets)
if p.dryRun {
continue
recName := strings.TrimSuffix(endpoint.DNSName, ".")
rec, err := p.client.GetRecord(zoneName, recName, endpoint.RecordType)
if err != nil {
// error not found?
if _, ok := err.(*dns.RecordError); !ok {
return fmt.Errorf("endpoint deletion. record validation failed. error: %s", err.Error())
}
_, err := p.request("PUT", "config-dns/v2/zones/"+zoneName+"/names/"+endpoint.DNSName+"/types/"+endpoint.RecordType, bytes.NewReader(body))
if err != nil {
log.Errorf("Failed to update Akamai endpoint DNSName: '%s' for zone: '%s'", endpoint.DNSName, zoneName)
failed = append(failed, endpoint)
continue
}
updated = append(updated, endpoint)
} else {
log.Warnf("No matching zone for endpoint update DNSName: '%s' RecordType: '%s'", endpoint.DNSName, endpoint.RecordType)
failed = append(failed, endpoint)
log.Infof("Endpoint deletion. Record doesn't exist. Name: %s, Type: %s", recName, endpoint.RecordType)
continue
}
if err := p.client.DeleteRecord(rec, zoneName, true); err != nil {
log.Errorf("edge dns recordset deletion failed. error: %s", err.Error())
return err
}
}
return updated, failed
return nil
}
// Update endpoint recordsets
func (p AkamaiProvider) updateNewRecordsets(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) error {
for _, endpoint := range endpoints {
zoneName, _ := zoneNameIDMapper.FindZone(endpoint.DNSName)
if zoneName == "" {
log.Debugf("Skipping Akamai Edge DNS endpoint update: '%s' type: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType)
continue
}
log.Infof("Akamai Edge DNS recordset update - Zone: '%s', DNSName: '%s', RecordType: '%s', Targets: '%+v'", zoneName, endpoint.DNSName, endpoint.RecordType, endpoint.Targets)
if p.dryRun {
continue
}
recName := strings.TrimSuffix(endpoint.DNSName, ".")
rec, err := p.client.GetRecord(zoneName, recName, endpoint.RecordType)
if err != nil {
log.Errorf("Endpoint update. Record validation failed. Error: %s", err.Error())
return err
}
rec.TTL = ttlAsInt(endpoint.RecordTTL)
rec.Target = cleanTargets(endpoint.RecordType, endpoint.Targets...)
if err := p.client.UpdateRecord(rec, zoneName, true); err != nil {
log.Errorf("Akamai Edge DNS recordset update failed. Error: %s", err.Error())
return err
}
}
return nil
}
// edgeChangesByZone separates a multi-zone change into a single change per zone.
func edgeChangesByZone(zoneMap provider.ZoneIDName, endpoints []*endpoint.Endpoint) map[string][]*endpoint.Endpoint {
createsByZone := make(map[string][]*endpoint.Endpoint, len(zoneMap))
for _, z := range zoneMap {
createsByZone[z] = make([]*endpoint.Endpoint, 0)
}
for _, ep := range endpoints {
zone, _ := zoneMap.FindZone(ep.DNSName)
if zone != "" {
createsByZone[zone] = append(createsByZone[zone], ep)
continue
}
log.Debugf("Skipping Akamai Edge DNS creation of endpoint: '%s' type: '%s', it does not match against Domain filters", ep.DNSName, ep.RecordType)
}
return createsByZone
}

View File

@ -17,148 +17,208 @@ limitations under the License.
package akamai
import (
"bytes"
"context"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
log "github.com/sirupsen/logrus"
"testing"
"github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid"
dns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
)
type mockAkamaiClient struct {
mock.Mock
type edgednsStubData struct {
objType string // zone, record, recordsets
output []interface{}
updateRecords []interface{}
createRecords []interface{}
}
func (m *mockAkamaiClient) NewRequest(config edgegrid.Config, met, p string, b io.Reader) (*http.Request, error) {
switch {
case met == "GET":
switch {
case strings.HasPrefix(p, "https:///config-dns/v2/zones?"):
b = bytes.NewReader([]byte("{\"zones\":[{\"contractId\":\"Test\",\"zone\":\"example.com\"},{\"contractId\":\"Exclude-Me\",\"zone\":\"exclude.me\"}]}"))
case strings.HasPrefix(p, "https:///config-dns/v2/zones/example.com/"):
b = bytes.NewReader([]byte("{\"recordsets\":[{\"name\":\"www.example.com\",\"type\":\"A\",\"ttl\":300,\"rdata\":[\"10.0.0.2\",\"10.0.0.3\"]},{\"name\":\"www.example.com\",\"type\":\"TXT\",\"ttl\":300,\"rdata\":[\"heritage=external-dns,external-dns/owner=default\"]}]}"))
case strings.HasPrefix(p, "https:///config-dns/v2/zones/exclude.me/"):
b = bytes.NewReader([]byte("{\"recordsets\":[{\"name\":\"www.exclude.me\",\"type\":\"A\",\"ttl\":300,\"rdata\":[\"192.168.0.1\",\"192.168.0.2\"]}]}"))
}
case met == "DELETE":
b = bytes.NewReader([]byte("{\"title\": \"Success\", \"status\": 200, \"detail\": \"Record deleted\", \"requestId\": \"4321\"}"))
case met == "ERROR":
b = bytes.NewReader([]byte("{\"status\": 404 }"))
}
req := httptest.NewRequest(met, p, b)
return req, nil
type edgednsStub struct {
stubData map[string]edgednsStubData
}
func (m *mockAkamaiClient) Do(config edgegrid.Config, req *http.Request) (*http.Response, error) {
handler := func(w http.ResponseWriter, r *http.Request) (isError bool) {
b, _ := ioutil.ReadAll(r.Body)
io.WriteString(w, string(b))
return string(b) == "{\"status\": 404 }"
func newStub() *edgednsStub {
return &edgednsStub{
stubData: make(map[string]edgednsStubData),
}
w := httptest.NewRecorder()
err := handler(w, req)
resp := w.Result()
}
if err == true {
resp.StatusCode = 400
func createAkamaiStubProvider(stub *edgednsStub, domfilter endpoint.DomainFilter, idfilter provider.ZoneIDFilter) (*AkamaiProvider, error) {
akamaiConfig := AkamaiConfig{
DomainFilter: domfilter,
ZoneIDFilter: idfilter,
ServiceConsumerDomain: "testzone.com",
ClientToken: "test_token",
ClientSecret: "test_client_secret",
AccessToken: "test_access_token",
}
prov, err := NewAkamaiProvider(akamaiConfig, stub)
aprov := prov.(*AkamaiProvider)
return aprov, err
}
func (r *edgednsStub) createStubDataEntry(objtype string) {
log.Debugf("Creating stub data entry")
if _, exists := r.stubData[objtype]; !exists {
r.stubData[objtype] = edgednsStubData{objType: objtype}
}
return
}
func (r *edgednsStub) setOutput(objtype string, output []interface{}) {
log.Debugf("Setting output to %v", output)
r.createStubDataEntry(objtype)
stubdata := r.stubData[objtype]
stubdata.output = output
r.stubData[objtype] = stubdata
return
}
func (r *edgednsStub) setUpdateRecords(objtype string, records []interface{}) {
log.Debugf("Setting updaterecords to %v", records)
r.createStubDataEntry(objtype)
stubdata := r.stubData[objtype]
stubdata.updateRecords = records
r.stubData[objtype] = stubdata
return
}
func (r *edgednsStub) setCreateRecords(objtype string, records []interface{}) {
log.Debugf("Setting createrecords to %v", records)
r.createStubDataEntry(objtype)
stubdata := r.stubData[objtype]
stubdata.createRecords = records
r.stubData[objtype] = stubdata
return
}
func (r *edgednsStub) ListZones(queryArgs dns.ZoneListQueryArgs) (*dns.ZoneListResponse, error) {
log.Debugf("Entering ListZones")
// Ignore Metadata`
resp := &dns.ZoneListResponse{}
zones := make([]*dns.ZoneResponse, 0)
for _, zname := range r.stubData["zone"].output {
log.Debugf("Processing output: %v", zname)
zn := &dns.ZoneResponse{Zone: zname.(string), ContractId: "contract"}
log.Debugf("Created Zone Object: %v", zn)
zones = append(zones, zn)
}
resp.Zones = zones
return resp, nil
}
func (r *edgednsStub) GetRecordsets(zone string, queryArgs dns.RecordsetQueryArgs) (*dns.RecordSetResponse, error) {
log.Debugf("Entering GetRecordsets")
// Ignore Metadata`
resp := &dns.RecordSetResponse{}
sets := make([]dns.Recordset, 0)
for _, rec := range r.stubData["recordset"].output {
rset := rec.(dns.Recordset)
sets = append(sets, rset)
}
resp.Recordsets = sets
return resp, nil
}
func TestRequestError(t *testing.T) {
config := AkamaiConfig{}
func (r *edgednsStub) CreateRecordsets(recordsets *dns.Recordsets, zone string, reclock bool) error {
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
m := "ERROR"
p := ""
b := ""
x, err := c.request(m, p, bytes.NewReader([]byte(b)))
assert.Nil(t, x)
assert.NotNil(t, err)
return nil
}
func (r *edgednsStub) GetRecord(zone string, name string, record_type string) (*dns.RecordBody, error) {
resp := &dns.RecordBody{}
return resp, nil
}
func (r *edgednsStub) DeleteRecord(record *dns.RecordBody, zone string, recLock bool) error {
return nil
}
func (r *edgednsStub) UpdateRecord(record *dns.RecordBody, zone string, recLock bool) error {
return nil
}
// Test FetchZones
func TestFetchZonesZoneIDFilter(t *testing.T) {
config := AkamaiConfig{
ZoneIDFilter: provider.NewZoneIDFilter([]string{"Test"}),
}
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
stub := newStub()
domfilter := endpoint.DomainFilter{}
idfilter := provider.NewZoneIDFilter([]string{"Test"})
c, err := createAkamaiStubProvider(stub, domfilter, idfilter)
assert.Nil(t, err)
stub.setOutput("zone", []interface{}{"test1.testzone.com", "test2.testzone.com"})
x, _ := c.fetchZones()
y, _ := json.Marshal(x)
if assert.NotNil(t, y) {
assert.Equal(t, "{\"zones\":[{\"contractId\":\"Test\",\"zone\":\"example.com\"}]}", string(y))
assert.Equal(t, "{\"zones\":[{\"contractId\":\"contract\",\"zone\":\"test1.testzone.com\"},{\"contractId\":\"contract\",\"zone\":\"test2.testzone.com\"}]}", string(y))
}
}
//
func TestFetchZonesEmpty(t *testing.T) {
config := AkamaiConfig{
DomainFilter: endpoint.NewDomainFilter([]string{"Nonexistent"}),
ZoneIDFilter: provider.NewZoneIDFilter([]string{"Nonexistent"}),
}
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
stub := newStub()
domfilter := endpoint.NewDomainFilter([]string{"Nonexistent"})
idfilter := provider.NewZoneIDFilter([]string{"Nonexistent"})
c, err := createAkamaiStubProvider(stub, domfilter, idfilter)
assert.Nil(t, err)
stub.setOutput("zone", []interface{}{})
x, _ := c.fetchZones()
y, _ := json.Marshal(x)
if assert.NotNil(t, y) {
assert.Equal(t, "{\"zones\":null}", string(y))
}
}
func TestFetchRecordset1(t *testing.T) {
config := AkamaiConfig{}
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
x, _ := c.fetchRecordSet("example.com")
y, _ := json.Marshal(x)
if assert.NotNil(t, y) {
assert.Equal(t, "{\"recordsets\":[{\"name\":\"www.example.com\",\"type\":\"A\",\"ttl\":300,\"rdata\":[\"10.0.0.2\",\"10.0.0.3\"]},{\"name\":\"www.example.com\",\"type\":\"TXT\",\"ttl\":300,\"rdata\":[\"heritage=external-dns,external-dns/owner=default\"]}]}", string(y))
}
}
func TestFetchRecordset2(t *testing.T) {
config := AkamaiConfig{}
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
x, _ := c.fetchRecordSet("exclude.me")
y, _ := json.Marshal(x)
if assert.NotNil(t, y) {
assert.Equal(t, "{\"recordsets\":[{\"name\":\"www.exclude.me\",\"type\":\"A\",\"ttl\":300,\"rdata\":[\"192.168.0.1\",\"192.168.0.2\"]}]}", string(y))
assert.Equal(t, "{\"zones\":[]}", string(y))
}
}
// TestAkamaiRecords tests record endpoint
func TestAkamaiRecords(t *testing.T) {
config := AkamaiConfig{}
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
stub := newStub()
domfilter := endpoint.DomainFilter{}
idfilter := provider.ZoneIDFilter{}
c, err := createAkamaiStubProvider(stub, domfilter, idfilter)
assert.Nil(t, err)
stub.setOutput("zone", []interface{}{"test1.testzone.com"})
recordsets := make([]interface{}, 0)
recordsets = append(recordsets, dns.Recordset{
Name: "www.example.com",
Type: endpoint.RecordTypeA,
Rdata: []string{"10.0.0.2", "10.0.0.3"},
})
recordsets = append(recordsets, dns.Recordset{
Name: "www.example.com",
Type: endpoint.RecordTypeTXT,
Rdata: []string{"heritage=external-dns,external-dns/owner=default"},
})
recordsets = append(recordsets, dns.Recordset{
Name: "www.exclude.me",
Type: endpoint.RecordTypeA,
Rdata: []string{"192.168.0.1", "192.168.0.2"},
})
stub.setOutput("recordset", recordsets)
endpoints := make([]*endpoint.Endpoint, 0)
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3"))
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"))
@ -170,29 +230,43 @@ func TestAkamaiRecords(t *testing.T) {
}
}
//
func TestAkamaiRecordsEmpty(t *testing.T) {
config := AkamaiConfig{
ZoneIDFilter: provider.NewZoneIDFilter([]string{"Nonexistent"}),
}
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
stub := newStub()
domfilter := endpoint.DomainFilter{}
idfilter := provider.NewZoneIDFilter([]string{"Nonexistent"})
c, err := createAkamaiStubProvider(stub, domfilter, idfilter)
assert.Nil(t, err)
stub.setOutput("zone", []interface{}{"test1.testzone.com"})
recordsets := make([]interface{}, 0)
stub.setOutput("recordset", recordsets)
x, _ := c.Records(context.Background())
assert.Nil(t, x)
}
//
func TestAkamaiRecordsFilters(t *testing.T) {
config := AkamaiConfig{
DomainFilter: endpoint.NewDomainFilter([]string{"www.exclude.me"}),
ZoneIDFilter: provider.NewZoneIDFilter([]string{"Exclude-Me"}),
}
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
stub := newStub()
domfilter := endpoint.NewDomainFilter([]string{"www.exclude.me"})
idfilter := provider.ZoneIDFilter{}
c, err := createAkamaiStubProvider(stub, domfilter, idfilter)
assert.Nil(t, err)
stub.setOutput("zone", []interface{}{"www.exclude.me"})
recordsets := make([]interface{}, 0)
recordsets = append(recordsets, dns.Recordset{
Name: "www.example.com",
Type: endpoint.RecordTypeA,
Rdata: []string{"10.0.0.2", "10.0.0.3"},
})
recordsets = append(recordsets, dns.Recordset{
Name: "www.exclude.me",
Type: endpoint.RecordTypeA,
Rdata: []string{"192.168.0.1", "192.168.0.2"},
})
stub.setOutput("recordset", recordsets)
endpoints := make([]*endpoint.Endpoint, 0)
endpoints = append(endpoints, endpoint.NewEndpoint("www.exclude.me", endpoint.RecordTypeA, "192.168.0.1", "192.168.0.2"))
@ -202,32 +276,32 @@ func TestAkamaiRecordsFilters(t *testing.T) {
}
}
// TestCreateRecords tests create function
// (p AkamaiProvider) createRecordsets(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) error
func TestCreateRecords(t *testing.T) {
config := AkamaiConfig{}
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
stub := newStub()
domfilter := endpoint.DomainFilter{}
idfilter := provider.ZoneIDFilter{}
c, err := createAkamaiStubProvider(stub, domfilter, idfilter)
assert.Nil(t, err)
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
endpoints := make([]*endpoint.Endpoint, 0)
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3"))
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"))
x, _ := c.createRecords(zoneNameIDMapper, endpoints)
if assert.NotNil(t, x) {
assert.Equal(t, endpoints, x)
}
err = c.createRecordsets(zoneNameIDMapper, endpoints)
assert.Nil(t, err)
}
func TestCreateRecordsDomainFilter(t *testing.T) {
config := AkamaiConfig{
DomainFilter: endpoint.NewDomainFilter([]string{"example.com"}),
}
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
stub := newStub()
domfilter := endpoint.DomainFilter{}
idfilter := provider.ZoneIDFilter{}
c, err := createAkamaiStubProvider(stub, domfilter, idfilter)
assert.Nil(t, err)
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
endpoints := make([]*endpoint.Endpoint, 0)
@ -235,38 +309,36 @@ func TestCreateRecordsDomainFilter(t *testing.T) {
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"))
exclude := append(endpoints, endpoint.NewEndpoint("www.exclude.me", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3"))
x, _ := c.createRecords(zoneNameIDMapper, exclude)
if assert.NotNil(t, x) {
assert.Equal(t, endpoints, x)
}
err = c.createRecordsets(zoneNameIDMapper, exclude)
assert.Nil(t, err)
}
// TestDeleteRecords validate delete
func TestDeleteRecords(t *testing.T) {
config := AkamaiConfig{}
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
stub := newStub()
domfilter := endpoint.DomainFilter{}
idfilter := provider.ZoneIDFilter{}
c, err := createAkamaiStubProvider(stub, domfilter, idfilter)
assert.Nil(t, err)
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
endpoints := make([]*endpoint.Endpoint, 0)
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3"))
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"))
x, _ := c.deleteRecords(zoneNameIDMapper, endpoints)
if assert.NotNil(t, x) {
assert.Equal(t, endpoints, x)
}
err = c.deleteRecordsets(zoneNameIDMapper, endpoints)
assert.Nil(t, err)
}
//
func TestDeleteRecordsDomainFilter(t *testing.T) {
config := AkamaiConfig{
DomainFilter: endpoint.NewDomainFilter([]string{"example.com"}),
}
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
stub := newStub()
domfilter := endpoint.NewDomainFilter([]string{"example.com"})
idfilter := provider.ZoneIDFilter{}
c, err := createAkamaiStubProvider(stub, domfilter, idfilter)
assert.Nil(t, err)
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
endpoints := make([]*endpoint.Endpoint, 0)
@ -274,38 +346,36 @@ func TestDeleteRecordsDomainFilter(t *testing.T) {
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"))
exclude := append(endpoints, endpoint.NewEndpoint("www.exclude.me", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3"))
x, _ := c.deleteRecords(zoneNameIDMapper, exclude)
if assert.NotNil(t, x) {
assert.Equal(t, endpoints, x)
}
err = c.deleteRecordsets(zoneNameIDMapper, exclude)
assert.Nil(t, err)
}
// Test record update func
func TestUpdateRecords(t *testing.T) {
config := AkamaiConfig{}
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
stub := newStub()
domfilter := endpoint.DomainFilter{}
idfilter := provider.ZoneIDFilter{}
c, err := createAkamaiStubProvider(stub, domfilter, idfilter)
assert.Nil(t, err)
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
endpoints := make([]*endpoint.Endpoint, 0)
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3"))
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"))
x, _ := c.updateNewRecords(zoneNameIDMapper, endpoints)
if assert.NotNil(t, x) {
assert.Equal(t, endpoints, x)
}
err = c.updateNewRecordsets(zoneNameIDMapper, endpoints)
assert.Nil(t, err)
}
//
func TestUpdateRecordsDomainFilter(t *testing.T) {
config := AkamaiConfig{
DomainFilter: endpoint.NewDomainFilter([]string{"example.com"}),
}
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
stub := newStub()
domfilter := endpoint.NewDomainFilter([]string{"example.com"})
idfilter := provider.ZoneIDFilter{}
c, err := createAkamaiStubProvider(stub, domfilter, idfilter)
assert.Nil(t, err)
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
endpoints := make([]*endpoint.Endpoint, 0)
@ -313,19 +383,19 @@ func TestUpdateRecordsDomainFilter(t *testing.T) {
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"))
exclude := append(endpoints, endpoint.NewEndpoint("www.exclude.me", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3"))
x, _ := c.updateNewRecords(zoneNameIDMapper, exclude)
if assert.NotNil(t, x) {
assert.Equal(t, endpoints, x)
}
err = c.updateNewRecordsets(zoneNameIDMapper, exclude)
assert.Nil(t, err)
}
func TestAkamaiApplyChanges(t *testing.T) {
config := AkamaiConfig{}
client := &mockAkamaiClient{}
c := NewAkamaiProvider(config)
c.client = client
stub := newStub()
domfilter := endpoint.NewDomainFilter([]string{"example.com"})
idfilter := provider.ZoneIDFilter{}
c, err := createAkamaiStubProvider(stub, domfilter, idfilter)
assert.Nil(t, err)
stub.setOutput("zone", []interface{}{"example.com"})
changes := &plan.Changes{}
changes.Create = []*endpoint.Endpoint{
{DNSName: "www.example.com", RecordType: "A", Targets: endpoint.Targets{"target"}, RecordTTL: 300},