working on RP

This commit is contained in:
Thomas Limoncelli 2025-12-01 14:51:34 -05:00
parent e42dbcda57
commit 76dbb0ed67
No known key found for this signature in database
12 changed files with 203 additions and 61 deletions

View file

@ -115,6 +115,7 @@ func matrixData() *FeatureMatrix {
DomainModifierNaptr = "[`NAPTR`](../language-reference/domain-modifiers/NAPTR.md)"
DomainModifierOpenpgpkey = "[`DNSKEY`](../language-reference/domain-modifiers/OPENPGPKEY.md)"
DomainModifierPtr = "[`PTR`](../language-reference/domain-modifiers/PTR.md)"
DomainModifierRP = "[`RP`](../language-reference/domain-modifiers/RP.md)"
DomainModifierSMIMEA = "[`SMIMEA`](../language-reference/domain-modifiers/SMIMEA.md)"
DomainModifierSoa = "[`SOA`](../language-reference/domain-modifiers/SOA.md)"
DomainModifierSrv = "[`SRV`](../language-reference/domain-modifiers/SRV.md)"
@ -280,6 +281,10 @@ func matrixData() *FeatureMatrix {
DomainModifierPtr,
providers.CanUsePTR,
)
setCapability(
DomainModifierRP,
providers.CanUseRP,
)
setCapability(
DomainModifierSMIMEA,
providers.CanUseSMIMEA,

View file

@ -78,7 +78,7 @@ func init() {
}
```
STep 2b: Create the struct
Step 2b: Create the struct
Create a struct that will store the fields of this rtype.
@ -172,6 +172,108 @@ TODO:
* Run the integration tests for BIND
* Write documentation
## Add a capability for the record type
You'll need to mark which providers support this record type. The
initial PR should implement this record for the `BIND` provider at
a minimum. `BIND` outputs non-standard rtypes as a comment.
- Add the capability to the file `dnscontrol/providers/capabilities.go` (look for `CanUseAlias` and add
it to the end of the list.)
- Run stringer to auto-update the file `dnscontrol/providers/capability_string.go`
```shell
pushd providers/
go tool stringer -type=Capability
popd
```
alternatively
```shell
pushd providers/
go generate
popd
```
- Add this feature to the feature matrix in `dnscontrol/build/generate/featureMatrix.go`. Add it to the variable `matrix` maintaining alphabetical ordering, which should look like this:
{% code title="dnscontrol/build/generate/featureMatrix.go" %}
```diff
func matrixData() *FeatureMatrix {
const (
...
DomainModifierCaa = "[`CAA`](language-reference/domain-modifiers/CAA.md)"
+ DomainModifierFoo = "[`FOO`](language-reference/domain-modifiers/FOO.md)"
DomainModifierLoc = "[`LOC`](language-reference/domain-modifiers/LOC.md)"
...
)
matrix := &FeatureMatrix{
Providers: map[string]FeatureMap{},
Features: []string{
...
DomainModifierCaa,
+ DomainModifierFoo,
DomainModifierLoc,
...
},
}
```
{% endcode %}
then add it later in the file with a `setCapability()` statement, which should look like this:
{% code title="dnscontrol/build/generate/featureMatrix.go" %}
```diff
...
+ setCapability(
+ DomainModifierFoo,
+ providers.CanUseFOO,
+ )
...
```
{% endcode %}
- Add the capability to the list of features that zones are validated
against (i.e. if you want DNSControl to report an error if this
feature is used with a DNS provider that doesn't support it). That's
in the `checkProviderCapabilities` function in
`pkg/normalize/validate.go`. It should look like this:
{% code title="pkg/normalize/validate.go" %}
```diff
var providerCapabilityChecks = []pairTypeCapability{
...
+ capabilityCheck("FOO", providers.CanUseFOO),
...
```
{% endcode %}
- Mark the `bind` provider as supporting this record type by updating `dnscontrol/providers/bind/bindProvider.go` (look for `providers.CanUse` and you'll see what to do).
DNSControl will warn/error if this new record is used with a
provider that does not support the capability.
- Add the capability to the validations in `pkg/normalize/validate.go`
by adding it to `providerCapabilityChecks`
- Some capabilities can't be tested for. If
such testing can't be done, add it to the whitelist in function
`TestCapabilitiesAreFiltered` in
`pkg/normalize/capabilities_test.go`
If the capabilities testing is not configured correctly, `go test ./...`
will report something like the `MISSING` message below. In this
example we removed `providers.CanUseCAA` from the
`providerCapabilityChecks` list.
```text
--- FAIL: TestCapabilitiesAreFiltered (0.00s)
capabilities_test.go:66: ok: providers.CanUseAlias (0) is checked for with "ALIAS"
capabilities_test.go:68: MISSING: providers.CanUseCAA (1) is not checked by checkProviderCapabilities
capabilities_test.go:66: ok: providers.CanUseNAPTR (3) is checked for with "NAPTR"
```
# Update providers
When a provider needs to create a THING, they have two choices

View file

@ -174,6 +174,7 @@ func makeTests() []*TestGroup {
),
testgroup("RP",
requires(providers.CanUseRP),
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.")),

View file

@ -763,6 +763,7 @@ var providerCapabilityChecks = []pairTypeCapability{
capabilityCheck("OPENPGPKEY", providers.CanUseOPENPGPKEY),
capabilityCheck("PTR", providers.CanUsePTR),
capabilityCheck("R53_ALIAS", providers.CanUseRoute53Alias),
capabilityCheck("RP", providers.CanUseRP),
capabilityCheck("SMIMEA", providers.CanUseSMIMEA),
capabilityCheck("SOA", providers.CanUseSOA),
capabilityCheck("SRV", providers.CanUseSRV),

View file

@ -48,3 +48,19 @@ func (handle *RP) CopyToLegacyFields(rec *models.RecordConfig) {
rp := rec.F.(*RP)
_ = rec.SetTarget(rp.Mbox + " " + rp.Txt)
}
// func (handle *RP) TestData() {
// return []itest.TestGroup[
// itest.Testgroup("RP",
// itest.tc("Create RP", rp("foo", "user.example.com.", "bar.com.")),
// itest.tc("Create RP", rp("foo", "other.example.com.", "bar.com.")),
// itest.tc("Create RP", rp("foo", "other.example.com.", "example.com.")),
// ),
// itest.Testgroup("RP-apex",
// itest.tc("Create RP", rp("@", "user.example.com.", "bar.com.")),
// itest.tc("Create RP", rp("@", "other.example.com.", "bar.com.")),
// itest.tc("Create RP", rp("@", "other.example.com.", "example.com.")),
// ),
// ]
// }

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"
)
@ -57,6 +58,22 @@ func NewRecordConfigFromRaw(t string, ttl uint32, args []any, dc *models.DomainC
return rec, nil
}
func NewRecordConfigFromString(name string, ttl uint32, t string, s string, 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")
}
rec, err := dns.NewRR(fmt.Sprintf("$ORIGIN .\n. %d IN %s %s", ttl, t, s))
if err != nil {
return nil, err
}
return NewRecordConfigFromStruct(name, ttl, t, rec, dc)
}
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)
@ -73,11 +90,6 @@ func NewRecordConfigFromStruct(name string, ttl uint32, t string, fields any, dc
}
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
// }
err := Func[t].FromStruct(dc, rec, name, fields)
if err != nil {
return nil, err

View file

@ -36,18 +36,19 @@ var features = providers.DocumentationNotes{
// The default for unlisted capabilities is 'Cannot'.
// See providers/capabilities.go for the entire list of capabilities.
providers.CanAutoDNSSEC: providers.Can("Just writes out a comment indicating DNSSEC was requested"),
providers.CanGetZones: providers.Can(),
providers.CanConcur: providers.Can(),
providers.CanGetZones: providers.Can(),
providers.CanUseCAA: providers.Can(),
providers.CanUseDHCID: providers.Can(),
providers.CanUseDNAME: providers.Can(),
providers.CanUseDS: providers.Can(),
providers.CanUseDNSKEY: providers.Can(),
providers.CanUseDS: providers.Can(),
providers.CanUseHTTPS: providers.Can(),
providers.CanUseLOC: providers.Can(),
providers.CanUseNAPTR: providers.Can(),
providers.CanUseOPENPGPKEY: providers.Can(),
providers.CanUsePTR: providers.Can(),
providers.CanUseRP: providers.Can(),
providers.CanUseSMIMEA: providers.Can(),
providers.CanUseSOA: providers.Can(),
providers.CanUseSRV: providers.Can(),

View file

@ -79,6 +79,9 @@ const (
// CanUseRoute53Alias indicates the provider support the specific R53_ALIAS records that only the Route53 provider supports
CanUseRoute53Alias
// CanUseRP indicates the provider can handle RP records
CanUseRP
// CanUseSMIMEA indicates the provider can handle SMIMEA records
CanUseSMIMEA

View file

@ -25,23 +25,24 @@ func _() {
_ = x[CanUseNAPTR-14]
_ = x[CanUsePTR-15]
_ = x[CanUseRoute53Alias-16]
_ = x[CanUseSMIMEA-17]
_ = x[CanUseSOA-18]
_ = x[CanUseSRV-19]
_ = x[CanUseSSHFP-20]
_ = x[CanUseSVCB-21]
_ = x[CanUseTLSA-22]
_ = x[CanUseDNSKEY-23]
_ = x[CanUseOPENPGPKEY-24]
_ = x[DocCreateDomains-25]
_ = x[DocDualHost-26]
_ = x[DocOfficiallySupported-27]
_ = x[CanUseAKAMAITLC-28]
_ = x[CanUseRP-17]
_ = x[CanUseSMIMEA-18]
_ = x[CanUseSOA-19]
_ = x[CanUseSRV-20]
_ = x[CanUseSSHFP-21]
_ = x[CanUseSVCB-22]
_ = x[CanUseTLSA-23]
_ = x[CanUseDNSKEY-24]
_ = x[CanUseOPENPGPKEY-25]
_ = x[DocCreateDomains-26]
_ = x[DocDualHost-27]
_ = x[DocOfficiallySupported-28]
_ = x[CanUseAKAMAITLC-29]
}
const _Capability_name = "CanAutoDNSSECCanConcurCanGetZonesCanOnlyDiff1FeaturesCanUseAKAMAICDNCanUseAliasCanUseAzureAliasCanUseCAACanUseDHCIDCanUseDNAMECanUseDSCanUseDSForChildrenCanUseHTTPSCanUseLOCCanUseNAPTRCanUsePTRCanUseRoute53AliasCanUseSMIMEACanUseSOACanUseSRVCanUseSSHFPCanUseSVCBCanUseTLSACanUseDNSKEYCanUseOPENPGPKEYDocCreateDomainsDocDualHostDocOfficiallySupportedCanUseAKAMAITLC"
const _Capability_name = "CanAutoDNSSECCanConcurCanGetZonesCanOnlyDiff1FeaturesCanUseAKAMAICDNCanUseAliasCanUseAzureAliasCanUseCAACanUseDHCIDCanUseDNAMECanUseDSCanUseDSForChildrenCanUseHTTPSCanUseLOCCanUseNAPTRCanUsePTRCanUseRoute53AliasCanUseRPCanUseSMIMEACanUseSOACanUseSRVCanUseSSHFPCanUseSVCBCanUseTLSACanUseDNSKEYCanUseOPENPGPKEYDocCreateDomainsDocDualHostDocOfficiallySupportedCanUseAKAMAITLC"
var _Capability_index = [...]uint16{0, 13, 22, 33, 53, 68, 79, 95, 104, 115, 126, 134, 153, 164, 173, 184, 193, 211, 223, 232, 241, 252, 262, 272, 284, 300, 316, 327, 349, 364}
var _Capability_index = [...]uint16{0, 13, 22, 33, 53, 68, 79, 95, 104, 115, 126, 134, 153, 164, 173, 184, 193, 211, 219, 231, 240, 249, 260, 270, 280, 292, 308, 324, 335, 357, 372}
func (i Capability) String() string {
idx := int(i) - 0

View file

@ -77,13 +77,16 @@ func init() {
//providers.RegisterCustomRecordType("CF_TEMP_REDIRECT", providerName, "")
providers.RegisterCustomRecordType("CF_WORKER_ROUTE", providerName, "")
providers.RegisterMaintainer(providerName, providerMaintainer)
// providers.SupportedRecordTypes(provderName,
// "CLOUDFLAREAPI_SINGLE_REDIRECT",
// )
}
// cloudflareProvider is the handle for API calls.
type cloudflareProvider struct {
ipConversions []transform.IPConversion
ignoredLabels []string
//manageRedirects bool // Old "Page Rule"-style redirects.
manageWorkers bool
accountID string
cfClient *cloudflare.API
@ -137,15 +140,6 @@ func (c *cloudflareProvider) GetZoneRecords(domain string, meta map[string]strin
}
}
// if c.manageRedirects { // if old-style "page rules" are still being managed.
// fmt.Printf("DEBUG: Getting old-style page rules for %s???\n", domain)
// // prs, err := c.getPageRules(domainID, domain)
// // if err != nil {
// // return nil, err
// // }
// // records = append(records, prs...)
// }
if c.manageSingleRedirects { // if new xor old
// Download the list of Single Redirects.
// For each one, generate a SINGLEREDIRECT record
@ -285,11 +279,6 @@ func genComparable(rec *models.RecordConfig) string {
func (c *cloudflareProvider) mkCreateCorrection(newrec *models.RecordConfig, domainID, msg string) []*models.Correction {
switch newrec.Type {
// case "PAGE_RULE":
// return []*models.Correction{{
// Msg: msg,
// F: func() error { return c.createPageRule(domainID, *newrec.CloudflareRedirect) },
// }}
case "WORKER_ROUTE":
return []*models.Correction{{
Msg: msg,
@ -310,8 +299,6 @@ func (c *cloudflareProvider) mkCreateCorrection(newrec *models.RecordConfig, dom
func (c *cloudflareProvider) mkChangeCorrection(oldrec, newrec *models.RecordConfig, domainID string, msg string) []*models.Correction {
var idTxt string
switch oldrec.Type {
// case "PAGE_RULE":
// idTxt = oldrec.Original.(cloudflare.PageRule).ID
case "WORKER_ROUTE":
idTxt = oldrec.Original.(cloudflare.WorkerRoute).ID
case "CLOUDFLAREAPI_SINGLE_REDIRECT":
@ -322,13 +309,6 @@ func (c *cloudflareProvider) mkChangeCorrection(oldrec, newrec *models.RecordCon
msg = msg + color.YellowString(" id=%v", idTxt)
switch newrec.Type {
// case "PAGE_RULE":
// return []*models.Correction{{
// Msg: msg,
// F: func() error {
// return c.updatePageRule(idTxt, domainID, *newrec.CloudflareRedirect)
// },
// }}
case "CLOUDFLAREAPI_SINGLE_REDIRECT":
return []*models.Correction{{
Msg: msg,

View file

@ -7,6 +7,8 @@ import (
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
"github.com/StackExchange/dnscontrol/v4/pkg/rtypecontrol"
"github.com/StackExchange/dnscontrol/v4/pkg/rtypeinfo"
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
"github.com/go-gandi/go-gandi/livedns"
)
@ -18,25 +20,42 @@ func nativeToRecords(n livedns.DomainRecord, origin string) (rcs []*models.Recor
// records for a label, all the IP addresses are listed in
// n.RrsetValues rather than having many livedns.DomainRecord's.
// We must split them out into individual records, one for each value.
dc := models.MakeFakeDomainConfig(origin)
for _, value := range n.RrsetValues {
rc := &models.RecordConfig{
TTL: uint32(n.RrsetTTL),
Original: n,
}
rc.SetLabel(n.RrsetName, origin)
var rc *models.RecordConfig
var err error
switch rtype := n.RrsetType; rtype {
case "ALIAS":
rc.Type = "ALIAS"
err = rc.SetTarget(value)
default:
err = rc.PopulateFromStringFunc(rtype, value, origin, txtutil.ParseQuoted)
}
if err != nil {
return nil, fmt.Errorf("unparsable record received from gandi: %w", err)
}
rtype := n.RrsetType
if rtypeinfo.IsModernType(rtype) {
// func NewRecordConfigFromString(name string, ttl uint32, t string, s string, dc *models.DomainConfig) (*models.RecordConfig, error) {
rc, err = rtypecontrol.NewRecordConfigFromString(n.RrsetName, uint32(n.RrsetTTL), rtype, value, dc)
if err != nil {
return nil, fmt.Errorf("unparsable record received from gandi: %w", err)
}
rc.Original = n
} else {
rc = &models.RecordConfig{
TTL: uint32(n.RrsetTTL),
Original: n,
}
rc.SetLabel(n.RrsetName, origin)
switch rtype := n.RrsetType; rtype {
case "ALIAS":
rc.Type = "ALIAS"
err = rc.SetTarget(value)
default:
err = rc.PopulateFromStringFunc(rtype, value, origin, txtutil.ParseQuoted)
}
if err != nil {
return nil, fmt.Errorf("unparsable record received from gandi: %w", err)
}
}
rcs = append(rcs, rc)
}
return rcs, nil

View file

@ -60,6 +60,7 @@ var features = providers.DocumentationNotes{
providers.CanUseDSForChildren: providers.Can(),
providers.CanUseLOC: providers.Cannot(),
providers.CanUsePTR: providers.Can(),
providers.CanUseRP: providers.Can(),
providers.CanUseSRV: providers.Can(),
providers.CanUseSSHFP: providers.Can(),
providers.CanUseTLSA: providers.Can(),