diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/facter/dns_zone_serial.rb | 11 | ||||
-rw-r--r-- | lib/puppet/provider/dns_record2/named.rb | 137 | ||||
-rw-r--r-- | lib/puppet/provider/dns_zone2/named.rb | 96 | ||||
-rw-r--r-- | lib/puppet/type/dns_record2.rb | 93 | ||||
-rw-r--r-- | lib/puppet/type/dns_zone2.rb | 133 |
5 files changed, 469 insertions, 1 deletions
diff --git a/lib/facter/dns_zone_serial.rb b/lib/facter/dns_zone_serial.rb index 6f25148..6575757 100644 --- a/lib/facter/dns_zone_serial.rb +++ b/lib/facter/dns_zone_serial.rb @@ -1,5 +1,13 @@ require 'resolv' +def ensure_period(s) + if s[-1] == '.' + s + else + "#{s}." + end +end + Facter.add(:dns_zone_serial) do confine do Facter::Util::Resolution.which('named-checkconf') @@ -13,7 +21,8 @@ Facter.add(:dns_zone_serial) do .filter { |zone| zone.last == 'master' } zone_map = {} zones.map do |zone| - zone_map["#{zone[0]}."] = dns.getresource(zone[0], Resolv::DNS::Resource::IN::SOA).serial + z = ensure_period(zone[0]) + zone_map[z] = dns.getresource(z, Resolv::DNS::Resource::IN::SOA).serial end zone_map end diff --git a/lib/puppet/provider/dns_record2/named.rb b/lib/puppet/provider/dns_record2/named.rb new file mode 100644 index 0000000..61f4a96 --- /dev/null +++ b/lib/puppet/provider/dns_record2/named.rb @@ -0,0 +1,137 @@ +# require 'resolv' + +Puppet::Type.type(:dns_record2).provide(:named) do + def self.instances + objects = [] + # `named-checkconf -l`.split("\n").each do |zone| + # zonename, cls, view, _ = zone.split(' ') + # path = `rndc zonestatus #{zonename} #{cls} #{view} 2>/dev/null | awk -F' ' '/^files:/ { print $2 }'`.strip; + # `named-checkzone -q -D #{zonename} /var/named/#{path} | sed 's/[[:space:]]\+/ /g'`.split("\n").each do |record| + # key, ttl, cls, type, *value = record.split(' ') + # value = value.join(' ') + + # name = "#{zonename} #{cls} #{view} #{key} #{type} #{value}" + + # objects << new(:name => name, + # :key => key, + # :ttl => ttl, + # :class => cls, + # :type => type, + # :value => value) + # end + # end + objects + end + + def create + # print("Create\n") + end + + def destroy + # print("Destroy\n") + end + + def exists? + # print("record = [#{record().inspect}]\n") + record() != nil + end + + def type + resource[:type] + end + + def value + # print("Get old value (#{resource[:value]}, #{get(:value)})\n") + t = record() + case t + when NilClass + :absent + when String + t + else + "INVALID VALUE (#{t.class}, #{t})" + end + end + + def value=(v) + # print("Set new value (#{v})\n") + # resource[:value] = v + end + + def key + resource[:key] + end + + # def key=(v) + # resource[:key] = v + # end + + def zone + resource[:zone] + end + + def full_key + if resource[:key] == '@' + resource[:zone] + elsif resource[:key][-1] == '.' + resource[:key] + else + "#{resource[:key]}.#{resource[:zone]}" + end + end + + # def zone=(v) + # # set(:zone, v) + # resource[:zone] = v + # end + + def record + # dns = Resolv::DNS.new(nameserver: ['localhost']) + # type = { + # A: Resolv::DNS::Resource::IN::A, + # AAAA: Resolv::DNS::Resource::IN::AAAA, + # CNAME: Resolv::DNS::Resource::IN::CNAME, + # HINFO: Resolv::DNS::Resource::IN::HINFO, + # MINFO: Resolv::DNS::Resource::IN::MINFO, + # MX: Resolv::DNS::Resource::IN::MX, + # NS: Resolv::DNS::Resource::IN::NS, + # PTR: Resolv::DNS::Resource::IN::PTR, + # SOA: Resolv::DNS::Resource::IN::SOA, + # TXT: Resolv::DNS::Resource::IN::TXT, + # WKS: Resolv::DNS::Resource::IN::WKS, + # }[resource[:type]] + + # if type == nil + # nil + # else + # rs = dns.getresources(full_key, type) + # if rs.length == 0 + # nil + # else + # rs[0] + # end + # end + lines = `named-checkzone -j -q -D #{zone} /var/named/zones/#{zone}db` + .split("\n") + .map {|line| line.gsub(/[[:space:]]+/, ' ').split(' ') } + + matches = lines + .filter {|line| + line[0] == full_key and line[3].to_sym == resource[:type] + } + .map {|line| line[(4..)].join(' ') } + + + if matches.length == 0 + nil + elsif matches.length == 1 + matches[0] + else + matches + end + end + + def cls + 'IN' + end +end diff --git a/lib/puppet/provider/dns_zone2/named.rb b/lib/puppet/provider/dns_zone2/named.rb new file mode 100644 index 0000000..e249be6 --- /dev/null +++ b/lib/puppet/provider/dns_zone2/named.rb @@ -0,0 +1,96 @@ +Puppet::Type.type(:dns_zone2).provide(:named) do + def self.instances + `named-checkconf -l`.split("\n").map do |record| + name, cls, view, type = record.split(' '); + new(:name => name, :cls => cls, :view => view, :type => type) + end + end + + def create(records) + print("Create #{resource[:name]}\n") + write_zone records + end + + def destroy + print("Remove #{resource[:name]}\n") + end + + def refresh(records) + print("Refresh #{resource[:name]}\n") + write_zone records + end + + def exists? + # instances.find { |r| r.name == resource[:name] } + # resource[:ensure] == :present + `named-checkconf -l` + .split("\n") + .grep(/#{resource[:origin]} /) + .length > 0 + end + + def filename + "/var/named/zones/#{resource[:name]}db" + end + + def zone_content(records) + content = <<~EOF + ; File managed by Puppet. + ; Local changes WILL be overwritten + ; File last generated #{Time.now} + + $ORIGIN #{resource[:origin]} + $TTL #{resource[:default_ttl]} + + @ #{resource[:soa_ttl]} IN SOA #{resource[:mname]} #{resource[:rname]} ( + #{serial+1} ; serial + #{resource[:refresh]} ; refresh + #{resource[:retry]} ; retry + #{resource[:expire]} ; expire + #{resource[:negative_ttl]} ; Negative TTL + ) + EOF + + records + .filter {|r| r[:zone] == resource[:name] } + .group_by {|r| r[:type]} + .sort_by {|(type, _)| + # Bit of a hack, but ensures that SOA is always first, + # NS is after + # And the rest have stable order + { + SOA: 0, + NS: 1, + A: 2, + AAAA: 3, + CNAME: 4, + HINFO: 5, + MINFO: 6, + MX: 7, + PTR: 8, + TXT: 9, + WKS: 10, + }[type] + }.each{|(type, values)| + content += <<~EOF + ; #{type} Records + EOF + values.each {|val| + content += <<~EOF + #{val[:key]} #{val[:ttl]} IN #{val[:type]} #{val[:value]} + EOF + } + } + content + end + + def write_zone(content) + File.open(filename, 'w') do |file| + file.write content + end + end + + def serial + `rndc zonestatus #{name} | awk -F' ' '/^serial:/ { print $2 }'`.to_i + end +end diff --git a/lib/puppet/type/dns_record2.rb b/lib/puppet/type/dns_record2.rb new file mode 100644 index 0000000..2074782 --- /dev/null +++ b/lib/puppet/type/dns_record2.rb @@ -0,0 +1,93 @@ +Puppet::Type.newtype(:dns_record2) do + @doc = %q{ + } + + #"named-checkconf -j -D -F raw $zonename $zonefile" + + newproperty(:ensure) do + newvalue(:present) do + provider.create + end + + newvalue(:absent) do + provider.destroy + end + + defaultto :present + + def retrieve + if provider.exists? + :present + else + :absent + end + end + end + + def exists? + provider.exists? + end + + newparam(:name) do + end + + newproperty(:type) do + desc %q{ + Resource Record type, such as A, AAAA, ... + + Only a few types are supported, due to the DNS library only supporting these. + } + newvalues(:A, + :AAAA, + :CNAME, + :HINFO, + :MINFO, + :MX, + :NS, + :PTR, + :SOA, + :TXT, + :WKS, + ) + end + + newproperty(:value) do + desc %q{ + DNS payload. For example an IP address. + } + end + + newproperty(:key) do + desc %q{ + DNS key. + Such as 'www' + } + end + + newproperty(:zone) do + desc %q{ + Zone this record belongs to + } + + munge do |value| + if value[-1] == '.' + value + else + "#{value}." + end + end + end + + newproperty(:cls) do + desc %q{ + } + end + + newproperty(:ttl) do + end + + # autobefore('dns::zone') { value(:zone) } + autonotify('dns_zone2') { [value(:zone)] } + # autobefore('dns_zone2') { [value(:zone)] } + # TODO view +end diff --git a/lib/puppet/type/dns_zone2.rb b/lib/puppet/type/dns_zone2.rb new file mode 100644 index 0000000..6cee6f2 --- /dev/null +++ b/lib/puppet/type/dns_zone2.rb @@ -0,0 +1,133 @@ +Puppet::Type.newtype(:dns_zone2, :self_refresh => true) do + @doc = %q{ + } + + newproperty(:ensure) do + newvalue(:present) do + provider.write_zone (resource.should_content) + end + + # This should ideally remove the zone. This is however managed + # "higher" up in the puppet code. + # dns::init has a purge on the directory, and the named + # configuration file is fully managed by us. + # This means that decomissioning is as simple as removing the zone + # from the puppet environment. + newvalue(:absent) do + # provider.destroy + end + + defaultto :present + + def retrieve + if provider.exists? + :present + else + :absent + end + end + end + + def refresh + provider.write_zone(should_content) + end + + newparam(:origin, :namevar => true) do + munge do |value| + if value[-1] == '.' + value + else + "#{value}." + end + end + end + + newparam(:cls) do + defaultto :IN + end + + newparam(:view) do + defaultto '_default' + end + + newparam(:type) do + newvalues(:master, :slave, + :mirror, :hint, :stub, :static_stub, :forward, :redirect) + aliasvalue :primary, :master + aliasvalue :secondary, :slave + defaultto :master + end + + + newparam(:default_ttl) do + defaultto '300' + end + + newparam(:mname) do + munge do |value| + if value[-1] == '.' + value + else + "#{value}." + end + end + end + + newparam(:rname) do + munge do |value| + if value[-1] == '.' + value + else + "#{value}." + end + end + end + + newparam(:soa_ttl) do + end + + newparam(:refresh) do + end + + newparam(:retry) do + end + + newparam(:expire) do + end + + newparam(:negative_ttl) do + end + + # List of all DNS records (at all) + # + # Should ideally be limited to records in this zone here instead of + # further down. + def records + catalog.resources.filter do |r| + r.is_a?(Puppet::Type.type(:dns_record2)) + end + end + + # Create the file resource for us. + + # This prevents the directory purge on /var/named/zones from deleting us. + def generate + [Puppet::Type.type(:file).new({ + ensure: :present, + path: provider.filename, + })] + end + + def should_content + provider.zone_content(records) + end + + # def eval_generate + # content = should_content + # catalog.resource("File[/var/named/zones/#{self[:name]}db]")[:content] = content + # # provider.refresh (resource.records) + # [catalog.resource("File[/var/named/zones/#{self[:name]}db]")] + # end + + # autorequire(:file) {["/var/named/zones/#{self[:name]}db"]} +end |