mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-12-11 14:45:52 +08:00
# Issue * New record type: "RP" (supported by BIND and GANDI_V5) * Cloudflare: CF_REDIRECT/CF_TEMP_REDIRECT now generate CF_SINGLE_REDIRECT records. All PAGE_RULE-based code is removed. PAGE_RULEs are deprecated at Cloudflare. (be careful when upgrading!) * New "v2" RecordConfig: RP and CF_SINGLE_REDIRECT are the only record types that use this method. It shifts most of the work out of JavaScript and into the Go code, making new record types easier to make, easier to test, and easier to use by providers. This opens the door to new things like a potential code-generator for rtypes. Converting existing rtypes will happen over the next year. * When only the TTL changes (MODIFY-TTL), the output lists the TTL change first, not at the end of the line where it is visually lost. * CF_REDIRECT/CF_TEMP_REDIRECT generate different rule "names". They will be updated the first time you "push" with this release. The order of the rules may also change. If you rules depend on a particular order, be very careful with this upgrade! Refactoring: * New "v2" RecordConfig: Record types using this new method simply package the parameters from dnsconfig.js statements like CF_REDIRECT(foo,bar) and send them (raw) to the Go code. The Go code does all processing, validation, etc. and turns them into RecordConfig that store all the rdata in `RecordConfig.F`. No more adding fields to RecordConfig for each new record type! * RecordConfig.IsModernType() returns true if the record uses the new v2 record mechanism. * PostProcess is now a method on DnsConfig and DomainConfig. * DOC: How to create new rtypes using the v2 method (incomplete) Other things: * Integration tests for CF "full proxy" are removed. This feature doesn't exist any more. * DEV: Debugger tips now includes VSCode advice * TESTING: The names of testgroup's can now have extra spaces to make data align better * CF_TEMP_REDIRECT/CF_REDIRECT is now a "builder" that generates CLOUDFLAREAPI_SINGLE_REDIRECT records. * And more! # Resolution --------- Co-authored-by: Jakob Ackermann <das7pad@outlook.com>
416 lines
11 KiB
Go
416 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/StackExchange/dnscontrol/v4/providers"
|
|
_ "github.com/StackExchange/dnscontrol/v4/providers/_all"
|
|
"github.com/fbiville/markdown-table-formatter/pkg/markdown"
|
|
)
|
|
|
|
func generateFeatureMatrix() error {
|
|
var replacementContent = "Jump to a table:\n\n"
|
|
matrix := matrixData()
|
|
|
|
for _, tableTitle := range matrix.FeatureTablesTitles {
|
|
var jumptotableContent = ""
|
|
|
|
var anchor = strings.ToLower(tableTitle)
|
|
anchor = strings.Replace(anchor, " ", "-", -1)
|
|
|
|
jumptotableContent += fmt.Sprintf("- [%s](#%s)\n", tableTitle, anchor)
|
|
replacementContent += jumptotableContent
|
|
}
|
|
|
|
for i, tableTitle := range matrix.FeatureTablesTitles {
|
|
replacementContent += fmt.Sprintf("\n### %s <!--(table %d/%d)-->\n\n",
|
|
tableTitle, i+1, len(matrix.FeatureTablesTitles))
|
|
markdownTable, err := markdownTable(matrix, int32(i))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
replacementContent += markdownTable
|
|
replacementContent += "\n"
|
|
}
|
|
|
|
replaceInlineContent(
|
|
"documentation/provider/index.md",
|
|
"<!-- provider-matrix-start -->",
|
|
"<!-- provider-matrix-end -->",
|
|
replacementContent,
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
func markdownTable(matrix *FeatureMatrix, tableNumber int32) (string, error) {
|
|
var tableHeaders []string
|
|
tableHeaders = append(tableHeaders, "Provider name")
|
|
tableHeaders = append(tableHeaders, matrix.FeatureTables[tableNumber]...)
|
|
|
|
var tableData [][]string
|
|
for _, providerName := range allProviderNames() {
|
|
featureMap := matrix.Providers[providerName]
|
|
|
|
var tableDataRow []string
|
|
tableDataRow = append(tableDataRow, "[`"+providerName+"`]("+strings.ToLower(providerName)+".md)")
|
|
for _, featureName := range matrix.FeatureTables[tableNumber] {
|
|
tableDataRow = append(tableDataRow, featureEmoji(featureMap, featureName))
|
|
}
|
|
skipThisRow := true
|
|
for status := range tableDataRow[1:] {
|
|
if tableDataRow[status+1] != "❔" {
|
|
skipThisRow = false
|
|
}
|
|
}
|
|
if !skipThisRow {
|
|
tableData = append(tableData, tableDataRow)
|
|
}
|
|
}
|
|
|
|
markdownTable, err := markdown.NewTableFormatterBuilder().
|
|
Build(tableHeaders...).
|
|
Format(tableData)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return markdownTable, nil
|
|
}
|
|
|
|
func featureEmoji(
|
|
featureMap FeatureMap,
|
|
featureName string,
|
|
) string {
|
|
if featureMap[featureName] == nil {
|
|
return "❔"
|
|
}
|
|
|
|
if featureMap[featureName].HasFeature {
|
|
return "✅"
|
|
} else if featureMap[featureName].Unimplemented {
|
|
return "❔"
|
|
}
|
|
return "❌"
|
|
}
|
|
|
|
func matrixData() *FeatureMatrix {
|
|
const (
|
|
OfficialSupport = "Official Support" // vs. community supported
|
|
ProviderDNSProvider = "DNS Provider"
|
|
ProviderRegistrar = "Registrar"
|
|
ProviderThreadSafe = "[Concurrency Verified](../advanced-features/concurrency-verified.md)"
|
|
DomainModifierAlias = "[`ALIAS`](../language-reference/domain-modifiers/ALIAS.md)"
|
|
DomainModifierCaa = "[`CAA`](../language-reference/domain-modifiers/CAA.md)"
|
|
DomainModifierDhcid = "[`DHCID`](../language-reference/domain-modifiers/DHCID.md)"
|
|
DomainModifierDname = "[`DNAME`](../language-reference/domain-modifiers/DNAME.md)"
|
|
DomainModifierDnskey = "[`DNSKEY`](../language-reference/domain-modifiers/DNSKEY.md)"
|
|
DomainModifierDnssec = "[`AUTODNSSEC`](../language-reference/domain-modifiers/AUTODNSSEC_ON.md)"
|
|
DomainModifierDs = "[`DS`](../language-reference/domain-modifiers/DS.md)"
|
|
DomainModifierHTTPS = "[`HTTPS`](../language-reference/domain-modifiers/HTTPS.md)"
|
|
DomainModifierLoc = "[`LOC`](../language-reference/domain-modifiers/LOC.md)"
|
|
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)"
|
|
DomainModifierSshfp = "[`SSHFP`](../language-reference/domain-modifiers/SSHFP.md)"
|
|
DomainModifierSvcb = "[`SVCB`](../language-reference/domain-modifiers/SVCB.md)"
|
|
DomainModifierTlsa = "[`TLSA`](../language-reference/domain-modifiers/TLSA.md)"
|
|
DualHost = "[dual host](../advanced-features/dual-host.md)"
|
|
CreateDomains = "create-domains"
|
|
GetZones = "get-zones"
|
|
)
|
|
|
|
matrix := &FeatureMatrix{
|
|
Providers: map[string]FeatureMap{},
|
|
FeatureTablesTitles: []string{
|
|
"Provider Type",
|
|
"Provider API",
|
|
"DNS extensions",
|
|
"Service discovery",
|
|
"Security",
|
|
"DNSSEC",
|
|
},
|
|
FeatureTables: [][]string{
|
|
[]string{ // provider type
|
|
OfficialSupport,
|
|
ProviderDNSProvider,
|
|
ProviderRegistrar,
|
|
},
|
|
[]string{ // provider API
|
|
ProviderThreadSafe,
|
|
DualHost,
|
|
CreateDomains,
|
|
// NoPurge,
|
|
GetZones,
|
|
},
|
|
[]string{ // DNS extensions
|
|
DomainModifierAlias,
|
|
DomainModifierDname,
|
|
DomainModifierLoc,
|
|
DomainModifierPtr,
|
|
DomainModifierSoa,
|
|
},
|
|
[]string{ // service discovery
|
|
DomainModifierDhcid,
|
|
DomainModifierNaptr,
|
|
DomainModifierSrv,
|
|
DomainModifierSvcb,
|
|
},
|
|
[]string{ // security
|
|
DomainModifierCaa,
|
|
DomainModifierHTTPS,
|
|
DomainModifierSMIMEA,
|
|
DomainModifierSshfp,
|
|
DomainModifierTlsa,
|
|
},
|
|
[]string{ // dnssec
|
|
DomainModifierDnssec,
|
|
DomainModifierDnskey,
|
|
DomainModifierDs,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, providerName := range allProviderNames() {
|
|
featureMap := FeatureMap{}
|
|
providerNotes := providers.Notes[providerName]
|
|
if providerNotes == nil {
|
|
providerNotes = providers.DocumentationNotes{}
|
|
}
|
|
|
|
setCapability := func(
|
|
featureName string,
|
|
capability providers.Capability,
|
|
) {
|
|
if providerNotes[capability] != nil {
|
|
featureMap[featureName] = providerNotes[capability]
|
|
return
|
|
}
|
|
featureMap.SetSimple(
|
|
featureName,
|
|
true,
|
|
func() bool { return providers.ProviderHasCapability(providerName, capability) },
|
|
)
|
|
}
|
|
|
|
setDocumentation := func(
|
|
featureName string,
|
|
capability providers.Capability,
|
|
defaultNo bool,
|
|
) {
|
|
if providerNotes[capability] != nil {
|
|
featureMap[featureName] = providerNotes[capability]
|
|
} else if defaultNo {
|
|
featureMap[featureName] = &providers.DocumentationNote{
|
|
HasFeature: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
setDocumentation(
|
|
OfficialSupport,
|
|
providers.DocOfficiallySupported,
|
|
true,
|
|
)
|
|
featureMap.SetSimple(
|
|
ProviderDNSProvider,
|
|
false,
|
|
func() bool { return providers.DNSProviderTypes[providerName].Initializer != nil },
|
|
)
|
|
featureMap.SetSimple(
|
|
ProviderRegistrar,
|
|
false,
|
|
func() bool { return providers.RegistrarTypes[providerName] != nil },
|
|
)
|
|
setCapability(
|
|
ProviderThreadSafe,
|
|
providers.CanConcur,
|
|
)
|
|
setCapability(
|
|
DomainModifierAlias,
|
|
providers.CanUseAlias,
|
|
)
|
|
setCapability(
|
|
DomainModifierDnssec,
|
|
providers.CanAutoDNSSEC,
|
|
)
|
|
setCapability(
|
|
DomainModifierCaa,
|
|
providers.CanUseCAA,
|
|
)
|
|
setCapability(
|
|
DomainModifierDhcid,
|
|
providers.CanUseDHCID,
|
|
)
|
|
setCapability(
|
|
DomainModifierDname,
|
|
providers.CanUseDNAME,
|
|
)
|
|
setCapability(
|
|
DomainModifierDs,
|
|
providers.CanUseDS,
|
|
)
|
|
setCapability(
|
|
DomainModifierDnskey,
|
|
providers.CanUseDNSKEY,
|
|
)
|
|
setCapability(
|
|
DomainModifierHTTPS,
|
|
providers.CanUseHTTPS,
|
|
)
|
|
setCapability(
|
|
DomainModifierLoc,
|
|
providers.CanUseLOC,
|
|
)
|
|
setCapability(
|
|
DomainModifierNaptr,
|
|
providers.CanUseNAPTR,
|
|
)
|
|
setCapability(
|
|
DomainModifierOpenpgpkey,
|
|
providers.CanUseOPENPGPKEY,
|
|
)
|
|
setCapability(
|
|
DomainModifierPtr,
|
|
providers.CanUsePTR,
|
|
)
|
|
setCapability(
|
|
DomainModifierRP,
|
|
providers.CanUseRP,
|
|
)
|
|
setCapability(
|
|
DomainModifierSMIMEA,
|
|
providers.CanUseSMIMEA,
|
|
)
|
|
setCapability(
|
|
DomainModifierSoa,
|
|
providers.CanUseSOA,
|
|
)
|
|
setCapability(
|
|
DomainModifierSrv,
|
|
providers.CanUseSRV,
|
|
)
|
|
setCapability(
|
|
DomainModifierSshfp,
|
|
providers.CanUseSSHFP,
|
|
)
|
|
setCapability(
|
|
DomainModifierSvcb,
|
|
providers.CanUseSVCB,
|
|
)
|
|
setCapability(
|
|
DomainModifierTlsa,
|
|
providers.CanUseTLSA,
|
|
)
|
|
setCapability(
|
|
GetZones,
|
|
providers.CanGetZones,
|
|
)
|
|
setDocumentation(
|
|
CreateDomains,
|
|
providers.DocCreateDomains,
|
|
true,
|
|
)
|
|
setDocumentation(
|
|
DualHost,
|
|
providers.DocDualHost,
|
|
false,
|
|
)
|
|
|
|
// // no purge is a freaky double negative
|
|
// cantUseNOPURGE := providers.CantUseNOPURGE
|
|
// if providerNotes[cantUseNOPURGE] != nil {
|
|
// featureMap[NoPurge] = providerNotes[cantUseNOPURGE]
|
|
// } else {
|
|
// featureMap.SetSimple(
|
|
// NoPurge,
|
|
// false,
|
|
// func() bool { return !providers.ProviderHasCapability(providerName, cantUseNOPURGE) },
|
|
// )
|
|
// }
|
|
matrix.Providers[providerName] = featureMap
|
|
}
|
|
return matrix
|
|
}
|
|
|
|
func allProviderNames() []string {
|
|
const ProviderNameNone = "NONE"
|
|
|
|
allProviderNames := map[string]bool{}
|
|
for providerName := range providers.RegistrarTypes {
|
|
if providerName == ProviderNameNone {
|
|
continue
|
|
}
|
|
allProviderNames[providerName] = true
|
|
}
|
|
for providerName := range providers.DNSProviderTypes {
|
|
if providerName == ProviderNameNone {
|
|
continue
|
|
}
|
|
allProviderNames[providerName] = true
|
|
}
|
|
|
|
var allProviderNamesAsString []string
|
|
for providerName := range allProviderNames {
|
|
allProviderNamesAsString = append(allProviderNamesAsString, providerName)
|
|
}
|
|
sort.Strings(allProviderNamesAsString)
|
|
|
|
return allProviderNamesAsString
|
|
}
|
|
|
|
// FeatureMap maps provider names to compliance documentation.
|
|
type FeatureMap map[string]*providers.DocumentationNote
|
|
|
|
// SetSimple configures a provider's setting in featureMap.
|
|
func (featureMap FeatureMap) SetSimple(
|
|
name string,
|
|
unknownsAllowed bool,
|
|
f func() bool,
|
|
) {
|
|
if f() {
|
|
featureMap[name] = &providers.DocumentationNote{HasFeature: true}
|
|
} else if !unknownsAllowed {
|
|
featureMap[name] = &providers.DocumentationNote{HasFeature: false}
|
|
}
|
|
}
|
|
|
|
// FeatureMatrix describes features and which providers support it.
|
|
type FeatureMatrix struct {
|
|
Providers map[string]FeatureMap
|
|
FeatureTables [][]string
|
|
FeatureTablesTitles []string
|
|
}
|
|
|
|
func replaceInlineContent(
|
|
file string,
|
|
startMarker string,
|
|
endMarker string,
|
|
newContent string,
|
|
) {
|
|
contentBytes, err := os.ReadFile(file)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
content := string(contentBytes)
|
|
|
|
start := strings.Index(content, startMarker)
|
|
end := strings.Index(content, endMarker)
|
|
|
|
newContentString := startMarker + "\n" + newContent + endMarker
|
|
newContentBytes := []byte(newContentString)
|
|
contentBytes = []byte(content)
|
|
contentBytes = append(contentBytes[:start], append(newContentBytes, contentBytes[end+len(endMarker):]...)...)
|
|
|
|
err = os.WriteFile(file, contentBytes, 0o644)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|