adguardhome/rules.go
Rama main · réplica de lectura del repositorio original.
package adguardhome
import (
"encoding/json"
"errors"
"fmt"
"strings"
log "github.com/sirupsen/logrus"
"sigs.k8s.io/external-dns/endpoint"
)
type ruleFormat struct {
formatRules func([]*endpoint.Endpoint, string) ([]string, error)
needsMigration func(string) bool
}
func parseRules(rules []string, suffix string) (endpoints []*endpoint.Endpoint, unmanaged []string, err error) {
unmanaged = make([]string, 0)
for i := 0; i < len(rules); i++ {
rule := strings.TrimSpace(rules[i])
e, parseErr := parseRule(rule, suffix)
if errors.Is(parseErr, errNotManaged) {
unmanaged = append(unmanaged, rules[i])
continue
}
if errors.Is(parseErr, errArtificialRecord) {
continue
}
if parseErr != nil {
return nil, nil, fmt.Errorf("failed to parse rule %s: %w", rule, parseErr)
}
if strings.HasPrefix(rule, "! ") {
rewrite, err := dnsRewriteRule(e)
if err != nil {
return nil, nil, fmt.Errorf("invalid rewrite metadata: %w", err)
}
if i+1 >= len(rules) {
return nil, nil, fmt.Errorf("missing DNS rewrite after metadata: %s", rule)
}
active := strings.TrimSpace(rules[i+1])
if active != rewrite {
return nil, nil, fmt.Errorf("mismatched DNS rewrite after metadata: %s", rule)
}
i++
}
endpoints = append(endpoints, e)
}
return endpoints, unmanaged, nil
}
func parseRule(rule, suffix string) (*endpoint.Endpoint, error) {
rule = strings.TrimSpace(rule)
if data, ok := strings.CutPrefix(rule, "! "+suffix+" "); ok {
var e endpoint.Endpoint
if err := json.Unmarshal([]byte(data), &e); err != nil {
return nil, fmt.Errorf("invalid TXT metadata: %w", err)
}
if e.RecordType != endpoint.RecordTypeTXT || e.DNSName == "" || len(e.Targets) != 1 {
return nil, fmt.Errorf("TXT metadata must contain a hostname and one TXT target")
}
return &e, nil
}
if strings.HasPrefix(rule, "! "+managedBy) {
return nil, errNotManaged
}
_, tail, managed := strings.Cut(rule, suffix)
for managed && tail != "" {
if strings.HasPrefix(tail, ";labels=") && json.Valid([]byte(strings.TrimPrefix(tail, ";labels="))) {
break
}
_, next, found := strings.Cut(tail, suffix)
if !found {
break
}
tail = next
}
if !managed || (tail != "" && !strings.HasPrefix(tail, ";labels=")) {
return nil, errNotManaged
}
rule = strings.TrimPrefix(rule, "! ")
// Ignore artificial rules that we manage and will reconstruct
if strings.HasPrefix(rule, "@@||") {
return nil, errArtificialRecord
}
// Extract labels from the rule if present
var labels endpoint.Labels
ruleWithoutLabels := rule
if tail != "" {
labelsJSON := strings.TrimPrefix(tail, ";labels=")
labels = make(endpoint.Labels)
if err := json.Unmarshal([]byte(labelsJSON), &labels); err != nil {
log.Warnf("failed to parse labels from rule %s: %v", rule, err)
labels = nil
}
ruleWithoutLabels = strings.TrimSuffix(rule, tail)
}
if strings.HasPrefix(rule, "#") {
r := &endpoint.Endpoint{
RecordType: endpoint.RecordTypeTXT,
Labels: labels,
}
content := strings.TrimSuffix(strings.TrimPrefix(ruleWithoutLabels, "# "), " "+suffix)
separator := strings.LastIndexByte(content, ' ')
if separator < 0 || separator == len(content)-1 {
return nil, fmt.Errorf("invalid rule: %s", rule)
}
r.DNSName = content[separator+1:]
r.Targets = endpoint.Targets{content[:separator]}
return r, nil
}
parts := strings.SplitN(ruleWithoutLabels, " ", 3)
if len(parts) != 3 {
return nil, fmt.Errorf("invalid rule: %s", rule)
}
r := &endpoint.Endpoint{
RecordType: endpoint.RecordTypeA,
DNSName: parts[1],
Targets: endpoint.Targets{parts[0]},
Labels: labels,
}
return r, nil
}
func endpointToString(e *endpoint.Endpoint, suffix string) string {
// Serialize labels if present
labelsSuffix := ""
if len(e.Labels) > 0 {
labelsJSON, err := json.Marshal(e.Labels)
if err == nil {
labelsSuffix = fmt.Sprintf(";labels=%s", string(labelsJSON))
}
}
if e.RecordType == endpoint.RecordTypeTXT {
return fmt.Sprintf("# %s %s %s%s", e.Targets[0], e.DNSName, suffix, labelsSuffix)
}
return fmt.Sprintf("%s %s #%s%s", e.Targets[0], e.DNSName, suffix, labelsSuffix)
}