mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-11-08 07:20:49 +08:00
Add LUA record type for PowerDNS Provider
This commit is contained in:
parent
70c1febe23
commit
4c44d627d4
15 changed files with 362 additions and 6 deletions
|
|
@ -367,6 +367,8 @@ func formatDsl(rec *models.RecordConfig, defaultTTL uint32) string {
|
|||
case "TXT":
|
||||
target = jsonQuoted(rec.GetTargetTXTJoined())
|
||||
// TODO(tlim): If this is an SPF record, generate a SPF_BUILDER().
|
||||
case "LUA":
|
||||
target = fmt.Sprintf("%q, %s", rec.LuaRType, jsonQuoted(rec.GetTargetTXTJoined()))
|
||||
case "NS":
|
||||
// NS records at the apex should be NAMESERVER() records.
|
||||
// DnsControl uses the API to get this info. NAMESERVER() is just
|
||||
|
|
|
|||
100
commands/types/dnscontrol.d.ts
vendored
100
commands/types/dnscontrol.d.ts
vendored
|
|
@ -1971,6 +1971,106 @@ declare function LOC_BUILDER_DMS_STR(opts: { label?: string; str: string; alt?:
|
|||
*/
|
||||
declare function LOC_BUILDER_STR(opts: { label?: string; str: string; alt?: number; ttl?: Duration }): DomainModifier;
|
||||
|
||||
/**
|
||||
* # LUA
|
||||
*
|
||||
* `LUA()` adds a **PowerDNS Lua record** to a domain. Use this when you want answers computed at **query time** (traffic steering, geo/ASN steering, weighted pools, health-based failover, time-based values, etc.) using the PowerDNS Authoritative Server’s built-in Lua engine.
|
||||
*
|
||||
* > **Provider support:** `LUA()` is supported **only** by the **PowerDNS** DNS provider in DNSControl. Ensure your zones are served by PowerDNS and that Lua records are enabled.
|
||||
* > See: PowerDNS provider page and Supported providers matrix.
|
||||
* > (References at the end.)
|
||||
*
|
||||
* ## Signature
|
||||
*
|
||||
* ```typescript
|
||||
* LUA(
|
||||
* name: string,
|
||||
* rtype: string, // e.g. "A", "AAAA", "CNAME", "TXT", "PTR", "LOC", ...
|
||||
* contents: string | string[], // the Lua snippet
|
||||
* ...modifiers: RecordModifier[]
|
||||
* ): DomainModifier
|
||||
* ```
|
||||
*
|
||||
* - **`name`** — label for the record (`"@"` for the zone apex).
|
||||
* - **`rtype`** — the RR type the Lua snippet **emits** (e.g., `"A"`, `"AAAA"`, `"CNAME"`, `"TXT"`, `"PTR"`, `"LOC"`).
|
||||
* - **`contents`** — the Lua snippet (string or array). See **Syntax** below.
|
||||
* - **`modifiers`** — standard record modifiers like `TTL(60)`.
|
||||
*
|
||||
* ## Prerequisites (PowerDNS)
|
||||
*
|
||||
* PowerDNS Authoritative Server **4.2+** supports Lua records. You must enable Lua records either **globally** (in `pdns.conf`) or **per-zone** via domain metadata.
|
||||
*
|
||||
* - **Global:** set `enable-lua-records=yes` (or `shared`) and reload PowerDNS.
|
||||
* - **Per-zone:** set metadata `ENABLE-LUA-RECORDS = 1` for the zone.
|
||||
*
|
||||
* See PowerDNS’s **Lua Records** overview and **Lua Reference** for details and helpers.
|
||||
*
|
||||
* ## Syntax
|
||||
*
|
||||
* PowerDNS evaluates the `contents` with two modes:
|
||||
*
|
||||
* - **Single expression (most common):** write **just the expression** — **no `return`**. PowerDNS implicitly treats the snippet as if it were the argument to `return`.
|
||||
* - **Multi-statement script:** start the snippet with a **leading semicolon (`;`)**. In this mode you can write multiple statements and must include your own `return`.
|
||||
*
|
||||
* The value produced must be valid **RDATA** for the chosen `rtype` (IPv4 for `A`, IPv6 for `AAAA`, a single FQDN with trailing dot for `CNAME`, proper text for `TXT`, etc.). Helper functions and preset variables (e.g., `pickrandom`, `pickclosest`, `country`, `continent`, `qname`, `ifportup`) are defined in the PowerDNS Lua reference.
|
||||
*
|
||||
* ## Examples
|
||||
*
|
||||
* ### Single expression (implicit `return`)
|
||||
*
|
||||
* ```javascript
|
||||
* // Weighted/random selection
|
||||
* LUA("app", "A", "pickrandom({'192.0.2.11',3}, {'192.0.2.22',1})", TTL(60));
|
||||
*
|
||||
* // Health-aware pool: only addresses with TCP/443 up are served
|
||||
* LUA("www", "A", "ifportup(443, {'192.0.2.1','192.0.2.2'})", TTL(60));
|
||||
*
|
||||
* // Geo proximity
|
||||
* LUA("edge", "A", "pickclosest({'192.0.2.1','192.0.2.2','198.51.100.1'})", TTL(60));
|
||||
* ```
|
||||
*
|
||||
* ### Multi-statement (leading `;`)
|
||||
*
|
||||
* ```javascript
|
||||
* LUA("api", "A", `
|
||||
* ; if continent('EU') then
|
||||
* return {'198.51.100.1'}
|
||||
* else
|
||||
* return {'192.0.2.10','192.0.2.20'}
|
||||
* end
|
||||
* `, TTL(60));
|
||||
*
|
||||
* // Dynamic TXT, showing the queried name (string building example)
|
||||
* LUA("_diag", "TXT", "; return 'Got a TXT query for ' .. qname:toString()", TTL(30));
|
||||
* ```
|
||||
*
|
||||
* ### Other RR types
|
||||
*
|
||||
* Lua can emit data for many RR types as long as the RDATA is valid for that type:
|
||||
*
|
||||
* ```javascript
|
||||
* LUA("edge", "CNAME", "('edgesvc.example.net.')", TTL(60));
|
||||
* LUA("pop.asu", "LOC", "latlonloc(-25.286, -57.645, 100)", TTL(300)); // ~Asunción, 100m
|
||||
* ```
|
||||
*
|
||||
* ## Tips & gotchas
|
||||
*
|
||||
* - **Use low TTLs** (e.g., 30–120s) for dynamic behavior to update promptly.
|
||||
* - **Don’t mix address families:** `A` answers must be IPv4; `AAAA` answers must be IPv6.
|
||||
* - **Serials & transfers:** Lua answers are computed at query time; changing only the Lua behavior does **not** change the zone’s SOA serial. Zone transfer and serial behavior follow normal PowerDNS rules.
|
||||
* - **Provider limitation:** Only the **PowerDNS** provider in DNSControl accepts `LUA()`; other providers will ignore or reject it.
|
||||
*
|
||||
* ## References
|
||||
*
|
||||
* - PowerDNS **Lua Records** overview (syntax, examples).
|
||||
* - PowerDNS **Lua Reference** (functions, preset variables, objects).
|
||||
* - DNSControl **PowerDNS provider** page.
|
||||
* - DNSControl **Supported providers** table.
|
||||
*
|
||||
* @see https://docs.dnscontrol.org/language-reference/domain-modifiers/lua
|
||||
*/
|
||||
declare function LUA(name: string, rtype: string, contents: string | string[], ...modifiers: RecordModifier[]): DomainModifier;
|
||||
|
||||
/**
|
||||
* DNSControl offers a `M365_BUILDER` which can be used to simply set up Microsoft 365 for a domain in an opinionated way.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@
|
|||
* [CF_WORKER_ROUTE](language-reference/domain-modifiers/CF_WORKER_ROUTE.md)
|
||||
* ClouDNS
|
||||
* [CLOUDNS_WR](language-reference/domain-modifiers/CLOUDNS_WR.md)
|
||||
* PowerDNS
|
||||
* [LUA](language-reference/domain-modifiers/LUA.md)
|
||||
* Record Modifiers
|
||||
* [TTL](language-reference/record-modifiers/TTL.md)
|
||||
* Service Provider specific
|
||||
|
|
|
|||
117
documentation/language-reference/domain-modifiers/LUA.md
Normal file
117
documentation/language-reference/domain-modifiers/LUA.md
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
---
|
||||
name: LUA
|
||||
parameters:
|
||||
- name
|
||||
- rtype
|
||||
- contents
|
||||
- modifiers...
|
||||
parameter_types:
|
||||
name: string
|
||||
rtype: string
|
||||
contents: string | string[]
|
||||
"modifiers...": RecordModifier[]
|
||||
---
|
||||
|
||||
|
||||
# LUA
|
||||
|
||||
`LUA()` adds a **PowerDNS Lua record** to a domain. Use this when you want answers computed at **query time** (traffic steering, geo/ASN steering, weighted pools, health-based failover, time-based values, etc.) using the PowerDNS Authoritative Server’s built-in Lua engine.
|
||||
|
||||
> **Provider support:** `LUA()` is supported **only** by the **PowerDNS** DNS provider in DNSControl. Ensure your zones are served by PowerDNS and that Lua records are enabled.
|
||||
> See: PowerDNS provider page and Supported providers matrix.
|
||||
> (References at the end.)
|
||||
|
||||
## Signature
|
||||
|
||||
{% code title="dnsconfig.js" %}
|
||||
```typescript
|
||||
LUA(
|
||||
name: string,
|
||||
rtype: string, // e.g. "A", "AAAA", "CNAME", "TXT", "PTR", "LOC", ...
|
||||
contents: string | string[], // the Lua snippet
|
||||
...modifiers: RecordModifier[]
|
||||
): DomainModifier
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
- **`name`** — label for the record (`"@"` for the zone apex).
|
||||
- **`rtype`** — the RR type the Lua snippet **emits** (e.g., `"A"`, `"AAAA"`, `"CNAME"`, `"TXT"`, `"PTR"`, `"LOC"`).
|
||||
- **`contents`** — the Lua snippet (string or array). See **Syntax** below.
|
||||
- **`modifiers`** — standard record modifiers like `TTL(60)`.
|
||||
|
||||
## Prerequisites (PowerDNS)
|
||||
|
||||
PowerDNS Authoritative Server **4.2+** supports Lua records. You must enable Lua records either **globally** (in `pdns.conf`) or **per-zone** via domain metadata.
|
||||
|
||||
- **Global:** set `enable-lua-records=yes` (or `shared`) and reload PowerDNS.
|
||||
- **Per-zone:** set metadata `ENABLE-LUA-RECORDS = 1` for the zone.
|
||||
|
||||
See PowerDNS’s **Lua Records** overview and **Lua Reference** for details and helpers.
|
||||
|
||||
## Syntax
|
||||
|
||||
PowerDNS evaluates the `contents` with two modes:
|
||||
|
||||
- **Single expression (most common):** write **just the expression** — **no `return`**. PowerDNS implicitly treats the snippet as if it were the argument to `return`.
|
||||
- **Multi-statement script:** start the snippet with a **leading semicolon (`;`)**. In this mode you can write multiple statements and must include your own `return`.
|
||||
|
||||
The value produced must be valid **RDATA** for the chosen `rtype` (IPv4 for `A`, IPv6 for `AAAA`, a single FQDN with trailing dot for `CNAME`, proper text for `TXT`, etc.). Helper functions and preset variables (e.g., `pickrandom`, `pickclosest`, `country`, `continent`, `qname`, `ifportup`) are defined in the PowerDNS Lua reference.
|
||||
|
||||
## Examples
|
||||
|
||||
### Single expression (implicit `return`)
|
||||
|
||||
{% code title="dnsconfig.js" %}
|
||||
```javascript
|
||||
// Weighted/random selection
|
||||
LUA("app", "A", "pickrandom({'192.0.2.11',3}, {'192.0.2.22',1})", TTL(60));
|
||||
|
||||
// Health-aware pool: only addresses with TCP/443 up are served
|
||||
LUA("www", "A", "ifportup(443, {'192.0.2.1','192.0.2.2'})", TTL(60));
|
||||
|
||||
// Geo proximity
|
||||
LUA("edge", "A", "pickclosest({'192.0.2.1','192.0.2.2','198.51.100.1'})", TTL(60));
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
### Multi-statement (leading `;`)
|
||||
|
||||
{% code title="dnsconfig.js" %}
|
||||
```javascript
|
||||
LUA("api", "A", `
|
||||
; if continent('EU') then
|
||||
return {'198.51.100.1'}
|
||||
else
|
||||
return {'192.0.2.10','192.0.2.20'}
|
||||
end
|
||||
`, TTL(60));
|
||||
|
||||
// Dynamic TXT, showing the queried name (string building example)
|
||||
LUA("_diag", "TXT", "; return 'Got a TXT query for ' .. qname:toString()", TTL(30));
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
### Other RR types
|
||||
|
||||
Lua can emit data for many RR types as long as the RDATA is valid for that type:
|
||||
|
||||
{% code title="dnsconfig.js" %}
|
||||
```javascript
|
||||
LUA("edge", "CNAME", "('edgesvc.example.net.')", TTL(60));
|
||||
LUA("pop.asu", "LOC", "latlonloc(-25.286, -57.645, 100)", TTL(300)); // ~Asunción, 100m
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
## Tips & gotchas
|
||||
|
||||
- **Use low TTLs** (e.g., 30–120s) for dynamic behavior to update promptly.
|
||||
- **Don’t mix address families:** `A` answers must be IPv4; `AAAA` answers must be IPv6.
|
||||
- **Serials & transfers:** Lua answers are computed at query time; changing only the Lua behavior does **not** change the zone’s SOA serial. Zone transfer and serial behavior follow normal PowerDNS rules.
|
||||
- **Provider limitation:** Only the **PowerDNS** provider in DNSControl accepts `LUA()`; other providers will ignore or reject it.
|
||||
|
||||
## References
|
||||
|
||||
- PowerDNS **Lua Records** overview (syntax, examples).
|
||||
- PowerDNS **Lua Reference** (functions, preset variables, objects).
|
||||
- DNSControl **PowerDNS provider** page.
|
||||
- DNSControl **Supported providers** table.
|
||||
|
|
@ -142,7 +142,7 @@ func (dc *DomainConfig) Punycode() error {
|
|||
if err := rec.SetTarget(rec.GetTargetField()); err != nil {
|
||||
return err
|
||||
}
|
||||
case "A", "AAAA", "CAA", "DHCID", "DNSKEY", "DS", "HTTPS", "LOC", "NAPTR", "OPENPGPKEY", "SMIMEA", "SOA", "SSHFP", "SVCB", "TXT", "TLSA", "AZURE_ALIAS":
|
||||
case "A", "AAAA", "CAA", "DHCID", "DNSKEY", "DS", "HTTPS", "LOC", "LUA", "NAPTR", "OPENPGPKEY", "SMIMEA", "SOA", "SSHFP", "SVCB", "TXT", "TLSA", "AZURE_ALIAS":
|
||||
// Nothing to do.
|
||||
default:
|
||||
return fmt.Errorf("Punycode rtype %v unimplemented", rec.Type)
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ type RecordConfig struct {
|
|||
LocLatitude uint32 `json:"loclatitude,omitempty"`
|
||||
LocLongitude uint32 `json:"loclongitude,omitempty"`
|
||||
LocAltitude uint32 `json:"localtitude,omitempty"`
|
||||
LuaRType string `json:"luartype,omitempty"`
|
||||
NaptrOrder uint16 `json:"naptrorder,omitempty"`
|
||||
NaptrPreference uint16 `json:"naptrpreference,omitempty"`
|
||||
NaptrFlags string `json:"naptrflags,omitempty"`
|
||||
|
|
@ -227,6 +228,7 @@ func (rc *RecordConfig) UnmarshalJSON(b []byte) error {
|
|||
LocLatitude int `json:"loclatitude,omitempty"`
|
||||
LocLongitude int `json:"loclongitude,omitempty"`
|
||||
LocAltitude uint32 `json:"localtitude,omitempty"`
|
||||
LuaRType string `json:"luartype,omitempty"`
|
||||
NaptrOrder uint16 `json:"naptrorder,omitempty"`
|
||||
NaptrPreference uint16 `json:"naptrpreference,omitempty"`
|
||||
NaptrFlags string `json:"naptrflags,omitempty"`
|
||||
|
|
@ -387,6 +389,8 @@ func (rc *RecordConfig) ToComparableNoTTL() string {
|
|||
r := txtutil.EncodeQuoted(rc.target)
|
||||
// fmt.Fprintf(os.Stdout, "DEBUG: ToComNoTTL cmp txts=%s q=%q\n", r, r)
|
||||
return r
|
||||
case "LUA":
|
||||
return rc.luaCombined()
|
||||
case "UNKNOWN":
|
||||
return fmt.Sprintf("rtype=%s rdata=%s", rc.UnknownTypeName, rc.target)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,21 @@ func (rc *RecordConfig) PopulateFromStringFunc(rtype, contents, origin string, t
|
|||
return fmt.Errorf("invalid TXT record: %s", contents)
|
||||
}
|
||||
return rc.SetTargetTXT(t)
|
||||
case "LUA":
|
||||
luaType, payload := ParseLuaContent(contents)
|
||||
rc.LuaRType = luaType
|
||||
if txtFn != nil {
|
||||
value, err := txtFn(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid LUA record: %s", contents)
|
||||
}
|
||||
return rc.SetTargetTXT(value)
|
||||
}
|
||||
value, err := DecodeLuaPayload(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid LUA record: %s", contents)
|
||||
}
|
||||
return rc.SetTargetTXT(value)
|
||||
case "SRV":
|
||||
return rc.SetTargetSRVString(contents)
|
||||
case "SSHFP":
|
||||
|
|
@ -192,6 +207,14 @@ func (rc *RecordConfig) PopulateFromString(rtype, contents, origin string) error
|
|||
return rc.SetTargetSOAString(contents)
|
||||
case "SPF", "TXT":
|
||||
return rc.SetTargetTXTs(ParseQuotedTxt(contents))
|
||||
case "LUA":
|
||||
luaType, payload := ParseLuaContent(contents)
|
||||
rc.LuaRType = luaType
|
||||
value, err := DecodeLuaPayload(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid LUA record: %s", contents)
|
||||
}
|
||||
return rc.SetTargetTXT(value)
|
||||
case "SRV":
|
||||
return rc.SetTargetSRVString(contents)
|
||||
case "SSHFP":
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package models
|
|||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
|
||||
)
|
||||
|
||||
/*
|
||||
|
|
@ -36,7 +39,7 @@ There are 2 ways to get the value (target) of a TXT record:
|
|||
// identical to TXT, such as SPF. For more details, read
|
||||
// https://tools.ietf.org/html/rfc4408#section-3.1.1
|
||||
func (rc *RecordConfig) HasFormatIdenticalToTXT() bool {
|
||||
return rc.Type == "TXT" || rc.Type == "SPF"
|
||||
return rc.Type == "TXT" || rc.Type == "SPF" || rc.Type == "LUA"
|
||||
}
|
||||
|
||||
// SetTargetTXT sets the TXT fields when there is 1 string.
|
||||
|
|
@ -91,3 +94,32 @@ func splitChunks(buf string, lim int) []string {
|
|||
}
|
||||
return chunks
|
||||
}
|
||||
|
||||
// ParseLuaContent splits a PowerDNS LUA record content string into its emitted rtype and payload.
|
||||
func ParseLuaContent(content string) (rtype string, payload string) {
|
||||
trimmed := strings.TrimSpace(content)
|
||||
if trimmed == "" {
|
||||
return "", ""
|
||||
}
|
||||
splitIndex := -1
|
||||
for i, r := range trimmed {
|
||||
if unicode.IsSpace(r) {
|
||||
splitIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if splitIndex == -1 {
|
||||
return strings.ToUpper(trimmed), ""
|
||||
}
|
||||
rtype = strings.ToUpper(trimmed[:splitIndex])
|
||||
payload = strings.TrimSpace(trimmed[splitIndex:])
|
||||
return rtype, payload
|
||||
}
|
||||
|
||||
// DecodeLuaPayload normalizes the LUA payload for storage in RecordConfig.target.
|
||||
func DecodeLuaPayload(payload string) (string, error) {
|
||||
if payload == "" {
|
||||
return "", nil
|
||||
}
|
||||
return txtutil.ParseQuoted(payload)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
|
|
@ -31,7 +32,7 @@ func (rc *RecordConfig) GetTargetIP() net.IP {
|
|||
// string. How TXT records are encoded is defined by encodeFn. If encodeFn is
|
||||
// nil the TXT data is returned unaltered.
|
||||
func (rc *RecordConfig) GetTargetCombinedFunc(encodeFn func(s string) string) string {
|
||||
if rc.Type == "TXT" {
|
||||
if rc.Type == "TXT" || rc.Type == "LUA" {
|
||||
if encodeFn == nil {
|
||||
return rc.target
|
||||
}
|
||||
|
|
@ -48,6 +49,8 @@ func (rc *RecordConfig) GetTargetCombined() string {
|
|||
// Pseudo records:
|
||||
if _, ok := dns.StringToType[rc.Type]; !ok {
|
||||
switch rc.Type { // #rtype_variations
|
||||
case "LUA":
|
||||
return rc.luaCombined()
|
||||
case "R53_ALIAS":
|
||||
// Differentiate between multiple R53_ALIASs on the same label.
|
||||
return fmt.Sprintf("%s atype=%s zone_id=%s evaluate_target_health=%s", rc.target, rc.R53Alias["type"], rc.R53Alias["zone_id"], rc.R53Alias["evaluate_target_health"])
|
||||
|
|
@ -92,6 +95,26 @@ func (rc *RecordConfig) zoneFileQuoted() string {
|
|||
return full[len(header):]
|
||||
}
|
||||
|
||||
func (rc *RecordConfig) luaCombined() string {
|
||||
rtype := rc.luaTypeUpper()
|
||||
payload := rc.target
|
||||
if rtype == "" {
|
||||
return payload
|
||||
}
|
||||
payload = txtutil.EncodeQuoted(payload)
|
||||
if payload == "" {
|
||||
return rtype
|
||||
}
|
||||
return fmt.Sprintf("%s %s", rtype, payload)
|
||||
}
|
||||
|
||||
func (rc *RecordConfig) luaTypeUpper() string {
|
||||
if rc.LuaRType == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.ToUpper(rc.LuaRType)
|
||||
}
|
||||
|
||||
// GetTargetRFC1035Quoted returns the target as it would be in an
|
||||
// RFC1035-style zonefile.
|
||||
// Do not use this function if RecordConfig might be a pseudo-rtype
|
||||
|
|
@ -103,13 +126,16 @@ func (rc *RecordConfig) GetTargetRFC1035Quoted() string {
|
|||
// GetTargetDebug returns a string with the various fields spelled out.
|
||||
func (rc *RecordConfig) GetTargetDebug() string {
|
||||
target := rc.target
|
||||
if rc.Type == "TXT" {
|
||||
//if rc.Type == "TXT" {
|
||||
if rc.HasFormatIdenticalToTXT() {
|
||||
target = fmt.Sprintf("%q", target)
|
||||
}
|
||||
content := fmt.Sprintf("%s %s %s %d", rc.Type, rc.NameFQDN, target, rc.TTL)
|
||||
switch rc.Type { // #rtype_variations
|
||||
case "A", "AAAA", "AKAMAICDN", "CNAME", "DHCID", "NS", "OPENPGPKEY", "PTR", "TXT":
|
||||
// Nothing special.
|
||||
case "LUA":
|
||||
content += " luartype=" + rc.luaTypeUpper()
|
||||
case "AZURE_ALIAS":
|
||||
content += " type=" + rc.AzureAlias["type"]
|
||||
case "CAA":
|
||||
|
|
|
|||
|
|
@ -668,6 +668,23 @@ var TXT = recordBuilder('TXT', {
|
|||
},
|
||||
});
|
||||
|
||||
var LUA = recordBuilder('LUA', {
|
||||
args: [
|
||||
['name', _.isString],
|
||||
['rtype', _.isString],
|
||||
['target', isStringOrArray],
|
||||
],
|
||||
transform: function (record, args, modifiers) {
|
||||
record.name = args.name;
|
||||
record.luartype = args.rtype.toUpperCase();
|
||||
if (_.isString(args.target)) {
|
||||
record.target = args.target;
|
||||
} else {
|
||||
record.target = args.target.join('');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Parses coordinates of the form 41°24'12.2"N 2°10'26.5"E
|
||||
function parseDMSCoordinatesString(inputString) {
|
||||
var lat = inputString.match(/(-?\d+).(\d+).([\d\.]+).?\ ?([NS])/);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/transform"
|
||||
"github.com/StackExchange/dnscontrol/v4/providers"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/miekg/dns/dnsutil"
|
||||
)
|
||||
|
||||
|
|
@ -131,7 +132,7 @@ func checkLabel(label string, rType string, domain string, meta map[string]strin
|
|||
// are used in a way we consider typical. Yes, we're opinionated here.
|
||||
|
||||
// Don't warn for certain rtypes:
|
||||
for _, ex := range []string{"SRV", "TLSA", "TXT"} {
|
||||
for _, ex := range []string{"SRV", "TLSA", "TXT", "LUA"} {
|
||||
if rType == ex {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -226,6 +227,16 @@ func checkTargets(rec *models.RecordConfig, domain string) (errs []error) {
|
|||
}
|
||||
case "SRV":
|
||||
check(checkTarget(target))
|
||||
case "LUA":
|
||||
upper := strings.ToUpper(rec.LuaRType)
|
||||
if upper == "" {
|
||||
check(errors.New("LUA records must specify an emitted rtype"))
|
||||
break
|
||||
}
|
||||
if _, ok := dns.StringToType[upper]; !ok {
|
||||
check(fmt.Errorf("LUA emitted rtype (%s) is not a valid DNS type", rec.LuaRType))
|
||||
}
|
||||
rec.LuaRType = upper
|
||||
case "CAA", "DHCID", "DNSKEY", "DS", "HTTPS", "IMPORT_TRANSFORM", "OPENPGPKEY", "SMIMEA", "SSHFP", "SVCB", "TLSA", "TXT":
|
||||
default:
|
||||
if rec.Metadata["orig_custom_type"] != "" {
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ func zoneRrtypeLess(a, b string) bool {
|
|||
|
||||
for _, t := range []string{
|
||||
"SOA", "NS", "CNAME",
|
||||
"A", "AAAA", "MX", "SRV", "TXT",
|
||||
"A", "AAAA", "MX", "SRV", "TXT", "LUA",
|
||||
} {
|
||||
if a == t {
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -27,6 +27,14 @@ func toRecordConfig(domain string, r zones.Record, ttl int, name string, rtype s
|
|||
// So we need to strip away " and split into multiple string
|
||||
// We can't use SetTargetRFC1035Quoted, it would split the long strings into multiple parts
|
||||
return rc, rc.SetTargetTXTs(parseTxt(r.Content))
|
||||
case "LUA":
|
||||
luaType, payload := models.ParseLuaContent(r.Content)
|
||||
rc.LuaRType = luaType
|
||||
value, err := models.DecodeLuaPayload(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rc, rc.SetTargetTXT(value)
|
||||
default:
|
||||
return rc, rc.PopulateFromString(rtype, r.Content, domain)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,19 @@ func TestToRecordConfig(t *testing.T) {
|
|||
recordConfig.String())
|
||||
assert.Equal(t, uint32(5), recordConfig.TTL)
|
||||
assert.Equal(t, "TXT", recordConfig.Type)
|
||||
|
||||
luaRecord := zones.Record{
|
||||
Content: "TXT \"return 'Hello, world!'\"",
|
||||
}
|
||||
recordConfig, err = toRecordConfig("example.com", luaRecord, 3600, "script", "LUA")
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "script.example.com", recordConfig.NameFQDN)
|
||||
assert.Equal(t, "LUA", recordConfig.Type)
|
||||
assert.Equal(t, "TXT", recordConfig.LuaRType)
|
||||
assert.Equal(t, "return 'Hello, world!'", recordConfig.GetTargetTXTJoined())
|
||||
assert.Equal(t, "TXT \"return 'Hello, world!'\"", recordConfig.GetTargetCombined())
|
||||
assert.Equal(t, uint32(3600), recordConfig.TTL)
|
||||
}
|
||||
|
||||
func TestParseText(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ func init() {
|
|||
}
|
||||
providers.RegisterDomainServiceProviderType(providerName, fns, features)
|
||||
providers.RegisterMaintainer(providerName, providerMaintainer)
|
||||
providers.RegisterCustomRecordType("LUA", providerName, "")
|
||||
}
|
||||
|
||||
// powerdnsProvider represents the powerdnsProvider DNSServiceProvider.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue