From 0dad896add1f95880a18b8fa696709135f1def64 Mon Sep 17 00:00:00 2001 From: Carlos Lima Date: Mon, 29 Jun 2015 02:58:59 +0800 Subject: [PATCH] The :geo_location attribute needs to be xml formatted before calling aws Without this, calling `record.destroy` on a record that has geolocation set will cause an exception with: Fog::DNS::AWS::Error: MalformedInput => Unexpected complex element termination --- .../dns/change_resource_record_sets.rb | 162 +++++++++--------- .../dns/change_resource_record_sets_tests.rb | 19 ++ 2 files changed, 103 insertions(+), 78 deletions(-) diff --git a/lib/fog/aws/requests/dns/change_resource_record_sets.rb b/lib/fog/aws/requests/dns/change_resource_record_sets.rb index 409e47418..b1f18a598 100644 --- a/lib/fog/aws/requests/dns/change_resource_record_sets.rb +++ b/lib/fog/aws/requests/dns/change_resource_record_sets.rb @@ -61,84 +61,7 @@ module Fog # change_resource_record_sets("ABCDEFGHIJKLMN", change_batch_options) # def change_resource_record_sets(zone_id, change_batch, options = {}) - # AWS methods return zone_ids that looks like '/hostedzone/id'. Let the caller either use - # that form or just the actual id (which is what this request needs) - zone_id = zone_id.sub('/hostedzone/', '') - - optional_tags = '' - options.each do |option, value| - case option - when :comment - optional_tags += "#{value}" - end - end - - #build XML - if change_batch.count > 0 - - changes = "#{optional_tags}" - - change_batch.each do |change_item| - action_tag = %Q{#{change_item[:action]}} - name_tag = %Q{#{change_item[:name]}} - type_tag = %Q{#{change_item[:type]}} - - # TTL must be omitted if using an alias record - ttl_tag = '' - ttl_tag += %Q{#{change_item[:ttl]}} unless change_item[:alias_target] - - weight_tag = '' - set_identifier_tag = '' - region_tag = '' - if change_item[:set_identifier] - set_identifier_tag += %Q{#{change_item[:set_identifier]}} - if change_item[:weight] # Weighted Record - weight_tag += %Q{#{change_item[:weight]}} - elsif change_item[:region] # Latency record - region_tag += %Q{#{change_item[:region]}} - end - end - - failover_tag = if change_item[:failover] - %Q{#{change_item[:failover]}} - end - - geolocation_tag = if change_item[:geo_location] - %Q{#{change_item[:geo_location]}} - end - - resource_records = change_item[:resource_records] || [] - resource_record_tags = '' - resource_records.each do |record| - resource_record_tags += %Q{#{record}} - end - - # ResourceRecords must be omitted if using an alias record - resource_tag = '' - resource_tag += %Q{#{resource_record_tags}} if resource_records.any? - - alias_target_tag = '' - if change_item[:alias_target] - # Accept either underscore or camel case for hash keys. - dns_name = change_item[:alias_target][:dns_name] || change_item[:alias_target][:DNSName] - hosted_zone_id = change_item[:alias_target][:hosted_zone_id] || change_item[:alias_target][:HostedZoneId] || AWS.hosted_zone_for_alias_target(dns_name) - evaluate_target_health = change_item[:alias_target][:evaluate_target_health] || change_item[:alias_target][:EvaluateTargetHealth] || false - evaluate_target_health_xml = !evaluate_target_health.nil? ? %Q{#{evaluate_target_health}} : '' - alias_target_tag += %Q{#{hosted_zone_id}#{dns_name}#{evaluate_target_health_xml}} - end - - health_check_id_tag = if change_item[:health_check_id] - %Q{#{change_item[:health_check_id]}} - end - - change_tags = %Q{#{action_tag}#{name_tag}#{type_tag}#{set_identifier_tag}#{weight_tag}#{region_tag}#{failover_tag}#{geolocation_tag}#{ttl_tag}#{resource_tag}#{alias_target_tag}#{health_check_id_tag}} - changes += change_tags - end - - changes += '' - end - - body = %Q{#{changes}} + body = AWS.change_resource_record_sets_data(zone_id, change_batch, options) request({ :body => body, :idempotent => true, @@ -150,6 +73,89 @@ module Fog end end + # Returns the xml request for a given changeset + def self.change_resource_record_sets_data(zone_id, change_batch, options = {}) + # AWS methods return zone_ids that looks like '/hostedzone/id'. Let the caller either use + # that form or just the actual id (which is what this request needs) + zone_id = zone_id.sub('/hostedzone/', '') + + optional_tags = '' + options.each do |option, value| + case option + when :comment + optional_tags += "#{value}" + end + end + + #build XML + if change_batch.count > 0 + + changes = "#{optional_tags}" + + change_batch.each do |change_item| + action_tag = %Q{#{change_item[:action]}} + name_tag = %Q{#{change_item[:name]}} + type_tag = %Q{#{change_item[:type]}} + + # TTL must be omitted if using an alias record + ttl_tag = '' + ttl_tag += %Q{#{change_item[:ttl]}} unless change_item[:alias_target] + + weight_tag = '' + set_identifier_tag = '' + region_tag = '' + if change_item[:set_identifier] + set_identifier_tag += %Q{#{change_item[:set_identifier]}} + if change_item[:weight] # Weighted Record + weight_tag += %Q{#{change_item[:weight]}} + elsif change_item[:region] # Latency record + region_tag += %Q{#{change_item[:region]}} + end + end + + failover_tag = if change_item[:failover] + %Q{#{change_item[:failover]}} + end + + geolocation_tag = if change_item[:geo_location] + xml_geo = change_item[:geo_location].map { |k,v| "<#{k}>#{v}" }.join + %Q{#{xml_geo}} + end + + resource_records = change_item[:resource_records] || [] + resource_record_tags = '' + resource_records.each do |record| + resource_record_tags += %Q{#{record}} + end + + # ResourceRecords must be omitted if using an alias record + resource_tag = '' + resource_tag += %Q{#{resource_record_tags}} if resource_records.any? + + alias_target_tag = '' + if change_item[:alias_target] + # Accept either underscore or camel case for hash keys. + dns_name = change_item[:alias_target][:dns_name] || change_item[:alias_target][:DNSName] + hosted_zone_id = change_item[:alias_target][:hosted_zone_id] || change_item[:alias_target][:HostedZoneId] || AWS.hosted_zone_for_alias_target(dns_name) + evaluate_target_health = change_item[:alias_target][:evaluate_target_health] || change_item[:alias_target][:EvaluateTargetHealth] || false + evaluate_target_health_xml = !evaluate_target_health.nil? ? %Q{#{evaluate_target_health}} : '' + alias_target_tag += %Q{#{hosted_zone_id}#{dns_name}#{evaluate_target_health_xml}} + end + + health_check_id_tag = if change_item[:health_check_id] + %Q{#{change_item[:health_check_id]}} + end + + change_tags = %Q{#{action_tag}#{name_tag}#{type_tag}#{set_identifier_tag}#{weight_tag}#{region_tag}#{failover_tag}#{geolocation_tag}#{ttl_tag}#{resource_tag}#{alias_target_tag}#{health_check_id_tag}} + changes += change_tags + end + + changes += '' + end + + body = %Q{#{changes}} + end + class Mock SET_PREFIX = 'SET_' def record_exist?(zone,change,change_name) diff --git a/tests/requests/dns/change_resource_record_sets_tests.rb b/tests/requests/dns/change_resource_record_sets_tests.rb index ab3047424..fbd420fed 100644 --- a/tests/requests/dns/change_resource_record_sets_tests.rb +++ b/tests/requests/dns/change_resource_record_sets_tests.rb @@ -7,4 +7,23 @@ Shindo.tests('Fog::DNS[:aws] | change_resource_record_sets', ['aws', 'dns']) do zone_id == Fog::DNS::AWS.elb_hosted_zone_mapping['eu-west-1'] end end + tests("#change_resource_record_sets_data formats geolocation properly") do + change_batch = [{ + :action=>"CREATE", + :name=>"ark.m.example.net.", + :resource_records=>["1.1.1.1"], + :ttl=>"300", + :type=>"A", + :set_identifier=>"ark", + :geo_location=>{"CountryCode"=>"US", "SubdivisionCode"=>"AR"}, + }] + + result = Fog::DNS::AWS.change_resource_record_sets_data('zone_id123', change_batch) + .match(%r{.*}) + returns("USAR") { + result ? result[0] : '' + } + + result + end end