class Ec2Backup

Public Class Methods

new() click to toggle source
# File lib/ec2-backup/ec2-backup.rb, line 5
def initialize

  @settings = YAML.load_file("#{ENV['HOME']}/.ec2-backup.yml")

  @hourly_snapshots  = @settings['hourly_snapshots']
  @daily_snapshots   = @settings['daily_snapshots']
  @weekly_snapshots  = @settings['weekly_snapshots']
  @monthly_snapshots = @settings['monthly_snapshots']
  @tags              = @settings['tags']

end

Public Instance Methods

create_snapshot(options) click to toggle source

def create_snapshot

Purpose: Creates an EBS snapshot Parameters:

options<~Hash>
  volume_id<~String>: The volume id to snapshot
  description<~String>: The description of the snapshot
  snapshot_type<~String>: The type of snapshot being created (hourly, etc)
  tags<~Hash>: Key-value pairs of tags to apply to the snapshot

Returns: nil

# File lib/ec2-backup/ec2-backup.rb, line 88
def create_snapshot(options)
  snapshot = ec2.snapshots.new
  snapshot.volume_id = options['volume_id']
  snapshot.description = options['description']

  attempts = 0

  begin
    snapshot.save
    snapshot.reload
  rescue Fog::Compute::AWS::Error
    sleep 5
    attempts += 1
    if attempts == 5
      log "Error communicating with API; Unable to save volume `#{options['volume_id']}` (Desc: #{options['description']})"
    end
    return unless attempts == 5
  end

  options['tags'].each do |k,v|
    begin
      ec2.tags.create({resource_id: snapshot.id, key: k, value: v})
    rescue Errno::EINPROGRESS , Errno::EISCONN
      log "API Connection Error"
      sleep 1
      retry
    rescue Fog::Compute::AWS::Error
      log "Failed attaching tag `'#{k}' => #{v}` to #{options['snapshot_type']} snapshot #{snapshot.id}"
      sleep 1
      retry
    end
  end

end
delete_snapshot(snapshot_id) click to toggle source

def delete_snapshot

Purpose: Delete an EBS snapshot from Amazon EC2 Parameters:

snapshot_id<~String>: The id of the snapshot to be deleted

Returns: nil

# File lib/ec2-backup/ec2-backup.rb, line 131
def delete_snapshot(snapshot_id)
  log "\e[0;31m:: Deleting snapshot:\e[0m #{snapshot_id}"

  begin
    ec2.delete_snapshot(snapshot_id)
    sleep 0.2
  rescue Fog::Compute::AWS::NotFound
    log "Failed to delete snapshot: #{snapshot_id}; setting { 'protected' => true }"
    ec2.tags.create({resource_id: snapshot_id, key: 'protected', value: 'true'})
  rescue Fog::Compute::AWS::Error
    log "API Error"
  end

end
ec2() click to toggle source

def ec2

Purpose: Connects to the Amazon API Parameters: None Returns: Fog::Compute::AWS

# File lib/ec2-backup/ec2-backup.rb, line 37
def ec2
  Fog::Compute::AWS.new(aws_access_key_id: @aws_access_key_id, aws_secret_access_key: @aws_secret_access_key)
end
find_instances(tags) click to toggle source

def find_instances

Purpose: Returns all servers with matching key-value tags Parameters:

tags<~Hash>: key-value pairs of tags to match against EC2 instances

Returns: <~Array>

Fog::Compute::AWS::Server
# File lib/ec2-backup/ec2-backup.rb, line 64
def find_instances(tags)
  attempts = 0
  begin
    ec2.servers.select { |server| tags.reject { |k,v| server.tags[k] == tags[k] }.empty? }
  rescue Excon::Errors::ServiceUnavailable
    sleep 5
    attempts += 1
    return [] if attempts == 5
    retry
  end
end
log(text) click to toggle source

def log

Purpose: Neatly logs events to the screen Parameters:

text<~String>: The text to log to the screen

Returns:

<~String> - Full line of text
# File lib/ec2-backup/ec2-backup.rb, line 26
def log(text)
  puts "[#{Time.now}] \e[0;30mCaller: #{caller[0][/`(.*)'/,1]} \e[0m| #{text}"
end
start() click to toggle source

def start

Purpose: Start the backup process Parameters: none Returns: nil

# File lib/ec2-backup/ec2-backup.rb, line 184
def start

  @settings['accounts'].each do |account,keys|

    puts "Account: #{account}"
    @aws_access_key_id     = keys['access_key_id']
    @aws_secret_access_key = keys['secret_access_key']

    # Find all servers with tags matching the supplied Hash
    find_instances(@tags).each do |server|

      # Begin snapshotting each volume attached to the server
      #
      server.block_device_mapping.each do |block_device|

        log "\e[0;32m Searching for matching snapshots \e[0m(#{server.id}:#{block_device}).."
        snapshots = volume_snapshots(block_device['volumeId'])

        # Create each type of backup we'll be using
        #
        %w(hourly daily weekly monthly).each do |snapshot_type|

          # Build snapshot history for the working volume and return all snapshots
          # matching our particular snapshot type
          history = snapshots.select do |snapshot|
            snapshot.tags['snapshot_type'] == snapshot_type  &&
              snapshot.tags['volume_id'] == block_device['volumeId'] &&
              snapshot.tags['protected'] == 'false'
          end

          history.sort_by! { |snapshot| snapshot.created_at }

          unless too_soon?(history,snapshot_type) || instance_variable_get("@#{snapshot_type}_snapshots") == 0

            # Check against threshold limits for backup history and delete as needed
            #
            while history.size >= instance_variable_get("@#{snapshot_type}_snapshots")
              delete_snapshot(history.first.id)
              history.delete(history.first)
            end

            log "Creating #{snapshot_type} for #{block_device['volumeId']}.."
            create_snapshot({
              'volume_id'     => block_device['volumeId'],
              'snapshot_type' => snapshot_type,
              'description'   => "Snapshot::#{snapshot_type.capitalize}> Server: #{server.id}",
              'tags'          => {
                'snapshot_time' => "#{Time.now}",
                'snapshot_type' => snapshot_type,
                'instance_id'   => server.id,
                'volume_id'     => block_device['volumeId'],
                'deviceName'    => block_device['deviceName'],
                'protected'     => 'false'
              }
            })
          end
        end
      end
    end
  end

end
too_soon?(history,snapshot_type) click to toggle source

def too_soon?

Purpose: Determines if enough time has passed between taking snapshots Parameters:

history<~Array>
  Fog::Compute::AWS::Snapshot: Volume snapshot
snapshot_type<~String>: The type of snapshot (hourly, etc)

Returns: Boolean

# File lib/ec2-backup/ec2-backup.rb, line 156
def too_soon?(history,snapshot_type)

  # If the backup history size is zero,
  # the server doesn't have any backups yet.
  return false if history.size == 0

  elapsed = Time.now - history.last.created_at

  case snapshot_type
  when 'hourly'
    elapsed < 1.hour
  when 'daily'
    elapsed < 1.day
  when 'weekly'
    elapsed < 1.week
  when 'monthly'
    elapsed < 1.month
  end

end
volume_snapshots(volume_id) click to toggle source

def volume_snapshots

Purpose: Returns all snapshots associated with an EBS volume id Parameters:

volume_id<~String>: The volume id of the EBS volume

Returns: <~Array>

Fog::AWS::Snapshot
# File lib/ec2-backup/ec2-backup.rb, line 50
def volume_snapshots(volume_id)
  ec2.snapshots.select { |snapshot| snapshot.volume_id == volume_id }
end