Class: Ec2Backup

Inherits:
Object
  • Object
show all
Defined in:
lib/ec2-backup/ec2-backup.rb

Instance Method Summary collapse

Constructor Details

#initializeEc2Backup

Returns a new instance of Ec2Backup.



5
6
7
8
9
10
11
12
13
14
15
# 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

Instance Method Details

#create_snapshot(options) ⇒ Object

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



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# 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) ⇒ Object

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



131
132
133
134
135
136
137
138
139
140
141
142
143
144
# 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

#ec2Object

def ec2

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



37
38
39
# 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) ⇒ Object

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


64
65
66
67
68
69
70
71
72
73
74
# 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) ⇒ Object

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


26
27
28
# 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

#startObject

def start

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



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/ec2-backup/ec2-backup.rb', line 184

def start

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

    puts "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) ⇒ Boolean

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

Returns:

  • (Boolean)


156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# 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) ⇒ Object

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


50
51
52
# File 'lib/ec2-backup/ec2-backup.rb', line 50

def volume_snapshots(volume_id)
  ec2.snapshots.select { |snapshot| snapshot.volume_id == volume_id }
end