ec2-backup 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ff55011774526499c64260a0d43975fde63d1e59
4
+ data.tar.gz: 4215bc6ffd251eab34fae1112bdcabb3abb10153
5
+ SHA512:
6
+ metadata.gz: eabadc478c110797af49223fda452c3bddcd78959f2744fd917dfbca5fe55617cba16a4bc6a7060952d969710c4702fd3d7ccd7846bf8cf26dd372f03b54ef3d
7
+ data.tar.gz: ef47001adb02aab9aaae01e84601be1f806f34c9bdec98e6a6e248f04208ab21fdb61c658442467b39d8125e67c77dde939010cfe40a2b0d16e6d289911f76f8
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Alfred Moreno
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,33 @@
1
+ ec2-backup
2
+ ==========
3
+
4
+ Automate backups of your infrastructure dynamically via AWS EC2 Tagging and Snapshots
5
+
6
+ Installation
7
+ ==========
8
+
9
+ `gem install ec2-backup`
10
+
11
+ Configuration
12
+ ==========
13
+
14
+ * accounts - An array of accounts you wish to configure backups for
15
+
16
+ Each account has a key for the name of the account followed by the
17
+ `access_key_id` and `secret_access_key` for the account
18
+
19
+ * hourly_snapshots - The amount of hourly snapshots to retain
20
+ * daily_snapshots - The amount of daily snapshots to retain
21
+ * weekly_snapshots - The amount of weekly snapshots to retain
22
+ * monthly_snapshots - The amount of monthly snapshots to retain
23
+
24
+ * tags - The AWS EC2 Tags used for finding instances to be snapshotted.
25
+
26
+ Usage
27
+ ==========
28
+
29
+ Create a `ec2-backup.yml` as shown in the example file in the repository
30
+ and place it in your home directory as `.ec2-backup.yml`
31
+
32
+ When you're ready to start backing up your instances, execute the
33
+ `ec2-backup` command from your terminal.
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ec2-backup'
4
+
5
+ backup = Ec2Backup.new
6
+ backup.start
@@ -0,0 +1,19 @@
1
+
2
+ accounts:
3
+ production:
4
+ access_key_id: ''
5
+ secret_access_key: ''
6
+ corporate:
7
+ access_key_id: ''
8
+ secret_access_key: ''
9
+ testing:
10
+ access_key_id: ''
11
+ secret_access_key: ''
12
+
13
+ hourly_snapshots: 24
14
+ daily_snapshots: 30
15
+ weekly_snapshots: 4
16
+ monthly_snapshots: 12
17
+
18
+ tags:
19
+ backup: 'true'
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ec2-backup/ec2-backup'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "ec2-backup"
8
+ gem.version = '1.0.0'
9
+ gem.authors = ["Alfred Moreno"]
10
+ gem.email = ["kryptek@gmail.com"]
11
+ gem.description = %q{Automate backups of your infrastructure dynamically via AWS EC2 Tagging and Snapshots}
12
+ gem.summary = %q{A configurable backup gem for ec2 volumes. See the github page for more}
13
+ gem.homepage = "https://github.com/kryptek/ec2-backup"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'active_support'
21
+ gem.add_dependency 'fog'
22
+
23
+ gem.license = 'MIT'
24
+ end
@@ -0,0 +1,5 @@
1
+ __LIB_DIR__ = File.expand_path(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift __LIB_DIR__ unless $LOAD_PATH.include?(__LIB_DIR__)
3
+
4
+ require 'ec2-backup/ec2-backup'
5
+
@@ -0,0 +1,246 @@
1
+ require 'active_support/all'
2
+ require 'fog'
3
+
4
+ class Ec2Backup
5
+ def initialize
6
+
7
+ @settings = YAML.load_file("#{ENV['HOME']}/.ec2-backup.yml")
8
+
9
+ @hourly_snapshots = @settings['hourly_snapshots']
10
+ @daily_snapshots = @settings['daily_snapshots']
11
+ @weekly_snapshots = @settings['weekly_snapshots']
12
+ @monthly_snapshots = @settings['monthly_snapshots']
13
+ @tags = @settings['tags']
14
+
15
+ end
16
+
17
+ ###############################################################################
18
+ # def log
19
+ #
20
+ # Purpose: Neatly logs events to the screen
21
+ # Parameters:
22
+ # text<~String>: The text to log to the screen
23
+ # Returns:
24
+ # <~String> - Full line of text
25
+ ###############################################################################
26
+ def log(text)
27
+ puts "[#{Time.now}] \e[0;30mCaller: #{caller[0][/`(.*)'/,1]} \e[0m| #{text}"
28
+ end
29
+
30
+ ###############################################################################
31
+ # def ec2
32
+ #
33
+ # Purpose: Connects to the Amazon API
34
+ # Parameters: None
35
+ # Returns: Fog::Compute::AWS
36
+ ###############################################################################
37
+ def ec2
38
+ Fog::Compute::AWS.new(aws_access_key_id: @aws_access_key_id, aws_secret_access_key: @aws_secret_access_key)
39
+ end
40
+
41
+ ###############################################################################
42
+ # def volume_snapshots
43
+ #
44
+ # Purpose: Returns all snapshots associated with an EBS volume id
45
+ # Parameters:
46
+ # volume_id<~String>: The volume id of the EBS volume
47
+ # Returns: <~Array>
48
+ # Fog::AWS::Snapshot
49
+ ###############################################################################
50
+ def volume_snapshots(volume_id)
51
+ ec2.snapshots.select { |snapshot| snapshot.volume_id == volume_id }
52
+ end
53
+
54
+ ###############################################################################
55
+ # def find_instances
56
+ #
57
+ # Purpose: Returns all servers with matching key-value tags
58
+ # Parameters:
59
+ # tags<~Hash>: key-value pairs of tags to match against EC2 instances
60
+ # Returns: <~Array>
61
+ # Fog::Compute::AWS::Server
62
+ #
63
+ ###############################################################################
64
+ def find_instances(tags)
65
+ attempts = 0
66
+ begin
67
+ ec2.servers.select { |server| tags.reject { |k,v| server.tags[k] == tags[k] }.empty? }
68
+ rescue Excon::Errors::ServiceUnavailable
69
+ sleep 5
70
+ attempts += 1
71
+ return [] if attempts == 5
72
+ retry
73
+ end
74
+ end
75
+
76
+ ###############################################################################
77
+ # def create_snapshot
78
+ #
79
+ # Purpose: Creates an EBS snapshot
80
+ # Parameters:
81
+ # options<~Hash>
82
+ # volume_id<~String>: The volume id to snapshot
83
+ # description<~String>: The description of the snapshot
84
+ # snapshot_type<~String>: The type of snapshot being created (hourly, etc)
85
+ # tags<~Hash>: Key-value pairs of tags to apply to the snapshot
86
+ # Returns: nil
87
+ ###############################################################################
88
+ def create_snapshot(options)
89
+ snapshot = ec2.snapshots.new
90
+ snapshot.volume_id = options['volume_id']
91
+ snapshot.description = options['description']
92
+
93
+ attempts = 0
94
+
95
+ begin
96
+ snapshot.save
97
+ snapshot.reload
98
+ rescue Fog::Compute::AWS::Error
99
+ sleep 5
100
+ attempts += 1
101
+ if attempts == 5
102
+ log "Error communicating with API; Unable to save volume `#{options['volume_id']}` (Desc: #{options['description']})"
103
+ end
104
+ return unless attempts == 5
105
+ end
106
+
107
+ options['tags'].each do |k,v|
108
+ begin
109
+ ec2.tags.create({resource_id: snapshot.id, key: k, value: v})
110
+ rescue Errno::EINPROGRESS , Errno::EISCONN
111
+ log "API Connection Error"
112
+ sleep 1
113
+ retry
114
+ rescue Fog::Compute::AWS::Error
115
+ log "Failed attaching tag `'#{k}' => #{v}` to #{options['snapshot_type']} snapshot #{snapshot.id}"
116
+ sleep 1
117
+ retry
118
+ end
119
+ end
120
+
121
+ end
122
+
123
+ ###############################################################################
124
+ # def delete_snapshot
125
+ #
126
+ # Purpose: Delete an EBS snapshot from Amazon EC2
127
+ # Parameters:
128
+ # snapshot_id<~String>: The id of the snapshot to be deleted
129
+ # Returns: nil
130
+ ###############################################################################
131
+ def delete_snapshot(snapshot_id)
132
+ log "\e[0;31m:: Deleting snapshot:\e[0m #{snapshot_id}"
133
+
134
+ begin
135
+ ec2.delete_snapshot(snapshot_id)
136
+ sleep 0.2
137
+ rescue Fog::Compute::AWS::NotFound
138
+ log "Failed to delete snapshot: #{snapshot_id}; setting { 'protected' => true }"
139
+ ec2.tags.create({resource_id: snapshot_id, key: 'protected', value: 'true'})
140
+ rescue Fog::Compute::AWS::Error
141
+ log "API Error"
142
+ end
143
+
144
+ end
145
+
146
+ ###############################################################################
147
+ # def too_soon?
148
+ #
149
+ # Purpose: Determines if enough time has passed between taking snapshots
150
+ # Parameters:
151
+ # history<~Array>
152
+ # Fog::Compute::AWS::Snapshot: Volume snapshot
153
+ # snapshot_type<~String>: The type of snapshot (hourly, etc)
154
+ # Returns: Boolean
155
+ ###############################################################################
156
+ def too_soon?(history,snapshot_type)
157
+
158
+ # If the backup history size is zero,
159
+ # the server doesn't have any backups yet.
160
+ return false if history.size == 0
161
+
162
+ elapsed = Time.now - history.last.created_at
163
+
164
+ case snapshot_type
165
+ when 'hourly'
166
+ elapsed < 1.hour
167
+ when 'daily'
168
+ elapsed < 1.day
169
+ when 'weekly'
170
+ elapsed < 1.week
171
+ when 'monthly'
172
+ elapsed < 1.month
173
+ end
174
+
175
+ end
176
+
177
+ ###############################################################################
178
+ # def start
179
+ #
180
+ # Purpose: Start the backup process
181
+ # Parameters: none
182
+ # Returns: nil
183
+ ###############################################################################
184
+ def start
185
+
186
+ @settings['accounts'].each do |account,keys|
187
+
188
+ puts "Account: #{account}"
189
+ @aws_access_key_id = keys['access_key_id']
190
+ @aws_secret_access_key = keys['secret_access_key']
191
+
192
+ # Find all servers with tags matching the supplied Hash
193
+ find_instances(@tags).each do |server|
194
+
195
+ # Begin snapshotting each volume attached to the server
196
+ #
197
+ server.block_device_mapping.each do |block_device|
198
+
199
+ log "\e[0;32m Searching for matching snapshots \e[0m(#{server.id}:#{block_device}).."
200
+ snapshots = volume_snapshots(block_device['volumeId'])
201
+
202
+ # Create each type of backup we'll be using
203
+ #
204
+ %w(hourly daily weekly monthly).each do |snapshot_type|
205
+
206
+ # Build snapshot history for the working volume and return all snapshots
207
+ # matching our particular snapshot type
208
+ history = snapshots.select do |snapshot|
209
+ snapshot.tags['snapshot_type'] == snapshot_type &&
210
+ snapshot.tags['volume_id'] == block_device['volumeId'] &&
211
+ snapshot.tags['protected'] == 'false'
212
+ end
213
+
214
+ history.sort_by! { |snapshot| snapshot.created_at }
215
+
216
+ unless too_soon?(history,snapshot_type) || instance_variable_get("@#{snapshot_type}_snapshots") == 0
217
+
218
+ # Check against threshold limits for backup history and delete as needed
219
+ #
220
+ while history.size >= instance_variable_get("@#{snapshot_type}_snapshots")
221
+ delete_snapshot(history.first.id)
222
+ history.delete(history.first)
223
+ end
224
+
225
+ log "Creating #{snapshot_type} for #{block_device['volumeId']}.."
226
+ create_snapshot({
227
+ 'volume_id' => block_device['volumeId'],
228
+ 'snapshot_type' => snapshot_type,
229
+ 'description' => "Snapshot::#{snapshot_type.capitalize}> Server: #{server.id}",
230
+ 'tags' => {
231
+ 'snapshot_time' => "#{Time.now}",
232
+ 'snapshot_type' => snapshot_type,
233
+ 'instance_id' => server.id,
234
+ 'volume_id' => block_device['volumeId'],
235
+ 'deviceName' => block_device['deviceName'],
236
+ 'protected' => 'false'
237
+ }
238
+ })
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end
244
+
245
+ end
246
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ec2-backup
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Alfred Moreno
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: active_support
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: fog
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Automate backups of your infrastructure dynamically via AWS EC2 Tagging
42
+ and Snapshots
43
+ email:
44
+ - kryptek@gmail.com
45
+ executables:
46
+ - ec2-backup
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - ".gitignore"
51
+ - LICENSE
52
+ - README.md
53
+ - bin/ec2-backup
54
+ - ec2-backup.example.yml
55
+ - ec2-backup.gemspec
56
+ - lib/ec2-backup.rb
57
+ - lib/ec2-backup/ec2-backup.rb
58
+ homepage: https://github.com/kryptek/ec2-backup
59
+ licenses:
60
+ - MIT
61
+ metadata: {}
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 2.0.2
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: A configurable backup gem for ec2 volumes. See the github page for more
82
+ test_files: []
83
+ has_rdoc: