aws-tools 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/bin/aws_manager.rb +227 -0
  2. data/lib/aws_region.rb +472 -169
  3. metadata +50 -35
  4. checksums.yaml +0 -15
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'aws-sdk-core'
4
+ require 'yaml'
5
+ require 'optparse'
6
+ require 'erb'
7
+ require 'aws_region'
8
+
9
+ VALID_COMMANDS=['start',
10
+ 'stop',
11
+ 'start_environment',
12
+ 'connect',
13
+ 'create_db',
14
+ 'delete_db',
15
+ 'wait_for_db',
16
+ 'get_db_endpoint',
17
+ 'get_instance_status',
18
+ 'get_instance_ip',
19
+ 'get_db_status',
20
+ 'purge_db_snapshots',
21
+ 'put_cw_metric',
22
+ 'put_to_bucket',
23
+ 'run_instance',
24
+ 'sns',
25
+ 'terminate_instance']
26
+
27
+ def filter_instances(all_instances, statuses)
28
+ instances = []
29
+ if statuses.length == 0
30
+ instances = all_instances
31
+ else
32
+ all_instances.each do |i|
33
+ if statuses.include?(i._instance[:state][:name])
34
+ instances << i
35
+ end
36
+ end
37
+ end
38
+ instances
39
+ end
40
+ def locate_instance_choice(instances, instance_id, options={})
41
+ selection_filter = options.has_key?(:selection_filter) ? options[:selection_filter] : nil
42
+ if instances.length == 0
43
+ puts "There are no instances matching criteria"
44
+ return nil
45
+ elsif instances.length == 1
46
+ instances[0]
47
+ else
48
+ if !instance_id.nil?
49
+ instance = nil
50
+ if instance_id.length > 2 # This is an instance id
51
+ instances.each do |i|
52
+ if i.id == instance_id
53
+ instance = i
54
+ end
55
+ end
56
+ if instance.nil?
57
+ "Error, can't locate instance with instance id: #{instance_id}"
58
+ return nil
59
+ end
60
+ else
61
+ if instances.length - 1 >= instance_id.to_i
62
+ instance = instances[instance_id.to_i]
63
+ end
64
+ end
65
+ return instance
66
+ else
67
+ if selection_filter == :first
68
+ return instances[0]
69
+ elsif selection_filter == :oldest
70
+ oldest = instances[0]
71
+ instances.each do |i|
72
+ oldest = i if i._instance[:launch_time] < oldest._instance[:launch_time]
73
+ end
74
+ return oldest
75
+ elsif selection_filter == :newest
76
+ newest = instances[0]
77
+ instances.each do |i|
78
+ newest = i if i._instance[:launch_time] > newest._instance[:launch_time]
79
+ end
80
+ return newest
81
+ else
82
+ puts "Error, there are multiple instances that match these tags. Please choose one:"
83
+ puts "index: private_ip / public_ip / instance_id / launch_time"
84
+ for i in 0..(instances.length - 1)
85
+ puts "#{i}: #{instances[i]._instance[:private_ip_address]} / #{instances[i]._instance[:public_ip_address]} / #{instances[i]._instance[:instance_id]} / #{instances[i]._instance[:launch_time]}"
86
+ end
87
+ ans = STDIN.gets.chomp().to_i
88
+ return instances[ans]
89
+ end
90
+ end
91
+ nil
92
+ end
93
+
94
+ end
95
+ def syntax
96
+ puts "Syntax:"
97
+ puts " EC2 Instance commands:"
98
+ puts " aws_manager.rb --region region run_instance <instance template file> "
99
+ puts " aws_manager.rb --region region [--environment environment] [--purpose purpose] [--choose first|oldest|newest] connect [id]"
100
+ puts " aws_manager.rb --region region [--environment environment] [--purpose purpose] [--choose first|oldest|newest] start [id]"
101
+ puts " aws_manager.rb --region region [--environment environment] [--purpose purpose] [--choose first|oldest|newest] [--keep-one] stop [id]"
102
+ puts " aws_manager.rb --region region [--environment environment] [--purpose purpose] [--choose first|oldest|newest] get_instance_status [id]"
103
+ puts " aws_manager.rb --region region [--environment environment] [--purpose purpose] [--choose first|oldest|newest] get_instance_ip [id]"
104
+ puts " aws_manager.rb --region region [--environment environment] [--purpose purpose] [--choose first|oldest|newest] [--keep-one] terminate_instance [id]"
105
+ puts " CW commands:"
106
+ puts " aws_manager.rb --region region put_cw_metric csv_metric"
107
+ puts " S3 commands:"
108
+ puts " aws_manager.rb --region region put_to_bucket filename s3_filename"
109
+ puts " RDS commands:"
110
+ puts " aws_manager.rb --region region create_db <db template file> "
111
+ puts " aws_manager.rb --region region [--environment environment] [--purpose purpose] delete_db [id]"
112
+ puts " aws_manager.rb --region region [--environment environment] [--purpose purpose] wait_for_db [id]"
113
+ puts " aws_manager.rb --region region [--environment environment] [--purpose purpose] get_db_status [id]"
114
+ puts " aws_manager.rb --region region [--environment environment] [--purpose purpose] get_db_endpoint [id]"
115
+ puts " aws_manager.rb --region region [--environment environment] [--purpose purpose] purge_db_snapshots [id]"
116
+ puts " SNS commands:"
117
+ puts " aws_manager.rb --region region sns \"<topic_arn>\" \"subject\""
118
+ puts " example topic_arn: 'arn:aws:sns:us-east-1:795987318935:prod_app_start_failure'"
119
+ puts " Other commands:"
120
+ puts " aws_manager.rb --help"
121
+ puts "\nNote that there are shortened versions of the options flags:"
122
+ puts " --region = -r"
123
+ puts " --environment = -e"
124
+ puts " --purpose = -p"
125
+ puts " --choose = -c"
126
+ puts " --keep-one = -k"
127
+ puts " --help = -h"
128
+ exit
129
+ end
130
+ def main
131
+ syntax if ARGV.length <= 0
132
+ params = ARGV.getopts("hr:e:p:fkc:", "choose:", "keep-one", "help", "region:", "environment:", "purpose:")
133
+ syntax if params['h'] or params['help']
134
+ purpose = params['p'] || params['purpose']
135
+ environment = params['e'] || params['environment']
136
+ region = params['r'] || params['region']
137
+ keep_one = params['k'] || params['keep-one']
138
+ selection_criteria = params['c'] || params['choose']
139
+ selection_criteria = selection_criteria.downcase.to_sym if selection_criteria
140
+ syntax if selection_criteria and !([:first,:oldest, :newest].include?(selection_criteria))
141
+ syntax if ARGV.length <= 0
142
+ command = ARGV.shift
143
+ save_cl = ARGV.dup
144
+ name = ARGV.length == 1 ? ARGV.shift : nil
145
+
146
+ syntax if !region or !(['or', 'ca', 'va'].include?(region))
147
+ syntax if !(VALID_COMMANDS.include?(command))
148
+
149
+ cfg = YAML::load File.read("/etc/auth.yaml")
150
+ region = AwsRegion.new(region, cfg["account_id"], cfg["access_key_id"], cfg["secret_access_key"])
151
+
152
+ if command == 'stop'
153
+ if keep_one and filter_instances(region.find_instances({environment: environment, purpose: purpose }), ['running']).length < 2
154
+ puts "Error, there are less than 2 instances, and keep_one flag set. Exiting."
155
+ exit
156
+ end
157
+ instance = locate_instance_choice(filter_instances(region.find_instances({environment: environment, purpose: purpose }), ['running']),name, {:selection_filter => selection_criteria})
158
+ instance.stop(wait=true)
159
+ elsif command == 'start'
160
+ instance = locate_instance_choice(filter_instances(region.find_instances({environment: environment, purpose: purpose }), ['stopped']),name, {:selection_filter => selection_criteria})
161
+ instance.start(wait=true)
162
+ elsif command == 'connect'
163
+ instance = locate_instance_choice(filter_instances(region.find_instances({environment: environment, purpose: purpose }), ['running']),name, {:selection_filter => selection_criteria})
164
+ instance.connect
165
+ elsif command == 'create_db'
166
+ options = YAML::load File.read(name) if File.exists? name
167
+ instance = region.create_db_instance(options)
168
+ elsif command == 'delete_db'
169
+ instances = region.find_db_instances({:environment => environment, :purpose => purpose, :instance_id => name})
170
+ instances[0].delete
171
+ elsif command == 'wait_for_db'
172
+ instances = region.find_db_instances({:environment => environment, :purpose => purpose, :instance_id => name})
173
+ instances[0].wait
174
+ elsif command == 'get_db_endpoint'
175
+ instances = region.find_db_instances({:environment => environment, :purpose => purpose, :instance_id => name})
176
+ puts instances[0].endpoint
177
+ elsif command == 'get_db_status'
178
+ instances = region.find_db_instances({:environment => environment, :purpose => purpose, :instance_id => name})
179
+ puts instances[0].status
180
+ elsif command == 'get_instance_status'
181
+ instance = locate_instance_choice(filter_instances(region.find_instances({environment: environment, purpose: purpose }), []), name, {:selection_filter => selection_criteria})
182
+ puts instance.state
183
+ elsif command == 'get_instance_ip'
184
+ instance = locate_instance_choice(filter_instances(region.find_instances({environment: environment, purpose: purpose }), []), name, {:selection_filter => selection_criteria})
185
+ puts instance.public_ip
186
+ elsif command == 'purge_db_snapshots'
187
+ instances = region.find_db_instances({:environment => environment, :purpose => purpose, :instance_id => name})
188
+ instances[0].purge_db_snapshots
189
+ elsif command == 'put_cw_metric'
190
+ syntax if ARGV.length <= 1
191
+ instance = region.create_cw_instance
192
+ instance.put_metric(name)
193
+ elsif command == 'put_to_bucket'
194
+ syntax if ARGV.length <= 0
195
+ (bucket,file_identity) = ARGV[0].split(/:/)
196
+ syntax if bucket.strip.length <=0 or file_identity.strip.length <= 0
197
+ instance = region.find_bucket({bucket: bucket})
198
+ instance.put_file(name, file_identity)
199
+ elsif command == 'run_instance'
200
+ instance_template = name
201
+ if !File.exists?(instance_template)
202
+ puts "Cannot find instance template to build server from: #{instance_template}"
203
+ exit
204
+ end
205
+ image_options = YAML.load(ERB.new(File.read(instance_template)).result)
206
+ instance = region.create_instance(image_options)
207
+ elsif command == 'terminate_instance'
208
+ if keep_one and filter_instances(region.find_instances({environment: environment, purpose: purpose }), ['running']).length < 2
209
+ puts "There are less than 2 instances, and keep_one flag set. Exiting."
210
+ exit
211
+ end
212
+ instance = locate_instance_choice(filter_instances(region.find_instances({environment: environment, purpose: purpose }), []), name, {:selection_filter => selection_criteria})
213
+ if instance.nil?
214
+ puts "No instances matching criteria found #{{environment: environment, purpose: purpose, selection_filter: selection_criteria}.inspect}"
215
+ else
216
+ instance.terminate
217
+ end
218
+ elsif command == 'sns'
219
+ instance = region.create_sns_instance
220
+ instance.publish save_cl[0], save_cl[1]
221
+ exit
222
+ elsif
223
+ syntax
224
+ end
225
+ end
226
+
227
+ main()
@@ -1,12 +1,26 @@
1
- #!/usr/bin/env ruby
2
1
  require 'aws-sdk-core'
