dnscontrol/models/rawrecords.go
2025-02-20 14:38:12 -05:00

132 lines
3.9 KiB
Go

package models
import (
"fmt"
"github.com/go-acme/lego/v4/log"
)
// RawRecordConfig stores the raw user-input from dnsconfig.js for a DNS Record
// (A, MX, SRV, etc). This is later processed to become a RecordConfig. NOTE:
// Only newer rtypes are processed this way. Eventually the legacy types will
// be converted and removed.
type RawRecordConfig struct {
Type string `json:"type"`
Args []string `json:"args,omitempty"`
Metadata []map[string]any `json:"metas,omitempty"`
TTL uint32 `json:"ttl,omitempty"`
SubDomain string `json:"subdomain,omitempty"`
// Override NO_PURGE and delete this record
EnsureAbsent bool `json:"ensure_absent,omitempty"`
}
// FromRaw converts the RawRecordConfig into a RecordConfig by calling the
// conversion function provided when the rtype was registered.
func FromRaw(rc *RecordConfig, origin string, typeName string, args []string, meta map[string]string) error {
rt, ok := rtypeDB[typeName]
if !ok {
return fmt.Errorf("unknown (FromRaw) rtype %q", typeName)
}
return rt.PopulateFromRaw(rc, args, meta, effectiveOrigin(rc.SubDomain, origin))
}
// CheckAndFixImport checks the records for any that were created with a
// provider that has not yet been upgraded. In theory leaving providers in the
// legacy state should not cause any issues, but it is a good idea to fix them
// as soon as possible.
func CheckAndFixImport(recs []*RecordConfig, origin string) bool {
found := false
for _, rec := range recs {
//fmt.Printf("DEBUG: Found record %s %s %v\n", rec.Type, rec.Name, rec)
// Was this created wrong?
if IsTypeUpgraded(rec.Type) && rec.Fields == nil {
found = true
log.Warnf("LEGACY PROVIDER needs fixing! Created invalid record: %s %s %v\n", rec.Type, rec.Name, rec)
if err := rec.ImportFromLegacy(origin); err != nil {
log.Warnf("Error fixing record: %s %s %v: %v\n", rec.Type, rec.Name, rec, err)
}
}
}
return found
}
// MustImportFromLegacy is like ImportFromLegacy but panics on error. Use only in tests and init() functions.
func (rc *RecordConfig) MustImportFromLegacy(origin string) {
if err := rc.ImportFromLegacy(origin); err != nil {
panic(err)
}
}
// TransformRawRecords converts the RawRecordConfigs from dnsconfig.js into RecordConfig.
func TransformRawRecords(domains []*DomainConfig) error {
for _, dc := range domains {
for _, rawRec := range dc.RawRecords {
if rawRec.TTL == 0 {
rawRec.TTL = dc.DefaultTTL
}
rec := &RecordConfig{
Type: rawRec.Type,
TTL: rawRec.TTL,
SubDomain: rawRec.SubDomain,
Metadata: map[string]string{},
}
// Copy the metadata (convert values to string)
for _, m := range rawRec.Metadata {
for mk, mv := range m {
if v, ok := mv.(string); ok {
rec.Metadata[mk] = v // Already a string
} else {
rec.Metadata[mk] = fmt.Sprintf("%v", mv)
}
}
}
rt, ok := rtypeDB[rawRec.Type]
if !ok {
return fmt.Errorf("unknown (TRR) rtype %q", rawRec.Type)
}
err := rt.PopulateFromRaw(rec, rawRec.Args, rec.Metadata, effectiveOrigin(rec.SubDomain, dc.Name))
if err != nil {
return fmt.Errorf("%s (%q, dom=%q) record error: %w",
rawRec.Type,
rec.Name,
dc.Name,
err)
}
// Free memeory:
clear(rawRec.Args)
rawRec.Args = nil
if rawRec.EnsureAbsent {
dc.EnsureAbsent = append(dc.EnsureAbsent, rec)
} else {
dc.Records = append(dc.Records, rec)
}
}
dc.RawRecords = nil
}
return nil
}
// effectiveOrigin returns the effective origin given a "subdomain" and an
// "origin". The concept of a subdomain is only relevant in dnsconfig.js and
// RawRecordConfig. In the RecordConfig, the "Name" field is the full name
// (minor the dc.Name) and any .Target or other fields are FQDNs or relative to
// the effective origin.
func effectiveOrigin(sub, origin string) string {
if sub == "" {
return origin
}
return sub + "." + origin
}