Skip to content

Commit

Permalink
add setup of arbitrary dns records via nettica or zerigo apis
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Conway committed Oct 6, 2009
1 parent d984451 commit 61ca87e
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 97 deletions.
79 changes: 79 additions & 0 deletions generators/vulcanize/templates/base/config/rubber/rubber-dns.yml
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,79 @@
# OPTIONAL: The dns provider to use. Need to exist in dns_providers below
# dns_provider: nettica

# OPTIONAL: The configuration for each dns provider (nettica|zerigo|dyndns)
# This lets rubber update a dynamic dns service with the instance alias and ip
#
dns_providers:
nettica:
user: joe
password: sekret
type: A
ttl: 300
zerigo:
customer_id: 1234
email: foo@bar.com
token: hexxy
type: A
ttl: 300
dyndns:
user: joe
password: sekret
update_url: https://members.dyndns.org/nic/update?hostname=%host%&myip=%ip%'

# OPTIONAL: Lets you configure your dns service, for example to add other CNAMES
# or setup dns round robin, etc. Run "cap rubber:setup_dns_records"
# to apply them as rubber only sets up instance aliases as part of
# the standard lifecycle
#
# dns_records:
# # simple A record
# - host: bar
# data: 1.1.1.1
#
# # more detailed A record
# - host: bar
# domain: otherdomain.com
# data: 1.1.1.1
# type: A
# ttl: 300
#
# # tld A record
# - host: ''
# data: 1.1.1.1
# type: A
#
# # simple CNAME record
# - host: otherbar
# domain: foo.com
# data: bar.foo.com
# type: CNAME
# ttl: 300
#
# # 2 of the same A records is a round robin dns
# - host: rr
# domain: foo.com
# data: 1.1.1.1
# type: A
# ttl: 300
# - host: rr
# domain: foo.com
# data: 1.1.1.2
# type: A
# ttl: 300
#
# # A record, grabbing ip from instance config
# - host: baz
# domain: foo.com
# data: "#{rubber_instances.for_role('web').first.external_ip}"
# type: A
# ttl: 300
#
# # MX record
# - host: ''
# domain: foo.com
# data: mail.foo.com
# type: MX
# ttl: 300
# priority: 10

81 changes: 4 additions & 77 deletions generators/vulcanize/templates/base/config/rubber/rubber.yml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -32,83 +32,10 @@ timezone: US/Eastern
# #
domain: foo.com domain: foo.com


# OPTIONAL: The configuration for each dns provider (nettica|zerigo|dyndns) # OPTIONAL: See rubber-dns.yml for dns configuration
# This lets rubber updatea dynamic dns service with the instance alias and ip # This lets rubber update a dynamic dns service with the instance alias
# # and ip when they are created. It also allows setting up arbitrary
dns_providers: # dns records (CNAME, MX, Round Robin DNS, etc)
nettica:
user: joe
password: sekret
type: A
ttl: 300
zerigo:
customer_id: 1234
email: foo@bar.com
token: hexxy
type: A
ttl: 300
dyndns:
user: joe
password: sekret
update_url: https://members.dyndns.org/nic/update?hostname=%host%&myip=%ip%'

# OPTIONAL: The dns provider to use
# dns_provider: nettica

# OPTIONAL: Lets you configure your dns service, for example to add other CNAMES
# or setup dns round robin, etc.
#
# dns_records:
# # simple A record
# - host: bar
# data: 1.1.1.1
#
# # more detailed A record
# - host: bar
# domain: otherdomain.com
# data: 1.1.1.1
# type: A
# ttl: 300
#
# # tld A record
# - host: ''
# data: 1.1.1.1
# type: A
#
# # simple CNAME record
# - host: otherbar
# domain: foo.com
# data: bar.foo.com
# type: CNAME
# ttl: 300
#
# # 2 of the same A records is a round robin dns
# - host: rr
# domain: foo.com
# data: 1.1.1.1
# type: A
# ttl: 300
# - host: rr
# domain: foo.com
# data: 1.1.1.2
# type: A
# ttl: 300
#
# # A record, grabbing ip from instance config
# - host: baz
# domain: foo.com
# data: "#{rubber_instances.for_role('web').first.external_ip}"
# type: A
# ttl: 300
#
# # MX record
# - host: mail
# domain: foo.com
# data: 1.1.1.1
# type: MX
# ttl: 300
# priority: 10



# OPTIONAL: Additional rubber file to pull config from if it exists. This file will # OPTIONAL: Additional rubber file to pull config from if it exists. This file will
# also be pushed to remote host at RUBBER_ROOT/config/rubber/rubber-secret.yml # also be pushed to remote host at RUBBER_ROOT/config/rubber/rubber-secret.yml
Expand Down
24 changes: 18 additions & 6 deletions lib/rubber/dns/base.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -52,16 +52,28 @@ def destroy_host_record(opts = {})
raise "destroy_host_record not implemented" raise "destroy_host_record not implemented"
end end


protected def host_records_equal?(lhs_opts = {}, rhs_opts = {})
lhs = setup_opts(lhs_opts)
rhs = setup_opts(rhs_opts)
[lhs, rhs].each {|h| h.delete(:id); h.delete(:priority) if h[:priority] == 0}
lhs == rhs
end

def setup_opts(opts, required=[])
default_opts = {:domain => @env.domain,
:type => @provider_env['type'] || @provider_env.record_type || 'A',
:ttl => @provider_env.ttl || 300}

if opts.delete(:no_defaults)
actual_opts = Rubber::Util::symbolize_keys(opts)
else
actual_opts = default_opts.merge(Rubber::Util::symbolize_keys(opts))
end


def setup_opts(opts, required =[])
default_opts = {:domain => @provider_env.domain,
:type => @provider_env['type'] || @provider_env.record_type,
:ttl => @provider_env.ttl}
actual_opts = default_opts.merge(Rubber::Util::symbolize_keys(opts))
required.each do |r| required.each do |r|
raise "Missing required options: #{r}" unless actual_opts[r] raise "Missing required options: #{r}" unless actual_opts[r]
end end

return actual_opts return actual_opts
end end


Expand Down
25 changes: 19 additions & 6 deletions lib/rubber/dns/nettica.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ def find_host_records(opts = {})
ht = opts[:type] ht = opts[:type]
hd = opts[:data] hd = opts[:data]


domain_info = check_status @client.list_domain(opts[:domain]) domain_info = find_or_create_zone(opts[:domain])
raise "Domain needs to exist in nettica before records can be updated" unless domain_info.record


domain_info.record.each do |h| domain_info.record.each do |h|
keep = true keep = true
Expand All @@ -56,6 +55,7 @@ def find_host_records(opts = {})


def create_host_record(opts = {}) def create_host_record(opts = {})
opts = setup_opts(opts, [:host, :data, :domain, :type, :ttl]) opts = setup_opts(opts, [:host, :data, :domain, :type, :ttl])
find_or_create_zone(opts[:domain])
record = opts_to_record(opts) record = opts_to_record(opts)
check_status @client.add_record(record) check_status @client.add_record(record)
end end
Expand All @@ -69,6 +69,7 @@ def destroy_host_record(opts = {})


def update_host_record(old_opts = {}, new_opts = {}) def update_host_record(old_opts = {}, new_opts = {})
old_opts = setup_opts(old_opts, [:host, :domain]) old_opts = setup_opts(old_opts, [:host, :domain])
new_opts = setup_opts(new_opts.merge(:no_defaults =>true), [])
find_host_records(old_opts).each do |h| find_host_records(old_opts).each do |h|
old_record = opts_to_record(h) old_record = opts_to_record(h)
new_record = opts_to_record(h.merge(new_opts)) new_record = opts_to_record(h.merge(new_opts))
Expand All @@ -78,6 +79,18 @@ def update_host_record(old_opts = {}, new_opts = {})


private private


def find_or_create_zone(domain)
domain_info = @client.list_domain(domain)
if domain_info.record
check_status domain_info
else
check_status @client.create_zone(domain)
domain_info = check_status @client.list_domain(domain)
raise "Could not create zone in nettica: #{domain}" unless domain_info.record
end
return domain_info
end