3
2
  require 'yaml'
4
3
  require 'json'
5
4
 
6
- class AwsRegion
7
- attr_accessor :ec2, :region, :rds, :account_id, :elb, :cw, :s3
5
+ class AwsBase
6
+ def log(msg)
7
+ @logger.write("#{Time.now.strftime("%b %D, %Y %H:%S:%M:")} #{msg}\n") if @logger
8
+ end
9
+ end
10
+
11
+ # AwsRegion is a simplified wrapper on top of a few of the Aws core objects
12
+ # The main goal is to expose a extremely simple interface for some our most
13
+ # frequently used Aws facilities.
14
+ class AwsRegion < AwsBase
15
+ attr_accessor :ec2, :region, :rds, :account_id, :elb, :cw, :s3, :sns
8
16
  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)
17
+
18
+ # @param region [String] must be one of the keys of the {AwsRegion::REGIONS REGIONS} static hash
19
+ # @param account_id [String] Aws account id
20
+ # @param access_key_id [String] Aws access key id
21
+ # @param secret_access_key [String] Aws secret access key
22
+ def initialize(region, account_id, access_key_id, secret_access_key, logger = nil)
23
+ @logger = logger
10
24
  @region = REGIONS[region]
11
25
  @account_id = account_id
12
26
  Aws.config = {:access_key_id => access_key_id,
@@ -16,12 +30,22 @@ class AwsRegion
16
30
  @elb = Aws::ElasticLoadBalancing.new({:region => @region})
17
31
  @cw = Aws::CloudWatch.new({:region => @region})
18
32
  @s3 = Aws::S3.new({:region => @region})
33
+ @sns = Aws::SNS.new({:region => @region})
34
+ end
19
35
 
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})
36
+ # Simple EC2 Instance finder. Can find using instance_id, or using
37
+ # :environment and :purpose instance tags which must both match.
38
+ #
39
+ # @param options [Hash] containing search criteria. Values can be:
40
+ # * :instance_id - identifies an exact instance
41
+ # * :environment - instance tag
42
+ # * :purpose - instance tag
43
+ # @return [Array<AwsInstance>] instances found to match criteria
44
+ def find_instances(options={})
45
+ instances = []
46
+ @ec2.describe_instances[:reservations].each do |i|
47
+ i.instances.each do |y|
48
+ instance = AwsInstance.new(self, {:instance => y})
25
49
  if instance.state != 'terminated'
