diff --git a/commands/getZones.go b/commands/getZones.go index 8eb3e95cc..4839ba343 100644 --- a/commands/getZones.go +++ b/commands/getZones.go @@ -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 diff --git a/commands/types/dnscontrol.d.ts b/commands/types/dnscontrol.d.ts index 68fd3c542..518144fa5 100644 --- a/commands/types/dnscontrol.d.ts +++ b/commands/types/dnscontrol.d.ts @@ -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. * diff --git a/documentation/SUMMARY.md b/documentation/SUMMARY.md index 1fe790910..d3877753d 100644 --- a/documentation/SUMMARY.md +++ b/documentation/SUMMARY.md @@ -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 diff --git a/documentation/language-reference/domain-modifiers/LUA.md b/documentation/language-reference/domain-modifiers/LUA.md new file mode 100644 index 000000000..aa6455775 --- /dev/null +++ b/documentation/language-reference/domain-modifiers/LUA.md @@ -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. diff --git a/models/domain.go b/models/domain.go index f26244705..364cbc340 100644 --- a/models/domain.go +++ b/models/domain.go @@ -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) diff --git a/models/record.go b/models/record.go index 9205263a0..afb12a519 100644 --- a/models/record.go +++ b/models/record.go @@ -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) } diff --git a/models/t_parse.go b/models/t_parse.go index 14e15fb17..dfc072987 100644 --- a/models/t_parse.go +++ b/models/t_parse.go @@ -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": diff --git a/models/t_txt.go b/models/t_txt.go index 53796a9b1..219d5a52d 100644 --- a/models/t_txt.go +++ b/models/t_txt.go @@ -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) +} diff --git a/models/target.go b/models/target.go index 9983b36c6..438cc8180 100644 --- a/models/target.go +++ b/models/target.go @@ -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": diff --git a/pkg/js/helpers.js b/pkg/js/helpers.js index 48ff786b2..220a48f9d 100644 --- a/pkg/js/helpers.js +++ b/pkg/js/helpers.js @@ -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])/); diff --git a/pkg/normalize/validate.go b/pkg/normalize/validate.go index 6b093d5ea..8112e2e7f 100644 --- a/pkg/normalize/validate.go +++ b/pkg/normalize/validate.go @@ -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"] != "" { diff --git a/pkg/prettyzone/sorting.go b/pkg/prettyzone/sorting.go index e7095e5bd..e3dfdfac7 100644 --- a/pkg/prettyzone/sorting.go +++ b/pkg/prettyzone/sorting.go @@ -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 diff --git a/providers/powerdns/convert.go b/providers/powerdns/convert.go index 591cb1882..eb16150b8 100644 --- a/providers/powerdns/convert.go +++ b/providers/powerdns/convert.go @@ -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) } diff --git a/providers/powerdns/convert_test.go b/providers/powerdns/convert_test.go index 49779a933..2a95edd66 100644 --- a/providers/powerdns/convert_test.go +++ b/providers/powerdns/convert_test.go @@ -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) { diff --git a/providers/powerdns/powerdnsProvider.go b/providers/powerdns/powerdnsProvider.go index 726779a2c..532e531ab 100644 --- a/providers/powerdns/powerdnsProvider.go +++ b/providers/powerdns/powerdnsProvider.go @@ -46,6 +46,7 @@ func init() { } providers.RegisterDomainServiceProviderType(providerName, fns, features) providers.RegisterMaintainer(providerName, providerMaintainer) + providers.RegisterCustomRecordType("LUA", providerName, "") } // powerdnsProvider represents the powerdnsProvider DNSServiceProvider.