def opts_to_record(opts) def opts_to_record(opts)
record = @client.create_domain_record(opts[:domain], record = @client.create_domain_record(opts[:domain],
opts[:host], opts[:host],
Expand All @@ -90,12 +103,12 @@ def opts_to_record(opts)


def record_to_opts(record) def record_to_opts(record)
opts = {} opts = {}
opts[:host] = record.hostName opts[:host] = record.hostName || ''
opts[:domain] = record.domainName opts[:domain] = record.domainName
opts[:type] = record.recordType opts[:type] = record.recordType
opts[:data] = record.data opts[:data] = record.data if record.data
opts[:ttl] = record.tTL opts[:ttl] = record.tTL if record.tTL
opts[:priority] = record.priority opts[:priority] = record.priority if record.priority
return opts return opts
end end
end end
Expand Down
19 changes: 11 additions & 8 deletions lib/rubber/dns/zerigo.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ def refresh
if ! @zone if ! @zone
zone = new_zone() zone = new_zone()
zone['domain'] = @domain zone['domain'] = @domain
@zone = check_status self.class.post('/zones.xml', :body => {:zone => zone}) zones = check_status self.class.post('/zones.xml', :body => {:zone => zone})
@zone = zones['zone']
end end
end end


Expand All @@ -109,20 +110,21 @@ def new_zone
def opts_to_host(opts, host={}) def opts_to_host(opts, host={})
host['hostname'] = opts[:host] host['hostname'] = opts[:host]
host['host_type'] = opts[:type] host['host_type'] = opts[:type]
host['data'] = opts[:data] host['data'] = opts[:data] if opts[:data]
host['ttl'] = opts[:ttl] host['ttl'] = opts[:ttl] if opts[:ttl]
host['priority'] = opts[:priority] host['priority'] = opts[:priority] if opts[:priority]
return host return host
end end


def host_to_opts(host) def host_to_opts(host)
opts = {} opts = {}
opts[:id] = host['id'] opts[:id] = host['id']
opts[:host] = host['hostname'] opts[:domain] = @domain
opts[:host] = host['hostname'] || ''
opts[:type] = host['host_type'] opts[:type] = host['host_type']
opts[:data] = host['data'] opts[:data] = host['data'] if host['data']
opts[:ttl] = host['ttl'] opts[:ttl] = host['ttl'] if host['ttl']
opts[:priority] = host['priority'] opts[:priority] = host['priority'] if host['priority']
return opts return opts
end end
end end
Expand Down Expand Up @@ -158,6 +160,7 @@ def destroy_host_record(opts = {})


def update_host_record(old_opts={}, new_opts={}) def update_host_record(old_opts={}, new_opts={})
old_opts = setup_opts(old_opts, [:host, :domain]) old_opts = setup_opts(old_opts, [:host, :domain])
new_opts = setup_opts(new_opts.merge(:no_defaults =>true), [])
zone = Zone.get_zone(old_opts[:domain], provider_env) zone = Zone.get_zone(old_opts[:domain], provider_env)


find_host_records(old_opts).each do |h| find_host_records(old_opts).each do |h|
Expand Down
77 changes: 77 additions & 0 deletions lib/rubber/recipes/rubber/setup.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -61,6 +61,83 @@
end end
end end


desc <<-DESC
Sets up the additional dns records supplied in the dns_records config in rubber.yml
DESC
required_task :setup_dns_records do
records = rubber_env.dns_records
if records && rubber_env.dns_provider
provider = Rubber::Dns::get_provider(rubber_env.dns_provider, rubber_env)

# collect the round robin records (those with the same host/domain/type)
rr_records = []
records.each_with_index do |record, i|
m = records.find_all {|r| record['host'] == r['host'] && record['domain'] == r['domain'] && record['type'] == r['type']}
m = m.sort {|a,b| a.object_id <=> b.object_id}
rr_records << m if m.size > 1 && ! rr_records.include?(m)
end

# simple records are those that aren't round robin ones
simple_records = records - rr_records.flatten

# for each simple record, create or update as necessary
simple_records.each do |record|
matching = provider.find_host_records(:host => record['host'], :domain =>record['domain'], :type => record['type'])
if matching.size > 1
msg = "Multiple records in dns provider, but not in rubber.yml\n"
msg << "Round robin records need to be in both, or neither.\n"
msg << "Please fix manually:\n"
msg << matching.pretty_inspect
fatal(msg)
end

record = provider.setup_opts(record)
if matching.size == 1
match = matching.first
if provider.host_records_equal?(record, match)
logger.info "Simple dns record already up to date: #{record[:host]}.#{record[:domain]}:#{record[:type]} => #{record[:data]}"
else
logger.info "Updating simple dns record: #{record[:host]}.#{record[:domain]}:#{record[:type]} => #{record[:data]}"
provider.update_host_record(match, record)
end
else
logger.info "Creating simple dns record: #{record[:host]}.#{record[:domain]}:#{record[:type]} => #{record[:data]}"
provider.create_host_record(record)
end
end

# group round robin records
rr_records.each do |rr_group|
host = rr_group.first['host']
domain = rr_group.first['domain']
type = rr_group.first['type']
matching = provider.find_host_records(:host => host, :domain => domain, :type => type)

# remove from consideration the local records that are the same as remote ones
matching.clone.each do |r|
rr_group.delete_if {|rg| provider.host_records_equal?(r, rg) }
matching.delete_if {|rg| provider.host_records_equal?(r, rg) }
end
if rr_group.size == 0 && matching.size == 0
logger.info "Round robin dns records already up to date: #{host}.#{domain}:#{type}"
end

# create the local records that don't exist remotely
rr_group.each do |r|
r = provider.setup_opts(r)
logger.info "Creating round robin dns record: #{r[:host]}.#{r[:domain]}:#{r[:type]} => #{r[:data]}"
provider.create_host_record(r)
end

# remove the remote records that don't exist locally
matching.each do |r|
logger.info "Removing round robin dns record: #{r[:host]}.#{r[:domain]}:#{r[:type]} => #{r[:data]}"
provider.destroy_host_record(r)
end
end
end
end

desc <<-DESC desc <<-DESC
Sets up aliases for instance hostnames based on contents of instance.yml. Sets up aliases for instance hostnames based on contents of instance.yml.
Generates /etc/hosts for remote machines and sets hostname on remote instances Generates /etc/hosts for remote machines and sets hostname on remote instances
Expand Down

0 comments on commit 61ca87e

Please sign in to comment.