module CustomResource::Route53::MixIns::Actions

Constants

WAIT_PERIOD_INSYNC

Public Instance Methods

action(verb,section,proc) click to toggle source
# File lib/customresource/route53/mixins/actions.rb, line 13
def action(verb,section,proc)
  prepare
  # data = {}
  restyp = section.gsub(/s$/,'').downcase
  method = "#{verb}_#{restyp}"

  resource = @event['ResourceProperties']
  @logger.debug "#{method} #{resource.ai}"

  # Convert the 'String' keys to :string symbols
  params = properties_to_params(resource.dup)
  # Weed out the invalid properties
  invalid_keys = validate_params(section,params)
  if invalid_keys.size > 0
    @logger.warn "Invalid keys in #{params[:name] || params.ai}:\n#{invalid_keys.ai}"
  end
  params = delete_invalid_keys(params,invalid_keys)
  # Restore these exceptions to the rule from the resource 'String' set e.g. :codec_options => { 'String': Value, }
  params = restore_exception_params(section,params,resource)
  params = infer_params(section,Mash.new(params))
  @logger.debug params.ai

  data = proc.call(params, restyp, method, resource)
  @logger.debug data
  respond('SUCCESS', data)
  0
end
change_resource_record_sets(zone, changes) click to toggle source
# File lib/customresource/route53/mixins/actions.rb, line 305
def change_resource_record_sets(zone, changes)
  while true
    begin
      resp = @awssdk.change_resource_record_sets(
          hosted_zone_id: zone[:id],
          change_batch: {
              changes: changes
          }
      )
      wait_for_insync(resp)
      return resp
    rescue Aws::Route53::Errors::PriorRequestNotComplete
      sleep WAIT_PERIOD_INSYNC
    end
  end
end
create(section) click to toggle source
# File lib/customresource/route53/mixins/actions.rb, line 41
def create(section)
  case section
  when /PrivateHostedZones/
    action('create', section, lambda do |params, restyp, method, resource|
      begin
        require 'date'
        data = nil
        set = list_hosted_zones(params)
        if set.size == 0
          @logger.info "create #{params[:name]}"
          resp = @awssdk.create_hosted_zone(
                                              name: params[:hostedzone],
                                               vpc: {
                                                      vpc_region: @region,
                                                          vpc_id: params[:vpcid]
                                                    },
                                              caller_reference: "#{params[:name]}::#{DateTime::now.strftime('%Y%m%dT%H%M%S%z')}",
                                              hosted_zone_config: {
                                                                         comment: params[:comment],
                                                                    # private_zone: true,
                                                                  }
                                            )
          wait_for_insync(resp)
          zone = get_hosted_zone(params)
        else
          zone = set[0] # get_hosted_zone(params)
          @logger.info "Existing hosted_zone #{zone[:id]}::#{zone[:name]}"
          resp = @awssdk.update_hosted_zone_comment( id: zone[:id], comment: params[:comment] )
          unless resp and resp[:hosted_zone]
            abort! "Failed to set comment on zone #{zone[:id]}::#{zone[:name]}"
          end
          zone = Mash.new(resp[:hosted_zone].to_hash)
        end
        if params[:ttl]
          rrs = list_resource_record_sets(zone)
          existing = rrs.select{ |rr| %w(NS SOA).include?(rr[:type]) }
          changes = []
          if existing.size > 0
            existing.each do |rr|
              rrc = rr.dup
              rrc[:ttl] = params[:ttl]
              changes << {
                  action: 'UPSERT',
                  resource_record_set: rrc
              }
            end
          end
          if changes.size > 0
            resp = change_resource_record_sets(zone, changes)
          end
        end
        data = get_item_data(zone, section, params)
        data
      rescue Exception => e
        abort! "#{restyp}/#{params[:name]}: #{e.message}"
      end
    end
    )
  when /ReverseDNSEntries/
    action('create',
           section,
           lambda do |params, restyp, method, resource|
             begin
               data = nil
               zone = get_hosted_zone(params)
               @logger.info "Found hosted_zone #{zone[:id]}::#{zone[:name]}"
               rrs = list_resource_record_sets(zone)
               existing = rrs.select{ |rr| rr[:name] == params[:name] }
               changes = []
               if existing.size > 0
                 existing.each do |rr|
                   changes << {
                       action: 'UPSERT',
                       resource_record_set: {
                           name: rr[:name],
                           type: rr[:type],
                           ttl: params[:ttl],
                           resource_records: params[:resourcerecords] || rr[:resource_records].map{ |r| { value: params[:value] } }
                       }
                   }
                 end
               else
                 changes << {
                     action: 'CREATE',
                     resource_record_set: {
                         name: params[:name],
                         type: params[:type],
                         ttl: params[:ttl],
                         resource_records: params[:resourcerecords] || [ { value: params[:value] } ]
                     }
                 }
               end
               if changes.size > 0
                 resp = change_resource_record_sets(zone, changes)
               end
               data = get_item_data(params, section, params)
               data
             rescue SystemExit => e
               raise e
             rescue Exception => e
               abort! "#{section}/#{params[:name]}: #{e.message}"
             end
           end
    )
  else
    abort! "Unsupported section #{section}"
  end
end
delete(section) click to toggle source
# File lib/customresource/route53/mixins/actions.rb, line 154
def delete(section)
  case section
    when /PrivateHostedZones/
      action('delete',
             section,
             lambda do |params, restyp, method, resource|
               begin
                 data = nil
                 set = list_hosted_zones(params)
                 unless set.size == 0
                   zone = set[0]
                   @logger.info "Delete existing hosted_zone #{zone[:id]}::#{zone[:name]}"
                   # if zone[:resource_record_set_count] > 2
                   #   rrs = list_resource_record_sets(zone)
                   #   changes = []
                   #   rrs.each do |rr|
                   #     changes << {
                   #                               action: 'DELETE',
                   #                  resource_record_set: {
                   #                                                     name: rr[:name],
                   #                                                     type: rr[:type],
                   #                                                      ttl: rr[:ttl],
                   #                                         resource_records: rr[:resource_records].map{ |r| { value: r[:value] } }
                   #                                       }
                   #                } unless %w(NS SOA).include?(rr[:type])
                   #   end
                   #   if changes.size > 0
                   #     change_resource_record_sets(zone, changes)
                   #   end
                   # end
                   @awssdk.delete_hosted_zone( { id: zone[:id] })
                   data = get_item_data(Mash.new({ id: zone[:id] }.merge(params)), section, params)
                 end
                 data
               rescue Exception => e
                 abort! "#{restyp}/#{params[:name]}: #{e.message}"
               end
             end
      )
    when /ReverseDNSEntries/
      action('delete',
             section,
             lambda do |params, restyp, method, resource|
               begin
                 data = nil
                 zone = get_hosted_zone(params)
                 @logger.info "Found hosted_zone #{zone[:id]}::#{zone[:name]}"
                 changes = []
                 rrs = list_resource_record_sets(zone)
                 if params[:resourcerecords]
                   rrs.select! do |rr|
                     rr[:name] == params[:name]
                   end
                   changes << {
                       action: 'DELETE',
                       resource_record_set: {
                           name: params[:name],
                           type: params[:type],
                           ttl: params[:ttl],
                           resource_records: params[:resourcerecords]
                       }
                   } if rrs.size > 0
                 else
                   rrs.each do |rr|
                     rrd = rr.dup
                     changes << {
                         action: 'DELETE',
                         resource_record_set: rrd
                     } if rr[:name] == params[:name]
                   end
                 end
                 if changes.size > 0
                   # abort! "Cannot delete #{params[:name]} from #{zone[:id]}::#{zone[:name]}"
                   change_resource_record_sets(zone,changes)
                 end
                 data = get_item_data(params, section, params)
                 data
               rescue NoSuchHostedZone => e
                 # [2014-12-15 Christo] TODO: May need to rethink considering the MIA ZoneId a successful deletion. The motivator for this is the fact that a stack will be stuck of someone manually deletes/recreates the zone ... :O
                 respond('SUCCESS', nil, e.message)
               rescue SystemExit => e
                 raise e
               rescue Exception => e
                 abort! "#{section}/#{params[:name]}: #{e.message}"
               end
             end
      )
    else
      abort! "Unsupported section #{section}"
  end
end
get_hosted_zone(params) click to toggle source
# File lib/customresource/route53/mixins/actions.rb, line 246
def get_hosted_zone(params)
  zone = nil
  begin
    if params[:hostedzoneid]
      resp = @awssdk.get_hosted_zone(id: params[:hostedzoneid])
      if resp
        zone = Mash.new(resp.hosted_zone.to_hash)
      end
    else
      set = list_hosted_zones(params)
      if set.size == 0
        raise NoSuchHostedZone.new("Cannot find hosted zone #{params[:hostedzone]}")
      else
        zone = set[0]
      end
    end
    zone
  rescue Aws::Route53::Errors::NoSuchHostedZone => e
    raise NoSuchHostedZone.new(e.message)
  end
end
list_hosted_zones(params) click to toggle source
# File lib/customresource/route53/mixins/actions.rb, line 288
def list_hosted_zones(params)
  set = []
  resp = @awssdk.list_hosted_zones
  if resp
    resp.hosted_zones.map { |item| set << item.to_h }
    while resp[:marker]
      resp = @awssdk.list_hosted_zones(marker: resp[:marker])
      if resp
        resp.hosted_zones.map { |item| set << Mash.new(item.to_h) }
      end
    end
    # Let's see if we have hostedzones which match this :name
    set.select!{ |item|  item[:name].match(/^#{params[:hostedzone]}$/) and item[:config][:private_zone] == true }
  end
  set
end
list_resource_record_sets(zone) click to toggle source
# File lib/customresource/route53/mixins/actions.rb, line 322
def list_resource_record_sets(zone)
  rrs = []
  resp = @awssdk.list_resource_record_sets(hosted_zone_id: zone[:id])
  while resp
    resp.resource_record_sets.map { |rr| rrs << Mash.new(rr.to_hash) }
    resp = if resp[:is_truncated]
             @awssdk.list_resource_record_sets(hosted_zone_id: zone[:id], marker: resp[:marker])
           else
             nil
           end
  end
  rrs
end
update(section) click to toggle source
# File lib/customresource/route53/mixins/actions.rb, line 150
def update(section)
  create section
end
wait_for_insync(resp) click to toggle source
# File lib/customresource/route53/mixins/actions.rb, line 268
def wait_for_insync(resp)
  unless resp and resp[:change_info]
    abort! 'Bad response to change_resource_record_sets'
  end
  change_info = Mash.new(resp[:change_info].to_hash)
  while resp
    resp = @awssdk.get_change(id: change_info[:id])
    unless resp and resp[:change_info]
      abort! "Bad response to get_change[id: #{change_info[:id]}]"
    end
    change_info = Mash.new(resp[:change_info].to_hash)
    if change_info[:status] != 'INSYNC'
      @logger.info "Change #{change_info[:id]} is #{change_info[:status]} ... Waiting #{WAIT_PERIOD_INSYNC}s"
      sleep WAIT_PERIOD_INSYNC
    else
      resp = nil
    end
  end
end