From 1c52a0265fa4da7e707e64af1d7e039d85066673 Mon Sep 17 00:00:00 2001 From: Yarden Bar Date: Thu, 25 Aug 2016 17:16:12 +0300 Subject: [PATCH 1/8] Make DynECT provider use Fog --- README.md | 14 +- lib/puppet/provider/dns_record/dynect.rb | 273 ++++++++++------------- 2 files changed, 128 insertions(+), 159 deletions(-) diff --git a/README.md b/README.md index d385b2e..e1fcce2 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This is a DNS record management module with support for creating records in DNSi ### Requirements -The gems 'rest-client' is needed to manage DynECT, while fog is needed to manage everything else except bind. +The 'fog' gem is needed to manage all providers except Bind. For Bind, you'll need to have a key configured for DDNS - https://wiki.debian.org/DDNS has more information @@ -80,6 +80,16 @@ dns_record { "test-1a-record.puppetware.org": ttl => '4800', } +# content can also accept array + +dns_record { "test-1a-record.puppetware.org": + ensure => present + domain => 'puppetware.org', + content => ['172.16.100.150', '172.16.100.134'], + type => 'A', + ttl => '4800', +} + dns_record { "test-cname.puppetware.org": ensure => present domain => 'puppetware.org', @@ -124,7 +134,7 @@ For the acceptance test, set up a few environment variables to ensure no issues. #####`type` *Required* The type of the DNS record. Accepts A, TXT, and CNAME for dynect, all types for bind9. #####`content` -*Required* The value of the DNS record. Can accept an array for bind9. +*Required* The value of the DNS record. Can accept an array for bind9 and DynECT. ## Limitations diff --git a/lib/puppet/provider/dns_record/dynect.rb b/lib/puppet/provider/dns_record/dynect.rb index bbfe807..bca10ed 100644 --- a/lib/puppet/provider/dns_record/dynect.rb +++ b/lib/puppet/provider/dns_record/dynect.rb @@ -1,9 +1,9 @@ -# Author:: Charles Dunbar +# Author:: Seekingalpha DevOps # Type Name:: dns_record # Provider:: dynect # -# Copyright 2015, Puppet Labs +# Copyright 2016, Seeking Alpha inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,178 +18,131 @@ # limitations under the License. # +require 'pp' + +module Dynect + module Connection + def dynect + @@dns ||= Fog::DNS.new({ + :provider => 'dynect', + :dynect_customer => @customername, + :dynect_username => @username, + :dynect_password => @password + }) + end + end +end + Puppet::Type.type(:dns_record).provide(:dynect) do desc "Manage DynECT records." - require 'json' - confine :feature => :rest_client - + confine :feature => :fog + include Dynect::Connection mk_resource_methods - - def self.instances - # Can't do anything - don't have credentials - end - - def self.get_token(resources) - url = "https://api.dynect.net/REST/Session/" - session_data = { :customer_name => resources.values[0][:customername], :user_name => resources.values[0][:username], :password => resources.values[0][:password] } - # Going to be reusing headers throughout the provider, has the auth_token - @headers = { "Content-Type" => 'application/json' } - response = RestClient.post(url,session_data.to_json,@headers) - # Parse and read the response, set new header to include auth code - obj = JSON.parse(response) - if obj['status'] == 'success' - auth_token = obj['data']['token'] - else - raise Puppet::Error, "Unable to authenticate to DynECT - check customer name, username and password are correct" - end - # Add Auth-Token to header - @headers = { "Content-Type" => 'application/json', 'Auth-Token' => auth_token } - end - - def self.prefetch(resources) - get_token(resources) - # Populate array with all resources - instances = [] - domains = [] - # Get unique list of domains and only grab those zones - resources.each do |name, resource| - domains << resource['domain'] unless domains.include?(resource['domain']) - end - domains.each do |dom| - url = "https://api.dynect.net/REST/AllRecord/#{dom}/" - response = RestClient.get(url,@headers) - obj = JSON.parse(response) - if obj['status'] != 'success' - #TODO error handle for dynect - end - instances << obj - end - resources.each do |name, resource| - Puppet.debug("prefetching for #{name}") - index = nil - objindex = nil - instances.each do |obj| - objindex = instances.index(obj) - # Find URL that contains the record ID, save it, and request more detailed information from dynect - index = obj['data'].index{|s| s.include?"#{resource[:type]}Record/#{resource[:domain]}/#{resource[:name]}"} - break unless index.nil? - end - if index.nil? - # Post and put used to determine if updating or creating a record - # Post == create, put == update - result = { :ensure => :absent} - result[:headers] = @headers - result[:ttl] = resource[:ttl] - result[:content] = resource[:content] - result[:type] = resource[:type] - result[:action] = "post" - resource.provider = new(result) - else - @url2 = "https://api.dynect.net#{instances[objindex]['data'][index]}" - response2 = RestClient.get(@url2,@headers) - obj2 = JSON.parse(response2) - if obj2['data']['fqdn'] == resource[:name] and obj2['data']['ttl'] == resource[:ttl].to_i and obj2['data']['rdata'] == set_rdata(resource) - result = { :ensure => :present } - result[:headers] = @headers - result[:url2] = @url2 - result[:ttl] = resource[:ttl] - result[:content] = resource[:content] - result[:type] = resource[:type] - resource.provider = new(result) - else - # Old data saved for puppet output - if obj2['data']['fqdn'] == resource[:name] and obj2['data']['ttl'] != resource[:ttl].to_i - @old_ttl = obj2['data']['ttl'] - end - if obj2['data']['fqdn'] == resource[:name] and obj2['data']['rdata'] != set_rdata(resource) - @old_rdata = obj2['data']['rdata'] - end - result = { :ensure => :present } - result[:headers] = @headers - result[:ttl] = obj2['data']['ttl'] - result[:content] = obj2['data']['rdata'].values[0].to_s - result[:type] = resource[:type] - result[:old_ttl] = @old_ttl unless @old_ttl.nil? - result[:old_rdata] = @old_rdata unless @old_rdata.nil? - result[:action] = "put" - resource.provider = new(result) - end + def self.instances(resources = nil) + if resources + resources.map do |res| + new({name:res[:name], + customername:res[:customername], + username:res[:username], + password:res[:password], + provider:'dynect', + type:res[:type], + ttl:res[:ttl], + content:Array(res[:content]), + domain:res[:domain], + ensure:res[:ensure], + require:res[:require] + }) end end end - def flush - Puppet.debug("flushing zone #{@resource[:domain]}") - if ! @property_hash.empty? && @property_hash[:ensure] != :absent - begin - Puppet.debug("Attempting to create record type #{resource[:type]} for #{resource[:name]} as #{resource[:content][0]}") if @property_hash[:action] == "post" - Puppet.debug("Attempting to edit record type #{resource[:type]} for #{resource[:name]} as #{resource[:content][0]}") if @property_hash[:action] == "put" - url = "https://api.dynect.net/REST/#{resource[:type]}Record/#{resource[:domain]}/#{resource[:name]}" - session_data = { :rdata => self.class.set_rdata(resource), :ttl => resource[:ttl] } - response = RestClient.send(@property_hash[:action].to_sym, url,session_data.to_json, @property_hash[:headers]) - obj = JSON.parse(response) - if obj['status'] == 'success' - # Publish the zone - url = "https://api2.dynect.net/REST/Zone/#{resource[:domain]}" - session_data = { "publish" => "true" } - response = RestClient.put(url,session_data.to_json,@property_hash[:headers]) - obj = JSON.parse(response) - if obj['status'] == 'success' - Puppet.info("DynECT: Created #{resource[:type]} record for #{resource[:name]} with ttl #{resource[:ttl]}") if @property_hash[:action] == "post" - if @property_hash[:old_ttl].nil? and @property_hash[:old_rdata] - Puppet.info("DynECT: Updated #{resource[:type]} record for #{resource[:name]} from #{@property_hash[:old_rdata].values[0]} to #{resource[:content][0]}") - elsif @property_hash[:old_ttl] and @property_hash[:old_rdata].nil? - Puppet.info("DynECT: Updated #{resource[:type]} record for #{resource[:name]} from ttl #{@property_hash[:old_ttl]} to #{resource[:ttl]}") - else - Puppet.info("DynECT: Updated #{resource[:type]} record for #{resource[:name]} from #{@property_hash[:old_rdata].values[0]} to #{resource[:content][0]} and ttl from #{@property_hash[:old_ttl]} to #{resource[:ttl]}") if @property_hash[:action] == "put" - end - end - end - rescue Excon::Errors::UnprocessableEntity - Puppet.info("DynECT: #{e.response.body}") - end - else - response = RestClient.delete(@property_hash[:url2],@property_hash[:headers]) - obj = JSON.parse(response) - if obj['status'] == 'success' - # Publish the zone - url = "https://api2.dynect.net/REST/Zone/#{resource[:domain]}" - session_data = { "publish" => "true" } - response = RestClient.put(url,session_data.to_json,@property_hash[:headers]) - obj = JSON.parse(response) - if obj['status'] == 'success' - Puppet.info("DynECT: destroyed #{resource[:type]} record for #{resource[:name]}") - end - end - end - @property_hash = resource.to_hash - end - - def self.set_rdata(resource) + def get_rdata(value) case resource[:type] - # Use strings over symbols because that's what dynect returns - # Makes compairison easier + # Use strings over symbols because that's what Dynect returns + # Makes comparison easier when "A" - return {"address" => "#{resource[:content][0]}"} + return {"address" => "#{value}"} when "CNAME" # Append trailing period if needed - resource[:content][0] << "." if resource[:content][0][-1,1] != "." - return {"cname" => "#{resource[:content][0]}"} + value << "." if value[-1,1] != "." + return {"cname" => "#{value}"} when "TXT" - return {"txtdata" => "#{resource[:content][0]}"} + return {"txtdata" => "#{value}"} end end - def self.post_resource_eval() - begin - url = "https://api2.dynect.net/REST/Session/" - response = RestClient.delete(url,@headers) - rescue => e - puts "DynECT error logging out - #{e}" + def flush + @customername, @username, @password = resource[:customername], resource[:username], resource[:password] + publish_zone = false + Puppet.debug("Flushing zone #{resource[:domain]}") + zone = dynect.zones.get(resource[:domain]) + content_dup = resource[:content].dup + Puppet.debug("content_dup: #{content_dup}") + case resource[:ensure] + when :present + existing = zone.records.all.select do |r| r.name == resource[:name] end + Puppet.debug("EXISTING: #{existing}") + + to_remove, existing = existing.partition do |r| + (!resource[:content].include?(r.rdata['address'])) && r.type == resource[:type] && r.name == resource[:name] + end + + Puppet.debug("EXISTING-2: #{existing}") + Puppet.debug("TO_REMOVE: #{to_remove}") + to_remove.each do |r| + Puppet.debug("Removing: #{r.inspect}") + r.destroy + publish_zone = true + end + existing.each do |r| + if r.type != resource[:type] + r.type = resource[:type] + needs_update = true + end + if (! r.ttl.nil?) && r.ttl != resource[:ttl] + r.ttl = resource[:ttl] + needs_update = true + end + if needs_update + Puppet.debug("Updating #{r.inspect}") + r.save + publish_zone = true + end + content_dup -= [r.rdata['address']] + end + Puppet.debug("content_dup-2: #{content_dup}") + if !content_dup.empty? + content_dup.each do |new_ip| + zone.records.new( + name:resource[:name], + type:resource[:type], + ttl:resource[:ttl], + rdata:get_rdata(new_ip) + ).save + publish_zone = true + end + end + when :absent + to_remove = zone.records.all.select do |r| + resource[:content].include?(r.rdata['address']) && r.type == resource[:type] && r.name == resource[:name] + end + Puppet.debug("Removing: #{resource[:name]}: #{to_remove}") + to_remove.each do |r| + r.destroy + publish_zone = true + end + else + Puppet.error(" Unknow ensure: #{resource[:ensure]}, #{resource}") + raise "Unknown ensure for dns_record" + end + if publish_zone + Puppet.debug("Publishing zone: #{zone.domain}") + zone.publish end end @@ -198,8 +151,14 @@ def create end def exists? - Puppet.debug("Evaluating #{resource[:name]}") - !(@property_hash[:ensure] == :absent or @property_hash.empty?) + Puppet.debug("Checking if exists #{resource}") + @customername, @username, @password = resource[:customername], resource[:username], resource[:password] + zone = dynect.zones.get(resource[:domain]) + existing = zone.records.all.select do |r| + r.name == resource[:name] && r.type == resource[:type] + end + Puppet.debug("EXISTING-3: #{existing}, content: #{resource[:content]}") + (resource[:content] - existing.map do |r| r.rdata['address'] end).empty? end def destroy From 91d3f967c9552d317b6eaef1d1c31cdabc96eec9 Mon Sep 17 00:00:00 2001 From: Yarden Bar Date: Tue, 13 Sep 2016 21:13:48 +0300 Subject: [PATCH 2/8] Format README.md Validate Puppet code Make 'ensure' be first in resource attributes --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e1fcce2..ea81d0b 100644 --- a/README.md +++ b/README.md @@ -45,19 +45,19 @@ dns_record { "test-2a-records.ops.puppetlabs.net": } dns_record { "test-cname.ops.puppetlabs.net": - domain => 'ops.puppetlabs.net', + ensure => present, + domain => 'ops.puppetlabs.net', content => 'test-1a-record.ops.puppetlabs.net', type => 'CNAME', ttl => '16000', - ensure => present } dns_record { "test-txt.ops.puppetlabs.net": - domain => 'ops.puppetlabs.net', + ensure => present, + domain => 'ops.puppetlabs.net', content => 'Test TXT Record', type => 'TXT', ttl => '32000', - ensure => present } ~~~ @@ -73,7 +73,7 @@ Dns_record { } dns_record { "test-1a-record.puppetware.org": - ensure => present + ensure => present, domain => 'puppetware.org', content => '172.16.100.150', type => 'A', @@ -83,7 +83,7 @@ dns_record { "test-1a-record.puppetware.org": # content can also accept array dns_record { "test-1a-record.puppetware.org": - ensure => present + ensure => present, domain => 'puppetware.org', content => ['172.16.100.150', '172.16.100.134'], type => 'A', @@ -91,7 +91,7 @@ dns_record { "test-1a-record.puppetware.org": } dns_record { "test-cname.puppetware.org": - ensure => present + ensure => present, domain => 'puppetware.org', content => 'test-1a-record.puppetware.org', type => 'CNAME', @@ -99,7 +99,7 @@ dns_record { "test-cname.puppetware.org": } dns_record { "test-txt.puppetware.org": - ensure => present + ensure => present, domain => 'puppetware.org', content => 'Test TXT Record', type => 'TXT', From be49ecba46b446ab2bb382cc10f10896b1efe417 Mon Sep 17 00:00:00 2001 From: Yarden Bar Date: Tue, 18 Oct 2016 16:42:14 +0300 Subject: [PATCH 3/8] Query DNS zone with relevant node-string (based on the resource[:name]) No need to retrieve all of the DNS records and then filter what we need --- lib/puppet/provider/dns_record/dynect.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/puppet/provider/dns_record/dynect.rb b/lib/puppet/provider/dns_record/dynect.rb index bca10ed..3271689 100644 --- a/lib/puppet/provider/dns_record/dynect.rb +++ b/lib/puppet/provider/dns_record/dynect.rb @@ -85,7 +85,7 @@ def flush Puppet.debug("content_dup: #{content_dup}") case resource[:ensure] when :present - existing = zone.records.all.select do |r| r.name == resource[:name] end + existing = zone.records.all({fqdn:resource[:name]}) Puppet.debug("EXISTING: #{existing}") to_remove, existing = existing.partition do |r| @@ -128,7 +128,7 @@ def flush end end when :absent - to_remove = zone.records.all.select do |r| + to_remove = zone.records.all({fqdn:resource[:name]}).select do |r| resource[:content].include?(r.rdata['address']) && r.type == resource[:type] && r.name == resource[:name] end Puppet.debug("Removing: #{resource[:name]}: #{to_remove}") @@ -154,8 +154,8 @@ def exists? Puppet.debug("Checking if exists #{resource}") @customername, @username, @password = resource[:customername], resource[:username], resource[:password] zone = dynect.zones.get(resource[:domain]) - existing = zone.records.all.select do |r| - r.name == resource[:name] && r.type == resource[:type] + existing = zone.records.all({fqdn:resource[:name]}).select do |r| + r.type == resource[:type] end Puppet.debug("EXISTING-3: #{existing}, content: #{resource[:content]}") (resource[:content] - existing.map do |r| r.rdata['address'] end).empty? From acf6ac731ec615b210bf8c133c4d716e3f212bc7 Mon Sep 17 00:00:00 2001 From: Yarden Bar Date: Tue, 18 Oct 2016 17:17:13 +0300 Subject: [PATCH 4/8] Comment and format record comparison --- lib/puppet/provider/dns_record/dynect.rb | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/puppet/provider/dns_record/dynect.rb b/lib/puppet/provider/dns_record/dynect.rb index 3271689..1448462 100644 --- a/lib/puppet/provider/dns_record/dynect.rb +++ b/lib/puppet/provider/dns_record/dynect.rb @@ -89,7 +89,9 @@ def flush Puppet.debug("EXISTING: #{existing}") to_remove, existing = existing.partition do |r| - (!resource[:content].include?(r.rdata['address'])) && r.type == resource[:type] && r.name == resource[:name] + (!resource[:content].include?(r.rdata['address'])) && + r.type == resource[:type] && + r.name == resource[:name] end Puppet.debug("EXISTING-2: #{existing}") @@ -116,6 +118,7 @@ def flush content_dup -= [r.rdata['address']] end Puppet.debug("content_dup-2: #{content_dup}") + # The remaining records in content_dup are 'new' and should be created if !content_dup.empty? content_dup.each do |new_ip| zone.records.new( @@ -129,7 +132,12 @@ def flush end when :absent to_remove = zone.records.all({fqdn:resource[:name]}).select do |r| - resource[:content].include?(r.rdata['address']) && r.type == resource[:type] && r.name == resource[:name] + resource[:content].include?(r.rdata['address']) && + r.type == resource[:type] && + # In case resource[:name] resolves to multiple records(e.g + # some.domain.com would resolve to (*.)some.domain.com) + # compare the record name to apply only on relevant record(s) + r.name == resource[:name] end Puppet.debug("Removing: #{resource[:name]}: #{to_remove}") to_remove.each do |r| @@ -155,7 +163,11 @@ def exists? @customername, @username, @password = resource[:customername], resource[:username], resource[:password] zone = dynect.zones.get(resource[:domain]) existing = zone.records.all({fqdn:resource[:name]}).select do |r| - r.type == resource[:type] + r.type == resource[:type] && + # In case resource[:name] resolves to multiple records(e.g + # some.domain.com would resolve to (*.)some.domain.com) + # compare the record name to apply only on relevant record(s) + r.name == resource[:name] end Puppet.debug("EXISTING-3: #{existing}, content: #{resource[:content]}") (resource[:content] - existing.map do |r| r.rdata['address'] end).empty? From 8758c0da123b965b710855219e13f044e394ae89 Mon Sep 17 00:00:00 2001 From: Yarden Bar Date: Tue, 18 Oct 2016 18:28:53 +0300 Subject: [PATCH 5/8] Fix TTL updating Remove type comparison as we're alredy filtering when we query the zone for the domain. Call record.save only once as it publishes the DNS zone. Per DNS RFC, all records under the same node must have the same TTL --- lib/puppet/provider/dns_record/dynect.rb | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/puppet/provider/dns_record/dynect.rb b/lib/puppet/provider/dns_record/dynect.rb index 1448462..1303615 100644 --- a/lib/puppet/provider/dns_record/dynect.rb +++ b/lib/puppet/provider/dns_record/dynect.rb @@ -101,22 +101,21 @@ def flush r.destroy publish_zone = true end + needs_update = false existing.each do |r| - if r.type != resource[:type] - r.type = resource[:type] - needs_update = true - end - if (! r.ttl.nil?) && r.ttl != resource[:ttl] + if r.ttl != resource[:ttl] r.ttl = resource[:ttl] needs_update = true end - if needs_update - Puppet.debug("Updating #{r.inspect}") - r.save - publish_zone = true - end content_dup -= [r.rdata['address']] end + if needs_update + Puppet.debug("Updating #{resource[:name]}") + # Calling 'save' applys changes on all DNS node records and 'publish's + # the zone. so it's enough to call save on the 1st record + existing[0].save(true) # replace + publish_zone = true + end Puppet.debug("content_dup-2: #{content_dup}") # The remaining records in content_dup are 'new' and should be created if !content_dup.empty? From 366c0dde97e1a93da1e57179a2b93336aa1df61c Mon Sep 17 00:00:00 2001 From: Yarden Bar Date: Wed, 31 Aug 2016 23:45:33 +0300 Subject: [PATCH 6/8] Bump ruby verison to 2.3.1 in .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0b50b86..5d0e8d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ script: "bundle exec rake validate && bundle exec rake lint && bundle exec rake spec SPEC_OPTS='--format documentation'" language: ruby rvm: - - "2.1.6" + - "2.3.1" sudo: false From 117cc974e3470127398c5d46697a7b5f0cf15197 Mon Sep 17 00:00:00 2001 From: Yarden Bar Date: Sat, 29 Oct 2016 20:02:18 +0300 Subject: [PATCH 7/8] Retire rest-client gem, not in use after the DynECT provider migration to 'fog' --- Gemfile | 2 -- lib/puppet/feature/rest_client.rb | 7 ------- spec/spec_helper_acceptance.rb | 2 +- 3 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 lib/puppet/feature/rest_client.rb diff --git a/Gemfile b/Gemfile index 6e06b01..4a86724 100644 --- a/Gemfile +++ b/Gemfile @@ -12,6 +12,4 @@ group :test do gem 'beaker-puppet_install_helper', :require => false gem 'net-dns' gem 'dnsruby' - gem 'rest-client' - end diff --git a/lib/puppet/feature/rest_client.rb b/lib/puppet/feature/rest_client.rb deleted file mode 100644 index 967e066..0000000 --- a/lib/puppet/feature/rest_client.rb +++ /dev/null @@ -1,7 +0,0 @@ -Puppet.features.add(:rest_client) do - begin - require 'rest_client' - rescue LoadError => e - warn "Gem 'rest-client' needed for DynECT. #{e}" - end -end diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb index 6fcd83c..838a2c2 100644 --- a/spec/spec_helper_acceptance.rb +++ b/spec/spec_helper_acceptance.rb @@ -14,7 +14,7 @@ if fact_on(host, 'osfamily') == 'Debian' install_package(master, 'ruby-dev build-essential libxml++2.6-dev') end - on master, "gem install rest-client fog --no-ri --no-rdoc" + on master, "gem install fog --no-ri --no-rdoc" on master, "gem list" end end From c3db5d51f628b8a7baa71c9837221a08a28c6d41 Mon Sep 17 00:00:00 2001 From: Yarden Bar Date: Sun, 13 Nov 2016 15:36:31 +0200 Subject: [PATCH 8/8] Handle missing FQDN Rescue Excon::Error::NotFound when FQDN doesn't exist --- lib/puppet/provider/dns_record/dynect.rb | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/puppet/provider/dns_record/dynect.rb b/lib/puppet/provider/dns_record/dynect.rb index 1303615..51571fb 100644 --- a/lib/puppet/provider/dns_record/dynect.rb +++ b/lib/puppet/provider/dns_record/dynect.rb @@ -85,8 +85,12 @@ def flush Puppet.debug("content_dup: #{content_dup}") case resource[:ensure] when :present - existing = zone.records.all({fqdn:resource[:name]}) - Puppet.debug("EXISTING: #{existing}") + existing = begin + zone.records.all({fqdn:resource[:name]}) + rescue Excon::Error::NotFound + [] + end + Puppet.debug("PUPPET_DNS: EXISTING: #{existing}") to_remove, existing = existing.partition do |r| (!resource[:content].include?(r.rdata['address'])) && @@ -161,12 +165,16 @@ def exists? Puppet.debug("Checking if exists #{resource}") @customername, @username, @password = resource[:customername], resource[:username], resource[:password] zone = dynect.zones.get(resource[:domain]) - existing = zone.records.all({fqdn:resource[:name]}).select do |r| - r.type == resource[:type] && - # In case resource[:name] resolves to multiple records(e.g - # some.domain.com would resolve to (*.)some.domain.com) - # compare the record name to apply only on relevant record(s) - r.name == resource[:name] + begin + existing = zone.records.all({fqdn:resource[:name]}).select do |r| + r.type == resource[:type] && + # In case resource[:name] resolves to multiple records(e.g + # some.domain.com would resolve to (*.)some.domain.com) + # compare the record name to apply only on relevant record(s) + r.name == resource[:name] + end + rescue Excon::Error::NotFound + return false end Puppet.debug("EXISTING-3: #{existing}, content: #{resource[:content]}") (resource[:content] - existing.map do |r| r.rdata['address'] end).empty?