class EC2::Snapshot::Replicator::Engine

Constants

DELETE_AFTER_TAG_KEY
SOURCE_SNAPSHOT_ID_TAG_KEY

Attributes

destination_ec2[R]
source_ec2[R]

Public Class Methods

new(config) click to toggle source
# File lib/ec2/snapshot/replicator/engine.rb, line 12
def initialize(config)
  @config = config

  set_credentials

  @source_ec2 = Aws::EC2::Resource.new(region: @config.source_region)
  @destination_ec2 = Aws::EC2::Resource.new(region: @config.destination_region)
end

Public Instance Methods

delete_snapshots() click to toggle source
# File lib/ec2/snapshot/replicator/engine.rb, line 126
def delete_snapshots
  Logger.info ">>> deleting snapshots..."
  @destination_ec2.snapshots(owner_ids: [@config.owner_id]).each do |snapshot|
    tag = snapshot.tags.find {|t| t.key == DELETE_AFTER_TAG_KEY }

    unless tag
      Logger.debug "[#{snapshot.id}] tag #{DELETE_AFTER_TAG_KEY} is not found."
      next
    end

    delete_after = Time.at(tag.value.to_i)
    if delete_after < Time.now
      Logger.info "[#{snapshot.id}] deleting..."
      ask_continue("Delete #{snapshot.id}.")
      snapshot.delete
    end
  end
end
mark_deleted_snapshots() click to toggle source
# File lib/ec2/snapshot/replicator/engine.rb, line 83
def mark_deleted_snapshots
  Logger.info ">>> marking deleted snapshots..."

  destination_snapshots = @destination_ec2.snapshots(owner_ids: [@config.owner_id]).select do |snapshot|
    if snapshot.tags.find {|t| t.key == DELETE_AFTER_TAG_KEY }
      next false
    end

    unless snapshot.tags.find {|t| t.key == SOURCE_SNAPSHOT_ID_TAG_KEY }
      Logger.debug "[#{snapshot.id}] tag #{SOURCE_SNAPSHOT_ID_TAG_KEY} is not found."
      next false
    end

    true
  end

  source_snapshot_ids = destination_snapshots.map do |snapshot|
    snapshot.tags.find {|t| t.key == SOURCE_SNAPSHOT_ID_TAG_KEY }.value
  end

  # The maximum number of filter values specified on a single call is 200
  source_snapshots = source_snapshot_ids.each_slice(190).flat_map do |ids|
    @source_ec2.snapshots(owner_ids: [@config.owner_id], filters: [{name: 'snapshot-id', values: ids}]).to_a
  end

  destination_snapshots.each do |snapshot|
    source_snapshot_id = snapshot.tags.find {|t| t.key == SOURCE_SNAPSHOT_ID_TAG_KEY }.value
    if source_snapshots.find {|s| s.id == source_snapshot_id }
      Logger.debug "[#{snapshot.id}] source snapshot (#{source_snapshot_id}) exists"
    else
      Logger.info "[#{snapshot.id}] creating #{DELETE_AFTER_TAG_KEY} tag because source snapshot (#{source_snapshot_id}) is deleted."

      delete_after = (Time.now + @config.delay_deletion_sec).to_i
      ask_continue("Create a tag #{DELETE_AFTER_TAG_KEY}:#{delete_after}.")
      snapshot.create_tags(
        tags: [
          {key: DELETE_AFTER_TAG_KEY, value: delete_after.to_s},
        ],
      )
    end
  end
end
replicate_snapshots() click to toggle source
# File lib/ec2/snapshot/replicator/engine.rb, line 37
def replicate_snapshots
  Logger.info ">>> replicating snapshots..."

  source_snapshots = @source_ec2.snapshots(owner_ids: [@config.owner_id])
  source_snapshot_ids = source_snapshots.map {|s| s.id }

  # The maximum number of filter values specified on a single call is 200
  destination_snapshots = source_snapshot_ids.each_slice(190).flat_map do |ids|
    @destination_ec2.snapshots(
      owner_ids: [@config.owner_id],
      filters: [{name: "tag:#{SOURCE_SNAPSHOT_ID_TAG_KEY}", values: ids}]
    ).to_a
  end

  source_snapshots.each do |snapshot|
    destination_snapshot = destination_snapshots.find do |s|
      s.tags.find do |t|
        t.key == SOURCE_SNAPSHOT_ID_TAG_KEY &&
          t.value == snapshot.id
      end
    end

    if destination_snapshot
      Logger.debug "[#{snapshot.id}] already replicated"
    else
      Logger.info "[#{snapshot.id}] replicating..."

      ask_continue("Copy snapshot.")

      res = @destination_ec2.snapshot(snapshot.id).copy(
        source_region: @config.source_region,
        destination_region: @config.destination_region,
        description: "(replicated) #{snapshot.description}",
      )

      Logger.debug "[#{res.snapshot_id}] created in #{@config.destination_region}"
      copied_snapshot = @destination_ec2.snapshot(res.snapshot_id)
      copied_snapshot.create_tags(
        tags: [
          {key: SOURCE_SNAPSHOT_ID_TAG_KEY, value: snapshot.id},
        ],
      )
    end
  end
end
run_once() click to toggle source
# File lib/ec2/snapshot/replicator/engine.rb, line 31
def run_once
  replicate_snapshots
  mark_deleted_snapshots
  delete_snapshots
end
start() click to toggle source
# File lib/ec2/snapshot/replicator/engine.rb, line 21
def start
  Logger.info "start loop"
  while true
    run_once

    Logger.info "sleeping for #{@config.interval_sec} sec..."
    sleep @config.interval_sec
  end
end

Private Instance Methods

ask_continue(msg) click to toggle source
# File lib/ec2/snapshot/replicator/engine.rb, line 147
def ask_continue(msg)
  return unless @config.debug

  print "#{msg} continue? (y/N): "
  unless $stdin.gets.downcase =~ /\Ay/
    abort
  end
end
set_credentials() click to toggle source
# File lib/ec2/snapshot/replicator/engine.rb, line 156
def set_credentials
  if @config.access_key_id && @config.secret_access_key
    Aws.config.update({
      credentials: Aws::Credentials.new(
        @config.access_key_id,
        @config.secret_access_key,
      ),
    })
  end
end