RP works FromStruct but hardcoding needs to be removed

This commit is contained in:
Thomas Limoncelli 2025-11-30 18:07:00 -05:00
parent a3ed1dc6b2
commit cbbb396026
No known key found for this signature in database
10 changed files with 157 additions and 120 deletions

View file

@ -11,5 +11,33 @@ dlv test github.com/StackExchange/dnscontrol/v4/pkg/diff2 -- -test.run Test_anal
Debug the integration tests:
```shell
dlv test github.com/StackExchange/dnscontrol/v4/integrationTest -- -test.v -test.run ^TestDNSProviders -verbose -profile NAMEDOTCOM -start 1 -end 1
dlv test github.com/StackExchange/dnscontrol/v4/integrationTest -- -test.v -test.run ^TestDNSProviders -verbose -profile BIND -start 7 -end 7
```
If you are using VSCode, the equivalent configuration is:
```
"configurations": [
{
"name": "Debug Integration Test",
"type": "go",
"request": "launch",
"mode": "test",
"program": "${workspaceFolder}/integrationTest",
"args": [
"-test.v",
"-test.run",
"^TestDNSProviders",
"-verbose",
"-profile",
"BIND",
"-start",
"7",
"-end",
"7"
],
"buildFlags": "",
"env": {},
"showLog": true
},
```

View file

@ -343,7 +343,7 @@ func cfSingleRedirectEnabled() bool {
}
func cfSingleRedirect(name string, code any, when, then string) *models.RecordConfig {
rec, err := rtypecontrol.NewRecordConfigFromRaw("CLOUDFLAREAPI_SINGLE_REDIRECT", []any{name, code, when, then}, globalDC)
rec, err := rtypecontrol.NewRecordConfigFromRaw("CLOUDFLAREAPI_SINGLE_REDIRECT", 1, []any{name, code, when, then}, globalDC)
panicOnErr(err)
return rec
}
@ -355,13 +355,13 @@ func cfWorkerRoute(pattern, target string) *models.RecordConfig {
}
func cfRedir(pattern, target string) *models.RecordConfig {
rec, err := rtypecontrol.NewRecordConfigFromRaw("CF_REDIRECT", []any{pattern, target}, globalDC)
rec, err := rtypecontrol.NewRecordConfigFromRaw("CF_REDIRECT", 1, []any{pattern, target}, globalDC)
panicOnErr(err)
return rec
}
func cfRedirTemp(pattern, target string) *models.RecordConfig {
rec, err := rtypecontrol.NewRecordConfigFromRaw("CF_TEMP_REDIRECT", []any{pattern, target}, globalDC)
rec, err := rtypecontrol.NewRecordConfigFromRaw("CF_TEMP_REDIRECT", 1, []any{pattern, target}, globalDC)
panicOnErr(err)
return rec
}
@ -487,7 +487,7 @@ func r53alias(name, aliasType, target, evalTargetHealth string) *models.RecordCo
}
func rp(name string, m, t string) *models.RecordConfig {
rec, err := rtypecontrol.NewRecordConfigFromRaw("RP", []any{name, m, t}, globalDC)
rec, err := rtypecontrol.NewRecordConfigFromRaw("RP", 300, []any{name, m, t}, globalDC)
panicOnErr(err)
return rec
}

View file

@ -174,9 +174,9 @@ func makeTests() []*TestGroup {
),
testgroup("RP",
tc("Create RP", rp("foo", "usr@example.com", "bar.com")),
tc("Create RP", rp("foo", "other@example.com", "bar.com")),
tc("Create RP", rp("foo", "other@example.com", "example.com")),
tc("Create RP", rp("foo", "user.example.com.", "bar.com.")),
tc("Create RP", rp("foo", "other.example.com.", "bar.com.")),
tc("Create RP", rp("foo", "other.example.com.", "example.com.")),
),
// TXT

43
pkg/rtype/rp.go Normal file
View file

