class Chef::Provider::AwsRoute53HostedZone

Constants

CREATE
DELETE
RRS_COMMENT
UPDATE

Attributes

record_set_list[RW]

Public Instance Methods

create_aws_object() click to toggle source
# File lib/chef/resource/aws_route53_hosted_zone.rb, line 107
def create_aws_object
  converge_by "create new Route 53 zone #{new_resource}" do
    # AWS stores some attributes off to the side here.
    hosted_zone_config = make_hosted_zone_config(new_resource)

    values = {
      name: new_resource.name,
      hosted_zone_config: hosted_zone_config,
      caller_reference: "chef-provisioning-aws-#{SecureRandom.uuid.upcase}", # required, unique each call
    }

    # this will validate the record_set resources prior to making any AWS calls.
    record_set_resources = get_record_sets_from_resource(new_resource)

    zone = new_resource.driver.route53_client.create_hosted_zone(values).hosted_zone
    new_resource.aws_route53_zone_id(zone.id)

    if record_set_resources
      populate_zone_info(record_set_resources, zone)

      change_list = record_set_resources.map { |rs| rs.to_aws_change_struct(UPDATE) }

      new_resource.driver.route53_client.change_resource_record_sets(hosted_zone_id: new_resource.aws_route53_zone_id,
                                                                     change_batch: {
                                                                       comment: RRS_COMMENT,
                                                                       changes: change_list
                                                                     })
    end
    zone
  end
end
destroy_aws_object(hosted_zone) click to toggle source
# File lib/chef/resource/aws_route53_hosted_zone.rb, line 198
def destroy_aws_object(hosted_zone)
  converge_by "delete Route53 zone #{new_resource}" do
    Chef::Log.info("Deleting all non-SOA/NS records for #{hosted_zone.name}")

    rr_changes = hosted_zone.resource_record_sets.reject do |aws_rr|
                   %w{SOA NS}.include?(aws_rr.type)
                 end.map do |aws_rr|
      {
        action: DELETE,
        resource_record_set: aws_rr.to_change_struct
      }
    end

    unless rr_changes.empty?
      aws_struct = {
        hosted_zone_id: hosted_zone.id,
        change_batch: {
          comment: "Purging RRs prior to deleting resource",
          changes: rr_changes
        }
      }

      new_resource.driver.route53_client.change_resource_record_sets(aws_struct)
    end

    result = new_resource.driver.route53_client.delete_hosted_zone(id: hosted_zone.id)
  end
end
get_record_sets_from_resource(new_resource) click to toggle source

`record_sets` is defined on the `aws_route53_hosted_zone` resource as a block attribute, so compile that, validate it, and return a list of AWSRoute53RecordSet resource objects.

# File lib/chef/resource/aws_route53_hosted_zone.rb, line 229
def get_record_sets_from_resource(new_resource)
  return nil unless new_resource.record_sets
  instance_eval(&new_resource.record_sets)

  # because we're in the provider, the RecordSet resources happen in their own mini Chef run, and they're the
  # only things in the resource_collection.
  record_set_resources = run_context.resource_collection.to_a
  return nil unless record_set_resources

  record_set_resources.each do |rs|
    rs.aws_route53_hosted_zone(new_resource)
    rs.aws_route53_zone_name(new_resource.name)

    if new_resource.defaults
      new_resource.class::DEFAULTABLE_ATTRS.each do |att|
        # check if the RecordSet has its own value, without triggering validation. in Chef >= 12.5, there is
        # #property_is_set?.
        if rs.instance_variable_get("@#{att}").nil? && !new_resource.defaults[att].nil?
          rs.send(att, new_resource.defaults[att])
        end
      end
    end

    rs.validate!
  end

  Chef::Resource::AwsRoute53RecordSet.verify_unique!(record_set_resources)
  record_set_resources
end
make_hosted_zone_config(new_resource) click to toggle source
# File lib/chef/resource/aws_route53_hosted_zone.rb, line 90
def make_hosted_zone_config(new_resource)
  config = {}
  # add :private_zone here once VPC validation is enabled.
  [:comment].each do |attr|
    value = new_resource.send(attr)
    config[attr] = value if value
  end
  config
end
populate_zone_info(record_set_resources, hosted_zone) click to toggle source

this happens at a slightly different time in the lifecycle from get_record_sets_from_resource.

# File lib/chef/resource/aws_route53_hosted_zone.rb, line 101
def populate_zone_info(record_set_resources, hosted_zone)
  record_set_resources.each do |rs|
    rs.aws_route53_zone_id(hosted_zone.id)
  end
end
update_aws_object(hosted_zone) click to toggle source
# File lib/chef/resource/aws_route53_hosted_zone.rb, line 139
def update_aws_object(hosted_zone)
  new_resource.aws_route53_zone_id(hosted_zone.id)

  # this will validate the record_set resources prior to making any AWS calls.
  record_set_resources = get_record_sets_from_resource(new_resource)

  if new_resource.comment != hosted_zone.config.comment
    new_resource.driver.route53_client.update_hosted_zone_comment(id: hosted_zone.id, comment: new_resource.comment)
  end

  if record_set_resources
    populate_zone_info(record_set_resources, hosted_zone)

    aws_record_sets = hosted_zone.resource_record_sets

    change_list = []

    # TODO: the SOA and NS records have identical :name properties (the zone name), so one of them will
    # be overwritten in the `keyed_aws_objects` hash. mostly we're declining to operate on SOA and NS,
    # so it probably doesn't matter, but bears investigating.

    # we already checked for duplicate Chef RR resources in #get_record_sets_from_resource.
    keyed_chef_resources = record_set_resources.each_with_object({}) { |rs, coll| (coll[rs.aws_key] ||= []) << rs; }
    keyed_aws_objects    = aws_record_sets.each_with_object({})      { |rs, coll| coll[rs.aws_key] = rs; }

    # because DNS is important, we're going to err on the side of caution and only operate on records for
    # which we have a Chef resource. "total management" might be a nice resource option to have.
    keyed_chef_resources.each do |key, chef_resource_ary|
      chef_resource_ary.each do |chef_resource|
        # RR already exists...
        if keyed_aws_objects.key?(key)
          # ... do we want to delete it?
          if chef_resource.action.first == :destroy
            change_list << chef_resource.to_aws_change_struct(DELETE)
          # ... update it, then, only if the fields differ.
          elsif chef_resource.to_aws_struct != keyed_aws_objects[key]
            change_list << chef_resource.to_aws_change_struct(UPDATE)
          end
        # otherwise, RR does not already exist...
        else
          # using UPSERT instead of CREATE; there are merits to both.
          change_list << chef_resource.to_aws_change_struct(UPSERT)
        end
      end
    end

    Chef::Log.debug("RecordSet changes: #{change_list.inspect}")
    if !change_list.empty?
      new_resource.driver.route53_client.change_resource_record_sets(hosted_zone_id: new_resource.aws_route53_zone_id,
                                                                     change_batch: {
                                                                       comment: RRS_COMMENT,
                                                                       changes: change_list
                                                                     })
    else
      Chef::Log.info("All aws_route53_record_set resources up to date (nothing to do).")
    end
  end
end