feat: init commit with main func
This commit is contained in:
121
dns.go
Normal file
121
dns.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// ZoneFile is the in-memory representation of a dynamic OctoDNS zone YAML file.
|
||||
// Keys are subdomain labels (empty string = apex record).
|
||||
// Values are either a single record map or a list of record maps (for multi-type).
|
||||
type ZoneFile map[string]interface{}
|
||||
|
||||
// RecordStats counts the changes between two zone states.
|
||||
type RecordStats struct {
|
||||
Added int
|
||||
Removed int
|
||||
Changed int
|
||||
}
|
||||
|
||||
// ReadZoneFile parses an OctoDNS YAML zone file.
|
||||
// If the file does not exist, returns an empty ZoneFile (not an error).
|
||||
func ReadZoneFile(path string) (ZoneFile, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if os.IsNotExist(err) {
|
||||
return ZoneFile{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read zone file %q: %w", path, err)
|
||||
}
|
||||
var zf ZoneFile
|
||||
if err := yaml.Unmarshal(data, &zf); err != nil {
|
||||
return nil, fmt.Errorf("parse zone file %q: %w", path, err)
|
||||
}
|
||||
if zf == nil {
|
||||
zf = ZoneFile{}
|
||||
}
|
||||
return zf, nil
|
||||
}
|
||||
|
||||
// WriteZoneFile writes an OctoDNS YAML zone file, creating parent directories as needed.
|
||||
// yaml.v3 sorts map keys alphabetically, ensuring deterministic output.
|
||||
func WriteZoneFile(path string, zf ZoneFile) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return fmt.Errorf("create directory for zone file: %w", err)
|
||||
}
|
||||
data, err := yaml.Marshal(map[string]interface{}(zf))
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal zone file: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(path, data, 0o644); err != nil {
|
||||
return fmt.Errorf("write zone file %q: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildZoneFile constructs the desired zone file from the set of active subdomains.
|
||||
func BuildZoneFile(subdomains map[string]struct{}, cfg *Config) ZoneFile {
|
||||
zf := make(ZoneFile, len(subdomains))
|
||||
for sub := range subdomains {
|
||||
zf[sub] = buildRecord(cfg)
|
||||
}
|
||||
return zf
|
||||
}
|
||||
|
||||
// buildRecord creates the YAML-serialisable record value for a subdomain.
|
||||
// Returns a single-record map (OctoDNS simple syntax) when only IPv4 is configured,
|
||||
// or a two-element list (OctoDNS list syntax) when both A and AAAA are required.
|
||||
func buildRecord(cfg *Config) interface{} {
|
||||
aRec := map[string]interface{}{
|
||||
"ttl": cfg.RecordTTL,
|
||||
"type": "A",
|
||||
"values": []string{cfg.PublicIP},
|
||||
}
|
||||
if cfg.PublicIPv6 == "" {
|
||||
return aRec
|
||||
}
|
||||
aaaaRec := map[string]interface{}{
|
||||
"ttl": cfg.RecordTTL,
|
||||
"type": "AAAA",
|
||||
"values": []string{cfg.PublicIPv6},
|
||||
}
|
||||
return []interface{}{aRec, aaaaRec}
|
||||
}
|
||||
|
||||
// DiffZoneFile computes the difference between the current (on-disk) and desired zone files.
|
||||
// It reports how many records were added, removed, or changed.
|
||||
func DiffZoneFile(current, desired ZoneFile) RecordStats {
|
||||
var stats RecordStats
|
||||
for k := range desired {
|
||||
if _, exists := current[k]; !exists {
|
||||
stats.Added++
|
||||
}
|
||||
}
|
||||
for k := range current {
|
||||
if _, exists := desired[k]; !exists {
|
||||
stats.Removed++
|
||||
}
|
||||
}
|
||||
// Detect value changes (e.g. IP or TTL updated in config).
|
||||
for k, dv := range desired {
|
||||
cv, exists := current[k]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
// Marshal both sides to compare: handles nested maps and slices uniformly.
|
||||
cs, _ := yaml.Marshal(cv)
|
||||
ds, _ := yaml.Marshal(dv)
|
||||
if string(cs) != string(ds) {
|
||||
stats.Changed++
|
||||
}
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
// ZoneFilePath returns the absolute path to the dynamic zone YAML file for a given zone.
|
||||
func ZoneFilePath(cfg *Config, zone string) string {
|
||||
return filepath.Join(cfg.RepoPath, cfg.DynamicDir, zone+".yaml")
|
||||
}
|
||||
Reference in New Issue
Block a user