aws-tools 0.0.2 → 0.0.3

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 (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=