26
50
  if options.has_key?(:environment) and options.has_key?(:purpose)
27
51
  instances << instance if instance.tags[:environment] == options[:environment] and instance.tags[:purpose] == options[:purpose]
@@ -29,81 +53,132 @@ class AwsRegion
29
53
  instances << instance if instance.id == options[:instance_id]
30
54
  end
31
55
  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
56
  end
50
- instances
51
57
  end
58
+ return instances
59
+ end
52
60
 
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]
61
+ # Simple DB Instance finder. Can find using instance_id, or using
62
+ # :environment and :purpose instance tags which must both match.
63
+ #
64
+ # @param options [Hash] containing search criteria. Values can be:
65
+ # * :instance_id - identifies an exact instance
66
+ # * :environment - instance tag
67
+ # * :purpose - instance tag
68
+ # @return [Array<AwsDbInstance>] instances found to match criteria
69
+ def find_db_instances(options={})
70
+ instances = []
71
+ @rds.describe_db_instances[:db_instances].each do |i|
72
+ instance = AwsDbInstance.new(self, {:instance => i})
73
+ if options.has_key?(:instance_id)
74
+ instance.id == options[:instance_id]
75
+ instances << instance
76
+ elsif instance.tags[:environment] == options[:environment] and
77
+ instance.tags[:purpose] == options[:purpose]
78
+ instances << instance
58
79
  end
59
- buckets
60
80
  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)
81
+ instances
82
+ end
83
+
84
+
85
+ # Search region for a bucket by name
86
+ #
87
+ # @param options [Hash] containing search criteria. Values can be:
88
+ # * :bucket - Bucket name
89
+ # @return [Array<AwsBucket>] instances found to match criteria
90
+ def find_buckets(options={})
91
+ buckets = []
92
+ _buckets = @s3.list_buckets()
93
+ _buckets[:buckets].each do |b|
94
+ buckets << AwsBucket.new(self, {id: b[:name]}) if b[:name] == options[:bucket]
95
+ end
96
+ buckets
97
+ end
98
+
99
+ # Construct new EC2 instance
100
+ #
101
+ # @param options [Hash] containing initialization parameters. See {AwsInstance#initialize}
102
+ # @return [AwsInstance]
103
+ def create_instance(options={})
104
+ AwsInstance.new(self, options)
105
+ end
106
+
107
+ # Construct new DB instance
108
+ #
109
+ # @param options [Hash] containing initialization parameters. See {AwsDbInstance#initialize}
110
+ # @return [AwsDbInstance]
111
+ def create_db_instance(options={})
112
+ AwsDbInstance.new(self, options)
113
+ end
114
+
115
+ # Construct new CloudWatch instance
116
+ #
117
+ # @param options [Hash] containing initialization parameters. See {AwsCw#initialize}
118
+ # @return [AwsCw]
119
+ def create_cw_instance(options={})
120
+ AwsCw.new(self, options)
121
+ end
122
+
123
+ # Construct new AwsBucket instance
124
+ #
125
+ # @param options [Hash] containing initialization parameters. See {AwsBucket#initialize}
126
+ # @return [AwsBucket]
127
+ def create_bucket(options={})
128
+ AwsBucket.new(self, options)
129
+ end
130
+
131
+ def create_sns_instance
132
+ AwsSns.new(self)
133
+ end
134
+
135
+ class AwsSns
136
+ attr_accessor :region
137
+ def initialize(region)
138
+ @region = region
69
139
  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
140
+ def publish(topic_arn, subject) #, message)
141
+ @region.sns.publish(topic_arn: topic_arn, message: "unused for texts", subject: subject)
83
142
  end
84
143
  end
85
144
 
86
- class AwsCw
145
+ # Methods for dealing with CloudWatch
146
+ class AwsCw < AwsBase
87
147
  attr_accessor :region
148
+
149
+ # @param region [String] - Value from REGION static hash
88
150
  def initialize(region, options={})
89
151
  @region = region
90
152
  end
153
+
154
+ # Put a cw metric
155
+ # @param arg_csv [String] - CSV row: "namespace,name,value,dims"
156
+ # * Note that dims is formatted as an arbitrary semicolon separated list of name:value dimensions. For example:
157
+ # * "activeservers,count,10,env:prod;purp:test"
158
+ # @return [Aws::PageableResponse]
91
159
  def put_metric(arg_csv)
92
160
  (namespace, name, value, dims) = arg_csv.split(",")
93
161
  dimensions = []
94
162
  dims.split(";").each do |d|
95
- (n,v) = d.split(":")
163
+ (n, v) = d.split(":")
96
164
  dimensions << {:name => n, :value => v}
97
165
  end
98
166
  args = {:namespace => namespace}
99
167
  metric ={:metric_name => name, :value => value.to_f, :timestamp => Time.now, :dimensions => dimensions}
100
168
  args[:metric_data] = [metric]
101
169
  @region.cw.put_metric_data(args)
102
- end
170
+ end
103
171
  end
104
172
 
105
- class AwsBucket
173
+ # Methods for dealing with S3 buckets
174
+ class AwsBucket < AwsBase
106
175
  attr_accessor :region
176
+
177
+ # Constructs a bucket instance from an existing bucket, or creates a new one with the name
178
+ # @param region [String] - Value from REGION static hash
179
+ # @param options [Hash] - Possible values:
180
+ # * :id - id of existing bucket
181
+ # * :bucket - Name of bucket to find or create
107
182
  def initialize(region, options={})
108
183
  @region = region
109
184
  if options.has_key?(:id)
@@ -120,9 +195,17 @@ class AwsRegion
120
195
  @id = bucket
121
196
  end
122
197
  end
198
+
199
+ # Delete this bucket instance
200
+ # @return [AwsPageableResponse]]
123
201
  def delete
124
202
  @region.s3.delete_bucket({bucket: @id})
125
203
  end
204
+
205
+ # Put a local file to this bucket
206
+ # @param filename [String] - local file name
207
+ # @param file_identity [String] - S3 file path
208
+ # @return [AwsPageableResponse]
126
209
  def put_file(filename, file_identity)
127
210
  File.open(filename, 'r') do |reading_file|
128
211
  resp = @region.s3.put_object(
@@ -134,13 +217,16 @@ class AwsRegion
134
217
  end
135
218
  end
136
219
 
220
+ # puts a local file to an s3 object in bucket on path
221
+ # example: put_local_file(:bucket=>"bucket", :local_file_path=>"/tmp/bar/foo.txt", :aws_path=>"b")
222
+ # would make an s3 object named foo.txt in bucket/b
223
+ # @param local_file_path [String] - Location of file to put
224
+ # @param aws_path [String] - S3 path to put the file
225
+ # @param options [Hash] - Can contain any valid S3 bucket options see [docs](http://docs.aws.amazon.com/sdkforruby/api/frames.html)
137
226
  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
227
  aws_path = aws_path[0..-2] if aws_path[-1..-1] == '/'
142
228
  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}"
229
+ log "s3 writing #{local_file_path} to bucket #{@id} path: #{aws_path} s3 path: #{s3_path}"
144
230
  f = File.open local_file_path, 'rb'
145
231
  options[:bucket] = @id
146
232
  options[:key] = s3_path
@@ -151,68 +237,110 @@ class AwsRegion
151
237
  result
152
238
  end
153
239
 
240
+ # prefix is something like: hchd-A-A-Items
241
+ # This will return in an array of strings the names of all objects in s3 in
242
+ # the :aws_path under :bucket starting with passed-in prefix
243
+ # example: :aws_path=>'development', :prefix=>'broadhead'
244
+ # would return array of names of objects in said bucket
245
+ # matching (in regex terms) development/broadhead.*
246
+ # @param options [Hash] - Can contain:
247
+ # * :aws_path - first part of S3 path to search
248
+ # * :prefix - Actually suffix of path to search.
249
+ # @return [Array<Hash>] - 0 or more objects
154
250
  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
251
  aws_path = options[:aws_path]
163
- prefix = options[:prefix]
252
+ prefix = options[:prefix]
164
253
  aws_path = '' if aws_path.nil?
165
254
  aws_path = aws_path[0..-2] if aws_path[-1..-1] == '/'
166
- puts "s3 searching bucket:#{@id} for #{aws_path}/#{prefix}"
255
+ log "s3 searching bucket:#{@id} for #{aws_path}/#{prefix}"
167
256
  objects = @region.s3.list_objects(:bucket => @id,
168
- :prefix => "#{aws_path}/#{prefix}")
257
+ :prefix => "#{aws_path}/#{prefix}")
169
258
  f = objects.contents.collect(&:key)
170
- puts "s3 searched got: #{f.inspect}"
259
+ log "s3 searched got: #{f.inspect}"
171
260
  f
172
261
  end
173
262
 
263
+ # writes contents of S3 object to local file
264
+ # example: get( :s3_path_to_object=>development/myfile.txt',
265
+ # :dest_file_path=>'/tmp/foo.txt')
266
+ # would write to local /tmp/foo.txt a file retrieved from s3
267
+ # at development/myfile.txt
268
+ # @param options [Hash] - Can contain:
269
+ # * :s3_path_to_object - S3 object path
270
+ # * :dest_file_path - local file were file will be written
271
+ # @return [Boolean]
174
272
  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
273
+ begin
274
+ s3_path_to_object = options[:s3_path_to_object]
275
+ dest_file_path = options[:dest_file_path]
276
+ File.delete dest_file_path if File.exists?(dest_file_path)
277
+ log "s3 get bucket:#{@id} path:#{s3_path_to_object} dest:#{dest_file_path}"
278
+ response = @region.s3.get_object(:bucket => @id,
279
+ :key => s3_path_to_object)
280
+ response.body.rewind
281
+ File.open(dest_file_path, 'wb') do |file|
282
+ response.body.each { |chunk| file.write chunk }
283
+ end
284
+ rescue Exception => e
285
+ return false
286
+ end
287
+ true
195
288
  end
196
289
 
290
+ # deletes from s3 an object at :s3_path_to_object
291
+ # @param options [Hash] - Can be:
292
+ # * :s3_path_to_object
293
+ # @return [Boolean]
197
294
  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."
295
+ begin
296
+ s3_path_to_object = options[:s3_path_to_object]
297
+ log "s3 delete #{s3_path_to_object}"
298
+ @region.s3.delete_object(:bucket => @id,
299
+ :key => s3_path_to_object)
300
+ rescue Exception => e
301
+ return false
302
+ end
303
+ true
204
304
  end
205
305
 
306
+ # delete all objects in a bucket
307
+ # @return [Boolean]
206
308
  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])
309
+ begin
310
+ response = @region.s3.list_objects({:bucket => @id})
311
+ response[:contents].each do |obj|
312
+ @region.s3.delete_object(:bucket => @id,
313
+ :key => obj[:key])
314
+ end
315
+ rescue Exception => e
316
+ return false
211
317
  end
318
+ true
212
319
  end
213
320
  end
214
- class AwsDbInstance
321
+
322
+ # Class to handle RDS Db instances
323
+ class AwsDbInstance < AwsBase
215
324
  attr_accessor :id, :tags, :region, :endpoint
325
+
326
+ # Creates an AwsDbInstance for an existing instance or creates a new database
327
+ # @param region [String] - - Value from REGION static hash
328
+ # @param options [Hash] - Can contain:
329
+ # * :instance - If specified, create an instance of this class using this RDS instance.
330
+ # * :opts - [Hash] - Includes parameters for constructing the database. The format is:
331
+ # * :db_instance_identifier - RDS instance identifier
332
+ # * :db_subnet_group_name - DB Subgroup name
333
+ # * :publicly_accessible - [true|false]
334
+ # * :db_instance_class - RDS db instance class
335
+ # * :availability_zone - RDS/Aws availability zone
336
+ # * :multi_az - [true|false]
337
+ # * :engine - RDS engine (Only tested with Mysql at this point)
338
+ # * :tags - Tags to be applied to RDS instance. The follow are required. Arbitrary tags may also be added.
339
+ # * :environment - Environment designation - can be anything. Used to locate instance with other aws-tools
340
+ # * :purpose - Purpose designation - can be anything. Used to locate instance with other aws-tools
341
+ # * :name - Name will appear in the Aws web page if you set this
342
+ # * :snapshot_name - Name of the snapshot that will be used to construct the new instance. This name will be matched with the RDS db_instance_identifier. The latest snapshot will be used.
343
+ # * :vpc_security_group_ids: - Comma separated list of security groups that will be applied to this instance
216
344
  def initialize(region, options = {})
217
345
  @region = region
218
346
  opts = options[:opts]
@@ -220,11 +348,11 @@ class AwsRegion
220
348
  @id = opts[:db_instance_identifier]
221
349
  snapshot_name = options[:snapshot_name]
222
350
  if 0 < @region.find_db_instances({:instance_id => @id}).length
223
- puts "Error, instance: #{@id} already exists"
351
+ log "Error, instance: #{@id} already exists"
224
352
  return
225
353
  end
226
354
  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}"
355
+ log "Restoring: #{last.db_instance_identifier}, snapshot: #{last.db_instance_identifier} from : #{last.snapshot_create_time}"
228
356
  opts[:db_snapshot_identifier] = last.db_snapshot_identifier
229
357
  response = @region.rds.restore_db_instance_from_db_snapshot(opts)
230
358
  @_instance = response[:db_instance]
@@ -234,8 +362,8 @@ class AwsRegion
234
362
 
235
363
  self.wait
236
364
 
237
- opts = { :db_instance_identifier => @id,
238
- :vpc_security_group_ids => options[:vpc_security_group_ids]}
365
+ opts = {:db_instance_identifier => @id,
366
+ :vpc_security_group_ids => options[:vpc_security_group_ids]}
239
367
  @region.rds.modify_db_instance(opts)
240
368
  else
241
369
  @_instance = options[:instance]
@@ -249,62 +377,83 @@ class AwsRegion
249
377
  @endpoint = @_instance.endpoint[:address]
250
378
  end
251
379
 
380
+ # Delete a database and be sure to capture a final stapshot
381
+ # @return [Boolean] - A return value of true only means that the command was issued. The caller should follow up later with a call to determine status in order to know when the delete has been completed
252
382
  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)
383
+ log "Deleting database: #{@id}"
384
+ opts = {:db_instance_identifier => @id,
385
+ :skip_final_snapshot => false,
386
+ :final_db_snapshot_identifier => "#{@id}-#{Time.now.strftime("%Y-%m-%d-%H-%M-%S")}"}
387
+ begin
388
+ i = @region.rds.delete_db_instance(opts)
389
+ rescue Exception => e
390
+ return false
391
+ end
392
+ true
258
393
  end
259
394
 
395
+ # Purge db snapshots, keep just one - the latest
396
+ # @return [Boolean]
260
397
  def purge_db_snapshots
261
398
  latest = 0
262
399
  @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
400
+ if i.snapshot_type == "manual" and i.db_instance_identifier == @id
401
+ if i.snapshot_create_time.to_i > latest
402
+ latest = i.snapshot_create_time.to_i
403
+ end
404
+ end
268
405
  end
269
406
  @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
407
+ if i.snapshot_type == "manual" and i.db_instance_identifier == @id
408
+ if i.snapshot_create_time.to_i != latest
409
+ log "Removing snapshot: #{i.db_snapshot_identifier}/#{i.snapshot_create_time.to_s}"
410
+ begin
411
+ @region.rds.delete_db_snapshot({:db_snapshot_identifier => i.db_snapshot_identifier})
412
+ rescue
413
+ log "Error removing snapshot: #{i.db_snapshot_identifier}/#{i.snapshot_create_time.to_s}"
414
+ return false
415
+ end
416
+ else
417
+ log "Keeping snapshot: #{i.db_snapshot_identifier}/#{i.snapshot_create_time.to_s}"
418
+ end
419
+ end
420
+ end
421
+ true
283
422
  end
284
423
 
424
+ # Wait for the database to get to a state - we are usually waiting for it to be "available"
425
+ # @param options [Hash] - Can be:
426
+ # * :desired_status - Default: "available" - The RDS Status that is sought
427
+ # * :timeout - Default: 600 seconds. - The time to wait for the status before returning failure
428
+ # @return [Boolean]
285
429
  def wait(options = {:desired_status => "available",
286
430
  :timeout => 600})
287
431
  inst = @region.find_db_instances({:instance_id => @id})[0]
288
432
  if !inst
289
- puts "Error, instance: #{@id} not found"
290
- return
433
+ log "Error, instance: #{@id} not found"
434
+ return false
291
435
  end
292
436
  t0 = Time.now.to_i
293
437
  while inst.status != options[:desired_status]
294
438
  inst = @region.find_db_instances({:instance_id => @id})[0]
295
- puts "Database: #{@id} at #{@endpoint}. Current status: #{inst.status}"
439
+ log "Database: #{@id} at #{@endpoint}. Current status: #{inst.status}"
296
440
  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
441
+ log "Timed out waiting for database: #{@id} at #{@endpoint} to move into status: #{options[:desired_status]}. Current status: #{inst.status}"
442
+ return false
299
443
  end
300
444
  sleep 20
301
445
  end
446
+ return true
302
447
  end
303
448
 
449
+ # Get the status of a database
450
+ # @return [String] - Current status of this database
304
451
  def status
305
- @_instance.db_instance_status
452
+ @_instance.db_instance_status
306
453
  end
307
454
 
455
+ # Get the name of the latest snapshot
456
+ # @return [Hash] - Hash describing RDS Snapshot. See [RDS Tech Docs](http://docs.aws.amazon.com/sdkforruby/api/Aws/RDS/V20130909.html)
308
457
  def get_latest_db_snapshot(options={})
309
458
  snapshot_name = options.has_key?(:snapshot_name) ? options[:snapshot_name] : @id
310
459
 
@@ -320,25 +469,41 @@ class AwsRegion
320
469
  end
321
470
 
322
471
  end
323
- class AwsInstance
472
+
473
+ # Class to handle EC2 Instances
474
+ class AwsInstance < AwsBase
324
475
  attr_accessor :id, :tags, :region, :private_ip, :public_ip, :_instance
476
+
325
477
  def initialize(region, options = {})
478
+ @tags = {}
326
479
  @region = region
327
- if !options.has_key?(:instance)
328
- resp = @region.ec2.run_instances(options)
480
+ if options.has_key?(:instance)
481
+ @_instance = options[:instance]
482
+ @id = @_instance[:instance_id]
483
+ @public_ip = @_instance[:public_ip_address]
484
+ @private_ip = @_instance[:private_ip_address]
485
+ else
486
+ resp = @region.ec2.run_instances(options[:template])
329
487
  raise "Error creating instance using options" if resp.nil? or resp[:instances].length <= 0
330
488
  @_instance = resp[:instances][0]
331
- else
332
- @_instance = options[:instance]
489
+ @id = @_instance[:instance_id]
490
+ @tags = options[:tags]
491
+ self.add_tags(@tags)
492
+ self.wait
493
+ instance = @region.ec2.describe_instances(:instance_ids => [@id])[0][0].instances[0]
494
+ @public_ip = instance[:public_ip_address]
495
+ @private_ip = instance[:private_ip_address]
496
+ raise "could not get ip address" if @public_ip.nil? && @private_ip.nil?
497
+ self.inject_into_environment
333
498
  end
334
- @id = @_instance[:instance_id]
335
- @tags = {}
336
499
  @_instance.tags.each do |t|
337
500
  @tags[t[:key].to_sym] = t[:value]
338
501
  end
339
- @public_ip = @_instance[:public_ip_address]
340
- @private_ip = @_instance[:private_ip_address]
341
502
  end
503
+
504
+
505
+ # Determine the state of an ec2 instance
506
+ # @param use_cached_state [Boolean] - When true will use a cached version of the state rather than querying EC2 directly
342
507
  def state(use_cached_state=true)
343
508
  if !use_cached_state
344
509
  response = @region.ec2.describe_instances({instance_ids: [@id]})
@@ -354,49 +519,57 @@ class AwsRegion
354
519
  @_instance.state[:name].strip()
355
520
  end
356
521
  end
522
+
523
+ # Start an EC2 instance
524
+ # @param wait [Boolean] - When true, will wait for instance to move into "running" state before returning
357
525
  def start(wait=false)
358
526
  if self.state(use_cached_state = false) != "stopped"
359
- puts "Instance cannot be started - #{@region.region}://#{@id} is in the state: #{self.state}"
527
+ log "Instance cannot be started - #{@region.region}://#{@id} is in the state: #{self.state}"
360
528
  return
361
529
  end
362
- puts "Starting instance: #{@region.region}://#{@id}"
530
+ log "Starting instance: #{@region.region}://#{@id}"
363
531
  @region.ec2.start_instances({:instance_ids => [@id]})
364
532
  if wait
365
533
  begin
366
534
  sleep 10
367
- puts "Starting instance: #{@region.region}://#{@id} - state: #{self.state}"
535
+ log "Starting instance: #{@region.region}://#{@id} - state: #{self.state}"
368
536
  end while self.state(use_cached_state = false) != "running"
369
537
  end
370
538
  if @tags.has_key?("elastic_ip")
371
539
  @region.ec2.associate_address({:instance_id => @id, :public_ip => @tags['elastic_ip']})
372
- puts "Associated ip: #{@tags['elastic_ip']} with instance: #{@id}"
540
+ log "Associated ip: #{@tags['elastic_ip']} with instance: #{@id}"
373
541
  elsif @tags.has_key?("elastic_ip_allocation_id")
374
542
  @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}"
543
+ log "Associated allocation id: #{@tags['elastic_ip_allocation_id']} with instance: #{@id}"
376
544
  end
377
545
  if @tags.has_key?("elastic_lb")
378
546
  self.add_to_lb(@tags["elastic_lb"])
379
- puts "Adding instance: #{@id} to '#{@tags['elastic_lb']}' load balancer"
547
+ log "Adding instance: #{@id} to '#{@tags['elastic_lb']}' load balancer"
380
548
  end
381
549
  end
382
- def set_security_groups(groups)
383
- resp = @region.ec2.modify_instance_attribute({:instance_id => @id,
384
- :groups => groups})
385
- end
550
+
551
+ # Add tags to an instance
552
+ # @param h_tags [Hash] - Hash of tags to add to instance
386
553
  def add_tags(h_tags)
387
554
  tags = []
388
- h_tags.each do |k,v|
555
+ h_tags.each do |k, v|
389
556
  tags << {:key => k.to_s, :value => v}
390
557
  end
391
558
  resp = @region.ec2.create_tags({:resources => [@id],
392
559
  :tags => tags})
393
560
  end
394
561
 
562
+ # Add an instance to an elastic lb
563
+ # @param lb_name [String] - Name of elastic load balancer
395
564
  def add_to_lb(lb_name)
396
565
  @region.elb.register_instances_with_load_balancer({:load_balancer_name => lb_name,
397
566
  :instances => [{:instance_id => @id}]})
398
567
  end
399
568
 
569
+ # Remove instance from elastic lb
570
+ # @param instance [AwsInstance] Instance to remove from lb
571
+ # @param lb_name [String] Lb name from which the instance is to be removed
572
+ # @return [Aws::PageableResponse]
400
573
  def remove_from_lb(lb_name)
401
574
  lb = @region.elb.describe_load_balancers({:load_balancer_names => [lb_name]})
402
575
  if lb and lb[:load_balancer_descriptions].length > 0
@@ -404,48 +577,178 @@ class AwsRegion
404
577
  if lb_i[:instance_id] == @id
405
578
  @elb.deregister_instances_from_load_balancer({:load_balancer_name => lb_name,
406
579
  :instances => [{:instance_id => @id}]})
580
+ sleep 30
407
581
  end
408
582
  end
409
583
  end
410
584
  end
411
585
 
586
+ # Terminates ec2 instance
412
587
  def terminate()
588
+ eject_from_environment
413
589
  @region.ec2.terminate_instances({:instance_ids => [@id]})
414
590
  end
415
591
 
416
- def archive_logs()
417
- end
418
-
592
+ # Stops an ec2 instance
593
+ # @param wait [Boolean] - When true, will wait for the instance to be completely stopped before returning
419
594
  def stop(wait=false)
420
595
  if self.state(use_cached_state = false) != "running"
421
- puts "Instance cannot be stopped - #{@region.region}://#{@id} is in the state: #{self.state}"
596
+ log "Instance cannot be stopped - #{@region.region}://#{@id} is in the state: #{self.state}"
422
597
  return
423
598
  end
599
+ self.eject_from_environment
424
600
  if @tags.has_key?("elastic_lb")
425
- puts "Removing instance: #{@id} from '#{@tags['elastic_lb']}' load balancer"
601
+ log "Removing instance: #{@id} from '#{@tags['elastic_lb']}' load balancer"
426
602
  remove_from_lb(tags["elastic_lb"])
427
603
  end
428
- puts "Stopping instance: #{@region.region}://#{@id}"
604
+ log "Stopping instance: #{@region.region}://#{@id}"
429
605
  @region.ec2.stop_instances({:instance_ids => [@id]})
430
606
  while self.state(use_cached_state = false) != "stopped"
431
607
  sleep 10
432
- puts "Stopping instance: #{@region.region}://#{@id} - state: #{self.state}"
608
+ log "Stopping instance: #{@region.region}://#{@id} - state: #{self.state}"
433
609
  end if wait
434
610
  if self.state(use_cached_state = false) == "stopped"
435
- puts "Instance stopped: #{@region.region}://#{@id}"
611
+ log "Instance stopped: #{@region.region}://#{@id}"
436
612
  end
437
613
  end
614
+
615
+ # Connects using ssh to an ec2 instance
438
616
  def connect
439
617
  if self.state(use_cached_state = false) != "running"
440
- puts "Cannot connect, instance: #{@region.region}://#{@id} due to its state: #{self.state}"
618
+ log "Cannot connect, instance: #{@region.region}://#{@id} due to its state: #{self.state}"
441
619
  return
442
620
  end
443
621
  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}"
622
+ log "Connecting: ssh #{@tags[:user]}@#{ip}"
447
623
  exec "ssh #{@tags[:user]}@#{ip}"
448
624
  end
625
+ def eject_from_environment
626
+ if @tags.has_key?(:elastic_lb)
627
+ log "Removing instance: #{@id} from '#{@tags[:elastic_lb]}' load balancer"
628
+ self.remove_from_lb(tags[:elastic_lb])
629
+ end
630
+ if @tags.has_key?(:security_groups_foreign)
631
+ self.revoke_sg_ingress(@tags[:security_groups_foreign].split(","))
632
+ end
633
+ end
634
+
635
+ def inject_into_environment
636
+ if @tags.has_key?(:elastic_ip)
637
+ @region.ec2.associate_address({:instance_id => @id, :public_ip => @tags[:elastic_ip]})
638
+ log "Associated ip: #{@tags[:elastic_ip]} with instance: #{@id}"
639
+ elsif @tags.has_key?(:elastic_ip_allocation_id)
640
+ @region.ec2.associate_address({:instance_id => @id, :allocation_id => @tags[:elastic_ip_allocation_id]})
641
+ log "Associated allocation id: #{@tags[:elastic_ip_allocation_id]} with instance: #{@id}"
642
+ end
643
+ if @tags.has_key?(:mount_points)
644
+ mounts = @tags[:mount_points].split(";")
645
+ mounts.each do |mnt|
646
+ (volume_id,device) = mnt.split(",")
647
+ log "Mounting volume: #{volume_id} on #{device}"
648
+ self.mount(volume_id, device)
649
+ end
650
+ end
651
+ if @tags.has_key?(:security_group_ids)
652
+ self.set_security_groups(@tags[:security_group_ids].split(","))
653
+ end
654
+ if @tags.has_key?(:security_groups_foreign)
655
+ self.authorize_sg_ingress(@tags[:security_groups_foreign].split(","))
656
+ end
657
+
658
+ # if any of the above fails, we probably do not want it in the lb
659
+ if @tags.has_key?(:elastic_lb)
660
+ self.add_to_lb(@tags[:elastic_lb])
661
+ log "Adding instance: #{@id} to '#{@tags[:elastic_lb]}' load balancer"
662
+ end
663
+ end
664
+ def wait(options = {:timeout => 300, :desired_status => "running"})
665
+ t0 = Time.now.to_i
666
+ begin
667
+ sleep 10
668
+ log "Waiting on instance: #{@region.region}://#{@id} - current state: #{self.state}"
669
+ return if Time.now.to_i - t0 > options[:timeout]
670
+ end while self.state(use_cached_state = false) != options[:desired_status]
671
+ end
672
+
673
+ def set_security_groups(groups)
674
+ # only works on instances in a vpc
675
+ @region.ec2.modify_instance_attribute({:instance_id => @id, :groups => groups})
676
+ end
677
+
678
+ # Does a security group allow ingress on a port for the public IP of this instance
679
+ # @param group_port [String] - security_group:port like "sg_xxxxxx:8080"
680
+ # @return [Boolean] true if the security group allows ingress for the public IP of this instance on a certain port
681
+ def has_sg_rule?(group_port)
682
+ options = get_simple_sg_options(group_port)
683
+ options_cidr_ip = options[:ip_permissions][0][:ip_ranges][0][:cidr_ip]
684
+ group_id = options[:group_id]
685
+ raise "missing security group_id" if group_id.nil?
686
+ sg = @region.ec2.describe_security_groups(:group_ids => [group_id]).data.security_groups[0]
687
+ sg.ip_permissions.each do |p|
688
+ if p.ip_protocol == "tcp" &&
689
+ p.from_port == options[:ip_permissions][0][:from_port] &&
690
+ p.to_port == options[:ip_permissions][0][:to_port]
691
+ p[:ip_ranges].each do |ipr|
692
+ return true if ipr.cidr_ip == options_cidr_ip
693
+ end
694
+ end
695
+ end
696
+ false
697
+ end
698
+
699
+ # authorize security group ingress for public ip of this instance on port
700
+ # @param groups [Array] - each element is String: "security_group_id:port". For example: ["sg-0xxxxx:80", "sg-0xxxxx:8443", "sg-0yyyyy:3000"]
701
+ def authorize_sg_ingress(groups)
702
+ raise "no public ip" unless @public_ip.to_s.match /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
703
+ groups.each do |gp|
704
+ options = get_simple_sg_options(gp)
705
+ if has_sg_rule?(gp)
706
+ log "security group rule #{gp} for #{self.public_ip} already exists"
707
+ else
708
+ @region.ec2.authorize_security_group_ingress options
709
+ log "added #{self.public_ip} to security group for :port #{gp}"
710
+ end
711
+ end
712
+ end
713
+
714
+ # revoke security group ingress for public ip of this instance on port
715
+ # @param groups [Array] - each element is String: "security_group_id:port". For example: ["sg-0xxxxx:80", "sg-0xxxxx:8443", "sg-0yyyyy:3000"]
716
+ def revoke_sg_ingress(groups)
717
+ # revoke the public ip of this instance for ingress on port for security group
718
+ # groups is array of strings: security_group_id:port
719
+ raise "no public ip" unless @public_ip.to_s.match /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
720
+ groups.each do |gp|
721
+ options = get_simple_sg_options(gp)
722
+ if has_sg_rule?(gp)
723
+ @region.ec2.revoke_security_group_ingress options
724
+ log "removed #{self.public_ip} from security group for :port #{gp}"
725
+ else
726
+ log "not removing #{self.public_ip} rule #{gp} because it does not exist"
727
+ end
728
+ end
729
+ end
730
+ def mount(volume_id, device)
731
+ @region.ec2.attach_volume({:instance_id => @id,
732
+ :volume_id => volume_id,
733
+ :device => device})
734
+ end
735
+
736
+ # construct hash for authorize/revoke ingress and has_sg_rule?
737
+ # @param group_id_port [String] - security_group:port like "sg_xxxxxx:8080"
738
+ # @return [Hash] - Hash for ec2 call for security group management
739
+ def get_simple_sg_options(group_id_port)
740
+ security_group_id, port = group_id_port.split(':')
741
+ port = port.to_s.to_i
742
+ raise "no security group id" unless security_group_id.to_s.length > 0
743
+ raise "no, or invalid port" unless port.to_s.to_i > 0
744
+ {:group_id => security_group_id,
745
+ :ip_permissions => [ :ip_protocol => "tcp",
746
+ :from_port => port,
747
+ :to_port => port,
748
+ :ip_ranges => [:cidr_ip => "#{self.public_ip}/32"]
749
+ ]
750
+ }
751
+ end
449
752
 
450
753
 
451
754
  end
metadata CHANGED
@@ -1,58 +1,73 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: aws-tools
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.2
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 3
9
+ version: 0.0.3
5
10
  platform: ruby
6
- authors:
11
+ authors:
7
12
  - Kevin Connors
8
13
  autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
- date: 2014-12-02 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
16
+
17
+ date: 2014-12-02 00:00:00 -08:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
14
21
  name: rspec
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ! '>='
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
22
  prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ! '>='
25
- - !ruby/object:Gem::Version
26
- version: '0'
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :development
31
+ version_requirements: *id001
27
32
  description: A set of simple tools to bind some essential functions of aws resources
28
33
  email: karmet@gmail.com
29
- executables: []
34
+ executables:
35
+ - aws_manager.rb
30
36
  extensions: []
37
+
31
38
  extra_rdoc_files: []
32
- files:
39
+
40
+ files:
33
41
  - lib/aws_region.rb
42
+ has_rdoc: true
34
43
  homepage: http://rubygems.org/gems/aws-tools
35
- licenses:
44
+ licenses:
36
45
  - MIT
37
- metadata: {}
38
46
  post_install_message:
39
47
  rdoc_options: []
40
- require_paths:
48
+
49
+ require_paths:
41
50
  - 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'
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ segments:
63
+ - 0
64
+ version: "0"
52
65
  requirements: []
66
+
53
67
  rubyforge_project:
54
- rubygems_version: 2.2.2
68
+ rubygems_version: 1.3.6
55
69
  signing_key:
56
- specification_version: 4
70
+ specification_version: 3
57
71
  summary: A set of simple tools to bind some essential functions of aws resources
58
72
  test_files: []
73
+
checksums.yaml DELETED
@@ -1,15 +0,0 @@
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=