module Fog module DNS class AWS def self.hosted_zone_for_alias_target(dns_name) hosted_zones = if dns_name.match(/^dualstack\./) elb_dualstack_hosted_zone_mapping else elb_hosted_zone_mapping end Hash[hosted_zones.select { |k, _| dns_name =~ /\A.+\.#{k}\.elb\.amazonaws\.com\.?\z/ }].values.last end def self.elb_hosted_zone_mapping @elb_hosted_zone_mapping ||= { "ap-northeast-1" => "Z2YN17T5R711GT", "ap-southeast-1" => "Z1WI8VXHPB1R38", "ap-southeast-2" => "Z2999QAZ9SRTIC", "eu-west-1" => "Z3NF1Z3NOM5OY2", "eu-central-1" => "Z215JYRZR1TBD5", "sa-east-1" => "Z2ES78Y61JGQKS", "us-east-1" => "Z3DZXE0Q79N41H", "us-west-1" => "Z1M58G0W56PQJA", "us-west-2" => "Z33MTJ483KN6FU", } end # See https://forums.aws.amazon.com/message.jspa?messageID=612414 def self.elb_dualstack_hosted_zone_mapping @elb_dualstack_hosted_zone_mapping ||= { "ap-northeast-1" => "Z14GRHDCWA56QT", "ap-southeast-1" => "Z1LMS91P8CMLE5", "ap-southeast-2" => "Z1GM3OXH4ZPM65", "eu-central-1" => "Z215JYRZR1TBD5", "eu-west-1" => "Z32O12XQLNTSW2", "sa-east-1" => "Z2P70J7HTTTPLU", "us-east-1" => "Z35SXDOTRQ7X7K", "us-west-1" => "Z368ELLRRE2KJ0", "us-west-2" => "Z1H1FL5HABSF5", } end # Returns the xml request for a given changeset def self.change_resource_record_sets_data(zone_id, change_batch, version, 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 %Q{#{changes}} end class Real require 'fog/aws/parsers/dns/change_resource_record_sets' # Use this action to create or change your authoritative DNS information for a zone # http://docs.amazonwebservices.com/Route53/latest/DeveloperGuide/RRSchanges.html#RRSchanges_API # # ==== Parameters # * zone_id<~String> - ID of the zone these changes apply to # * options<~Hash> # * comment<~String> - Any comments you want to include about the change. # * change_batch<~Array> - The information for a change request # * changes<~Hash> - # * action<~String> - 'CREATE' or 'DELETE' # * name<~String> - This must be a fully-specified name, ending with a final period # * type<~String> - A | AAAA | CNAME | MX | NS | PTR | SOA | SPF | SRV | TXT # * ttl<~Integer> - Time-to-live value - omit if using an alias record # * weight<~Integer> - Time-to-live value - omit if using an alias record # * set_identifier<~String> - An identifier that differentiates among multiple resource record sets that have the same combination of DNS name and type. # * region<~String> - The Amazon EC2 region where the resource that is specified in this resource record set resides. (Latency only) # * failover<~String> - To configure failover, you add the Failover element to two resource record sets. For one resource record set, you specify PRIMARY as the value for Failover; for the other resource record set, you specify SECONDARY. # * geo_location<~String XML> - A complex type currently requiring XML that lets you control how Amazon Route 53 responds to DNS queries based on the geographic origin of the query. # * health_check_id<~String> - If you want Amazon Route 53 to return this resource record set in response to a DNS query only when a health check is passing, include the HealthCheckId element and specify the ID of the applicable health check. # * resource_records<~Array> - Omit if using an alias record # * alias_target<~Hash> - Information about the domain to which you are redirecting traffic (Alias record sets only) # * dns_name<~String> - The Elastic Load Balancing domain to which you want to reroute traffic # * hosted_zone_id<~String> - The ID of the hosted zone that contains the Elastic Load Balancing domain to which you want to reroute traffic # * evaluate_target_health<~Boolean> - Applies only to alias, weighted alias, latency alias, and failover alias resource record sets: If you set the value of EvaluateTargetHealth to true, the alias resource record sets inherit the health of the referenced resource record sets. # ==== Returns # * response<~Excon::Response>: # * body<~Hash>: # * 'ChangeInfo'<~Hash> # * 'Id'<~String> - The ID of the request # * 'Status'<~String> - status of the request - PENDING | INSYNC # * 'SubmittedAt'<~String> - The date and time the change was made # * status<~Integer> - 200 when successful # # ==== Examples # # Example changing a CNAME record: # # change_batch_options = [ # { # :action => "DELETE", # :name => "foo.example.com.", # :type => "CNAME", # :ttl => 3600, # :resource_records => [ "baz.example.com." ] # }, # { # :action => "CREATE", # :name => "foo.example.com.", # :type => "CNAME", # :ttl => 3600, # :resource_records => [ "bar.example.com." ] # } # ] # # change_resource_record_sets("ABCDEFGHIJKLMN", change_batch_options) # def change_resource_record_sets(zone_id, change_batch, options = {}) body = AWS.change_resource_record_sets_data(zone_id, change_batch, @version, options) request({ :body => body, :idempotent => true, :parser => Fog::Parsers::DNS::AWS::ChangeResourceRecordSets.new, :expects => 200, :method => 'POST', :path => "hostedzone/#{zone_id}/rrset" }) end end class Mock SET_PREFIX = 'SET_' def record_exist?(zone,change,change_name) return false if zone[:records][change[:type]].nil? current_records = zone[:records][change[:type]][change_name] return false if current_records.nil? if !change[:set_identifier].empty? !current_records[change[:SetIdentifier]].nil? else !current_records.empty? end end def change_resource_record_sets(zone_id, change_batch, options = {}) response = Excon::Response.new errors = [] if (zone = self.data[:zones][zone_id]) response.status = 200 change_id = Fog::AWS::Mock.change_id change_batch.each do |change| change_name = change[:name] change_name = change_name + "." unless change_name.end_with?(".") case change[:action] when "CREATE" if zone[:records][change[:type]].nil? zone[:records][change[:type]] = {} end if !record_exist?(zone, change, change_name) # raise change.to_s if change[:resource_records].nil? new_record = if change[:alias_target] record = { :alias_target => change[:alias_target] } else record = { :ttl => change[:ttl].to_s, } end new_record = { :change_id => change_id, :resource_records => change[:resource_records] || [], :name => change_name, :type => change[:type], :set_identifier => change[:set_identifier], :weight => change[:weight] }.merge(record) if change[:set_identifier].nil? zone[:records][change[:type]][change_name] = new_record else zone[:records][change[:type]][change_name] = {} if zone[:records][change[:type]][change_name].nil? zone[:records][change[:type]][change_name][SET_PREFIX + change[:set_identifier]] = new_record end else errors << "Tried to create resource record set #{change[:name]}. type #{change[:type]}, but it already exists" end when "DELETE" action_performed = false if !zone[:records][change[:type]].nil? && !zone[:records][change[:type]][change_name].nil? && !change[:set_identifier].nil? action_performed = true unless zone[:records][change[:type]][change_name].delete(SET_PREFIX + change[:set_identifier]).nil? zone[:records][change[:type]].delete(change_name) if zone[:records][change[:type]][change_name].empty? elsif !zone[:records][change[:type]].nil? action_performed = true unless zone[:records][change[:type]].delete(change_name).nil? end if !action_performed errors << "Tried to delete resource record set #{change[:name]}. type #{change[:type]}, but it was not found" end end end if errors.empty? change = { :id => change_id, :status => 'PENDING', :submitted_at => Time.now.utc.iso8601 } self.data[:changes][change[:id]] = change response.body = { 'Id' => change[:id], 'Status' => change[:status], 'SubmittedAt' => change[:submitted_at] } response else raise Fog::DNS::AWS::Error.new("InvalidChangeBatch => #{errors.join(", ")}") end else raise Fog::DNS::AWS::NotFound.new("NoSuchHostedZone => A hosted zone with the specified hosted zone ID does not exist.") end end end end end end