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.
- data/bin/aws_manager.rb +227 -0
- data/lib/aws_region.rb +472 -169
- metadata +50 -35
- checksums.yaml +0 -15
data/bin/aws_manager.rb
ADDED
@@ -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()
|
data/lib/aws_region.rb
CHANGED
@@ -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
|
7
|
-
|
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
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
71
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
255
|
+
log "s3 searching bucket:#{@id} for #{aws_path}/#{prefix}"
|
167
256
|
objects = @region.s3.list_objects(:bucket => @id,
|
168
|
-
|
257
|
+
:prefix => "#{aws_path}/#{prefix}")
|
169
258
|
f = objects.contents.collect(&:key)
|
170
|
-
|
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
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
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
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
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
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 = {
|
238
|
-
|
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
|
-
|
254
|
-
opts = {
|
255
|
-
|
256
|
-
|
257
|
-
|
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
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
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
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
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
|
-
|
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
|
-
|
439
|
+
log "Database: #{@id} at #{@endpoint}. Current status: #{inst.status}"
|
296
440
|
if Time.now.to_i - t0 > options[:timeout]
|
297
|
-
|
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
|
-
|
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
|
-
|
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
|
328
|
-
|
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
|
-
|
332
|
-
@
|
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
|
-
|
527
|
+
log "Instance cannot be started - #{@region.region}://#{@id} is in the state: #{self.state}"
|
360
528
|
return
|
361
529
|
end
|
362
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
547
|
+
log "Adding instance: #{@id} to '#{@tags['elastic_lb']}' load balancer"
|
380
548
|
end
|
381
549
|
end
|
382
|
-
|
383
|
-
|
384
|
-
|
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
|
-
|
417
|
-
|
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
|
-
|
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
|
-
|
601
|
+
log "Removing instance: #{@id} from '#{@tags['elastic_lb']}' load balancer"
|
426
602
|
remove_from_lb(tags["elastic_lb"])
|
427
603
|
end
|
428
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
23
|
-
requirements:
|
24
|
-
- -
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
|
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
|
-
|
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
|
-
|
48
|
+
|
49
|
+
require_paths:
|
41
50
|
- lib
|
42
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
-
requirements:
|
44
|
-
- -
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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:
|
68
|
+
rubygems_version: 1.3.6
|
55
69
|
signing_key:
|
56
|
-
specification_version:
|
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=
|