chef/cookbooks/windows/providers/dns.rb

149 lines
4.8 KiB
Ruby

#
# Author:: Richard Lavey (richard.lavey@calastone.com)
# Cookbook:: windows
# Provider:: dns
#
# Copyright:: 2015, Calastone Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# See this for info on DNSCMD
# https://technet.microsoft.com/en-gb/library/cc772069.aspx#BKMK_10
include Windows::Helper
action :create do
if @current_resource.exists
needs_change = (@new_resource.record_type != @current_resource.record_type) ||
(@new_resource.ttl > 0 && @new_resource.ttl != @current_resource.ttl) ||
(@new_resource.target.is_a?(String) && @new_resource.target != @current_resource.target) ||
(@new_resource.target.is_a?(Array) && !(@new_resource.target - @current_resource.target).empty?)
if needs_change
converge_by("Changing #{@new_resource.host_name}") do
update_dns
end
else
Chef::Log.debug("#{@new_resource.host_name} already exists - nothing to do")
end
else
converge_by("Creating #{@new_resource.host_name}") do
update_dns
end
end
end
action :delete do
if @current_resource.exists
converge_by("Deleting #{@current_resource.host_name}") do
execute_command! 'recorddelete', "#{@current_resource.record_type} /f"
end
else
Chef::Log.debug("#{@new_resource.host_name} does not exist - nothing to do")
end
end
def load_current_resource
# validate the new resource params : A records should be an array
if @new_resource.record_type == 'A' && @new_resource.target.is_a?(String)
raise 'target property must be an array for record_type A'
end
@current_resource = Chef::Resource::WindowsDns.new(@new_resource.name)
@current_resource.host_name(@new_resource.host_name)
@current_resource.dns_server(@new_resource.dns_server)
parts = @current_resource.host_name.scan(/(\w+)\.(.*)/)
@host = parts[0][0]
@domain = parts[0][1]
fetch_attributes
end
private
def fetch_attributes
@command = locate_sysnative_cmd('dnscmd.exe')
cmd = shell_out("#{@command} #{@current_resource.dns_server} /enumrecords #{@domain} #{@host}")
Chef::Log.debug "dnscmd reports: #{cmd.stdout}"
# extract values from returned text
if cmd.stdout.include?('DNS_ERROR_NAME_DOES_NOT_EXIST')
@current_resource.exists = false
@current_resource.target([])
elsif cmd.exitstatus == 0
@current_resource.exists = true
m = cmd.stdout.scan(/(\d+)\s(A)\s+(\d+\.\d+\.\d+\.\d+)/)
if m.empty?
m = cmd.stdout.scan(/(\d+)\s(CNAME)\s+((?:\w+\.)+)/)
if m.empty?
@current_resource.exists = false
@current_resource.target([])
else
# We have a cname record
@current_resource.record_type('CNAME')
@current_resource.ttl(m[0][0].to_i)
@current_resource.target(m[0][2].chomp('.'))
end
else
# we have A entries
@current_resource.record_type('A')
@current_resource.ttl(m[0][0].to_i)
addresses = []
m.each do |match|
addresses.push(match[2])
end
@current_resource.target(addresses)
end
else
raise "dnscmd returned error #{cmd.exitstatus} : #{cmd.stderr} #{cmd.stdout}"
end
end
def update_dns
ttl = @new_resource.ttl if @new_resource.ttl > 0
if @current_resource.record_type != @new_resource.record_type
# delete current record(s) as we're changing the type
execute_command! 'recorddelete', "#{@current_resource.record_type} /f"
end
if @new_resource.record_type == 'A'
# delete existing records that are no longer defined
(@current_resource.target - @new_resource.target).each do |address|
Chef::Log.info "Deleting #{address}"
execute_command! 'recorddelete', "A #{address} /f"
end
# add new records that don't exist
# if ttl has changed then update all records
addresses = if @current_resource.ttl == @new_resource.ttl
(@new_resource.target - @current_resource.target)
else
@new_resource.target
end
addresses.each do |address|
Chef::Log.info "Adding/Changing #{address}"
execute_command! 'recordadd', "#{ttl} A #{address}"
end
else
execute_command! 'recordadd', "#{ttl} CNAME #{@new_resource.target}"
end
end
def execute_command!(mode, options)
shell_out!("#{@command} #{@current_resource.dns_server} /#{mode} #{@domain} #{@host} #{options}")
end