diff --git a/README.md b/README.md index 9e18bcb..8bdbb53 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,21 @@ Remark: if the target\_zone is not managed with powerdns\_zone resource, powerdn Passwords can be passed either as plain-text strings or as [Puppet's Sensitive type](https://www.puppet.com/docs/puppet/7/lang_data_sensitive.html) when appropriate encrypted backend is configured on Puppet server. +### Manage autoprimaries (automatic provisioning of secondaries) +It's possible to manage the the 'autoprimaries' with puppet (For a decription of the autoprimary functionality in +powerdns see [powerdns manual](https://doc.powerdns.com/authoritative/modes-of-operation.html#autoprimary-automatic-provisioning-of-secondaries). +The autoprimaries are set with the powerdns\_autoprimary resource. As an example we add the primary 1.2.3.4 named ns1.example.org whith the account 'test' +``` yaml +powerdns_autoprimary{'1.2.3.4@ns1.example.org': + ensure => 'present', + account => 'test', +} +``` +As an alternative, you can set the autoprimaries parameter of the powerdns class to achive the same (eg. if you use hiera). + +For removal of an autoprimary set ensure to 'absent' or set the parameter purge\_autoprimaries of the powerdns class to true which willa +remove all autoprimaries that are not present in the puppet manifest. + ## Reference ### Parameters diff --git a/lib/puppet/provider/powerdns_autoprimary/pdnsutil.rb b/lib/puppet/provider/powerdns_autoprimary/pdnsutil.rb new file mode 100644 index 0000000..c826a60 --- /dev/null +++ b/lib/puppet/provider/powerdns_autoprimary/pdnsutil.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +Puppet::Type.type(:powerdns_autoprimary).provide(:pdnsutil) do + desc "@summary provider which provides autprimary, + using the pdnsutil command." + + commands pdnsutil: 'pdnsutil' + + def initialize(value = {}) + super(value) + @property_flush = {} + end + + def self.instances + pdnsutil('list-autoprimaries').split("\n").map do |line| + raise Puppet::Error, "Cannot parse invalid autoprimary line: #{line}" unless line =~ %r{^IP=(\S+),\s+NS=(\S+),\s+account=(\S*)$} + new( + ensure: :present, + name: Regexp.last_match(1) + '@' + Regexp.last_match(2), + account: Regexp.last_match(3), + ) + end + end + + def self.prefetch(resources) + autoprimaries = instances + resources.each_key do |name| + if (provider = autoprimaries.find { |aprim| aprim.name == name }) + resources[name].provider = provider + end + end + end + + def create + pdnsutil('add-autoprimary', resource[:name].split('@'), resource[:account]) + @property_hash[:ensure] = :present + end + + def destroy + pdnsutil('remove-autoprimary', resource[:name].split('@')) + @property_hash[:ensure] = :absent + end + + def account + @property_hash[:account] + end + + def account=(account) + pdnsutil('remove-autoprimary', resource[:name].split('@')) + pdnsutil('add-autoprimary', resource[:name].split('@'), account) + @property_hash[:ensure] = account + end + + def exists? + @property_hash[:ensure] == :present + end + + def flush + return if @property_flush.empty? + content = @property_flush[:content] || @resource[:content] + virsh_define(content) + @property_flush.clear + end +end diff --git a/lib/puppet/type/powerdns_autoprimary.rb b/lib/puppet/type/powerdns_autoprimary.rb new file mode 100644 index 0000000..ef8b28f --- /dev/null +++ b/lib/puppet/type/powerdns_autoprimary.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'puppet/parameter/boolean' + +Puppet::Type.newtype(:powerdns_autoprimary) do + @doc = 'ensures autoprimary servers (for automatic provisioning of secondaries) + ' + + ensurable do + desc 'Manage the state of this type.' + defaultvalues + defaultto :present + end + + newparam(:name, namevar: true) do + desc 'name of the autoprimary in the format IP@NAMESERVER' + + newvalues(%r{^\S+@\S+$}) + end + + newproperty(:account) do + desc 'account to ensure (default to no account)' + defaultto '' + + validate do |value| + raise ArgumentError, 'ip needs to be a string' unless value.is_a?(String) + end + end + + autorequire(:service) do + ['pdns'] + end +end diff --git a/manifests/init.pp b/manifests/init.pp index ea6e404..8afa530 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -1,4 +1,10 @@ # powerdns +# +# @param autoprimaries +# Hash of autoprimaries the ensurce (with resource powerdns_autoprimary) +# @param purge_autoprimaries +# Set this to true if you like to purge all autoprimaries not managed with puppet +# class powerdns ( Boolean $authoritative = true, Boolean $recursor = false, @@ -25,6 +31,8 @@ String[1] $mysql_schema_file = $powerdns::params::mysql_schema_file, String[1] $pgsql_schema_file = $powerdns::params::pgsql_schema_file, Hash $forward_zones = {}, + Powerdns::Autoprimaries $autoprimaries = {}, + Boolean $purge_autoprimaries = false, ) inherits powerdns::params { # Do some additional checks. In certain cases, some parameters are no longer optional. if $authoritative { @@ -68,4 +76,11 @@ $powerdns_recursor_defaults = { 'type' => 'recursor' } create_resources(powerdns::config, $powerdns_recursor_config, $powerdns_recursor_defaults) } + + if $purge_autoprimaries { + resources { 'powerdns_autoprimary': + purge => true, + } + } + create_resources('powerdns_autoprimary', $autoprimaries) } diff --git a/spec/type_aliases/autoprimaries_spec.rb b/spec/type_aliases/autoprimaries_spec.rb new file mode 100644 index 0000000..1e6f792 --- /dev/null +++ b/spec/type_aliases/autoprimaries_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe 'Powerdns::Autoprimaries' do + describe 'valid types' do + context 'with valid types' do + [ + {}, + { '1.2.3.4@ns1.example.org' => {} }, + { '2001:db8::1@ns1.example.org' => { 'account' => 'test' } }, + ].each do |value| + describe value.inspect do + it { is_expected.to allow_value(value) } + end + end + end + end + + describe 'invalid types' do + context 'with garbage inputs' do + [ + true, + nil, + { 'foo' => 'bar' }, + '55555', + { '@ns1.example.org' => {} }, + { '1.2.3.4@' => {} }, + ].each do |value| + describe value.inspect do + it { is_expected.not_to allow_value(value) } + end + end + end + end +end diff --git a/spec/unit/puppet/provider/powerdns_autoprimary/pdnsutil_spec.rb b/spec/unit/puppet/provider/powerdns_autoprimary/pdnsutil_spec.rb new file mode 100644 index 0000000..266f24c --- /dev/null +++ b/spec/unit/puppet/provider/powerdns_autoprimary/pdnsutil_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'spec_helper' + +provider_class = Puppet::Type.type(:powerdns_autoprimary).provider(:pdnsutil) + +describe provider_class do + let(:resource) do + Puppet::Type::Powerdns_autoprimary.new( + name: '1.2.3.4@ns1.example.com', + provider: described_class.name, + ) + end + + let(:provider) { provider_class.new(resource) } + + it 'has its name set' do + expect(resource[:name]).to eq('1.2.3.4@ns1.example.com') + end +end diff --git a/spec/unit/puppet/type/powerdns_autoprimary_spec.rb b/spec/unit/puppet/type/powerdns_autoprimary_spec.rb new file mode 100644 index 0000000..110f87b --- /dev/null +++ b/spec/unit/puppet/type/powerdns_autoprimary_spec.rb @@ -0,0 +1,13 @@ +require 'puppet' +require 'puppet/type/powerdns_autoprimary' + +describe Puppet::Type.type(:powerdns_autoprimary) do + let!(:autoprimary) { Puppet::Type.type(:powerdns_autoprimary).new(name: '1.2.3.4@ns1.example.com') } + + it 'has its name set' do + expect(autoprimary[:name]).to eq('1.2.3.4@ns1.example.com') + end + it 'has set account to empty string' do + expect(autoprimary[:account]).to eq('') + end +end diff --git a/types/autoprimaries.pp b/types/autoprimaries.pp new file mode 100644 index 0000000..d4840f1 --- /dev/null +++ b/types/autoprimaries.pp @@ -0,0 +1,6 @@ +type Powerdns::Autoprimaries=Hash[ + Pattern[/.+@.+/], + Struct[{ + account => Optional[String], + }] +]