Repository: StackExchange/dnscontrol
Pull request #1219 (2021-07-24 09:09:02)

Add INCLUDE statement to include records from other domains
Add the ability to include records from other domain.  This can be very handy for working with split horizon where you want to overwrite your public domain for an internal one, while still having (some) of these external records.


Example:
```
D("example.com!external", REGISTRAR, DnsProvider(R53),
  A("test", "8.8.8.8")
);

D("example.com!internal", REGISTRAR, DnsProvider(R53),
  INCLUDE("example.com!external"),
  A("home", "127.0.0.1")
);
```

tlimoncelli: Wow!  That's brilliant!  I'll review this over the weekend.

Patch:
diff --git a/docs/_functions/domain/INCLUDE.md b/docs/_functions/domain/INCLUDE.md
new file mode 100644
index 00000000..ec88a305
--- /dev/null
+++ b/docs/_functions/domain/INCLUDE.md
@@ -0,0 +1,23 @@
+---
+name: INCLUDE
+parameters:
+  - domain
+---
+
+Includes all records from a given domain
+
+
+{% include startExample.html %}
+{% highlight js %}
+
+D("example.com!external", REGISTRAR, DnsProvider(R53),
+  A("test", "8.8.8.8")
+);
+
+D("example.com!internal", REGISTRAR, DnsProvider(R53),
+  INCLUDE("example.com!external"),
+  A("home", "127.0.0.1")
+);
+
+{%endhighlight%}
+{% include endExample.html %}
diff --git a/docs/_includes/matrix.html b/docs/_includes/matrix.html
index 6f6e885d..3f76558e 100644
--- a/docs/_includes/matrix.html
+++ b/docs/_includes/matrix.html
@@ -41,6 +41,7 @@
 	<th class="rotate"><div><span>POWERDNS</span></div></th>
 	<th class="rotate"><div><span>ROUTE53</span></div></th>
 	<th class="rotate"><div><span>SOFTLAYER</span></div></th>
+	<th class="rotate"><div><span>TRANSIP</span></div></th>
 	<th class="rotate"><div><span>VULTR</span></div></th>
 	</tr>
 </thead>
@@ -155,6 +156,9 @@
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
+		<td class="danger">
+			<i class="fa fa-times text-danger" aria-hidden="true"></i>
+		</td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Can manage and serve DNS zones">DNS Provider</th>
@@ -266,6 +270,9 @@
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="The provider has registrar capabilities to set nameservers for zones">Registrar</th>
@@ -377,6 +384,9 @@
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
+		<td class="danger">
+			<i class="fa fa-times text-danger" aria-hidden="true"></i>
+		</td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports some kind of ALIAS, ANAME or flattened CNAME record type">ALIAS</th>
@@ -464,6 +474,9 @@
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
+		<td class="danger">
+			<i class="fa fa-times text-danger" aria-hidden="true"></i>
+		</td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can automatically manage DNSSEC">AUTODNSSEC</th>
@@ -520,6 +533,9 @@
 		</td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td class="danger">
+			<i class="fa fa-times text-danger" aria-hidden="true"></i>
+		</td>
 		<td><i class="fa fa-minus dim"></i></td>
 		</tr>
 	<tr>
@@ -616,6 +632,9 @@
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports adding PTR records for reverse lookup zones">PTR</th>
@@ -710,6 +729,7 @@
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
@@ -757,7 +777,9 @@
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
 		<td><i class="fa fa-minus dim"></i></td>
-		<td><i class="fa fa-minus dim"></i></td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
@@ -773,6 +795,9 @@
 		</td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		<td><i class="fa fa-minus dim"></i></td>
 		</tr>
 	<tr>
@@ -817,6 +842,7 @@
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Driver has explicitly implemented SRV record management">SRV</th>
@@ -918,6 +944,9 @@
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can manage SSHFP records">SSHFP</th>
@@ -995,6 +1024,9 @@
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider can manage TLSA records">TLSA</th>
@@ -1077,6 +1109,9 @@
 		</td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
