class AwsXRegionSync::RdsAutomatedSnapshotSync

Public Instance Methods

sync() click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 11
def sync
  setup = pre_sync_setup
  sync_snapshot_to_region setup[:source_region], setup[:destination_region], setup[:snapshot]
end
sync_required?() click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 16
def sync_required?
  setup = pre_sync_setup
  sync = needs_sync? setup[:destination_region], setup[:source_region], setup[:snapshot], discover_aws_account_id
  sync[:sync_required]
end
sync_snapshot_to_region(source_region, destination_region, source_snapshot) click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 22
def sync_snapshot_to_region source_region, destination_region, source_snapshot
  # The way automated snapshot copying seems to work is as long as you're copying snapshot from the same db instance between regions
  # every snapshot you copy from the source region after the first one is simply just an incremental copy.  Because of this we
  # should copy the latest snapshot

  # This first thing we should do is look for a sync tag in the destination snapshot (if present) and see we've already synced this
  # snapshot-id (or if the id is outdated)
  aws_account_id = discover_aws_account_id
  
  sync = needs_sync?(destination_region, source_region, source_snapshot, aws_account_id)
  if sync[:sync_required]
    sr = region(source_region)
    result = destination_region.client.copy_db_snapshot source_db_snapshot_identifier: arn(sr, aws_account_id, source_snapshot.id), target_db_snapshot_identifier: sanitize_snapshot_id(source_snapshot.id)

    if result[:db_snapshot_identifier]
      destination_snapshot_id = result[:db_snapshot_identifier]

      # Move the source snapshot's created timestamp and id to the destination snapshot's sync tag so that we know the last time
      # the snapshot was synced and what region it was synced from.  Also, log the sync against the source too so we have tangible evidence
      # that it was synced just by looking at it as well.
      dr = region(destination_region)
      
      dest_sync_tag = create_sync_tag sr, source_snapshot.id, timestamp: source_snapshot.created_at, sync_subtype: "From"
      source_sync_tag = create_sync_tag dr, destination_snapshot_id, timestamp: source_snapshot.created_at

      destination_region.client.add_tags_to_resource resource_name: arn(dr, aws_account_id, result[:db_snapshot_identifier]), tags: [dest_sync_tag]
      source_region.client.add_tags_to_resource resource_name: arn(sr, aws_account_id, source_snapshot.id), tags: [source_sync_tag]

      # We'll now clean up older snapshots if necessary
      cleanup_old_snapshots sync[:snapshots], max_snapshots, destination_region
    end
  end

  destination_snapshot_id
end
validate_config() click to toggle source
Calls superclass method AwsXRegionSync::AwsSync#validate_config
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 4
def validate_config
  raise AwsXRegionSyncConfigError, "The #{self.sync_name} configuration must provide a 'db_instance' option to use to locate automated snapshots." unless config['db_instance'] && config['db_instance'].length > 0
  # This call will raise if there's an invalid value..so just call it right now
  max_snapshots
  super
end

Private Instance Methods

arn(region, account_number, snapshot_id) click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 111
def arn region, account_number, snapshot_id
  # Strip all non-decimal characters from account numbers
  account_number = account_number.gsub(/\D/, "")
  "arn:aws:rds:#{region}:#{account_number}:snapshot:#{snapshot_id}"
end
cleanup_old_snapshots(destination_snapshots, max_snapshots_to_retain, destination_region) click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 178
def cleanup_old_snapshots destination_snapshots, max_snapshots_to_retain, destination_region
  if destination_snapshots.size > max_snapshots_to_retain
    # Grab the first max_snapshots # of destination_snapshots (since they're sorted already in ascending order) and just delete them
    snapshots_to_delete = destination_snapshots.take(destination_snapshots.size - max_snapshots_to_retain)

    snapshots_to_delete.each do |snapshot|
      destination_region.client.delete_db_snapshot db_snapshot_identifier: snapshot[:snapshot].id
    end
  end
end
extract_newest_snapshot(snapshots) click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 104
def extract_newest_snapshot snapshots
  # Grab the snapshot created at time for snapshot as the hash key, then we can sort on the keys and extract the last one from that sorted array
  snapshot_values = {}
  snapshots.each {|s| snapshot_values[s.created_at] = s}
  snapshot_values[snapshot_values.keys.sort[-1]]
end
find_sync_tag(tags, source_region_name, db_instance) click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 159
def find_sync_tag tags, source_region_name, db_instance
  sync_key = make_to_snapshot_sync_tag_key source_region_name

  tags.each do |t|
    return t if t[:key] == sync_key
  end

  nil
end
make_rds(config) click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 99
def make_rds config
  # purely for mocking
  AWS::RDS.new config
end
make_to_snapshot_sync_tag_key(source_region) click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 155
def make_to_snapshot_sync_tag_key source_region
  create_sync_tag(source_region, nil, sync_subtype: "From")[:key]
end
max_snapshots() click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 189
def max_snapshots
  retain = nil
  if config['max_snapshots_to_retain']
    retain = Integer(config['max_snapshots_to_retain']) 
  else
    retain = 2
  end

  retain
rescue 
  raise AwsXRegionSyncConfigError, "The #{self.sync_name} configuration must provide a valid 'max_snapshots_to_retain' option. '#{config['max_snapshots_to_retain']}' is not valid."
end
needs_sync?(destination_region, source_region, source_snapshot, aws_account_id) click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 80
def needs_sync? destination_region, source_region, source_snapshot, aws_account_id
  destination_snapshots = retrieve_destination_snapshots_for_instance destination_region, region(source_region), source_snapshot.db_instance.id, aws_account_id
  sync_needed = destination_snapshots.size == 0 || snapshot_needs_copying(destination_snapshots, source_snapshot)

  {sync_required: sync_needed, snapshots: destination_snapshots}
end
pre_sync_setup() click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 60
def pre_sync_setup
  # There doesn't appear to be a direct way of actually obtaining
  # a region reference after initializing the rds client - like there is for the s3 client
  # so we'll just create multiple clients.
  source_rds = rds_client config['source_region'], config['db_instance']
  destination_rds = rds_client config['destination_region'], config['db_instance']

  instance = source_rds.db_instances[config['db_instance']]
  raise AwsXRegionSyncConfigError, "No DB Instance with identifier '#{config['db_instance']}' is available for these credentials in region #{config['db_instance']}." unless instance

  # Find all the snapshots
  snapshots = instance.snapshots.with_type('automated').to_a
  raise AwsXRegionSyncConfigError, "No automated snapshots for db '#{config['db_instance']}' are available for these credentials in region #{config['source_region']}." unless snapshots.size > 0

  # Use the created_at attribute of the snapshot to find the newest one
  newest_snapshot = extract_newest_snapshot snapshots

  {source_region: source_rds, destination_region: destination_rds, snapshot: newest_snapshot}
end
rds_client(region, db_instance) click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 87
def rds_client region, db_instance
  # There doesn't appear to be a direct way of actually obtaining
  # a region reference after initializing an rds client object (unlike w/ the EC2 API)
  rds = make_rds aws_config.merge({region: region})
  # Just force an API call to validate the region setting.  Use the db instance call which we'll use later and, I believe, is cached
  # so there should be no penalty for calling it here (since we use it pretty much directly after this call anyway)
  rds.db_instances[db_instance]
  rds
rescue
  raise AwsXRegionSyncConfigError, "Region '#{region}' is invalid.  It either does not exist or the given credentials cannot access it."
end
region(client) click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 117
def region client
  client.config.region
end
retrieve_destination_snapshots_for_instance(destination_region, source_region_name, db_instance, account_number) click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 136
def retrieve_destination_snapshots_for_instance destination_region, source_region_name, db_instance, account_number
  snapshots = destination_region.db_instances[db_instance].snapshots.with_type('manual').to_a

  # order them newest to oldest based on their sync tags
  snapshot_list = []
  snapshots.each do |s|
    tags = tags_for_snapshot(destination_region, account_number, s.id)

    sync_tag = find_sync_tag tags, source_region_name, db_instance
    # Ignore any snapshot that doesn't have a sync tag, these are likely actual user initiated manual copies and we don't want to mess with them
    next unless sync_tag

    split_sync_value = parse_sync_tag_value(sync_tag[:value]) if sync_tag
    snapshot_list << {snapshot: s, tags: tags, :sync_timestamp => split_sync_value[:timestamp], :sync_tag=>sync_tag}
  end

  snapshot_list.sort_by {|snapshot| snapshot[:sync_timestamp]}
end
sanitize_snapshot_id(id) click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 169
def sanitize_snapshot_id id
  # The official "rules" for identifiers are:
  # Identifiers must begin with a letter; must contain only ASCII letters, digits, and hyphens;"
  # and must not end with a hyphen or contain two consecutive hyphens
  # Ids for automated snapshots appear to look like "rds:db_instance-YYYY-mm-dd-HH-MM"..that hyphen is invalid
  # For now, just translate any : to -
  id.gsub(":", "-")
end
snapshot_needs_copying(destination_snapshots, source_snapshot) click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 121
def snapshot_needs_copying destination_snapshots, source_snapshot
  # The snapshot array is already sorted, so the last value from the array is the newest one
  newest_snapshot_hash = destination_snapshots.last
  source_snapshot.created_at.to_i > newest_snapshot_hash[:sync_timestamp].to_i
end
tags_for_snapshot(rds, account_number, snapshot_id) click to toggle source
# File lib/aws_xregion_sync/rds_automated_snapshot_sync.rb, line 127
def tags_for_snapshot rds, account_number, snapshot_id
  region_client = rds.client
  region_name = region(region_client)

  aws_resource = arn(region_name, account_number, snapshot_id)
  result = region_client.list_tags_for_resource resource_name: arn(region_name, account_number, snapshot_id)
  result[:tag_list]
end