aws-tools 0.0.2

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.
Files changed (3) hide show
  1. checksums.yaml +15 -0
  2. data/lib/aws_region.rb +452 -0
  3. metadata +58 -0
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YjkzYzUwNTA3MWE0YTE1NDAzYmY5ZDcxZWI2ZjBlNzFlNGQ4NTc2Zg==
5
+ data.tar.gz: !binary |-
6
+ OTg4ZmVmMjNlMjJkZTQyY2NmYTU0NTc4ZDdiODFhMWQ2OWE3NTVjMQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZGRkOTFhYTRmOWZjMWI2N2ExMjEzOWJjMzU5MjlhMGY2N2Q5YmRmYjk3ZDY0
10
+ NGRjYjRlMWJmZGE1ZjkxZTZhOWQ5OTYzZGY1ZmM4MjNlM2Q4OWI2NGVhNWNm
11
+ OWYxNTgwM2NhMzg4Nzk2MWI3YjQzZTYzNjExNGMwYTAzNDllZGM=
12
+ data.tar.gz: !binary |-
13
+ MzY2NTY4OTAzOTk3ZjZlZDZhMzA3Y2JmMmVmMjAxNTQ3ZmI5YjcxNzdjOGFl
14
+ ZDgxYzg3ODYxMTY3MjEwZWI1MjFmNTI3ZmJhNWMzNjczYmY1NjhjZmE4ZTE4
15
+ Y2IzNjRhODM1MzdmNTU4M2Y3MDlmZjJmMjE0NDg0NWQ3Zjc4MTg=
@@ -0,0 +1,452 @@
1
+ #!/usr/bin/env ruby
2
+ require 'aws-sdk-core'
3
+ require 'yaml'
4
+ require 'json'
5
+
6
+ class AwsRegion
7
+ attr_accessor :ec2, :region, :rds, :account_id, :elb, :cw, :s3
8
+ REGIONS = {'or' => "us-west-2", 'ca' => "us-west-1", 'va' => 'us-east-1'}
9
+ def initialize(region, account_id, access_key_id, secret_access_key)
10
+ @region = REGIONS[region]
11
+ @account_id = account_id
12
+ Aws.config = {:access_key_id => access_key_id,
13
+ :secret_access_key => secret_access_key}
14
+ @ec2 = Aws::EC2.new({:region => @region})
15
+ @rds = Aws::RDS.new({:region => @region})
16
+ @elb = Aws::ElasticLoadBalancing.new({:region => @region})
17
+ @cw = Aws::CloudWatch.new({:region => @region})
18
+ @s3 = Aws::S3.new({:region => @region})
19
+
20
+ def find_instances(options={})
21
+ instances = []
22
+ @ec2.describe_instances[:reservations].each do |i|
23
+ i.instances.each do |y|
24
+ instance = AwsInstance.new(self,{:instance => y})
25
+ if instance.state != 'terminated'
26
+ if options.has_key?(:environment) and options.has_key?(:purpose)
27
+ instances << instance if instance.tags[:environment] == options[:environment] and instance.tags[:purpose] == options[:purpose]
28
+ elsif options.has_key?(:instance_id)
29
+ instances << instance if instance.id == options[:instance_id]
30
+ end
31
+ end
32
+ end
33
+ end
34
+ return instances
35
+ end
36
+ def find_db_instances(options={})
37
+ instances = []
38
+ @rds.describe_db_instances[:db_instances].each do |i|
39
+ instance = AwsDbInstance.new(self, {:instance => i})
40
+ if options.has_key?(:instance_id) and
41
+ (!options.has_key?(:environment) or !options.has_key?(:purpose)) and
42
+ instance.id == options[:instance_id]
43
+ instances << instance
44
+ elsif instance.id == options[:instance_id] and
45
+ instance.tags[:environment] == options[:environment] and
46
+ instance.tags[:purpose] == options[:purpose]
47
+ instances << instance
48
+ end
49
+ end
50
+ instances
51
+ end
52
+
53
+ def find_buckets(options={})
54
+ buckets = []
55
+ _buckets = @s3.list_buckets()
56
+ _buckets[:buckets].each do |b|
57
+ buckets << AwsBucket.new(self, {id: b[:name]}) if b[:name] == options[:bucket]
58
+ end
59
+ buckets
60
+ end
61
+ def create_instance(options={})
62
+ AwsInstance.new(self, options)
63
+ end
64
+ def create_db_instance(options={})
65
+ AwsDbInstance.new(self, options)
66
+ end
67
+ def create_cw_instance(options={})
68
+ AwsCw.new(self, options)
69
+ end
70
+ def create_bucket(options={})
71
+ AwsBucket.new(self, options)
72
+ end
73
+ def remove_instance_from_lb(instance, lb_name)
74
+ lb = @elb.describe_load_balancers({:load_balancer_names => [lb_name]})
75
+ if lb and lb[:load_balancer_descriptions].length > 0
76
+ lb[:load_balancer_descriptions][0][:instances].each do |lbi|
77
+ if lbi[:instance_id] == instance
78
+ @elb.deregister_instances_from_load_balancer({:load_balancer_name => lb_name,
79
+ :instances => [{:instance_id => instance}]})
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ class AwsCw
87
+ attr_accessor :region
88
+ def initialize(region, options={})
89
+ @region = region
90
+ end
91
+ def put_metric(arg_csv)
92
+ (namespace, name, value, dims) = arg_csv.split(",")
93
+ dimensions = []
94
+ dims.split(";").each do |d|
95
+ (n,v) = d.split(":")
96
+ dimensions << {:name => n, :value => v}
97
+ end
98
+ args = {:namespace => namespace}
99
+ metric ={:metric_name => name, :value => value.to_f, :timestamp => Time.now, :dimensions => dimensions}
100
+ args[:metric_data] = [metric]
101
+ @region.cw.put_metric_data(args)
102
+ end
103
+ end
104
+
105
+ class AwsBucket
106
+ attr_accessor :region
107
+ def initialize(region, options={})
108
+ @region = region
109
+ if options.has_key?(:id)
110
+ @id = options[:id]
111
+ elsif options.has_key?(:bucket)
112
+ bucket = options[:bucket]
113
+ if @region.find_buckets({bucket: bucket}).length <= 0
114
+ @region.s3.create_bucket({:bucket => bucket,
115
+ :create_bucket_configuration => {:location_constraint => @region.region}})
116
+ if @region.find_buckets({bucket: bucket}).length <= 0
117
+ raise "Error creating bucket: #{bucket} in region: #{@region.region}"
118
+ end
119
+ end
120
+ @id = bucket
121
+ end
122
+ end
123
+ def delete
124
+ @region.s3.delete_bucket({bucket: @id})
125
+ end
126
+ def put_file(filename, file_identity)
127
+ File.open(filename, 'r') do |reading_file|
128
+ resp = @region.s3.put_object(
129
+ acl: "bucket-owner-full-control",
130
+ body: reading_file,
131
+ bucket: @id,
132
+ key: file_identity
133
+ )
134
+ end
135
+ end
136
+
137
+ def put(local_file_path, aws_path, options={})
138
+ # puts a local file to an s3 object in bucket on path
139
+ # example: put_local_file {:bucket=>"bucket", :local_file_path=>"/tmp/bar/foo.txt", :aws_path=>"b"}
140
+ # would make an s3 object named foo.txt in bucket/b
141
+ aws_path = aws_path[0..-2] if aws_path[-1..-1] == '/'
142
+ s3_path = "#{aws_path}/#{File.basename(local_file_path)}"
143
+ puts "s3 writing #{local_file_path} to bucket #{@id} path: #{aws_path} s3 path: #{s3_path}"
144
+ f = File.open local_file_path, 'rb'
145
+ options[:bucket] = @id
146
+ options[:key] = s3_path
147
+ options[:body] = f
148
+ options[:storage_class] = 'REDUCED_REDUNDANCY'
149
+ result = @region.s3.put_object(params=options)
150
+ f.close
151
+ result
152
+ end
153
+
154
+ def find(options={})
155
+ # prefix is something like: hchd-A-A-Items
156
+ # This will return in an array of strings the names of all objects in s3 in
157
+ # the :aws_path under :bucket starting with passed-in prefix
158
+ # example: :bucket=>'mazama-inventory', :aws_path=>'development', :prefix=>'broadhead'
159
+ # would return array of names of objects in said bucket
160
+ # matching (in regex terms) development/broadhead.*
161
+ # return empty array if no matching objects exist
162
+ aws_path = options[:aws_path]
163
+ prefix = options[:prefix]
164
+ aws_path = '' if aws_path.nil?
165
+ aws_path = aws_path[0..-2] if aws_path[-1..-1] == '/'
166
+ puts "s3 searching bucket:#{@id} for #{aws_path}/#{prefix}"
167
+ objects = @region.s3.list_objects(:bucket => @id,
168
+ :prefix => "#{aws_path}/#{prefix}")
169
+ f = objects.contents.collect(&:key)
170
+ puts "s3 searched got: #{f.inspect}"
171
+ f
172
+ end
173
+
174
+ def get(options={})
175
+ # writes to local file an s3 object in :bucket at :s3_path_to_object to :dest_file_path
176
+ # example: get_object_as_local_file( {:bucket=>'mazama-inventory',
177
+ # :s3_path_to_object=>development/myfile.txt',
178
+ # :dest_file_path=>'/tmp/foo.txt'})
179
+ # would write to local /tmp/foo.txt a file retrieved from s3 in 'mazama-inventory' bucket
180
+ # at development/myfile.txt
181
+ s3_path_to_object = options[:s3_path_to_object]
182
+ dest_file_path = options[:dest_file_path]
183
+ File.delete dest_file_path if File.exists?(dest_file_path)
184
+ puts "s3 get bucket:#{@id} path:#{s3_path_to_object} dest:#{dest_file_path}"
185
+ response = @region.s3.get_object(:bucket => @id,
186
+ :key => s3_path_to_object)
187
+ response.body.rewind
188
+ # I DO NOT KNOW what happens if the body is "too big". I didn't see a method in the
189
+ # API to chunk it out... but perhaps response.body does this already.
190
+ File.open(dest_file_path, 'wb') do |file|
191
+ response.body.each { |chunk| file.write chunk }
192
+ end
193
+ puts "s3 got " + `ls -l #{dest_file_path}`.strip
194
+ nil
195
+ end
196
+
197
+ def delete_object(options={})
198
+ # deletes from s3 an object in :bucket at :s3_path_to_object
199
+ s3_path_to_object = options[:s3_path_to_object]
200
+ puts "s3 delete #{s3_path_to_object}"
201
+ @region.s3.delete_object( :bucket => @id,
202
+ :key => s3_path_to_object)
203
+ puts "s3 deleted."
204
+ end
205
+
206
+ def delete_all_objects
207
+ response = @region.s3.list_objects({:bucket => @id})
208
+ response[:contents].each do |obj|
209
+ @region.s3.delete_object( :bucket => @id,
210
+ :key => obj[:key])
211
+ end
212
+ end
213
+ end
214
+ class AwsDbInstance
215
+ attr_accessor :id, :tags, :region, :endpoint
216
+ def initialize(region, options = {})
217
+ @region = region
218
+ opts = options[:opts]
219
+ if !options.has_key?(:instance)
220
+ @id = opts[:db_instance_identifier]
221
+ snapshot_name = options[:snapshot_name]
222
+ if 0 < @region.find_db_instances({:instance_id => @id}).length
223
+ puts "Error, instance: #{@id} already exists"
224
+ return
225
+ end
226
+ last = self.get_latest_db_snapshot({:snapshot_name => snapshot_name})
227
+ puts "Restoring: #{last.db_instance_identifier}, snapshot: #{last.db_instance_identifier} from : #{last.snapshot_create_time}"
228
+ opts[:db_snapshot_identifier] = last.db_snapshot_identifier
229
+ response = @region.rds.restore_db_instance_from_db_snapshot(opts)
230
+ @_instance = response[:db_instance]
231
+ @region.rds.add_tags_to_resource({:resource_name => "arn:aws:rds:#{@region.region}:#{@region.account_id}:db:#{@id}",
232
+ :tags => [{:key => "environment", :value => options[:environment]},
233
+ {:key => "purpose", :value => options[:purpose]}]})
234
+
235
+ self.wait
236
+
237
+ opts = { :db_instance_identifier => @id,
238
+ :vpc_security_group_ids => options[:vpc_security_group_ids]}
239
+ @region.rds.modify_db_instance(opts)
240
+ else
241
+ @_instance = options[:instance]
242
+ @id = @_instance[:db_instance_identifier]
243
+ end
244
+ @tags = {}
245
+ _tags = @region.rds.list_tags_for_resource({:resource_name => "arn:aws:rds:#{@region.region}:#{@region.account_id}:db:#{@id}"})
246
+ _tags[:tag_list].each do |t|
247
+ @tags[t[:key].to_sym] = t[:value]
248
+ end
249
+ @endpoint = @_instance.endpoint[:address]
250
+ end
251
+
252
+ def delete(options={})
253
+ puts "Deleting database: #{@id}"
254
+ opts = { :db_instance_identifier => @id,
255
+ :skip_final_snapshot => false,
256
+ :final_db_snapshot_identifier => "#{@id}-#{Time.now.strftime("%Y-%m-%d-%H-%M-%S")}" }
257
+ i = @region.rds.delete_db_instance(opts)
258
+ end
259
+
260
+ def purge_db_snapshots
261
+ latest = 0
262
+ @region.rds.describe_db_snapshots[:db_snapshots].each do |i|
263
+ if i.snapshot_type == "manual" and i.db_instance_identifier == @id
264
+ if i.snapshot_create_time.to_i > latest
265
+ latest = i.snapshot_create_time.to_i
266
+ end
267
+ end
268
+ end
269
+ @region.rds.describe_db_snapshots[:db_snapshots].each do |i|
270
+ if i.snapshot_type == "manual" and i.db_instance_identifier == @id
271
+ if i.snapshot_create_time.to_i != latest
272
+ puts "Removing snapshot: #{i.db_snapshot_identifier}/#{i.snapshot_create_time.to_s}"
273
+ begin
274
+ @region.rds.delete_db_snapshot({:db_snapshot_identifier => i.db_snapshot_identifier})
275
+ rescue
276
+ puts "Error removing snapshot: #{i.db_snapshot_identifier}/#{i.snapshot_create_time.to_s}"
277
+ end
278
+ else
279
+ puts "Keeping snapshot: #{i.db_snapshot_identifier}/#{i.snapshot_create_time.to_s}"
280
+ end
281
+ end
282
+ end
283
+ end
284
+
285
+ def wait(options = {:desired_status => "available",
286
+ :timeout => 600})
287
+ inst = @region.find_db_instances({:instance_id => @id})[0]
288
+ if !inst
289
+ puts "Error, instance: #{@id} not found"
290
+ return
291
+ end
292
+ t0 = Time.now.to_i
293
+ while inst.status != options[:desired_status]
294
+ inst = @region.find_db_instances({:instance_id => @id})[0]
295
+ puts "Database: #{@id} at #{@endpoint}. Current status: #{inst.status}"
296
+ if Time.now.to_i - t0 > options[:timeout]
297
+ puts "Timed out waiting for database: #{@id} at #{@endpoint} to move into status: #{options[:desired_status]}. Current status: #{inst.status}"
298
+ return
299
+ end
300
+ sleep 20
301
+ end
302
+ end
303
+
304
+ def status
305
+ @_instance.db_instance_status
306
+ end
307
+
308
+ def get_latest_db_snapshot(options={})
309
+ snapshot_name = options.has_key?(:snapshot_name) ? options[:snapshot_name] : @id
310
+
311
+ last = nil
312
+ last_t = 0
313
+ @region.rds.describe_db_snapshots[:db_snapshots].each do |i|
314
+ if i.db_instance_identifier == snapshot_name and (last.nil? or i.snapshot_create_time > last_t)
315
+ last = i
316
+ last_t = i.snapshot_create_time
317
+ end
318
+ end
319
+ last
320
+ end
321
+
322
+ end
323
+ class AwsInstance
324
+ attr_accessor :id, :tags, :region, :private_ip, :public_ip, :_instance
325
+ def initialize(region, options = {})
326
+ @region = region
327
+ if !options.has_key?(:instance)
328
+ resp = @region.ec2.run_instances(options)
329
+ raise "Error creating instance using options" if resp.nil? or resp[:instances].length <= 0
330
+ @_instance = resp[:instances][0]
331
+ else
332
+ @_instance = options[:instance]
333
+ end
334
+ @id = @_instance[:instance_id]
335
+ @tags = {}
336
+ @_instance.tags.each do |t|
337
+ @tags[t[:key].to_sym] = t[:value]
338
+ end
339
+ @public_ip = @_instance[:public_ip_address]
340
+ @private_ip = @_instance[:private_ip_address]
341
+ end
342
+ def state(use_cached_state=true)
343
+ if !use_cached_state
344
+ response = @region.ec2.describe_instances({instance_ids: [@id]})
345
+ response[:reservations].each do |res|
346
+ res[:instances].each do |inst|
347
+ if inst[:instance_id] == @id
348
+ return inst[:state][:name].strip()
349
+ end
350
+ end
351
+ end
352
+ return ""
353
+ else
354
+ @_instance.state[:name].strip()
355
+ end
356
+ end
357
+ def start(wait=false)
358
+ if self.state(use_cached_state = false) != "stopped"
359
+ puts "Instance cannot be started - #{@region.region}://#{@id} is in the state: #{self.state}"
360
+ return
361
+ end
362
+ puts "Starting instance: #{@region.region}://#{@id}"
363
+ @region.ec2.start_instances({:instance_ids => [@id]})
364
+ if wait
365
+ begin
366
+ sleep 10
367
+ puts "Starting instance: #{@region.region}://#{@id} - state: #{self.state}"
368
+ end while self.state(use_cached_state = false) != "running"
369
+ end
370
+ if @tags.has_key?("elastic_ip")
371
+ @region.ec2.associate_address({:instance_id => @id, :public_ip => @tags['elastic_ip']})
372
+ puts "Associated ip: #{@tags['elastic_ip']} with instance: #{@id}"
373
+ elsif @tags.has_key?("elastic_ip_allocation_id")
374
+ @region.ec2.associate_address({:instance_id => @id, :allocation_id => @tags['elastic_ip_allocation_id']})
375
+ puts "Associated allocation id: #{@tags['elastic_ip_allocation_id']} with instance: #{@id}"
376
+ end
377
+ if @tags.has_key?("elastic_lb")
378
+ self.add_to_lb(@tags["elastic_lb"])
379
+ puts "Adding instance: #{@id} to '#{@tags['elastic_lb']}' load balancer"
380
+ end
381
+ end
382
+ def set_security_groups(groups)
383
+ resp = @region.ec2.modify_instance_attribute({:instance_id => @id,
384
+ :groups => groups})
385
+ end
386
+ def add_tags(h_tags)
387
+ tags = []
388
+ h_tags.each do |k,v|
389
+ tags << {:key => k.to_s, :value => v}
390
+ end
391
+ resp = @region.ec2.create_tags({:resources => [@id],
392
+ :tags => tags})
393
+ end
394
+
395
+ def add_to_lb(lb_name)
396
+ @region.elb.register_instances_with_load_balancer({:load_balancer_name => lb_name,
397
+ :instances => [{:instance_id => @id}]})
398
+ end
399
+
400
+ def remove_from_lb(lb_name)
401
+ lb = @region.elb.describe_load_balancers({:load_balancer_names => [lb_name]})
402
+ if lb and lb[:load_balancer_descriptions].length > 0
403
+ lb[:load_balancer_descriptions][0][:instances].each do |lb_i|
404
+ if lb_i[:instance_id] == @id
405
+ @elb.deregister_instances_from_load_balancer({:load_balancer_name => lb_name,
406
+ :instances => [{:instance_id => @id}]})
407
+ end
408
+ end
409
+ end
410
+ end
411
+
412
+ def terminate()
413
+ @region.ec2.terminate_instances({:instance_ids => [@id]})
414
+ end
415
+
416
+ def archive_logs()
417
+ end
418
+
419
+ def stop(wait=false)
420
+ if self.state(use_cached_state = false) != "running"
421
+ puts "Instance cannot be stopped - #{@region.region}://#{@id} is in the state: #{self.state}"
422
+ return
423
+ end
424
+ if @tags.has_key?("elastic_lb")
425
+ puts "Removing instance: #{@id} from '#{@tags['elastic_lb']}' load balancer"
426
+ remove_from_lb(tags["elastic_lb"])
427
+ end
428
+ puts "Stopping instance: #{@region.region}://#{@id}"
429
+ @region.ec2.stop_instances({:instance_ids => [@id]})
430
+ while self.state(use_cached_state = false) != "stopped"
431
+ sleep 10
432
+ puts "Stopping instance: #{@region.region}://#{@id} - state: #{self.state}"
433
+ end if wait
434
+ if self.state(use_cached_state = false) == "stopped"
435
+ puts "Instance stopped: #{@region.region}://#{@id}"
436
+ end
437
+ end
438
+ def connect
439
+ if self.state(use_cached_state = false) != "running"
440
+ puts "Cannot connect, instance: #{@region.region}://#{@id} due to its state: #{self.state}"
441
+ return
442
+ end
443
+ ip = self.public_ip != "" ? self.public_ip : self.private_ip
444
+ #puts "Connecting: ssh -i ~/.ssh/ec2.#{@region.region}.pem #{@tags[:user]}@#{ip}"
445
+ #exec "ssh -i ~/.ssh/ec2.#{@region.region}.pem #{@tags[:user]}@#{ip}"
446
+ puts "Connecting: ssh #{@tags[:user]}@#{ip}"
447
+ exec "ssh #{@tags[:user]}@#{ip}"
448
+ end
449
+
450
+
451
+ end
452
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws-tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Kevin Connors
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: A set of simple tools to bind some essential functions of aws resources
28
+ email: karmet@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/aws_region.rb
34
+ homepage: http://rubygems.org/gems/aws-tools
35
+ licenses:
36
+ - MIT
37
+ metadata: {}
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 2.2.2
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: A set of simple tools to bind some essential functions of aws resources
58
+ test_files: []