mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-11-10 00:10:36 +08:00
AXFRDDNS: Enable automated testing (#3290)
This commit is contained in:
parent
f265dbade1
commit
9c3a161ccd
5 changed files with 133 additions and 60 deletions
14
.github/workflows/pr_integration_tests.yml
vendored
14
.github/workflows/pr_integration_tests.yml
vendored
|
|
@ -52,7 +52,7 @@ jobs:
|
||||||
Write-Host "Integration test providers: $Providers"
|
Write-Host "Integration test providers: $Providers"
|
||||||
echo "integration_test_providers=$(ConvertTo-Json -InputObject $Providers -Compress)" >> $env:GITHUB_OUTPUT
|
echo "integration_test_providers=$(ConvertTo-Json -InputObject $Providers -Compress)" >> $env:GITHUB_OUTPUT
|
||||||
env:
|
env:
|
||||||
PROVIDERS: "['AZURE_DNS','BIND','BUNNY_DNS','CLOUDFLAREAPI','CLOUDNS','CNR','DIGITALOCEAN','GANDI_V5','GCLOUD','HEDNS','HEXONET','HUAWEICLOUD','INWX','MYTHICBEASTS', 'NAMEDOTCOM','NS1','POWERDNS','ROUTE53','SAKURACLOUD','TRANSIP']"
|
PROVIDERS: "['AXFRDDNS', 'AXFRDDNS_DNSSEC', 'AZURE_DNS','BIND','BUNNY_DNS','CLOUDFLAREAPI','CLOUDNS','CNR','DIGITALOCEAN','GANDI_V5','GCLOUD','HEDNS','HEXONET','HUAWEICLOUD','INWX','MYTHICBEASTS', 'NAMEDOTCOM','NS1','POWERDNS','ROUTE53','SAKURACLOUD','TRANSIP']"
|
||||||
ENV_CONTEXT: ${{ toJson(env) }}
|
ENV_CONTEXT: ${{ toJson(env) }}
|
||||||
VARS_CONTEXT: ${{ toJson(vars) }}
|
VARS_CONTEXT: ${{ toJson(vars) }}
|
||||||
SECRETS_CONTEXT: ${{ toJson(secrets) }}
|
SECRETS_CONTEXT: ${{ toJson(secrets) }}
|
||||||
|
|
@ -73,6 +73,8 @@ jobs:
|
||||||
# PROVIDER DOMAIN LIST
|
# PROVIDER DOMAIN LIST
|
||||||
# These providers will be tested if the env variable is set.
|
# These providers will be tested if the env variable is set.
|
||||||
# Set it to the domain name to use during the test.
|
# Set it to the domain name to use during the test.
|
||||||
|
AXFRDDNS_DOMAIN: ${{ vars.AXFRDDNS_DOMAIN }}
|
||||||
|
AXFRDDNS_DNSSEC_DOMAIN: ${{ vars.AXFRDDNS_DNSSEC_DOMAIN }}
|
||||||
AZURE_DNS_DOMAIN: ${{ vars.AZURE_DNS_DOMAIN }}
|
AZURE_DNS_DOMAIN: ${{ vars.AZURE_DNS_DOMAIN }}
|
||||||
BIND_DOMAIN: ${{ vars.BIND_DOMAIN }}
|
BIND_DOMAIN: ${{ vars.BIND_DOMAIN }}
|
||||||
BUNNY_DNS_DOMAIN: ${{ vars.BUNNY_DNS_DOMAIN }}
|
BUNNY_DNS_DOMAIN: ${{ vars.BUNNY_DNS_DOMAIN }}
|
||||||
|
|
@ -98,6 +100,16 @@ jobs:
|
||||||
# The above providers have additional env variables they
|
# The above providers have additional env variables they
|
||||||
# need for credentials and such.
|
# need for credentials and such.
|
||||||
#
|
#
|
||||||
|
AXFRDDNS_MASTER: ${{ secrets.AXFRDDNS_MASTER }}
|
||||||
|
AXFRDDNS_NAMESERVERS: ${{ secrets.AXFRDDNS_NAMESERVERS }}
|
||||||
|
AXFRDDNS_TRANSFER_KEY: ${{ secrets.AXFRDDNS_TRANSFER_KEY }}
|
||||||
|
AXFRDDNS_UPDATE_KEY: ${{ secrets.AXFRDDNS_UPDATE_KEY }}
|
||||||
|
#
|
||||||
|
AXFRDDNS_DNSSEC_MASTER: ${{ secrets.AXFRDDNS_DNSSEC_MASTER }}
|
||||||
|
AXFRDDNS_DNSSEC_NAMESERVERS: ${{ secrets.AXFRDDNS_DNSSEC_NAMESERVERS }}
|
||||||
|
AXFRDDNS_DNSSEC_TRANSFER_KEY: ${{ secrets.AXFRDDNS_DNSSEC_TRANSFER_KEY }}
|
||||||
|
AXFRDDNS_DNSSEC_UPDATE_KEY: ${{ secrets.AXFRDDNS_DNSSEC_UPDATE_KEY }}
|
||||||
|
#
|
||||||
AZURE_DNS_CLIENT_ID: ${{ secrets.AZURE_DNS_CLIENT_ID }}
|
AZURE_DNS_CLIENT_ID: ${{ secrets.AZURE_DNS_CLIENT_ID }}
|
||||||
AZURE_DNS_CLIENT_SECRET: ${{ secrets.AZURE_DNS_CLIENT_SECRET }}
|
AZURE_DNS_CLIENT_SECRET: ${{ secrets.AZURE_DNS_CLIENT_SECRET }}
|
||||||
AZURE_DNS_RESOURCE_GROUP: ${{ secrets.AZURE_DNS_RESOURCE_GROUP }}
|
AZURE_DNS_RESOURCE_GROUP: ${{ secrets.AZURE_DNS_RESOURCE_GROUP }}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ import (
|
||||||
"github.com/miekg/dns/dnsutil"
|
"github.com/miekg/dns/dnsutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var providerToRun = flag.String("provider", "", "Provider to run")
|
var providerFlag = flag.String("provider", "", "Provider to run (if empty, deduced from -profile)")
|
||||||
|
var profileFlag = flag.String("profile", "", "Entry in providers.json to use (if empty, copied from -provider)")
|
||||||
var startIdx = flag.Int("start", -1, "Test number to begin with")
|
var startIdx = flag.Int("start", -1, "Test number to begin with")
|
||||||
var endIdx = flag.Int("end", -1, "Test index to stop after")
|
var endIdx = flag.Int("end", -1, "Test index to stop after")
|
||||||
var verbose = flag.Bool("verbose", false, "Print corrections as you run them")
|
var verbose = flag.Bool("verbose", false, "Print corrections as you run them")
|
||||||
|
|
@ -47,60 +48,96 @@ func CfCProxyFull() *TestCase { return tc("cproxyf", cfProxyCNAME("cproxy", "exa
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
func getProvider(t *testing.T) (providers.DNSServiceProvider, string, map[string]string) {
|
func getProvider(t *testing.T) (providers.DNSServiceProvider, string, map[string]string) {
|
||||||
if *providerToRun == "" {
|
if *providerFlag == "" && *profileFlag == "" {
|
||||||
t.Log("No provider specified with -provider")
|
t.Log("No -provider or -profile specified")
|
||||||
return nil, "", nil
|
return nil, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load the profile values
|
||||||
|
|
||||||
jsons, err := credsfile.LoadProviderConfigs("providers.json")
|
jsons, err := credsfile.LoadProviderConfigs("providers.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error loading provider configs: %s", err)
|
t.Fatalf("Error loading provider configs: %s", err)
|
||||||
}
|
}
|
||||||
for name, cfg := range jsons {
|
|
||||||
if *providerToRun != name {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var metadata json.RawMessage
|
// Which profile are we using? Use the profile but default to the provider.
|
||||||
// CLOUDFLAREAPI tests related to CLOUDFLAREAPI_SINGLE_REDIRECT/CF_REDIRECT/CF_TEMP_REDIRECT
|
targetProfile := *profileFlag
|
||||||
// requires metadata to enable this feature.
|
if targetProfile == "" {
|
||||||
// In hindsight, I have no idea why this metadata flag is required to
|
targetProfile = *providerFlag
|
||||||
// use this feature. Maybe because we didn't have the capabilities
|
|
||||||
// feature at the time?
|
|
||||||
if name == "CLOUDFLAREAPI" {
|
|
||||||
items := []string{}
|
|
||||||
if *enableCFWorkers {
|
|
||||||
items = append(items, `"manage_workers": true`)
|
|
||||||
}
|
|
||||||
switch *enableCFRedirectMode {
|
|
||||||
case "":
|
|
||||||
items = append(items, `"manage_redirects": true`)
|
|
||||||
case "c":
|
|
||||||
items = append(items, `"manage_redirects": true`)
|
|
||||||
items = append(items, `"manage_single_redirects": true`)
|
|
||||||
case "n":
|
|
||||||
items = append(items, `"manage_single_redirects": true`)
|
|
||||||
case "o":
|
|
||||||
}
|
|
||||||
metadata = []byte(`{ ` + strings.Join(items, `, `) + ` }`)
|
|
||||||
}
|
|
||||||
|
|
||||||
provider, err := providers.CreateDNSProvider(name, cfg, metadata)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if name == "CLOUDFLAREAPI" && *enableCFWorkers {
|
|
||||||
// Cloudflare only. Will do nothing if provider != *cloudflareProvider.
|
|
||||||
if err := cloudflare.PrepareCloudflareTestWorkers(provider); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider, cfg["domain"], cfg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Fatalf("Provider %s not found", *providerToRun)
|
var profileName, profileType string
|
||||||
return nil, "", nil
|
var cfg map[string]string
|
||||||
|
|
||||||
|
// Find the profile we want to use.
|
||||||
|
for p, c := range jsons {
|
||||||
|
if p == targetProfile {
|
||||||
|
cfg = c
|
||||||
|
profileName = p
|
||||||
|
profileType = cfg["TYPE"]
|
||||||
|
if profileType == "" {
|
||||||
|
t.Fatalf("providers.json profile %q does not have a TYPE field", *profileFlag)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if profileName == "" {
|
||||||
|
t.Fatalf("Profile not found: -profile=%q -provider=%q", *profileFlag, *providerFlag)
|
||||||
|
return nil, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in -profile if blank.
|
||||||
|
if *profileFlag == "" {
|
||||||
|
*profileFlag = profileName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix legacy use of -provider.
|
||||||
|
if *providerFlag != profileType {
|
||||||
|
fmt.Printf("WARNING: -provider=%q does not match profile TYPE=%q. Using profile TYPE.\n", *providerFlag, profileType)
|
||||||
|
*providerFlag = profileType
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("DEBUG flag=%q Profile=%q TYPE=%q\n", *providerFlag, profileName, profileType)
|
||||||
|
fmt.Printf("Testing Profile=%q TYPE=%q\n", profileName, profileType)
|
||||||
|
|
||||||
|
var metadata json.RawMessage
|
||||||
|
|
||||||
|
// CLOUDFLAREAPI tests related to CLOUDFLAREAPI_SINGLE_REDIRECT/CF_REDIRECT/CF_TEMP_REDIRECT
|
||||||
|
// requires metadata to enable this feature.
|
||||||
|
// In hindsight, I have no idea why this metadata flag is required to
|
||||||
|
// use this feature. Maybe because we didn't have the capabilities
|
||||||
|
// feature at the time?
|
||||||
|
if profileType == "CLOUDFLAREAPI" {
|
||||||
|
items := []string{}
|
||||||
|
if *enableCFWorkers {
|
||||||
|
items = append(items, `"manage_workers": true`)
|
||||||
|
}
|
||||||
|
switch *enableCFRedirectMode {
|
||||||
|
case "":
|
||||||
|
items = append(items, `"manage_redirects": true`)
|
||||||
|
case "c":
|
||||||
|
items = append(items, `"manage_redirects": true`)
|
||||||
|
items = append(items, `"manage_single_redirects": true`)
|
||||||
|
case "n":
|
||||||
|
items = append(items, `"manage_single_redirects": true`)
|
||||||
|
case "o":
|
||||||
|
}
|
||||||
|
metadata = []byte(`{ ` + strings.Join(items, `, `) + ` }`)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := providers.CreateDNSProvider(profileType, cfg, metadata)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if profileType == "CLOUDFLAREAPI" && *enableCFWorkers {
|
||||||
|
// Cloudflare only. Will do nothing if provider != *cloudflareProvider.
|
||||||
|
if err := cloudflare.PrepareCloudflareTestWorkers(provider); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider, cfg["domain"], cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDNSProviders(t *testing.T) {
|
func TestDNSProviders(t *testing.T) {
|
||||||
|
|
@ -155,7 +192,7 @@ func testPermitted(p string, f TestGroup) error {
|
||||||
// If there are any required capabilities, make sure they all exist.
|
// If there are any required capabilities, make sure they all exist.
|
||||||
if len(f.required) != 0 {
|
if len(f.required) != 0 {
|
||||||
for _, c := range f.required {
|
for _, c := range f.required {
|
||||||
if !providers.ProviderHasCapability(*providerToRun, c) {
|
if !providers.ProviderHasCapability(*providerFlag, c) {
|
||||||
return fmt.Errorf("%s not supported", c)
|
return fmt.Errorf("%s not supported", c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -211,18 +248,18 @@ func makeChanges(t *testing.T, prv providers.DNSServiceProvider, dc *models.Doma
|
||||||
//}
|
//}
|
||||||
dom.Records = append(dom.Records, &rc)
|
dom.Records = append(dom.Records, &rc)
|
||||||
}
|
}
|
||||||
if *providerToRun == "AXFRDDNS" {
|
//if *providerToRun == "AXFRDDNS" {
|
||||||
// Bind will refuse a DDNS update when the resulting zone
|
// Bind will refuse a DDNS update when the resulting zone
|
||||||
// contains a NS record without an associated address
|
// contains a NS record without an associated address
|
||||||
// records (A or AAAA)
|
// records (A or AAAA)
|
||||||
dom.Records = append(dom.Records, a("ns."+domainName+".", "9.8.7.6"))
|
//dom.Records = append(dom.Records, a("ns."+domainName+".", "9.8.7.6"))
|
||||||
}
|
//}
|
||||||
dom.Unmanaged = tst.Unmanaged
|
dom.Unmanaged = tst.Unmanaged
|
||||||
dom.UnmanagedUnsafe = tst.UnmanagedUnsafe
|
dom.UnmanagedUnsafe = tst.UnmanagedUnsafe
|
||||||
models.PostProcessRecords(dom.Records)
|
models.PostProcessRecords(dom.Records)
|
||||||
dom2, _ := dom.Copy()
|
dom2, _ := dom.Copy()
|
||||||
|
|
||||||
if err := providers.AuditRecords(*providerToRun, dom.Records); err != nil {
|
if err := providers.AuditRecords(*providerFlag, dom.Records); err != nil {
|
||||||
t.Skipf("***SKIPPED(PROVIDER DOES NOT SUPPORT '%s' ::%q)", err, desc)
|
t.Skipf("***SKIPPED(PROVIDER DOES NOT SUPPORT '%s' ::%q)", err, desc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -299,7 +336,8 @@ func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abide by filter
|
// Abide by filter
|
||||||
if err := testPermitted(*providerToRun, *group); err != nil {
|
fmt.Printf("DEBUG testPermitted: prov=%q profile=%q\n", *providerFlag, *profileFlag)
|
||||||
|
if err := testPermitted(*profileFlag, *group); err != nil {
|
||||||
//t.Logf("%s: ***SKIPPED(%v)***", group.Desc, err)
|
//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)
|
makeChanges(t, prv, dc, tc("Empty"), fmt.Sprintf("%02d:%s ***SKIPPED(%v)***", gIdx, group.Desc, err), false, origConfig)
|
||||||
continue
|
continue
|
||||||
|
|
@ -345,7 +383,7 @@ func TestDualProviders(t *testing.T) {
|
||||||
t.Fatal("NO DOMAIN SET! Exiting!")
|
t.Fatal("NO DOMAIN SET! Exiting!")
|
||||||
}
|
}
|
||||||
dc := getDomainConfigWithNameservers(t, p, domain)
|
dc := getDomainConfigWithNameservers(t, p, domain)
|
||||||
if !providers.ProviderHasCapability(*providerToRun, providers.DocDualHost) {
|
if !providers.ProviderHasCapability(*providerFlag, providers.DocDualHost) {
|
||||||
t.Skip("Skipping. DocDualHost == Cannot")
|
t.Skip("Skipping. DocDualHost == Cannot")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -423,7 +461,7 @@ func TestNameserverDots(t *testing.T) {
|
||||||
t.Fatal("NO DOMAIN SET! Exiting!")
|
t.Fatal("NO DOMAIN SET! Exiting!")
|
||||||
}
|
}
|
||||||
dc := getDomainConfigWithNameservers(t, p, domain)
|
dc := getDomainConfigWithNameservers(t, p, domain)
|
||||||
if !providers.ProviderHasCapability(*providerToRun, providers.DocDualHost) {
|
if !providers.ProviderHasCapability(*providerFlag, providers.DocDualHost) {
|
||||||
t.Skip("Skipping. DocDualHost == Cannot")
|
t.Skip("Skipping. DocDualHost == Cannot")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -945,6 +983,7 @@ func makeTests() []*TestGroup {
|
||||||
// CNAME
|
// CNAME
|
||||||
|
|
||||||
testgroup("CNAME",
|
testgroup("CNAME",
|
||||||
|
not("AXFRDDNS_DNSSEC"),
|
||||||
tc("Create a CNAME", cname("testcname", "www.google.com.")),
|
tc("Create a CNAME", cname("testcname", "www.google.com.")),
|
||||||
tc("Change CNAME target", cname("testcname", "www.yahoo.com.")),
|
tc("Change CNAME target", cname("testcname", "www.yahoo.com.")),
|
||||||
),
|
),
|
||||||
|
|
@ -1077,6 +1116,7 @@ func makeTests() []*TestGroup {
|
||||||
// changes properly for you. Let's verify that we got it right!
|
// changes properly for you. Let's verify that we got it right!
|
||||||
|
|
||||||
testgroup("TypeChangeHard",
|
testgroup("TypeChangeHard",
|
||||||
|
not("AXFRDDNS_DNSSEC"),
|
||||||
tc("Create a CNAME", cname("foo", "google.com.")),
|
tc("Create a CNAME", cname("foo", "google.com.")),
|
||||||
tc("Change to A record", a("foo", "1.2.3.4")),
|
tc("Change to A record", a("foo", "1.2.3.4")),
|
||||||
tc("Change back to CNAME", cname("foo", "google2.com.")),
|
tc("Change back to CNAME", cname("foo", "google2.com.")),
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,15 @@
|
||||||
"transfer-key": "$AXFRDDNS_TRANSFER_KEY",
|
"transfer-key": "$AXFRDDNS_TRANSFER_KEY",
|
||||||
"update-key": "$AXFRDDNS_UPDATE_KEY"
|
"update-key": "$AXFRDDNS_UPDATE_KEY"
|
||||||
},
|
},
|
||||||
|
"AXFRDDNS_DNSSEC": {
|
||||||
|
"TYPE": "AXFRDDNS",
|
||||||
|
"buggy-cname": "$AXFRDDNS_DNSSEC_BUGGY_CNAME",
|
||||||
|
"domain": "$AXFRDDNS_DNSSEC_DOMAIN",
|
||||||
|
"master": "$AXFRDDNS_DNSSEC_MASTER",
|
||||||
|
"nameservers": "ns.example.com",
|
||||||
|
"transfer-key": "$AXFRDDNS_DNSSEC_TRANSFER_KEY",
|
||||||
|
"update-key": "$AXFRDDNS_DNSSEC_UPDATE_KEY"
|
||||||
|
},
|
||||||
"AZURE_DNS": {
|
"AZURE_DNS": {
|
||||||
"ClientID": "$AZURE_DNS_CLIENT_ID",
|
"ClientID": "$AZURE_DNS_CLIENT_ID",
|
||||||
"ClientSecret": "$AZURE_DNS_CLIENT_SECRET",
|
"ClientSecret": "$AZURE_DNS_CLIENT_SECRET",
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,21 @@
|
||||||
package axfrddns
|
package axfrddns
|
||||||
|
|
||||||
import "github.com/StackExchange/dnscontrol/v4/models"
|
import (
|
||||||
|
"github.com/StackExchange/dnscontrol/v4/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/v4/pkg/rejectif"
|
||||||
|
)
|
||||||
|
|
||||||
// AuditRecords returns a list of errors corresponding to the records
|
// AuditRecords returns a list of errors corresponding to the records
|
||||||
// that aren't supported by this provider. If all records are
|
// that aren't supported by this provider. If all records are
|
||||||
// supported, an empty list is returned.
|
// supported, an empty list is returned.
|
||||||
func AuditRecords(records []*models.RecordConfig) []error {
|
func AuditRecords(records []*models.RecordConfig) []error {
|
||||||
return nil
|
a := rejectif.Auditor{}
|
||||||
|
|
||||||
|
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2025-01-01
|
||||||
|
|
||||||
|
a.Add("TXT", rejectif.TxtHasBackslash) // Last verified 2025-01-01
|
||||||
|
|
||||||
|
a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2025-01-01
|
||||||
|
|
||||||
|
return a.Audit(records)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@ func initAxfrDdns(config map[string]string, providermeta json.RawMessage) (provi
|
||||||
"transfer-server",
|
"transfer-server",
|
||||||
"update-mode",
|
"update-mode",
|
||||||
"transfer-mode",
|
"transfer-mode",
|
||||||
|
"buggy-cname",
|
||||||
"domain",
|
"domain",
|
||||||
"TYPE":
|
"TYPE":
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue