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