@@ -1119,6 +1154,7 @@
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports Route 53 limited ALIAS">R53_ALIAS</th>
@@ -1164,6 +1200,7 @@
 		</td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports Azure DNS limited ALIAS">AZURE_ALIAS</th>
@@ -1209,6 +1246,7 @@
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports adding DS records">DS</th>
@@ -1271,6 +1309,9 @@
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td class="danger">
+			<i class="fa fa-times text-danger" aria-hidden="true"></i>
+		</td>
 		<td><i class="fa fa-minus dim"></i></td>
 		</tr>
 	<tr>
@@ -1313,6 +1354,7 @@
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This provider is recommended for use in &#39;dual hosting&#39; scenarios. Usually this means the provider allows full control over the apex NS records">dual host</th>
@@ -1408,6 +1450,7 @@
 		</td>
 		<td><i class="fa fa-minus dim"></i></td>
 		<td><i class="fa fa-minus dim"></i></td>
+		<td><i class="fa fa-minus dim"></i></td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This means the provider can automatically create domains that do not currently exist on your account. The &#39;dnscontrol create-domains&#39; command will initialize any missing domains">create-domains</th>
@@ -1516,6 +1559,9 @@
 		<td class="danger">
 			<i class="fa fa-times text-danger" aria-hidden="true"></i>
 		</td>
+		<td class="danger">
+			<i class="fa fa-times text-danger" aria-hidden="true"></i>
+		</td>
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
@@ -1630,6 +1676,9 @@
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		</tr>
 	<tr>
 		<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="indicates the dnscontrol get-zones subcommand is implemented.">get-zones</th>
@@ -1699,8 +1748,8 @@
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
-		<td class="info">
-			<i class="fa fa-circle-o text-info" aria-hidden="true"></i>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
@@ -1731,6 +1780,9 @@
 		<td class="success">
 			<i class="fa fa-check text-success" aria-hidden="true"></i>
 		</td>
+		<td class="success">
+			<i class="fa fa-check text-success" aria-hidden="true"></i>
+		</td>
 		</tr>
 	</tbody>
 </table>
diff --git a/models/record.go b/models/record.go
index a29d693f..5dc25cae 100644
--- a/models/record.go
+++ b/models/record.go
@@ -452,6 +452,16 @@ func (recs Records) FQDNMap() (m map[string]bool) {
 	return m
 }
 
