225bdc6020
ci/woodpecker/pr/ruby-validate Pipeline was successful
ci/woodpecker/pr/puppet-lint Pipeline was successful
ci/woodpecker/pr/yamllint Pipeline was successful
ci/woodpecker/pr/bolt-validate Pipeline was successful
ci/woodpecker/pr/erb-validate Pipeline was successful
ci/woodpecker/pr/epp-validate Pipeline was successful
ci/woodpecker/pr/ruby-check Pipeline was successful
ci/woodpecker/pr/puppet-validate Pipeline was successful
Publish records both ways during the k8s cutover, and expose expected vs deployed records for drift detection. - profiles::dns::updater + ::record: manage_nsupdate and manage_export booleans (both default on); export keeps the legacy master flow, so disable it once k8s is authoritative - dns_records fact: parses the expected records file and digs the authoritative server for each, reporting expected / in_sync / drift (plus dns_records_insync boolean); updater writes the server address to /var/lib/dns-updater/server for the fact - hiera: manage_export/manage_nsupdate = true (cutover)
89 lines
2.6 KiB
Ruby
89 lines
2.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# lib/facter/dns_records.rb
|
|
#
|
|
# Reports this host's expected DNS records (assembled by profiles::dns::updater
|
|
# into its records file) versus what is currently deployed on the authoritative
|
|
# server, so puppet can detect drift and re-apply.
|
|
#
|
|
# Structured value:
|
|
# { server, count, expected => [{zone,fqdn,type,ttl,value}], in_sync,
|
|
# drift => [{...,deployed => [...]}] }
|
|
|
|
# Helpers for the dns_records fact.
|
|
module DnsRecordsFact
|
|
RECORDS_FILE = '/var/lib/dns-updater/records'
|
|
SERVER_FILE = '/var/lib/dns-updater/server'
|
|
|
|
module_function
|
|
|
|
# normalise a value for comparison: strip, drop trailing dot, downcase
|
|
def norm(value)
|
|
value.to_s.strip.chomp('.').downcase
|
|
end
|
|
|
|
def server
|
|
File.exist?(SERVER_FILE) ? File.read(SERVER_FILE).strip : nil
|
|
end
|
|
|
|
# a name relative to a zone (or @) as a fully-qualified name
|
|
def to_fqdn(name, zone)
|
|
return "#{zone}." if name.to_s.empty? || name == '@'
|
|
|
|
"#{name}.#{zone}."
|
|
end
|
|
|
|
# parse one "zone|name|type|ttl|value" line into a record hash (nil to skip)
|
|
def parse_line(line)
|
|
line = line.strip
|
|
return nil if line.empty? || line.start_with?('#')
|
|
|
|
zone, name, type, ttl, value = line.split('|', 5)
|
|
return nil unless zone && type && value
|
|
|
|
{ 'zone' => zone, 'fqdn' => to_fqdn(name, zone), 'type' => type, 'ttl' => ttl, 'value' => value }
|
|
end
|
|
|
|
# parse the records file into record hashes
|
|
def expected
|
|
return [] unless File.exist?(RECORDS_FILE)
|
|
|
|
File.readlines(RECORDS_FILE).filter_map { |line| parse_line(line) }
|
|
end
|
|
|
|
# the values currently deployed for a record, per the authoritative server
|
|
def deployed(record, srv)
|
|
cmd = ['dig', '+short', '+time=2', '+tries=1']
|
|
cmd << "@#{srv}" if srv && !srv.empty?
|
|
cmd += [record['fqdn'], record['type']]
|
|
out = Facter::Core::Execution.execute(cmd.join(' '), on_fail: '')
|
|
out.to_s.split("\n").map { |line| norm(line) }.reject(&:empty?)
|
|
end
|
|
|
|
def report
|
|
srv = server
|
|
exp = expected
|
|
drift = exp.filter_map do |record|
|
|
dep = deployed(record, srv)
|
|
record.merge('deployed' => dep) unless dep.include?(norm(record['value']))
|
|
end
|
|
{ 'server' => srv, 'count' => exp.length, 'expected' => exp, 'in_sync' => drift.empty?, 'drift' => drift }
|
|
end
|
|
end
|
|
|
|
Facter.add(:dns_records) do
|
|
confine kernel: 'Linux'
|
|
setcode do
|
|
File.exist?(DnsRecordsFact::RECORDS_FILE) ? DnsRecordsFact.report : nil
|
|
end
|
|
end
|
|
|
|
# Convenience boolean for `if $facts['dns_records_insync']` guards.
|
|
Facter.add(:dns_records_insync) do
|
|
confine kernel: 'Linux'
|
|
setcode do
|
|
v = Facter.value(:dns_records)
|
|
v.nil? ? nil : v['in_sync']
|
|
end
|
|
end
|