@ -0,0 +1,43 @@
package rtype
import (
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/rtypecontrol"
"github.com/miekg/dns"
)
func init() {
rtypecontrol.Register(&RP{})
}
// RP RR. See RFC 1138, Section 2.2.
type RP struct {
dns.RP
}
func (handle *RP) Name() string {
return "RP"
}
func (handle *RP) FromArgs(dc *models.DomainConfig, rec *models.RecordConfig, args []any) error {
if err := rtypecontrol.PaveArgs(args[1:], "ss"); err != nil {
return err
}
rec.F = &RP{
dns.RP{
Mbox: args[1].(string),
Txt: args[2].(string),
},
}
// TODO: Generate friendly Comparable and ZonefilePartial values.
rec.Comparable = rec.F.(*RP).Mbox + " " + rec.F.(*RP).Txt
rec.ZonefilePartial = rec.Comparable
return nil
}
func (handle *RP) CopyToLegacyFields(rec *models.RecordConfig) {
rp := rec.F.(*RP)
_ = rec.SetTarget(rp.Mbox + " " + rp.Txt)
}

View file

@ -5,6 +5,7 @@ import (
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/domaintags"
"github.com/miekg/dns"
"github.com/miekg/dns/dnsutil"
)
@ -14,7 +15,7 @@ func ImportRawRecords(domains []*models.DomainConfig) error {
for _, dc := range domains {
for _, rawRec := range dc.RawRecords {
rec, err := NewRecordConfigFromRaw(rawRec.Type, rawRec.Args, dc)
rec, err := NewRecordConfigFromRaw(rawRec.Type, rawRec.TTL, rawRec.Args, dc)
rec.FilePos = models.FixPosition(rawRec.FilePos)
if err != nil {
return fmt.Errorf("%s: %w", rec.FilePos, err)
@ -32,25 +33,22 @@ func ImportRawRecords(domains []*models.DomainConfig) error {
return nil
}
func NewRecordConfigFromRaw(t string, args []any, dc *models.DomainConfig) (*models.RecordConfig, error) {
//fmt.Printf("DEBUG: NewRecordConfigFromRaw t=%q args=%+v\n", t, args)
func NewRecordConfigFromRaw(t string, ttl uint32, args []any, dc *models.DomainConfig) (*models.RecordConfig, error) {
if _, ok := Func[t]; !ok {
return nil, fmt.Errorf("record type %q is not supported", t)
}
if t == "" {
panic("rtypecontrol: NewRecordConfigFromRaw: empty record type")
}
// Create as much of the RecordConfig as we can now. Allow New() to fill in the reset.
rec := &models.RecordConfig{
Type: t,
Name: args[0].(string), // May be fixed later.
TTL: ttl,
Metadata: map[string]string{},
}
setRecordNames(rec, dc, args[0].(string))
if rec.Type == "" {
panic("rtypecontrol: NewRecordConfigFromRaw: empty record type")
}
// Fill in the .F/.Fields* fields.
err := Func[t].FromArgs(dc, rec, args)
if err != nil {
@ -60,19 +58,30 @@ func NewRecordConfigFromRaw(t string, args []any, dc *models.DomainConfig) (*mod
return rec, nil
}
// func stringifyMetas(metas []map[string]any) map[string]string {
// result := make(map[string]string)
// for _, m := range metas {
// for mk, mv := range m {
// if v, ok := mv.(string); ok {
// result[mk] = v // Already a string. No new malloc.
// } else {
// result[mk] = fmt.Sprintf("%v", mv)
// }
// }
// }
// return result
// }
func NewRecordConfigFromStruct(name string, ttl uint32, t string, fields any, dc *models.DomainConfig) (*models.RecordConfig, error) {
if _, ok := Func[t]; !ok {
return nil, fmt.Errorf("record type %q is not supported", t)
}
if t == "" {
panic("rtypecontrol: NewRecordConfigFromStruct: empty record type")
}
// Create as much of the RecordConfig as we can now. Allow New() to fill in the reset.
rec := &models.RecordConfig{
Type: t,
TTL: ttl,
Metadata: map[string]string{},
}
setRecordNames(rec, dc, name)
// Fill in the .F/.Fields* fields.
err := Func[t].FromArgs(dc, rec, []any{name, fields.(*dns.RP).Mbox, fields.(*dns.RP).Txt})
if err != nil {
return nil, err
}
return rec, nil
}
func setRecordNames(rec *models.RecordConfig, dc *models.DomainConfig, n string) {
@ -87,9 +96,9 @@ func setRecordNames(rec *models.RecordConfig, dc *models.DomainConfig, n string)
rec.NameRaw = n
rec.NameUnicode = domaintags.EfficientToUnicode(n)
}
rec.NameFQDN = dnsutil.AddOrigin(rec.Name, dc.Name)
rec.NameFQDNRaw = dnsutil.AddOrigin(rec.NameRaw, dc.NameRaw)
rec.NameFQDNUnicode = dnsutil.AddOrigin(rec.NameUnicode, dc.NameUnicode)
rec.NameFQDN = dc.Name
rec.NameFQDNRaw = dc.NameRaw
rec.NameFQDNUnicode = dc.NameUnicode
} else {
// _EXTEND() mode:
// FIXME(tlim): Not implemented.

View file

@ -27,6 +27,7 @@ import (
"github.com/StackExchange/dnscontrol/v4/pkg/domaintags"
"github.com/StackExchange/dnscontrol/v4/pkg/prettyzone"
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
"github.com/StackExchange/dnscontrol/v4/pkg/rtypecontrol"
"github.com/StackExchange/dnscontrol/v4/providers"
"github.com/miekg/dns"
)
@ -204,10 +205,31 @@ func ParseZoneContents(content string, zoneName string, zonefileName string) (mo
foundRecords := models.Records{}
for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
rec, err := models.RRtoRCTxtBug(rr, zoneName)
if err != nil {
return nil, err
var rec models.RecordConfig
var prec *models.RecordConfig
var err error
// Modern types:
rtype := rr.Header().Rrtype
switch rtype {
case dns.TypeRP:
name := rr.Header().Name
name = strings.TrimSuffix(name, ".")
prec, err = rtypecontrol.NewRecordConfigFromStruct(name, rr.Header().Ttl, "RP", rr, models.MakeFakeDomainConfig(zoneName))
if err != nil {
return nil, err
}
rec = *prec
rec.TTL = rr.Header().Ttl
fmt.Printf("DEBUG: RP record parsed as %+v\n", rec)
default:
// Legacy types:
rec, err = models.RRtoRCTxtBug(rr, zoneName)
if err != nil {
return nil, err
}
}
foundRecords = append(foundRecords, &rec)
}

View file

@ -509,58 +509,6 @@ func (c *cloudflareProvider) preprocessConfig(dc *models.DomainConfig) error {
}
}
// // CF_REDIRECT record types:
// if rec.Type == "CF_REDIRECT" || rec.Type == "CF_TEMP_REDIRECT" {
// if !c.manageRedirects && !c.manageSingleRedirects {
// return errors.New("you must add 'manage_single_redirects: true' metadata to cloudflare provider to use CF_REDIRECT/CF_TEMP_REDIRECT records")
// }
// code := uint16(301)
// if rec.Type == "CF_TEMP_REDIRECT" {
// code = 302
// }
// part := strings.SplitN(rec.GetTargetField(), ",", 2)
// prWhen, prThen := part[0], part[1]
// prPriority++
// // Convert this record to a PAGE_RULE.
// if err := cfsingleredirect.MakePageRule(rec, prPriority, code, prWhen, prThen); err != nil {
// return err
// }
// rec.SetLabel("@", dc.Name)
// if c.manageRedirects && !c.manageSingleRedirects {
// // Old-Style only. No additional work needed.
// } else if !c.manageRedirects && c.manageSingleRedirects {
// // New-Style only. Convert PAGE_RULE to SINGLEREDIRECT.
// if err := cfsingleredirect.TranscodePRtoSR(rec); err != nil {
// return err
// }
// if err := c.LogTranscode(dc.Name, rec.CloudflareRedirect); err != nil {
// return err
// }
// } else {
// // Both old-style and new-style enabled!
// // Retain the PAGE_RULE and append an additional SINGLEREDIRECT.
// // make a copy:
// newRec, err := rec.Copy()
// if err != nil {
// return err
// }
// // The copy becomes the CF SingleRedirect
// if err := cfsingleredirect.TranscodePRtoSR(rec); err != nil {
// return err
// }
// if err := c.LogTranscode(dc.Name, rec.CloudflareRedirect); err != nil {
// return err
// }
// // Append the copy to the end of the list.
// dc.Records = append(dc.Records, newRec)
// // The original PAGE_RULE remains untouched.
// }
// } else
if rec.Type == "CLOUDFLAREAPI_SINGLE_REDIRECT" {
// SINGLEREDIRECT record types. Verify they are enabled.
if !c.manageSingleRedirects {
@ -797,18 +745,6 @@ func uint16Zero(value interface{}) uint16 {
return 0
}
// // intZero converts value to uint16 or returns 0.
// func intZero(value interface{}) uint16 {
// switch v := value.(type) {
// case float64:
// return uint16(v)
// case int:
// return uint16(v)
// case nil:
// }
// return 0
// }
// stringDefault returns the value as a string or returns the default value if nil.
func stringDefault(value interface{}, def string) string {
switch v := value.(type) {

View file

@ -299,6 +299,7 @@ func (c *cloudflareProvider) getSingleRedirects(id string, domain string) ([]*mo
rec, err := rtypecontrol.NewRecordConfigFromRaw(
"CLOUDFLAREAPI_SINGLE_REDIRECT",
1,
[]any{srName, code, srWhen, srThen},
models.MakeFakeDomainConfig(domain))
if err != nil {
@ -434,17 +435,3 @@ func (c *cloudflareProvider) createWorkerRoute(domainID string, target string) e
_, err := c.cfClient.CreateWorkerRoute(context.Background(), cloudflare.ZoneIdentifier(domainID), wr)
return err
}
// https://github.com/dominikh/go-tools/issues/1137 which is a dup of
// https://github.com/dominikh/go-tools/issues/810
//
//lint:ignore U1000 false positive due to
// type pageRuleConstraint struct {
// Operator string `json:"operator"`
// Value string `json:"value"`
// }
// type pageRuleFwdInfo struct {
// URL string `json:"url"`
// StatusCode uint16 `json:"status_code"`
// }

View file

@ -24,6 +24,10 @@ func (handle *CfRedirect) FromArgs(dc *models.DomainConfig, rec *models.RecordCo
return FromArgs_helper(dc, rec, args, 301)
}
// func (handle *CfRedirect) FromStruct(dc *models.DomainConfig, rec *models.RecordConfig, fields any) error {
// panic("CF_REDIRECT: FromStruct not implemented")
// }
func (handle *CfRedirect) CopyToLegacyFields(rec *models.RecordConfig) {
// Nothing needs to be copied. The CLOUDFLAREAPI_SINGLE_REDIRECT FromArgs copies everything needed.
}
@ -39,6 +43,10 @@ func (handle *CfTempRedirect) FromArgs(dc *models.DomainConfig, rec *models.Reco
return FromArgs_helper(dc, rec, args, 302)
}
// func (handle *CfTempRedirect) FromStruct(dc *models.DomainConfig, rec *models.RecordConfig, fields any) error {
// panic("CF_TEMP_REDIRECT: FromStruct not implemented")
// }
func (handle *CfTempRedirect) CopyToLegacyFields(rec *models.RecordConfig) {
// Nothing needs to be copied. The CLOUDFLAREAPI_SINGLE_REDIRECT FromArgs copies everything needed.
}

View file

@ -17,12 +17,12 @@ type SingleRedirectConfig struct {
Code uint16 `json:"code,omitempty"` // 301 or 302
//
// SR == SingleRedirect
SRName string `json:"sr_name,omitempty"` // How is this displayed to the user
SRWhen string `json:"sr_when,omitempty"`
SRThen string `json:"sr_then,omitempty"`
SRRRulesetID string `json:"sr_rulesetid,omitempty"`
SRRRulesetRuleID string `json:"sr_rulesetruleid,omitempty"`
SRDisplay string `json:"sr_display,omitempty"` // How is this displayed to the user (SetTarget) for CF_SINGLE_REDIRECT
SRName string `json:"sr_name,omitempty"` // How is this displayed to the user
SRWhen string `json:"sr_when,omitempty"` // Condition for redirect
SRThen string `json:"sr_then,omitempty"` // Formula for redirect
SRRRulesetID string `json:"sr_rulesetid,omitempty"` // ID of the ruleset containing this rule (populated by API)
SRRRulesetRuleID string `json:"sr_rulesetruleid,omitempty"` // ID of this rule within the ruleset (populated by API)
SRDisplay string `json:"sr_display,omitempty"` // How is this displayed to the user (SetTarget) for CF_SINGLE_REDIRECT
}
// Name returns the text (all caps) name of the rtype.
@ -76,6 +76,10 @@ func (handle *SingleRedirectConfig) FromArgs(dc *models.DomainConfig, rec *model
return nil
}
// func (handle *SingleRedirectConfig) FromStruct(dc *models.DomainConfig, rec *models.RecordConfig, fields any) error {
// panic("CLOUDFLAREAPI_SINGLE_REDIRECT: FromStruct not implemented")
// }
// targetFromRaw create the display text used for a normal Redirect.
func targetFromRaw(name string, code uint16, when, then string) string {
return fmt.Sprintf("name=(%s) code=(%03d) when=(%s) then=(%s)",