+func (recs Records) GetByType(typeName string) Records {
+	results := Records{}
+	for _, rec := range recs {
+		if rec.Type == typeName {
+			results = append(results, rec)
+		}
+	}
+	return results
+}
+
 // GroupedByKey returns a map of keys to records.
 func (recs Records) GroupedByKey() map[RecordKey]Records {
 	groups := map[RecordKey]Records{}
diff --git a/pkg/js/helpers.js b/pkg/js/helpers.js
index 3689c669..b1185938 100644
--- a/pkg/js/helpers.js
+++ b/pkg/js/helpers.js
@@ -103,6 +103,16 @@ function D(name, registrar) {
     conf.domain_names.push(name);
 }
 
+function INCLUDE(name) {
+    var domain = _getDomainObject(name);
+    if (domain == null) {
+        throw name + ' was not declared yet and therefore cannot be updated. Use D() before.';
+    }
+    return function(d) {
+      d.records.push.apply(d.records, domain.obj.records);
+    }
+}
+
 // D_EXTEND(name): Update a DNS Domain already added with D(), or subdomain thereof
 function D_EXTEND(name) {
     var domain = _getDomainObject(name);
diff --git a/pkg/normalize/flatten.go b/pkg/normalize/flatten.go
index f541f160..44c2a0a7 100644
--- a/pkg/normalize/flatten.go
+++ b/pkg/normalize/flatten.go
@@ -15,9 +15,9 @@ func flattenSPFs(cfg *models.DNSConfig) []error {
 	var errs []error
 	var err error
 	for _, domain := range cfg.Domains {
-		apexTXTs := domain.Records.GroupedByKey()[models.RecordKey{Type: "TXT", NameFQDN: domain.Name}]
+		txtRecords := domain.Records.GetByType("TXT")
 		// flatten all spf records that have the "flatten" metadata
-		for _, txt := range apexTXTs {
+		for _, txt := range txtRecords {
 			var rec *spflib.SPFRecord
 			txtTarget := strings.Join(txt.TxtStrings, "")
 			if txt.Metadata["flatten"] != "" || txt.Metadata["split"] != "" {
diff --git a/providers/inwx/inwxProvider.go b/providers/inwx/inwxProvider.go
index d007fd60..fc992463 100644
--- a/providers/inwx/inwxProvider.go
+++ b/providers/inwx/inwxProvider.go
@@ -161,11 +161,16 @@ func newInwxDsp(m map[string]string, metadata json.RawMessage) (providers.DNSSer
 func makeNameserverRecordRequest(domain string, rec *models.RecordConfig) *goinwx.NameserverRecordRequest {
 	content := rec.GetTargetField()
 
+	name := rec.GetLabel()
+	if name == "@" {
+		name = ""
+	}
+
 	req := &goinwx.NameserverRecordRequest{
 		Domain:  domain,
 		Type:    rec.Type,
 		Content: content,
-		Name:    rec.GetLabel(),
+		Name:    name,
 		TTL:     int(rec.TTL),
 	}
 
@@ -299,6 +304,9 @@ func (api *inwxAPI) GetZoneRecords(domain string) (models.Records, error) {
 	var records = []*models.RecordConfig{}
 
 	for _, record := range info.Records {
+		if record.Name == "" {
+			record.Name = "@"
+		}
 		if record.Type == "SOA" {
 			continue
 		}


Test patch:
diff --git a/pkg/js/parse_tests/039-include.js b/pkg/js/parse_tests/039-include.js
new file mode 100644
index 00000000..3d75ffbc
--- /dev/null
+++ b/pkg/js/parse_tests/039-include.js
@@ -0,0 +1,12 @@
+
+var REG = NewRegistrar("Third-Party","NONE");
+var CF = NewDnsProvider("Cloudflare", "CLOUDFLAREAPI");
+
+D("foo.com!external",REG,DnsProvider(CF),
+    A("@","1.2.3.4")
+);
+
+D("foo.com!internal",REG,DnsProvider(CF),
+  INCLUDE("foo.com!external"),
+  A("local","127.0.0.1")
+);
diff --git a/pkg/js/parse_tests/039-include.json b/pkg/js/parse_tests/039-include.json
new file mode 100644
index 00000000..e7014a61
--- /dev/null
+++ b/pkg/js/parse_tests/039-include.json
@@ -0,0 +1,49 @@
+{
+  "registrars": [
+    {
+      "name": "Third-Party",
+      "type": "NONE"
+    }
+  ],
+  "dns_providers": [
+    {
+      "name": "Cloudflare",
+      "type": "CLOUDFLAREAPI"
+    }
+  ],
+  "domains": [
+    {
+      "name": "foo.com!external",
+      "registrar": "Third-Party",
+      "dnsProviders": {
+        "Cloudflare": -1
+      },
+      "records": [
+        {
+          "type": "A",
+          "name": "@",
+          "target": "1.2.3.4"
+        }
+      ]
+    },
+    {
+      "name": "foo.com!internal",
+      "registrar": "Third-Party",
+      "dnsProviders": {
+        "Cloudflare": -1
+      },
+      "records": [
+        {
+          "type": "A",
+          "name": "@",
+          "target": "1.2.3.4"
+        },
+        {
+          "type": "A",
+          "name": "local",
+          "target": "127.0.0.1"
+        }
+      ]
+    }
+  ]
+}