provisioning-core/nulib/domain/coredns/corefile.nu

359 lines
9.9 KiB
Text
Raw Normal View History

# CoreDNS Corefile Generator
# Generates and manages Corefile configuration for CoreDNS
# ../utils/log.nu does not exist — dangling import removed (ADR-025 L2).
# Generate Corefile from configuration
export def generate-corefile [
config: record # CoreDNS configuration
] -> string {
log debug $"Generating Corefile from config: ($config)"
let local_config = $config.local? | default {}
let zones = $local_config.zones? | default ["provisioning.local"]
let port = $local_config.port? | default 5353
let zones_path = $local_config.zones_path? | default "~/.provisioning/coredns/zones"
let upstream = $config.upstream? | default ["8.8.8.8", "1.1.1.1"]
let enable_logging = $config.enable_logging? | default true
let enable_metrics = $config.enable_metrics? | default true
let metrics_port = $config.metrics_port? | default 9153
let dynamic_enabled = $config.dynamic_updates?.enabled? | default true
let dynamic_endpoint = $config.dynamic_updates?.api_endpoint? | default "http://localhost:8080/dns"
mut corefile_content = []
# Generate zone blocks
for zone in $zones {
let zone_block = generate-zone-block $zone $port $zones_path $enable_logging $dynamic_enabled
$corefile_content = ($corefile_content | append $zone_block)
}
# Generate catch-all forward block
let forward_block = generate-forward-block $port $upstream $enable_logging
$corefile_content = ($corefile_content | append $forward_block)
# Generate metrics block if enabled
if $enable_metrics {
let metrics_block = generate-metrics-block $metrics_port
$corefile_content = ($corefile_content | append $metrics_block)
}
$corefile_content | str join "\n\n"
}
# Generate zone block for Corefile
def generate-zone-block [
zone: string
port: int
zones_path: string
enable_logging: bool
dynamic_enabled: bool
] -> string {
let expanded_path = $zones_path | path expand
mut plugins = []
# File plugin
$plugins = ($plugins | append $" file ($expanded_path)/($zone).zone")
# Dynamic updates via HTTP (if enabled)
if $dynamic_enabled {
$plugins = ($plugins | append " # Dynamic DNS updates via orchestrator API")
$plugins = ($plugins | append " # Updates handled through external API")
}
# Logging
if $enable_logging {
$plugins = ($plugins | append " log")
}
# Error handling
$plugins = ($plugins | append " errors")
# Cache
$plugins = ($plugins | append " cache 30")
let plugins_str = $plugins | str join "\n"
$"($zone):($port) {
($plugins_str)
}"
}
# Generate forward block for Corefile
def generate-forward-block [
port: int
upstream: list<string>
enable_logging: bool
] -> string {
let upstream_str = $upstream | str join " "
mut plugins = []
$plugins = ($plugins | append $" forward . ($upstream_str)")
if $enable_logging {
$plugins = ($plugins | append " log")
}
$plugins = ($plugins | append " errors")
$plugins = ($plugins | append " cache 30")
let plugins_str = $plugins | str join "\n"
$".:($port) {
($plugins_str)
}"
}
# Generate metrics block for Corefile
def generate-metrics-block [
metrics_port: int
] -> string {
$".:($metrics_port) {
prometheus
errors
}"
}
# Generate zone file for domain
export def generate-zone-file [
zone_name: string # Zone name (e.g., "provisioning.local")
records: list # List of DNS records
config: record # CoreDNS configuration
] -> string {
log debug $"Generating zone file for ($zone_name)"
let default_ttl = $config.default_ttl? | default 3600
let admin_email = $"admin.($zone_name)"
let serial = date now | format date "%Y%m%d01" # YYYYMMDDnn format
mut zone_content = []
# Origin and TTL
$zone_content = ($zone_content | append $"$ORIGIN ($zone_name).")
$zone_content = ($zone_content | append $"$TTL ($default_ttl)")
$zone_content = ($zone_content | append "")
# SOA record
let soa_block = generate-soa-record $zone_name $admin_email $serial
$zone_content = ($zone_content | append $soa_block)
$zone_content = ($zone_content | append "")
# NS record
$zone_content = ($zone_content | append $"@ IN NS ns1.($zone_name).")
$zone_content = ($zone_content | append "")
# A record for nameserver
$zone_content = ($zone_content | append "ns1 IN A 127.0.0.1")
$zone_content = ($zone_content | append "")
# User-defined records
if ($records | is-not-empty) {
$zone_content = ($zone_content | append "; Infrastructure records")
for record in $records {
let record_line = generate-record-line $record
$zone_content = ($zone_content | append $record_line)
}
}
$zone_content | str join "\n"
}
# Generate SOA record
def generate-soa-record [
zone_name: string
admin_email: string
serial: string
] -> string {
$"@ IN SOA ns1.($zone_name). ($admin_email). (
($serial) ; Serial
3600 ; Refresh
1800 ; Retry
604800 ; Expire
86400 ) ; Minimum TTL"
}
# Generate DNS record line
def generate-record-line [
record: record
] -> string {
let name = $record.name
let type = $record.type
let value = $record.value
let ttl = $record.ttl? | default ""
let priority = $record.priority? | default ""
let comment = $record.comment? | default ""
mut parts = []
# Name (padded to 16 chars)
$parts = ($parts | append ($name | fill -a left -w 16))
# TTL (optional)
if ($ttl != "") {
$parts = ($parts | append ($ttl | into string | fill -a left -w 8))
} else {
$parts = ($parts | append ("" | fill -a left -w 8))
}
# Class (always IN)
$parts = ($parts | append "IN")
# Type
$parts = ($parts | append ($type | fill -a left -w 8))
# Priority (for MX and SRV)
if ($priority != "") {
$parts = ($parts | append ($priority | into string))
}
# Value
$parts = ($parts | append $value)
let record_line = $parts | str join " "
# Add comment if present
if ($comment != "") {
$"($record_line) ; ($comment)"
} else {
$record_line
}
}
# Validate Corefile syntax
export def validate-corefile [
corefile_path: string # Path to Corefile
] -> record {
log debug $"Validating Corefile at ($corefile_path)"
let expanded_path = $corefile_path | path expand
if not ($expanded_path | path exists) {
return {
valid: false
errors: ["Corefile not found"]
warnings: []
}
}
mut errors = []
mut warnings = []
let content = open $expanded_path
# Basic validation checks
if ($content | str contains "file ") {
# Check if zone files are referenced
log debug "Zone files referenced in Corefile"
} else {
$warnings = ($warnings | append "No zone files referenced")
}
if ($content | str contains "forward ") {
log debug "Forward plugin configured"
} else {
$warnings = ($warnings | append "No forward plugin configured")
}
# Check for balanced braces
let open_braces = $content | str replace -a -r '[^{]' '' | str length
let close_braces = $content | str replace -a -r '[^}]' '' | str length
if $open_braces != $close_braces {
$errors = ($errors | append "Unbalanced braces in Corefile")
}
{
valid: ($errors | is-empty)
errors: $errors
warnings: $warnings
}
}
# Add plugin to Corefile
export def add-corefile-plugin [
corefile_path: string # Path to Corefile
zone: string # Zone name
plugin_config: string # Plugin configuration line
] -> bool {
log debug $"Adding plugin to Corefile zone ($zone)"
let expanded_path = $corefile_path | path expand
if not ($expanded_path | path exists) {
log error $"Corefile not found at ($expanded_path)"
return false
}
let content = open $expanded_path
# Find zone block and add plugin
let zone_pattern = $"($zone):\\d+ \\{"
if ($content | str contains -r $zone_pattern) {
# Insert plugin before closing brace
let updated = $content | str replace -r $"($zone_pattern)(.*?)\\}" $"$1$2 ($plugin_config)\n}"
$updated | save -f $expanded_path
log info $"Added plugin to ($zone) in Corefile"
true
} else {
log error $"Zone ($zone) not found in Corefile"
false
}
}
# Write Corefile to disk
export def write-corefile [
corefile_path: string # Path to write Corefile
content: string # Corefile content
] -> bool {
log debug $"Writing Corefile to ($corefile_path)"
let expanded_path = $corefile_path | path expand
let parent_dir = $expanded_path | path dirname
# Create parent directory if needed
if not ($parent_dir | path exists) {
mkdir $parent_dir
}
let result = (do {
$content | save -f $expanded_path
} | complete)
if $result.exit_code == 0 {
log info $"Corefile written to [$expanded_path]"
true
} else {
log error $"Failed to write Corefile to [$expanded_path]"
false
}
}
# Read Corefile from disk
export def read-corefile [
corefile_path: string # Path to Corefile
] -> string {
let expanded_path = $corefile_path | path expand
if not ($expanded_path | path exists) {
log error $"Corefile not found at ($expanded_path)"
return ""
}
open $expanded_path
}
# Update Corefile with new configuration
export def update-corefile [
config: record # CoreDNS configuration
corefile_path: string # Path to Corefile
] -> bool {
log info "Updating Corefile with new configuration"
let new_content = generate-corefile $config
write-corefile $corefile_path $new_content
}