2017-03-17 13:42:53 +08:00
package main
import (
"flag"
"fmt"
2020-03-10 22:13:20 +08:00
"os"
2017-03-21 11:28:43 +08:00
"strconv"
"strings"
Switch to Go 1.13 error wrapping (#604)
* Replaced errors.Wrap with fmt.Errorf (#589)
* Find: errors\.Wrap\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Replaced errors.Wrapf with fmt.Errorf (#589)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])(,[^)]+)\)
* Replace: fmt.Errorf($2: %w$3$4, $1)
* Replaced errors.Errorf with fmt.Errorf (#589)
* Find: errors\.Errorf
Replace: fmt.Errorf
* Cleaned up remaining imports
* Cleanup
* Regenerate provider support matrix
This was broken by #533 ... and it's now the third time this has been missed.
2020-01-29 00:06:56 +08:00
"testing"
"github.com/miekg/dns/dnsutil"
2017-03-21 11:28:43 +08:00
2020-04-15 04:47:30 +08:00
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/nameservers"
"github.com/StackExchange/dnscontrol/v3/providers"
_ "github.com/StackExchange/dnscontrol/v3/providers/_all"
"github.com/StackExchange/dnscontrol/v3/providers/config"
2017-03-17 13:42:53 +08:00
)
var providerToRun = flag . String ( "provider" , "" , "Provider to run" )
2017-03-21 11:28:43 +08:00
var startIdx = flag . Int ( "start" , 0 , "Test number to begin with" )
var endIdx = flag . Int ( "end" , 0 , "Test index to stop after" )
var verbose = flag . Bool ( "verbose" , false , "Print corrections as you run them" )
2017-03-17 13:42:53 +08:00
func init ( ) {
2019-10-05 22:45:57 +08:00
testing . Init ( )
2017-03-17 13:42:53 +08:00
flag . Parse ( )
}
2020-03-03 00:25:42 +08:00
func getProvider ( t * testing . T ) ( providers . DNSServiceProvider , string , map [ int ] bool , map [ string ] string ) {
2017-03-17 13:42:53 +08:00
if * providerToRun == "" {
t . Log ( "No provider specified with -provider" )
2020-03-03 00:25:42 +08:00
return nil , "" , nil , nil
2017-03-17 13:42:53 +08:00
}
jsons , err := config . LoadProviderConfigs ( "providers.json" )
if err != nil {
t . Fatalf ( "Error loading provider configs: %s" , err )
}
2017-03-21 11:28:43 +08:00
fails := map [ int ] bool { }
2017-03-17 13:42:53 +08:00
for name , cfg := range jsons {
if * providerToRun != name {
continue
}
provider , err := providers . CreateDNSProvider ( name , cfg , nil )
if err != nil {
t . Fatal ( err )
}
2017-03-21 11:28:43 +08:00
if f := cfg [ "knownFailures" ] ; f != "" {
for _ , s := range strings . Split ( f , "," ) {
i , err := strconv . Atoi ( s )
if err != nil {
t . Fatal ( err )
}
fails [ i ] = true
}
}
2020-03-03 00:25:42 +08:00
return provider , cfg [ "domain" ] , fails , cfg
2017-03-17 13:42:53 +08:00
}
t . Fatalf ( "Provider %s not found" , * providerToRun )
2020-03-03 00:25:42 +08:00
return nil , "" , nil , nil
2017-03-17 13:42:53 +08:00
}
func TestDNSProviders ( t * testing . T ) {
2020-03-03 00:25:42 +08:00
provider , domain , fails , cfg := getProvider ( t )
2017-03-17 13:42:53 +08:00
if provider == nil {
return
}
t . Run ( fmt . Sprintf ( "%s" , domain ) , func ( t * testing . T ) {
2020-03-03 00:25:42 +08:00
runTests ( t , provider , domain , fails , cfg )
2017-03-17 13:42:53 +08:00
} )
}
func getDomainConfigWithNameservers ( t * testing . T , prv providers . DNSServiceProvider , domainName string ) * models . DomainConfig {
dc := & models . DomainConfig {
Name : domainName ,
}
// fix up nameservers
ns , err := prv . GetNameservers ( domainName )
if err != nil {
t . Fatal ( "Failed getting nameservers" , err )
}
dc . Nameservers = ns
nameservers . AddNSRecords ( dc )
return dc
}
2020-03-10 22:13:20 +08:00
// testPermitted returns nil if the test is permitted, otherwise an
// error explaining why it is not.
func testPermitted ( t * testing . T , p string , f TestGroup ) error {
// not() and only() can't be mixed.
if len ( f . only ) != 0 && len ( f . not ) != 0 {
return fmt . Errorf ( "invalid filter: can't mix not() and only()" )
2017-03-21 11:28:43 +08:00
}
2020-03-10 22:13:20 +08:00
// TODO(tlim): Have a separate validation pass so that such mistakes
// are more visible?
// If there are any required capabilities, make sure they all exist.
if len ( f . required ) != 0 {
for _ , c := range f . required {
if ! providers . ProviderHasCapability ( * providerToRun , c ) {
return fmt . Errorf ( "%s not supported" , c )
}
2017-03-17 13:42:53 +08:00
}
2020-03-10 22:13:20 +08:00
}
// If there are any "only" items, you must be one of them.
if len ( f . only ) != 0 {
for _ , provider := range f . only {
if p == provider {
return nil
2017-03-21 11:28:43 +08:00
}
2020-03-10 22:13:20 +08:00
}
return fmt . Errorf ( "disabled by only" )
}
// If there are any "not" items, you must NOT be one of them.
if len ( f . not ) != 0 {
for _ , provider := range f . not {
if p == provider {
return fmt . Errorf ( "excluded by not(\"%s\")" , provider )
2017-03-17 13:42:53 +08:00
}
2020-03-10 22:13:20 +08:00
}
return nil
}
return nil
}
//func makeClearFilter() *TestCase {
// tc := tc("Empty")
// tc.ChangeFilter = true
// return tc
//}
// desc := fmt.Sprintf("%d: %s", i, tst.Desc)
// makeChanges runs one set of DNS record tests. Returns true on success.
func makeChanges ( t * testing . T , prv providers . DNSServiceProvider , dc * models . DomainConfig , tst * TestCase , desc string , expectChanges bool , origConfig map [ string ] string ) bool {
domainName := dc . Name
2020-03-23 01:38:37 +08:00
return t . Run ( desc + ":" + tst . Desc , func ( t * testing . T ) {
2020-03-10 22:13:20 +08:00
dom , _ := dc . Copy ( )
for _ , r := range tst . Records {
rc := models . RecordConfig ( * r )
if strings . Contains ( rc . GetTargetField ( ) , "**current-domain**" ) {
_ = rc . SetTarget ( strings . Replace ( rc . GetTargetField ( ) , "**current-domain**" , domainName , 1 ) + "." )
2017-03-17 13:42:53 +08:00
}
2020-03-10 22:13:20 +08:00
if strings . Contains ( rc . GetTargetField ( ) , "**current-domain-no-trailing**" ) {
_ = rc . SetTarget ( strings . Replace ( rc . GetTargetField ( ) , "**current-domain-no-trailing**" , domainName , 1 ) )
2017-03-17 13:42:53 +08:00
}
2020-03-10 22:13:20 +08:00
if strings . Contains ( rc . GetLabelFQDN ( ) , "**current-domain**" ) {
rc . SetLabelFromFQDN ( strings . Replace ( rc . GetLabelFQDN ( ) , "**current-domain**" , domainName , 1 ) , domainName )
2017-03-17 13:42:53 +08:00
}
2020-03-10 22:13:20 +08:00
//if providers.ProviderHasCapability(*providerToRun, providers.CanUseAzureAlias) {
if strings . Contains ( rc . GetTargetField ( ) , "**subscription-id**" ) {
_ = rc . SetTarget ( strings . Replace ( rc . GetTargetField ( ) , "**subscription-id**" , origConfig [ "SubscriptionID" ] , 1 ) )
}
if strings . Contains ( rc . GetTargetField ( ) , "**resource-group**" ) {
_ = rc . SetTarget ( strings . Replace ( rc . GetTargetField ( ) , "**resource-group**" , origConfig [ "ResourceGroup" ] , 1 ) )
}
//}
dom . Records = append ( dom . Records , & rc )
}
dom . IgnoredLabels = tst . IgnoredLabels
models . PostProcessRecords ( dom . Records )
dom2 , _ := dom . Copy ( )
// get and run corrections for first time
corrections , err := prv . GetDomainCorrections ( dom )
if err != nil {
t . Fatal ( fmt . Errorf ( "runTests: %w" , err ) )
}
if len ( corrections ) == 0 && expectChanges {
t . Fatalf ( "Expected changes, but got none" )
}
for _ , c := range corrections {
if * verbose {
t . Log ( c . Msg )
}
err = c . F ( )
2017-03-17 13:42:53 +08:00
if err != nil {
t . Fatal ( err )
}
2020-03-10 22:13:20 +08:00
}
// If we just emptied out the zone, no need for a second pass.
if len ( tst . Records ) == 0 {
return
}
// run a second time and expect zero corrections
corrections , err = prv . GetDomainCorrections ( dom2 )
if err != nil {
t . Fatal ( err )
}
if len ( corrections ) != 0 {
t . Logf ( "Expected 0 corrections on second run, but found %d." , len ( corrections ) )
for i , c := range corrections {
t . Logf ( "#%d: %s" , i , c . Msg )
2017-03-17 13:42:53 +08:00
}
2020-03-10 22:13:20 +08:00
t . FailNow ( )
}
} )
}
func runTests ( t * testing . T , prv providers . DNSServiceProvider , domainName string , knownFailures map [ int ] bool , origConfig map [ string ] string ) {
dc := getDomainConfigWithNameservers ( t , prv , domainName )
testGroups := makeTests ( t )
firstGroup := * startIdx
lastGroup := * endIdx
if lastGroup == 0 {
lastGroup = len ( testGroups )
2017-03-17 13:42:53 +08:00
}
2020-03-10 22:13:20 +08:00
// Start the zone with a clean slate.
makeChanges ( t , prv , dc , tc ( "Empty" ) , "Clean Slate" , false , nil )
curGroup := - 1
for gIdx , group := range testGroups {
// Abide by -start -end flags
2020-03-11 04:53:17 +08:00
curGroup ++
2020-03-10 22:13:20 +08:00
if curGroup < firstGroup || curGroup > lastGroup {
continue
}
// Abide by filter
if err := testPermitted ( t , * providerToRun , * group ) ; err != nil {
//t.Logf("%s: ***SKIPPED(%v)***", group.Desc, err)
makeChanges ( t , prv , dc , tc ( "Empty" ) , fmt . Sprintf ( "%02d:%s ***SKIPPED(%v)***" , gIdx , group . Desc , err ) , false , origConfig )
continue
}
// Run the tests.
for _ , tst := range group . tests {
makeChanges ( t , prv , dc , tst , fmt . Sprintf ( "%02d:%s" , gIdx , group . Desc ) , true , origConfig )
if t . Failed ( ) {
break
}
}
// Remove all records so next group starts with a clean slate.
makeChanges ( t , prv , dc , tc ( "Empty" ) , "Post cleanup" , false , nil )
}
2017-03-17 13:42:53 +08:00
}
func TestDualProviders ( t * testing . T ) {
2020-03-03 00:25:42 +08:00
p , domain , _ , _ := getProvider ( t )
2017-03-17 13:42:53 +08:00
if p == nil {
return
}
dc := getDomainConfigWithNameservers ( t , p , domain )
// clear everything
run := func ( ) {
2017-03-23 00:54:55 +08:00
dom , _ := dc . Copy ( )
cs , err := p . GetDomainCorrections ( dom )
2017-03-17 13:42:53 +08:00
if err != nil {
t . Fatal ( err )
}
for i , c := range cs {
t . Logf ( "#%d: %s" , i + 1 , c . Msg )
if err = c . F ( ) ; err != nil {
t . Fatal ( err )
}
}
}
t . Log ( "Clearing everything" )
run ( )
// add bogus nameservers
dc . Records = [ ] * models . RecordConfig { }
2020-01-15 07:19:53 +08:00
dc . Nameservers = append ( dc . Nameservers , models . StringsToNameservers ( [ ] string { "ns1.example.com" , "ns2.example.com" } ) ... )
2017-03-17 13:42:53 +08:00
nameservers . AddNSRecords ( dc )
t . Log ( "Adding nameservers from another provider" )
run ( )
// run again to make sure no corrections
t . Log ( "Running again to ensure stability" )
cs , err := p . GetDomainCorrections ( dc )
if err != nil {
t . Fatal ( err )
}
if len ( cs ) != 0 {
2017-03-23 00:54:55 +08:00
t . Logf ( "Expect no corrections on second run, but found %d." , len ( cs ) )
for i , c := range cs {
t . Logf ( "#%d: %s" , i , c . Msg )
}
t . FailNow ( )
2017-03-17 13:42:53 +08:00
}
}
2020-03-10 22:13:20 +08:00
type TestGroup struct {
Desc string
required [ ] providers . Capability
only [ ] string
not [ ] string
tests [ ] * TestCase
}
2017-03-17 13:42:53 +08:00
type TestCase struct {
2018-01-16 04:39:29 +08:00
Desc string
Records [ ] * rec
IgnoredLabels [ ] string
2017-03-17 13:42:53 +08:00
}
type rec models . RecordConfig
2018-03-20 05:18:58 +08:00
func ( r * rec ) GetLabel ( ) string {
return r . Name
}
func ( r * rec ) SetLabel ( label , domain string ) {
r . Name = label
r . NameFQDN = dnsutil . AddOrigin ( label , "**current-domain**" )
}
func ( r * rec ) SetTarget ( target string ) {
r . Target = target
}
2017-03-17 13:42:53 +08:00
func a ( name , target string ) * rec {
return makeRec ( name , target , "A" )
}
func cname ( name , target string ) * rec {
return makeRec ( name , target , "CNAME" )
}
2017-04-20 03:13:28 +08:00
func alias ( name , target string ) * rec {
return makeRec ( name , target , "ALIAS" )
}
ROUTE53: Support Route53's ALIAS record type (#239) (#301)
* Stable comparison of metadata (#239)
Iterating over a map in Go never produces twice the same ordering.
Thus when comparing two metadata map with more than one key, the
`differ` is always finding differences.
To properly compare records metadata, we need to iterate the maps
in a deterministic way.
Signed-off-by: Brice Figureau <brice@daysofwonder.com>
* Support for Route53 ALIAS record type (#239)
Route53 ALIAS doesn't behave like a regular ALIAS, and is much more
limited as its target can only be some specific AWS resources or
another record in the same zone.
According to #239, this change adds a new directive R53_ALIAS which
implements this specific alias. This record type can only be used
with the Route53 provider.
This directive usage looks like this:
```js
D("example.com", REGISTRAR, DnsProvider("ROUTE53"),
R53_ALIAS("foo1", "A", "bar") // record in same zone
R53_ALIAS("foo2", "A",
"blahblah.elasticloadbalancing.us-west-1.amazonaws.com",
R53_ZONE('Z368ELLRRE2KJ0')) // ELB in us-west-1
```
Unfortunately, Route53 requires indicating the hosted zone id
where the target is defined (those are listed in AWS documentation,
see the R53_ALIAS documentation for links).
2018-01-16 18:53:12 +08:00
func r53alias ( name , aliasType , target string ) * rec {
r := makeRec ( name , target , "R53_ALIAS" )
r . R53Alias = map [ string ] string {
"type" : aliasType ,
}
return r
}
2020-03-03 00:25:42 +08:00
func azureAlias ( name , aliasType , target string ) * rec {
r := makeRec ( name , target , "AZURE_ALIAS" )
r . AzureAlias = map [ string ] string {
"type" : aliasType ,
}
return r
}
2017-03-21 11:28:43 +08:00
func ns ( name , target string ) * rec {
return makeRec ( name , target , "NS" )
}
2017-03-23 00:54:55 +08:00
func mx ( name string , prio uint16 , target string ) * rec {
r := makeRec ( name , target , "MX" )
2017-07-20 03:53:40 +08:00
r . MxPreference = prio
2017-03-23 00:54:55 +08:00
return r
}
2017-07-08 01:59:29 +08:00
func ptr ( name , target string ) * rec {
return makeRec ( name , target , "PTR" )
}
2019-03-28 22:40:13 +08:00
func naptr ( name string , order uint16 , preference uint16 , flags string , service string , regexp string , target string ) * rec {
r := makeRec ( name , target , "NAPTR" )
r . NaptrOrder = order
r . NaptrPreference = preference
r . NaptrFlags = flags
r . NaptrService = service
r . NaptrRegexp = regexp
return r
}
2017-07-20 03:53:40 +08:00
func srv ( name string , priority , weight , port uint16 , target string ) * rec {
r := makeRec ( name , target , "SRV" )
r . SrvPriority = priority
r . SrvWeight = weight
r . SrvPort = port
return r
}
2019-01-29 06:26:20 +08:00
func sshfp ( name string , algorithm uint8 , fingerprint uint8 , target string ) * rec {
r := makeRec ( name , target , "SSHFP" )
r . SshfpAlgorithm = algorithm
r . SshfpFingerprint = fingerprint
return r
}
2017-11-20 21:53:44 +08:00
func txt ( name , target string ) * rec {
2018-01-05 08:19:35 +08:00
// FYI: This must match the algorithm in pkg/js/helpers.js TXT.
r := makeRec ( name , target , "TXT" )
r . TxtStrings = [ ] string { target }
return r
}
func txtmulti ( name string , target [ ] string ) * rec {
// FYI: This must match the algorithm in pkg/js/helpers.js TXT.
r := makeRec ( name , target [ 0 ] , "TXT" )
r . TxtStrings = target
return r
2017-11-20 21:53:44 +08:00
}
2017-07-26 02:59:40 +08:00
func caa ( name string , tag string , flag uint8 , target string ) * rec {
r := makeRec ( name , target , "CAA" )
r . CaaFlag = flag
r . CaaTag = tag
return r
}
2017-09-15 21:03:29 +08:00
func tlsa ( name string , usage , selector , matchingtype uint8 , target string ) * rec {
r := makeRec ( name , target , "TLSA" )
r . TlsaUsage = usage
r . TlsaSelector = selector
r . TlsaMatchingType = matchingtype
return r
}
2018-01-16 04:39:29 +08:00
func ignore ( name string ) * rec {
2018-03-20 05:18:58 +08:00
r := & rec {
2018-01-16 04:39:29 +08:00
Type : "IGNORE" ,
}
2018-03-20 05:18:58 +08:00
r . SetLabel ( name , "**current-domain**" )
return r
2018-01-16 04:39:29 +08:00
}
2017-03-17 13:42:53 +08:00
func makeRec ( name , target , typ string ) * rec {
2018-03-20 05:18:58 +08:00
r := & rec {
Type : typ ,
TTL : 300 ,
2017-03-17 13:42:53 +08:00
}
2018-03-20 05:18:58 +08:00
r . SetLabel ( name , "**current-domain**" )
r . SetTarget ( target )
return r
2017-03-17 13:42:53 +08:00
}
func ( r * rec ) ttl ( t uint32 ) * rec {
r . TTL = t
return r
}
2020-03-10 22:13:20 +08:00
func manyA ( namePattern , target string , n int ) [ ] * rec {
recs := [ ] * rec { }
for i := 0 ; i < n ; i ++ {
recs = append ( recs , makeRec ( fmt . Sprintf ( namePattern , i ) , target , "A" ) )
}
return recs
}
func testgroup ( desc string , items ... interface { } ) * TestGroup {
group := & TestGroup { Desc : desc }
for _ , item := range items {
switch v := item . ( type ) {
case requiresFilter :
if len ( group . tests ) != 0 {
fmt . Printf ( "ERROR: requires() must be before all tc(): %v\n" , desc )
os . Exit ( 1 )
}
group . required = append ( group . required , v . caps ... )
case notFilter :
if len ( group . tests ) != 0 {
fmt . Printf ( "ERROR: not() must be before all tc(): %v\n" , desc )
os . Exit ( 1 )
}
group . not = append ( group . not , v . names ... )
case onlyFilter :
if len ( group . tests ) != 0 {
fmt . Printf ( "ERROR: only() must be before all tc(): %v\n" , desc )
os . Exit ( 1 )
}
group . only = append ( group . only , v . names ... )
case * TestCase :
group . tests = append ( group . tests , v )
default :
fmt . Printf ( "I don't know about type %T (%v)\n" , v , v )
}
}
return group
}
2017-03-17 13:42:53 +08:00
func tc ( desc string , recs ... * rec ) * TestCase {
2018-01-16 04:39:29 +08:00
var records [ ] * rec
var ignored [ ] string
for _ , r := range recs {
if r . Type == "IGNORE" {
2018-03-20 05:18:58 +08:00
ignored = append ( ignored , r . GetLabel ( ) )
2018-01-16 04:39:29 +08:00
} else {
records = append ( records , r )
}
}
2017-03-17 13:42:53 +08:00
return & TestCase {
2018-01-16 04:39:29 +08:00
Desc : desc ,
Records : records ,
IgnoredLabels : ignored ,
2017-03-17 13:42:53 +08:00
}
}
2020-03-10 22:13:20 +08:00
func clear ( items ... interface { } ) * TestCase {
return tc ( "Empty" )
2017-09-01 03:10:46 +08:00
}
2020-03-10 22:13:20 +08:00
type requiresFilter struct {
caps [ ] providers . Capability
}
2017-04-20 03:13:28 +08:00
2020-03-10 22:13:20 +08:00
func requires ( c ... providers . Capability ) requiresFilter {
return requiresFilter { caps : c }
}
2017-09-13 23:49:15 +08:00
2020-03-10 22:13:20 +08:00
type notFilter struct {
names [ ] string
}
func not ( n ... string ) notFilter {
return notFilter { names : n }
}
type onlyFilter struct {
names [ ] string
}
2017-09-13 23:49:15 +08:00
2020-03-10 22:13:20 +08:00
func only ( n ... string ) onlyFilter {
return onlyFilter { names : n }
}
//
func makeTests ( t * testing . T ) [ ] * TestGroup {
sha256hash := strings . Repeat ( "0123456789abcdef" , 4 )
sha512hash := strings . Repeat ( "0123456789abcdef" , 8 )
reversedSha512 := strings . Repeat ( "fedcba9876543210" , 8 )
// Each group of tests begins with testgroup("Title").
// The system will remove any records so that the tests
// begin with a clean slate (i.e. no records).
// Filters:
// Only apply to providers that CanUseAlias.
// requires(providers.CanUseAlias),
// Only apply to ROUTE53 + GANDI_V5:
// only("ROUTE53", "GANDI_V5")
// Only apply to all providers except ROUTE53 + GANDI_V5:
// not("ROUTE53", "GANDI_V5"),
// NOTE: You can't mix not() and only()
// reset(not("ROUTE53"), only("GCLOUD")), // ERROR!
// NOTE: All requires()/not()/only() must appear before any tc().
// tc()
// Each tc() indicates a set of records. The testgroup tries to
// migrate from one tc() to the next. For example the first tc()
// creates some records. The next tc() might list the same records
// but adds 1 new record and omits 1. Therefore migrating to this
// second tc() results in 1 record being created and 1 deleted; but
// for some providers it may be converting 1 record to another.
// Therefore some testgroups are testing the providers ability to
// transition between different states. Others are just testing
// whether or not a certain kind of record can be created and
// deleted.
// clear() is the same as tc("Empty"). It removes all records. You
// can use this to verify a provider can delete all the records in
// the last tc(), or to provide a clean slate for the next tc().
// Each testgroup() begins and ends with clear(), so you don't have
// to list the clear() yourself.
tests := [ ] * TestGroup {
//
// Basic functionality (add/rename/change/delete).
//
2020-05-08 22:56:58 +08:00
testgroup ( "GeneralACD" ,
// Test general ability to add/change/delete records of one
// type. These tests aren't specific to "A" records, but we
// don't do tests specific to A records because this exercises
// them very well.
2020-03-10 22:13:20 +08:00
tc ( "Create an A record" , a ( "@" , "1.1.1.1" ) ) ,
tc ( "Change it" , a ( "@" , "1.2.3.4" ) ) ,
tc ( "Add another" , a ( "@" , "1.2.3.4" ) , a ( "www" , "1.2.3.4" ) ) ,
tc ( "Add another(same name)" , a ( "@" , "1.2.3.4" ) , a ( "www" , "1.2.3.4" ) , a ( "www" , "5.6.7.8" ) ) ,
tc ( "Change a ttl" , a ( "@" , "1.2.3.4" ) . ttl ( 1000 ) , a ( "www" , "1.2.3.4" ) , a ( "www" , "5.6.7.8" ) ) ,
tc ( "Change single target from set" , a ( "@" , "1.2.3.4" ) . ttl ( 1000 ) , a ( "www" , "2.2.2.2" ) , a ( "www" , "5.6.7.8" ) ) ,
tc ( "Change all ttls" , a ( "@" , "1.2.3.4" ) . ttl ( 500 ) , a ( "www" , "2.2.2.2" ) . ttl ( 400 ) , a ( "www" , "5.6.7.8" ) . ttl ( 400 ) ) ,
tc ( "Delete one" , a ( "@" , "1.2.3.4" ) . ttl ( 500 ) , a ( "www" , "5.6.7.8" ) . ttl ( 400 ) ) ,
tc ( "Add back and change ttl" , a ( "www" , "5.6.7.8" ) . ttl ( 700 ) , a ( "www" , "1.2.3.4" ) . ttl ( 700 ) ) ,
tc ( "Change targets and ttls" , a ( "www" , "1.1.1.1" ) , a ( "www" , "2.2.2.2" ) ) ,
tc ( "Create wildcard" , a ( "*" , "1.2.3.4" ) , a ( "www" , "1.1.1.1" ) ) ,
tc ( "Delete wildcard" , a ( "www" , "1.1.1.1" ) ) ,
) ,
2020-05-08 22:56:58 +08:00
//
// Test the basic rtypes.
//
2020-03-10 22:13:20 +08:00
testgroup ( "CNAME" ,
tc ( "Create a CNAME" , cname ( "foo" , "google.com." ) ) ,
tc ( "Change CNAME target" , cname ( "foo" , "google2.com." ) ) ,
clear ( ) ,
tc ( "Record pointing to @" , cname ( "foo" , "**current-domain**" ) ) ,
) ,
testgroup ( "MX" ,
not ( "ACTIVEDIRECTORY_PS" ) , // Not implemented.
tc ( "MX record" , mx ( "@" , 5 , "foo.com." ) ) ,
tc ( "Second MX record, same prio" , mx ( "@" , 5 , "foo.com." ) , mx ( "@" , 5 , "foo2.com." ) ) ,
tc ( "3 MX" , mx ( "@" , 5 , "foo.com." ) , mx ( "@" , 5 , "foo2.com." ) , mx ( "@" , 15 , "foo3.com." ) ) ,
tc ( "Delete one" , mx ( "@" , 5 , "foo2.com." ) , mx ( "@" , 15 , "foo3.com." ) ) ,
tc ( "Change to other name" , mx ( "@" , 5 , "foo2.com." ) , mx ( "mail" , 15 , "foo3.com." ) ) ,
tc ( "Change Preference" , mx ( "@" , 7 , "foo2.com." ) , mx ( "mail" , 15 , "foo3.com." ) ) ,
tc ( "Record pointing to @" , mx ( "foo" , 8 , "**current-domain**" ) ) ,
) ,
2020-03-25 21:53:28 +08:00
testgroup ( "Null MX" ,
2020-05-14 04:37:49 +08:00
not ( "AZURE_DNS" , "GANDI_V5" , "NAMEDOTCOM" , "DIGITALOCEAN" , "NETCUP" , "DNSIMPLE" ) , // These providers don't support RFC 7505
2020-03-25 21:53:28 +08:00
tc ( "Null MX" , mx ( "@" , 0 , "." ) ) ,
) ,
2020-03-10 22:13:20 +08:00
testgroup ( "NS" ,
2020-04-18 01:58:44 +08:00
not ( "DNSIMPLE" , "EXOSCALE" , "NETCUP" ) ,
2020-03-10 22:13:20 +08:00
// DNSIMPLE: Does not support NS records nor subdomains.
// EXOSCALE: FILL IN
2020-04-18 01:58:44 +08:00
// Netcup: NS records not currently supported.
2020-03-10 22:13:20 +08:00
tc ( "NS for subdomain" , ns ( "xyz" , "ns2.foo.com." ) ) ,
tc ( "Dual NS for subdomain" , ns ( "xyz" , "ns2.foo.com." ) , ns ( "xyz" , "ns1.foo.com." ) ) ,
tc ( "NS Record pointing to @" , ns ( "foo" , "**current-domain**" ) ) ,
) ,
testgroup ( "IGNORE function" ,
tc ( "Create some records" , txt ( "foo" , "simple" ) , a ( "foo" , "1.2.3.4" ) ) ,
tc ( "Add a new record - ignoring foo" , a ( "bar" , "1.2.3.4" ) , ignore ( "foo" ) ) ,
clear ( ) ,
tc ( "Create some records" , txt ( "bar.foo" , "simple" ) , a ( "bar.foo" , "1.2.3.4" ) ) ,
tc ( "Add a new record - ignoring *.foo" , a ( "bar" , "1.2.3.4" ) , ignore ( "*.foo" ) ) ,
) ,
testgroup ( "single TXT" ,
tc ( "Create a TXT" , txt ( "foo" , "simple" ) ) ,
tc ( "Change a TXT" , txt ( "foo" , "changed" ) ) ,
clear ( ) ,
tc ( "Create a TXT with spaces" , txt ( "foo" , "with spaces" ) ) ,
tc ( "Create 1 TXT as array" , txtmulti ( "foo" , [ ] string { "simple" } ) ) ,
clear ( ) ,
tc ( "Create a 255-byte TXT" , txt ( "foo" , strings . Repeat ( "A" , 255 ) ) ) ,
) ,
2020-03-23 01:38:37 +08:00
testgroup ( "ws TXT" ,
not ( "CLOUDFLAREAPI" , "NAMEDOTCOM" ) ,
// These providers strip whitespace at the end of TXT records.
// TODO(tal): Add a check for this in normalize/validate.go
tc ( "Change a TXT with ws at end" , txt ( "foo" , "with space at end " ) ) ,
) ,
2020-04-18 01:58:44 +08:00
testgroup ( "empty TXT" ,
2020-05-14 04:37:49 +08:00
not ( "CLOUDFLAREAPI" , "NETCUP" ) ,
2020-03-10 22:13:20 +08:00
tc ( "TXT with empty str" , txt ( "foo1" , "" ) ) ,
// https://github.com/StackExchange/dnscontrol/issues/598
// We decided that permitting the TXT target to be an empty
// string is not a requirement (even though RFC1035 permits it).
// In the future we might make it "capability" to
// indicate which vendors support an empty TXT record.
// However at this time there is no pressing need for this
// feature.
) ,
//
2020-05-08 22:56:58 +08:00
// Tests that exercise the API protocol and/or code.
2020-03-10 22:13:20 +08:00
//
2020-05-08 22:56:58 +08:00
testgroup ( "TypeChange" ,
// Test whether the provider properly handles a label changing
// from one rtype to another.
tc ( "Create a CNAME" , cname ( "foo" , "google.com." ) ) ,
tc ( "Change to A record" , a ( "foo" , "1.2.3.4" ) ) ,
tc ( "Change back to CNAME" , cname ( "foo" , "google2.com." ) ) ,
) ,
2020-03-10 22:13:20 +08:00
testgroup ( "Case Sensitivity" ,
// The decoys are required so that there is at least one actual change in each tc.
tc ( "Create CAPS" , mx ( "BAR" , 5 , "BAR.com." ) ) ,
tc ( "Downcase label" , mx ( "bar" , 5 , "BAR.com." ) , a ( "decoy" , "1.1.1.1" ) ) ,
tc ( "Downcase target" , mx ( "bar" , 5 , "bar.com." ) , a ( "decoy" , "2.2.2.2" ) ) ,
tc ( "Upcase both" , mx ( "BAR" , 5 , "BAR.COM." ) , a ( "decoy" , "3.3.3.3" ) ) ,
) ,
testgroup ( "IDNA" ,
2020-05-23 22:10:40 +08:00
not ( "SOFTLAYER" , "CLOUDFLAREAPI" ) ,
2020-03-10 22:13:20 +08:00
// SOFTLAYER: fails at direct internationalization, punycode works, of course.
2020-05-23 22:10:40 +08:00
// CLOUDFLAREAPI: fails. Needs to be debugged.
2020-03-10 22:13:20 +08:00
tc ( "Internationalized name" , a ( "ööö" , "1.2.3.4" ) ) ,
tc ( "Change IDN" , a ( "ööö" , "2.2.2.2" ) ) ,
tc ( "Internationalized CNAME Target" , cname ( "a" , "ööö.com." ) ) ,
) ,
testgroup ( "IDNAs in CNAME targets" ,
2020-05-23 22:10:40 +08:00
not ( "LINODE" , "CLOUDFLAREAPI" ) ,
2020-03-10 22:13:20 +08:00
// LINODE: hostname validation does not allow the target domain TLD
tc ( "IDN CNAME AND Target" , cname ( "öoö" , "ööö.企业." ) ) ,
) ,
testgroup ( "page size" ,
// Tests the paging code of providers. Many providers page at 100.
// Notes:
2020-03-23 01:38:37 +08:00
// - Gandi: page size is 100, therefore we test with 99, 100, and 101
// - NS1: free acct only allows 50 records, therefore we skip
// - DigitalOcean: fails due to rate limiting, not page limits.
not ( "NS1" , "DIGITALOCEAN" ) ,
2020-03-10 22:13:20 +08:00
tc ( "99 records" , manyA ( "rec%04d" , "1.2.3.4" , 99 ) ... ) ,
tc ( "100 records" , manyA ( "rec%04d" , "1.2.3.4" , 100 ) ... ) ,
tc ( "101 records" , manyA ( "rec%04d" , "1.2.3.4" , 101 ) ... ) ,
) ,
testgroup ( "Large updates" ,
// Verify https://github.com/StackExchange/dnscontrol/issues/493
only ( "ROUTE53" ) ,
tc ( "600 records" , manyA ( "rec%04d" , "1.2.3.4" , 600 ) ... ) ,
tc ( "Update 600 records" , manyA ( "rec%04d" , "1.2.3.5" , 600 ) ... ) ,
tc ( "Empty" ) , // Delete them all
tc ( "1200 records" , manyA ( "rec%04d" , "1.2.3.4" , 1200 ) ... ) ,
tc ( "Update 1200 records" , manyA ( "rec%04d" , "1.2.3.5" , 1200 ) ... ) ,
) ,
//
// CanUse* types:
//
testgroup ( "CAA" ,
requires ( providers . CanUseCAA ) ,
tc ( "CAA record" , caa ( "@" , "issue" , 0 , "letsencrypt.org" ) ) ,
tc ( "CAA change tag" , caa ( "@" , "issuewild" , 0 , "letsencrypt.org" ) ) ,
tc ( "CAA change target" , caa ( "@" , "issuewild" , 0 , "example.com" ) ) ,
tc ( "CAA change flag" , caa ( "@" , "issuewild" , 128 , "example.com" ) ) ,
tc ( "CAA many records" ,
caa ( "@" , "issue" , 0 , "letsencrypt.org" ) ,
caa ( "@" , "issuewild" , 0 , "comodoca.com" ) ,
caa ( "@" , "iodef" , 128 , "mailto:test@example.com" ) ) ,
tc ( "CAA delete" , caa ( "@" , "issue" , 0 , "letsencrypt.org" ) ) ,
) ,
testgroup ( "CAA with ;" ,
requires ( providers . CanUseCAA ) , not ( "DIGITALOCEAN" ) ,
// Test support of ";" as a value
tc ( "CAA many records" , caa ( "@" , "issuewild" , 0 , ";" ) ) ,
) ,
testgroup ( "NAPTR" ,
requires ( providers . CanUseNAPTR ) ,
2019-04-17 17:23:32 +08:00
tc ( "NAPTR record" , naptr ( "test" , 100 , 10 , "U" , "E2U+sip" , "!^.*$!sip:customer-service@example.com!" , "example.foo.com." ) ) ,
tc ( "NAPTR second record" , naptr ( "test" , 102 , 10 , "U" , "E2U+email" , "!^.*$!mailto:information@example.com!" , "example.foo.com." ) ) ,
tc ( "NAPTR delete record" , naptr ( "test" , 100 , 10 , "U" , "E2U+email" , "!^.*$!mailto:information@example.com!" , "example.foo.com." ) ) ,
tc ( "NAPTR change target" , naptr ( "test" , 100 , 10 , "U" , "E2U+email" , "!^.*$!mailto:information@example.com!" , "example2.foo.com." ) ) ,
tc ( "NAPTR change order" , naptr ( "test" , 103 , 10 , "U" , "E2U+email" , "!^.*$!mailto:information@example.com!" , "example2.foo.com." ) ) ,
tc ( "NAPTR change preference" , naptr ( "test" , 103 , 20 , "U" , "E2U+email" , "!^.*$!mailto:information@example.com!" , "example2.foo.com." ) ) ,
tc ( "NAPTR change flags" , naptr ( "test" , 103 , 20 , "A" , "E2U+email" , "!^.*$!mailto:information@example.com!" , "example2.foo.com." ) ) ,
tc ( "NAPTR change service" , naptr ( "test" , 103 , 20 , "A" , "E2U+sip" , "!^.*$!mailto:information@example.com!" , "example2.foo.com." ) ) ,
tc ( "NAPTR change regexp" , naptr ( "test" , 103 , 20 , "A" , "E2U+sip" , "!^.*$!sip:customer-service@example.com!" , "example2.foo.com." ) ) ,
2020-03-10 22:13:20 +08:00
) ,
testgroup ( "PTR" , requires ( providers . CanUsePTR ) , not ( "ACTIVEDIRECTORY_PS" ) ,
tc ( "Create PTR record" , ptr ( "4" , "foo.com." ) ) ,
tc ( "Modify PTR record" , ptr ( "4" , "bar.com." ) ) ,
) ,
2019-03-28 22:40:13 +08:00
2020-03-10 22:13:20 +08:00
testgroup ( "SRV" , requires ( providers . CanUseSRV ) , not ( "ACTIVEDIRECTORY_PS" , "CLOUDNS" ) ,
2017-09-13 23:49:15 +08:00
tc ( "SRV record" , srv ( "_sip._tcp" , 5 , 6 , 7 , "foo.com." ) ) ,
tc ( "Second SRV record, same prio" , srv ( "_sip._tcp" , 5 , 6 , 7 , "foo.com." ) , srv ( "_sip._tcp" , 5 , 60 , 70 , "foo2.com." ) ) ,
tc ( "3 SRV" , srv ( "_sip._tcp" , 5 , 6 , 7 , "foo.com." ) , srv ( "_sip._tcp" , 5 , 60 , 70 , "foo2.com." ) , srv ( "_sip._tcp" , 15 , 65 , 75 , "foo3.com." ) ) ,
tc ( "Delete one" , srv ( "_sip._tcp" , 5 , 6 , 7 , "foo.com." ) , srv ( "_sip._tcp" , 15 , 65 , 75 , "foo3.com." ) ) ,
tc ( "Change Target" , srv ( "_sip._tcp" , 5 , 6 , 7 , "foo.com." ) , srv ( "_sip._tcp" , 15 , 65 , 75 , "foo4.com." ) ) ,
tc ( "Change Priority" , srv ( "_sip._tcp" , 52 , 6 , 7 , "foo.com." ) , srv ( "_sip._tcp" , 15 , 65 , 75 , "foo4.com." ) ) ,
tc ( "Change Weight" , srv ( "_sip._tcp" , 52 , 62 , 7 , "foo.com." ) , srv ( "_sip._tcp" , 15 , 65 , 75 , "foo4.com." ) ) ,
tc ( "Change Port" , srv ( "_sip._tcp" , 52 , 62 , 72 , "foo.com." ) , srv ( "_sip._tcp" , 15 , 65 , 75 , "foo4.com." ) ) ,
2020-03-10 22:13:20 +08:00
) ,
2020-03-23 01:38:37 +08:00
testgroup ( "SRV w/ null target" , not ( "EXOSCALE" , "HEXONET" , "NAMEDOTCOM" ) ,
2020-03-10 22:13:20 +08:00
tc ( "Null Target" , srv ( "_sip._tcp" , 52 , 62 , 72 , "foo.com." ) , srv ( "_sip._tcp" , 15 , 65 , 75 , "." ) ) ,
) ,
2017-09-13 23:49:15 +08:00
2020-03-10 22:13:20 +08:00
testgroup ( "SSHFP" ,
requires ( providers . CanUseSSHFP ) ,
2019-05-28 03:10:00 +08:00
tc ( "SSHFP record" ,
sshfp ( "@" , 1 , 1 , "66c7d5540b7d75a1fb4c84febfa178ad99bdd67c" ) ) ,
tc ( "SSHFP change algorithm" ,
sshfp ( "@" , 2 , 1 , "66c7d5540b7d75a1fb4c84febfa178ad99bdd67c" ) ) ,
2020-03-03 00:23:47 +08:00
tc ( "SSHFP change fingerprint and type" ,
2019-05-28 03:10:00 +08:00
sshfp ( "@" , 2 , 2 , "745a635bc46a397a5c4f21d437483005bcc40d7511ff15fbfafe913a081559bc" ) ) ,
tc ( "SSHFP Delete one" ) ,
tc ( "SSHFP add many records" ,
sshfp ( "@" , 1 , 1 , "66666666666d75a1fb4c84febfa178ad99bdd67c" ) ,
sshfp ( "@" , 1 , 2 , "777777777777797a5c4f21d437483005bcc40d7511ff15fbfafe913a081559bc" ) ,
sshfp ( "@" , 2 , 1 , "8888888888888888fb4c84febfa178ad99bdd67c" ) ) ,
tc ( "SSHFP delete two" ,
sshfp ( "@" , 1 , 1 , "66666666666d75a1fb4c84febfa178ad99bdd67c" ) ) ,
2020-03-10 22:13:20 +08:00
) ,
2020-01-25 01:21:01 +08:00
2020-03-10 22:13:20 +08:00
testgroup ( "TLSA" ,
requires ( providers . CanUseTLSA ) ,
2017-11-11 03:02:34 +08:00
tc ( "TLSA record" , tlsa ( "_443._tcp" , 3 , 1 , 1 , sha256hash ) ) ,
tc ( "TLSA change usage" , tlsa ( "_443._tcp" , 2 , 1 , 1 , sha256hash ) ) ,
tc ( "TLSA change selector" , tlsa ( "_443._tcp" , 2 , 0 , 1 , sha256hash ) ) ,
tc ( "TLSA change matchingtype" , tlsa ( "_443._tcp" , 2 , 0 , 2 , sha512hash ) ) ,
tc ( "TLSA change certificate" , tlsa ( "_443._tcp" , 2 , 0 , 2 , reversedSha512 ) ) ,
2020-03-10 22:13:20 +08:00
) ,
2017-09-15 21:03:29 +08:00
2020-03-10 22:13:20 +08:00
testgroup ( "TXTMulti" ,
requires ( providers . CanUseTXTMulti ) ,
2018-01-05 08:19:35 +08:00
tc ( "Create TXTMulti 1" ,
txtmulti ( "foo1" , [ ] string { "simple" } ) ,
) ,
tc ( "Create TXTMulti 2" ,
txtmulti ( "foo1" , [ ] string { "simple" } ) ,
txtmulti ( "foo2" , [ ] string { "one" , "two" } ) ,
) ,
tc ( "Create TXTMulti 3" ,
txtmulti ( "foo1" , [ ] string { "simple" } ) ,
txtmulti ( "foo2" , [ ] string { "one" , "two" } ) ,
txtmulti ( "foo3" , [ ] string { "eh" , "bee" , "cee" } ) ,
) ,
2018-01-11 20:23:59 +08:00
tc ( "Create TXTMulti with quotes" ,
txtmulti ( "foo1" , [ ] string { "simple" } ) ,
txtmulti ( "foo2" , [ ] string { "o\"ne" , "tw\"o" } ) ,
txtmulti ( "foo3" , [ ] string { "eh" , "bee" , "cee" } ) ,
) ,
2018-01-05 08:19:35 +08:00
tc ( "Change TXTMulti" ,
txtmulti ( "foo1" , [ ] string { "dimple" } ) ,
txtmulti ( "foo2" , [ ] string { "fun" , "two" } ) ,
txtmulti ( "foo3" , [ ] string { "eh" , "bzz" , "cee" } ) ,
) ,
2018-10-14 22:53:11 +08:00
tc ( "3x255-byte TXTMulti" ,
2020-03-10 22:13:20 +08:00
txtmulti ( "foo3" , [ ] string { strings . Repeat ( "X" , 255 ) , strings . Repeat ( "Y" , 255 ) , strings . Repeat ( "Z" , 255 ) } ) ) ,
) ,
2018-01-05 08:19:35 +08:00
2020-03-10 22:13:20 +08:00
//
// Pseudo rtypes:
//
ROUTE53: Support Route53's ALIAS record type (#239) (#301)
* Stable comparison of metadata (#239)
Iterating over a map in Go never produces twice the same ordering.
Thus when comparing two metadata map with more than one key, the
`differ` is always finding differences.
To properly compare records metadata, we need to iterate the maps
in a deterministic way.
Signed-off-by: Brice Figureau <brice@daysofwonder.com>
* Support for Route53 ALIAS record type (#239)
Route53 ALIAS doesn't behave like a regular ALIAS, and is much more
limited as its target can only be some specific AWS resources or
another record in the same zone.
According to #239, this change adds a new directive R53_ALIAS which
implements this specific alias. This record type can only be used
with the Route53 provider.
This directive usage looks like this:
```js
D("example.com", REGISTRAR, DnsProvider("ROUTE53"),
R53_ALIAS("foo1", "A", "bar") // record in same zone
R53_ALIAS("foo2", "A",
"blahblah.elasticloadbalancing.us-west-1.amazonaws.com",
R53_ZONE('Z368ELLRRE2KJ0')) // ELB in us-west-1
```
Unfortunately, Route53 requires indicating the hosted zone id
where the target is defined (those are listed in AWS documentation,
see the R53_ALIAS documentation for links).
2018-01-16 18:53:12 +08:00
2020-03-10 22:13:20 +08:00
testgroup ( "ALIAS" ,
requires ( providers . CanUseAlias ) ,
tc ( "ALIAS at root" , alias ( "@" , "foo.com." ) ) ,
tc ( "change it" , alias ( "@" , "foo2.com." ) ) ,
tc ( "ALIAS at subdomain" , alias ( "test" , "foo.com." ) ) ,
) ,
testgroup ( "AZURE_ALIAS" ,
requires ( providers . CanUseAzureAlias ) ,
2020-03-03 00:25:42 +08:00
tc ( "create dependent A records" , a ( "foo.a" , "1.2.3.4" ) , a ( "quux.a" , "2.3.4.5" ) ) ,
tc ( "ALIAS to A record in same zone" , a ( "foo.a" , "1.2.3.4" ) , a ( "quux.a" , "2.3.4.5" ) , azureAlias ( "bar.a" , "A" , "/subscriptions/**subscription-id**/resourceGroups/**resource-group**/providers/Microsoft.Network/dnszones/**current-domain-no-trailing**/A/foo.a" ) ) ,
tc ( "change it" , a ( "foo.a" , "1.2.3.4" ) , a ( "quux.a" , "2.3.4.5" ) , azureAlias ( "bar.a" , "A" , "/subscriptions/**subscription-id**/resourceGroups/**resource-group**/providers/Microsoft.Network/dnszones/**current-domain-no-trailing**/A/quux.a" ) ) ,
tc ( "create dependent CNAME records" , cname ( "foo.cname" , "google.com" ) , cname ( "quux.cname" , "google2.com" ) ) ,
tc ( "ALIAS to CNAME record in same zone" , cname ( "foo.cname" , "google.com" ) , cname ( "quux.cname" , "google2.com" ) , azureAlias ( "bar" , "CNAME" , "/subscriptions/**subscription-id**/resourceGroups/**resource-group**/providers/Microsoft.Network/dnszones/**current-domain-no-trailing**/CNAME/foo.cname" ) ) ,
tc ( "change it" , cname ( "foo.cname" , "google.com" ) , cname ( "quux.cname" , "google2.com" ) , azureAlias ( "bar.cname" , "CNAME" , "/subscriptions/**subscription-id**/resourceGroups/**resource-group**/providers/Microsoft.Network/dnszones/**current-domain-no-trailing**/CNAME/quux.cname" ) ) ,
2020-03-10 22:13:20 +08:00
) ,
testgroup ( "R53_ALIAS" ,
requires ( providers . CanUseRoute53Alias ) ,
tc ( "create dependent records" , a ( "foo" , "1.2.3.4" ) , a ( "quux" , "2.3.4.5" ) ) ,
tc ( "ALIAS to A record in same zone" , a ( "foo" , "1.2.3.4" ) , a ( "quux" , "2.3.4.5" ) , r53alias ( "bar" , "A" , "foo.**current-domain**" ) ) ,
tc ( "change it" , a ( "foo" , "1.2.3.4" ) , a ( "quux" , "2.3.4.5" ) , r53alias ( "bar" , "A" , "quux.**current-domain**" ) ) ,
) ,
2020-03-03 00:25:42 +08:00
}
2019-06-27 11:45:34 +08:00
2017-09-13 23:49:15 +08:00
return tests
2017-03-17 13:42:53 +08:00
}