ec2launcher 1.0.9 → 1.0.10
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +6 -0
- data/bin/ec2launcher +1 -1
- data/lib/ec2launcher/application.rb +228 -195
- data/lib/ec2launcher/backoff_runner.rb +33 -0
- data/lib/ec2launcher/block_device.rb +72 -69
- data/lib/ec2launcher/email_notification.rb +49 -46
- data/lib/ec2launcher/environment.rb +10 -18
- data/lib/ec2launcher/init_options.rb +121 -119
- data/lib/ec2launcher/security_group_handler.rb +60 -0
- data/lib/ec2launcher/version.rb +1 -1
- data/lib/ec2launcher.rb +6 -25
- metadata +4 -2
data/CHANGELOG.md
CHANGED
data/bin/ec2launcher
CHANGED
@@ -3,260 +3,293 @@
|
|
3
3
|
#
|
4
4
|
require 'ec2launcher/block_device'
|
5
5
|
require 'ec2launcher/email_notification'
|
6
|
+
require 'ec2launcher/security_group_handler'
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
module EC2Launcher
|
9
|
+
# Wrapper class to handle loading Application blocks.
|
10
|
+
class ApplicationDSL
|
11
|
+
attr_accessor :applications
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
+
def initialize
|
14
|
+
self.applications = []
|
15
|
+
end
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
def application(name, &block)
|
18
|
+
application = Application.new(name)
|
19
|
+
applications << application
|
20
|
+
application.instance_eval &block
|
21
|
+
application
|
22
|
+
end
|
20
23
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
+
def self.execute(dsl)
|
25
|
+
new.tap do |context|
|
26
|
+
context.instance_eval(dsl)
|
27
|
+
end
|
24
28
|
end
|
25
29
|
end
|
26
|
-
end
|
27
30
|
|
28
|
-
|
29
|
-
|
31
|
+
# Represents a single application stack.
|
32
|
+
class Application
|
33
|
+
include EC2Launcher::EmailNotifications
|
34
|
+
include EC2Launcher::SecurityGroupHandler
|
30
35
|
|
31
|
-
|
36
|
+
attr_reader :name
|
32
37
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
38
|
+
def initialize(name)
|
39
|
+
@name = name
|
40
|
+
@email_notifications = nil
|
41
|
+
end
|
37
42
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
+
def application(name)
|
44
|
+
@name = name
|
45
|
+
yield self
|
46
|
+
self
|
47
|
+
end
|
43
48
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
49
|
+
# Name of the AMI to use for new instances. Optional.
|
50
|
+
# Can be either a string or a regular expression.
|
51
|
+
#
|
52
|
+
# @param [Array, nil] Either an array of parameters or nil to return the AMI name.
|
53
|
+
def ami_name(*ami_name)
|
54
|
+
if ami_name.empty?
|
55
|
+
@ami_name
|
50
56
|
else
|
51
|
-
|
57
|
+
if ami_name[0].kind_of? String
|
58
|
+
@ami_name = /#{ami_name[0]}/
|
59
|
+
else
|
60
|
+
@ami_name = ami_name[0]
|
61
|
+
end
|
62
|
+
self
|
52
63
|
end
|
53
|
-
self
|
54
64
|
end
|
55
|
-
end
|
56
65
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
66
|
+
# Name of the availability zone to use for new instances. Optional.
|
67
|
+
# Must be one of EC2Launcher::AVAILABILITY_ZONES.
|
68
|
+
def availability_zone(*zone)
|
69
|
+
if zone.empty?
|
70
|
+
@availability_zone
|
71
|
+
else
|
72
|
+
@availability_zone = zone[0].to_s
|
73
|
+
self
|
74
|
+
end
|
63
75
|
end
|
64
|
-
end
|
65
76
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
77
|
+
# Defines a shorter name when building a host name for new instances. Optional.
|
78
|
+
# By default, new instances are named using the full application name. If you
|
79
|
+
# specify basename, it will be used instead. Typically, this is used if you
|
80
|
+
# want a shorter version of the full application name.
|
81
|
+
def basename(*name)
|
82
|
+
if name.empty?
|
83
|
+
@basename
|
84
|
+
else
|
85
|
+
@basename = name[0]
|
86
|
+
self
|
87
|
+
end
|
72
88
|
end
|
73
|
-
end
|
74
89
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
90
|
+
def block_devices(*block_device_data)
|
91
|
+
if block_device_data.empty?
|
92
|
+
@block_devices
|
93
|
+
else
|
94
|
+
self
|
95
|
+
end
|
80
96
|
end
|
81
|
-
end
|
82
97
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
98
|
+
def block_device(&block)
|
99
|
+
@block_devices = [] if @block_devices.nil?
|
100
|
+
device = EC2Launcher::BlockDevice.new
|
101
|
+
device.instance_exec(&block)
|
102
|
+
@block_devices << device
|
103
|
+
end
|
89
104
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
105
|
+
# Indicates the Amazon Elastic Load Balancer to which new instances should be
|
106
|
+
# attached after launch. Optional.
|
107
|
+
#
|
108
|
+
# The value can be either a String, indicating the name of the ELB, or a Hash
|
109
|
+
# that maps environment names to ELB names.
|
110
|
+
def elb(*elb)
|
111
|
+
if elb.empty?
|
112
|
+
@elb
|
97
113
|
else
|
98
|
-
@elb
|
114
|
+
@elb = Hash.new if @elb.nil?
|
115
|
+
if elb[0].kind_of? Hash
|
116
|
+
elb[0].keys.each {|key| @elb[key] = elb[0][key]}
|
117
|
+
else
|
118
|
+
@elb["default"] = elb[0].to_s
|
119
|
+
end
|
120
|
+
self
|
99
121
|
end
|
100
|
-
self
|
101
122
|
end
|
102
|
-
end
|
103
123
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
124
|
+
# Retrieves the ELB name for a given environment.
|
125
|
+
def elb_for_environment(environment)
|
126
|
+
elb_name = @elb[environment]
|
127
|
+
elb_name ||= @elb["default"]
|
128
|
+
elb_name
|
129
|
+
end
|
109
130
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
131
|
+
# Defines an Array of Chef roles that should be applied to new
|
132
|
+
# instances for a specific environment. Can be specified multiple times.
|
133
|
+
#
|
134
|
+
# Expects two parameters:
|
135
|
+
# * Name of an environment
|
136
|
+
# * Either the name of a single Chef role or an Array of Chef roles
|
137
|
+
def environment_roles(*data)
|
138
|
+
if data.empty?
|
139
|
+
@environment_roles
|
140
|
+
else
|
141
|
+
@environment_roles = Hash.new if @environment_roles.nil?
|
142
|
+
env_name = data[0]
|
143
|
+
env_roles = data[1]
|
117
144
|
|
118
|
-
|
119
|
-
|
145
|
+
environment_data = @environment_roles[env_name]
|
146
|
+
environment_data ||= []
|
120
147
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
148
|
+
if env_roles.kind_of? Array
|
149
|
+
environment_data += env_roles
|
150
|
+
else
|
151
|
+
environment_data << env_roles
|
152
|
+
end
|
153
|
+
@environment_roles[env_name] = environment_data
|
127
154
|
|
128
|
-
|
155
|
+
self
|
156
|
+
end
|
129
157
|
end
|
130
|
-
end
|
131
158
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
159
|
+
# Gems to install. Optional.
|
160
|
+
#
|
161
|
+
# Expects either a single String naming a gem to install or
|
162
|
+
# an Array of gems.
|
163
|
+
#
|
164
|
+
# Can be specified multiple times.
|
165
|
+
def gems(*gems)
|
166
|
+
if gems.empty?
|
167
|
+
@gems
|
168
|
+
else
|
169
|
+
@gems = [] if @gems.nil?
|
170
|
+
if gems[0].kind_of? Array
|
171
|
+
@gems += gems[0]
|
172
|
+
else
|
173
|
+
@gems << gems[0]
|
174
|
+
end
|
175
|
+
self
|
176
|
+
end
|
138
177
|
end
|
139
|
-
end
|
140
178
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
179
|
+
# Indicates that this application should inherit all of its settings from another named application.
|
180
|
+
# Optional.
|
181
|
+
def inherit(*inherit_type)
|
182
|
+
if inherit_type.empty?
|
183
|
+
@inherit_type
|
184
|
+
else
|
185
|
+
@inherit_type = inherit_type[0]
|
186
|
+
end
|
146
187
|
end
|
147
|
-
end
|
148
188
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
189
|
+
# The Amazon EC2 instance type that should be used for new instances.
|
190
|
+
# Must be one of EC2Launcher::INSTANCE_TYPES.
|
191
|
+
def instance_type(*type_name)
|
192
|
+
if type_name.empty?
|
193
|
+
@instance_type
|
194
|
+
else
|
195
|
+
@instance_type = type_name[0]
|
196
|
+
self
|
197
|
+
end
|
155
198
|
end
|
156
|
-
end
|
157
199
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
200
|
+
# Takes values from the other server type and merges them into this one
|
201
|
+
def merge(other_server)
|
202
|
+
@name = other_server.name
|
203
|
+
@ami_name = other_server.ami_name unless other_server.ami_name.nil?
|
204
|
+
@availability_zone = other_server.availability_zone unless other_server.availability_zone.nil?
|
205
|
+
@basename = other_server.basename unless other_server.basename.nil?
|
206
|
+
other_server.block_devices.each {|bd| @block_devices << bd } unless other_server.block_devices.nil?
|
207
|
+
other_server.elb.keys.each {|env_name| @elb[env_name] = other_server.elb[env_name] } unless other_server.elb.nil?
|
208
|
+
@instance_type = other_server.instance_type unless other_server.instance_type.nil?
|
209
|
+
@name_suffix = other_server.name_suffix unless other_server.name_suffix.nil?
|
210
|
+
other_server.roles.each {|role| @roles << role } unless other_server.roles.nil?
|
211
|
+
unless other_server.security_groups.nil?
|
212
|
+
other_server.security_groups.keys.each do |env_name|
|
213
|
+
unless @security_groups.has_key? env_name
|
214
|
+
@security_groups[env_name] = []
|
215
|
+
end
|
216
|
+
other_server.security_groups[env_name].each {|sg| @security_groups[env_name] << sg }
|
173
217
|
end
|
174
|
-
other_server.security_groups[env_name].each {|sg| @security_groups[env_name] << sg }
|
175
218
|
end
|
176
219
|
end
|
177
|
-
end
|
178
220
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
221
|
+
def name_suffix(*suffix)
|
222
|
+
if suffix.empty?
|
223
|
+
@name_suffix
|
224
|
+
else
|
225
|
+
@name_suffix = suffix[0]
|
226
|
+
end
|
184
227
|
end
|
185
|
-
end
|
186
228
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
229
|
+
def packages(*packages)
|
230
|
+
if packages.empty?
|
231
|
+
@packages
|
232
|
+
else
|
233
|
+
@packages = packages[0]
|
234
|
+
self
|
235
|
+
end
|
193
236
|
end
|
194
|
-
end
|
195
237
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
else
|
200
|
-
@roles = [] if @roles.nil?
|
201
|
-
if roles[0].kind_of? Array
|
202
|
-
@roles += roles[0]
|
238
|
+
def roles(*roles)
|
239
|
+
if roles.empty?
|
240
|
+
@roles
|
203
241
|
else
|
204
|
-
@roles = []
|
205
|
-
|
242
|
+
@roles = [] if @roles.nil?
|
243
|
+
if roles[0].kind_of? Array
|
244
|
+
@roles += roles[0]
|
245
|
+
else
|
246
|
+
@roles = []
|
247
|
+
@roles << roles[0]
|
248
|
+
end
|
249
|
+
self
|
206
250
|
end
|
207
|
-
self
|
208
251
|
end
|
209
|
-
end
|
210
252
|
|
211
|
-
|
212
|
-
|
213
|
-
|
253
|
+
def roles_for_environment(environment)
|
254
|
+
roles = []
|
255
|
+
roles += @roles unless @roles.nil?
|
214
256
|
|
215
|
-
|
216
|
-
|
257
|
+
unless @environment_roles.nil? || @environment_roles[environment].nil?
|
258
|
+
roles += @environment_roles[environment]
|
259
|
+
end
|
260
|
+
roles
|
261
|
+
end
|
262
|
+
|
263
|
+
# Retrieves the list of Security Group names for the specified environment.
|
264
|
+
#
|
265
|
+
# @return [Array] Returns the list of security groups for the environment. Returns
|
266
|
+
# the security groups for the "defaukt" environment if the requested
|
267
|
+
# environment is undefined. Returns an empty Array if both the
|
268
|
+
# requested environment and "default" environment are undefined.
|
269
|
+
def security_groups_for_environment(environment)
|
270
|
+
groups = @security_groups[environment]
|
271
|
+
groups ||= @security_groups["default"]
|
272
|
+
groups ||= []
|
273
|
+
groups
|
217
274
|
end
|
218
|
-
roles
|
219
|
-
end
|
220
275
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
else
|
225
|
-
@security_groups = Hash.new if @security_groups.nil?
|
226
|
-
if groups[0].kind_of? Array
|
227
|
-
@security_groups["default"] = groups[0]
|
228
|
-
elsif groups[0].kind_of? Hash
|
229
|
-
groups[0].keys.each {|key| @security_groups[key] = [ groups[0][key] ] }
|
276
|
+
def subnet(*subnet)
|
277
|
+
if subnet.empty?
|
278
|
+
@subnet
|
230
279
|
else
|
231
|
-
@
|
280
|
+
@subnet = subnet[0]
|
281
|
+
self
|
232
282
|
end
|
233
|
-
self
|
234
283
|
end
|
235
|
-
end
|
236
|
-
|
237
|
-
def security_groups_for_environment(environment)
|
238
|
-
groups = @security_groups[environment]
|
239
|
-
groups ||= @security_groups["default"]
|
240
|
-
groups ||= []
|
241
|
-
groups
|
242
|
-
end
|
243
284
|
|
244
|
-
|
245
|
-
|
246
|
-
@subnet
|
247
|
-
else
|
248
|
-
@subnet = subnet[0]
|
285
|
+
def load(dsl)
|
286
|
+
self.instance_eval(dsl)
|
249
287
|
self
|
250
288
|
end
|
251
|
-
end
|
252
|
-
|
253
|
-
def load(dsl)
|
254
|
-
self.instance_eval(dsl)
|
255
|
-
self
|
256
|
-
end
|
257
289
|
|
258
|
-
|
259
|
-
|
260
|
-
|
290
|
+
def self.load(dsl)
|
291
|
+
env = Application.new.instance_eval(dsl)
|
292
|
+
env
|
293
|
+
end
|
261
294
|
end
|
262
295
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2012 Sean Laurent
|
3
|
+
#
|
4
|
+
require 'aws-sdk'
|
5
|
+
|
6
|
+
module EC2Launcher
|
7
|
+
# Helper module to run AWS requests.
|
8
|
+
module BackoffRunner
|
9
|
+
# Runs an AWS request inside a Ruby block with an exponential backoff in case
|
10
|
+
# we exceed the allowed AWS RequestLimit.
|
11
|
+
#
|
12
|
+
# @param [Integer] max_time maximum amount of time to sleep before giving up.
|
13
|
+
# @param [Integer] sleep_time the initial amount of time to sleep before retrying.
|
14
|
+
# @param [message] message message to display if we get an exception.
|
15
|
+
# @param [Block] block Ruby code block to execute.
|
16
|
+
def run_with_backoff(max_time, sleep_time, message, &block)
|
17
|
+
if sleep_time > max_time
|
18
|
+
puts "AWS::EC2::Errors::RequestLimitExceeded ... failed #{message}"
|
19
|
+
return false
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
yield
|
24
|
+
rescue AWS::EC2::Errors::RequestLimitExceeded
|
25
|
+
puts "AWS::EC2::Errors::RequestLimitExceeded ... retrying #{message} in #{sleep_time} seconds"
|
26
|
+
sleep sleep_time
|
27
|
+
run_with_backoff(max_time, sleep_time * 2, message, &block)
|
28
|
+
end
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -1,91 +1,94 @@
|
|
1
1
|
#
|
2
2
|
# Copyright (c) 2012 Sean Laurent
|
3
3
|
#
|
4
|
-
class BlockDevice
|
5
|
-
attr_reader :mount_point
|
6
|
-
attr_reader :name
|
7
4
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
5
|
+
module EC2Launcher
|
6
|
+
class BlockDevice
|
7
|
+
attr_reader :mount_point
|
8
|
+
attr_reader :name
|
13
9
|
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
def initialize()
|
11
|
+
@count = 1
|
12
|
+
@group = "root"
|
13
|
+
@user = "root"
|
14
|
+
end
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
@count
|
21
|
-
else
|
22
|
-
@count = block_count[0]
|
23
|
-
self
|
16
|
+
def is_raid?()
|
17
|
+
@raid_level.nil?
|
24
18
|
end
|
25
|
-
end
|
26
19
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
20
|
+
def count(*block_count)
|
21
|
+
if block_count.empty?
|
22
|
+
@count
|
23
|
+
else
|
24
|
+
@count = block_count[0]
|
25
|
+
self
|
26
|
+
end
|
33
27
|
end
|
34
|
-
end
|
35
28
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
29
|
+
def group(*group)
|
30
|
+
if group.empty?
|
31
|
+
@group
|
32
|
+
else
|
33
|
+
@group = group[0]
|
34
|
+
self
|
35
|
+
end
|
42
36
|
end
|
43
|
-
end
|
44
37
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
38
|
+
def mount(*mount)
|
39
|
+
if mount.empty?
|
40
|
+
@mount
|
41
|
+
else
|
42
|
+
@mount_point = mount[0]
|
43
|
+
self
|
44
|
+
end
|
51
45
|
end
|
52
|
-
end
|
53
46
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
47
|
+
def name(*name)
|
48
|
+
if name.empty?
|
49
|
+
@name
|
50
|
+
else
|
51
|
+
@name = name[0]
|
52
|
+
self
|
53
|
+
end
|
60
54
|
end
|
61
|
-
end
|
62
55
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
56
|
+
def owner(*owner)
|
57
|
+
if owner.empty?
|
58
|
+
@owner
|
59
|
+
else
|
60
|
+
@owner = owner[0]
|
61
|
+
self
|
62
|
+
end
|
69
63
|
end
|
70
|
-
end
|
71
64
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
65
|
+
def raid_level(*raid_level)
|
66
|
+
if raid_level.empty?
|
67
|
+
@raid_level
|
68
|
+
else
|
69
|
+
@raid_level = raid_level[0]
|
70
|
+
self
|
71
|
+
end
|
78
72
|
end
|
79
|
-
end
|
80
73
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
74
|
+
def size(*volume_size)
|
75
|
+
if volume_size.empty?
|
76
|
+
@size
|
77
|
+
else
|
78
|
+
@size = volume_size[0].to_i
|
79
|
+
self
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_json(*a)
|
84
|
+
{
|
85
|
+
"name" => @name,
|
86
|
+
"count" => @count,
|
87
|
+
"raid_level" => @raid_level,
|
88
|
+
"mount_point" => @mount_point,
|
89
|
+
"owner" => @owner,
|
90
|
+
"group" => @group
|
91
|
+
}.to_json(*a)
|
92
|
+
end
|
90
93
|
end
|
91
94
|
end
|
@@ -1,62 +1,65 @@
|
|
1
1
|
#
|
2
2
|
# Copyright (c) 2012 Sean Laurent
|
3
3
|
#
|
4
|
-
module EmailNotifications
|
5
|
-
attr_reader :email_notifications
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@email_notifications = notifications
|
11
|
-
end
|
12
|
-
end
|
5
|
+
module EC2Launcher
|
6
|
+
module EmailNotifications
|
7
|
+
attr_reader :email_notifications
|
13
8
|
|
14
|
-
|
15
|
-
|
9
|
+
def email_notification(&block)
|
10
|
+
notifications = EmailNotification.new
|
11
|
+
notifications.instance_exec(&block)
|
12
|
+
@email_notifications = notifications
|
13
|
+
end
|
16
14
|
end
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
@from
|
21
|
-
else
|
22
|
-
@from = from[0]
|
23
|
-
self
|
16
|
+
class EmailNotification
|
17
|
+
def initialize()
|
24
18
|
end
|
25
|
-
end
|
26
19
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
20
|
+
def from(*from)
|
21
|
+
if from.empty?
|
22
|
+
@from
|
23
|
+
else
|
24
|
+
@from = from[0]
|
25
|
+
self
|
26
|
+
end
|
33
27
|
end
|
34
|
-
end
|
35
28
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
29
|
+
def to(*to)
|
30
|
+
if to.empty?
|
31
|
+
@to
|
32
|
+
else
|
33
|
+
@to = to[0]
|
34
|
+
self
|
35
|
+
end
|
42
36
|
end
|
43
|
-
end
|
44
37
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
38
|
+
def ses_access_key(*ses_access_key)
|
39
|
+
if ses_access_key.empty?
|
40
|
+
@ses_access_key
|
41
|
+
else
|
42
|
+
@ses_access_key = ses_access_key[0]
|
43
|
+
self
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def ses_secret_key(*ses_secret_key)
|
48
|
+
if ses_secret_key.empty?
|
49
|
+
@ses_secret_key
|
50
|
+
else
|
51
|
+
@ses_secret_key = ses_secret_key[0]
|
52
|
+
self
|
53
|
+
end
|
51
54
|
end
|
52
|
-
end
|
53
55
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
56
|
+
def to_json(*a)
|
57
|
+
{
|
58
|
+
"from" => @from,
|
59
|
+
"to" => @to,
|
60
|
+
"ses_access_key" => @ses_access_key,
|
61
|
+
"ses_secret_key" => @ses_secret_key
|
62
|
+
}.to_json(*a)
|
63
|
+
end
|
61
64
|
end
|
62
|
-
end
|
65
|
+
end
|
@@ -2,10 +2,12 @@
|
|
2
2
|
# Copyright (c) 2012 Sean Laurent
|
3
3
|
#
|
4
4
|
require 'ec2launcher/email_notification'
|
5
|
+
require 'ec2launcher/security_group_handler'
|
5
6
|
|
6
7
|
module EC2Launcher
|
7
8
|
class Environment
|
8
|
-
include EmailNotifications
|
9
|
+
include EC2Launcher::EmailNotifications
|
10
|
+
include EC2Launcher::SecurityGroupHandler
|
9
11
|
|
10
12
|
attr_reader :name
|
11
13
|
attr_reader :precommands
|
@@ -19,8 +21,7 @@ module EC2Launcher
|
|
19
21
|
@precommands = []
|
20
22
|
@postcommands = []
|
21
23
|
@roles = []
|
22
|
-
@security_groups =
|
23
|
-
|
24
|
+
@security_groups = {}
|
24
25
|
end
|
25
26
|
|
26
27
|
def environment(name)
|
@@ -157,20 +158,6 @@ module EC2Launcher
|
|
157
158
|
end
|
158
159
|
end
|
159
160
|
|
160
|
-
def security_groups(*security_groups)
|
161
|
-
if security_groups.empty?
|
162
|
-
@security_groups
|
163
|
-
else
|
164
|
-
@security_groups = [] if @security_groups.nil?
|
165
|
-
if security_groups[0].kind_of? Array
|
166
|
-
@security_groups += security_groups[0]
|
167
|
-
else
|
168
|
-
@security_groups << security_groups[0]
|
169
|
-
end
|
170
|
-
self
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
161
|
def short_name(*short_name)
|
175
162
|
if short_name.empty?
|
176
163
|
@short_name
|
@@ -198,7 +185,12 @@ module EC2Launcher
|
|
198
185
|
@roles += other_env.roles unless other_env.roles.nil?
|
199
186
|
@precommands += other_env.precommands unless other_env.precommands.nil?
|
200
187
|
@postcommands += other_env.postcommands unless other_env.postcommands.nil?
|
201
|
-
|
188
|
+
unless other_env.security_groups.nil?
|
189
|
+
other_env.security_groups.keys.each do |key|
|
190
|
+
@security_groups[key] = [] if @security_groups[key].nil?
|
191
|
+
@security_groups[key] += other_env.security_groups[key]
|
192
|
+
end
|
193
|
+
end
|
202
194
|
|
203
195
|
@aliases = other_env.aliases.nil? ? nil : other_env.aliases
|
204
196
|
|
@@ -7,160 +7,162 @@ require 'ostruct'
|
|
7
7
|
|
8
8
|
require 'ec2launcher/defaults'
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
module EC2Launcher
|
11
|
+
class InitOptions
|
12
|
+
attr_reader :command
|
13
|
+
attr_reader :options
|
14
|
+
attr_reader :location
|
14
15
|
|
15
|
-
|
16
|
+
SUB_COMMANDS = %w{init launch}
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
def initialize
|
19
|
+
@opts = OptionParser.new do |opts|
|
20
|
+
opts.banner = "Usage: ec2launcher [COMMAND]
|
20
21
|
|
21
|
-
where [COMMAND] is one of:
|
22
|
+
where [COMMAND] is one of:
|
22
23
|
|
23
|
-
|
24
|
-
|
24
|
+
init [LOCATION] Initialize a repository in the specified directory.
|
25
|
+
launch [OPTIONS] Launch a new instance.
|
25
26
|
|
26
|
-
and [OPTIONS] include:
|
27
|
+
and [OPTIONS] include:
|
27
28
|
|
28
|
-
"
|
29
|
-
|
29
|
+
"
|
30
|
+
opts.separator "Query options:"
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
opts.on("-l", "--list", "Show environments and applications.") do
|
33
|
+
@options.list = true
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
opts.on("-s", "--show-defaults", "Show settings, but do not launch any instances.") do
|
37
|
+
@options.show_defaults = true
|
38
|
+
end
|
38
39
|
|
39
|
-
|
40
|
-
|
40
|
+
opts.separator ""
|
41
|
+
opts.separator "Required Launch options:"
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
opts.on("-e", "--environment ENV", "The environment for the server.") do |env|
|
44
|
+
@options.environ = env
|
45
|
+
end
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
opts.on("-a", "--application NAME", "The name of the application class for the new server.") do |app_name|
|
48
|
+
@options.application = app_name
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
-
|
51
|
+
opts.separator ""
|
52
|
+
opts.separator "Additional launch options:"
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
opts.on("--command [CMD]", "Additional command to run during launch sequence.") do |command|
|
55
|
+
@options.commands << command
|
56
|
+
end
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
58
|
+
opts.on("-c", "--clone HOST", "Clone the latest snapshots from a specific host.") do |clone_host|
|
59
|
+
@options.clone_host = clone_host
|
60
|
+
end
|
60
61
|
|
61
|
-
|
62
|
-
|
62
|
+
opts.separator ""
|
63
|
+
opts.separator "Overrides:"
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
|
65
|
+
opts.on("-d", "--directory DIRECTORY", "Location of configuration directory. Defaults to current directory.") do |directory|
|
66
|
+
@options.directory = directory
|
67
|
+
end
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
|
69
|
+
opts.on("-h", "--hostname NAME", "The name for the new server.") do |hostname|
|
70
|
+
@options.hostname = hostname
|
71
|
+
end
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
|
73
|
+
opts.on("-u", "--chef-validation-url", "URL for the Chef validation pem file.") do |chef_validation_url|
|
74
|
+
@options.chef_validation_url = chef_validation_url
|
75
|
+
end
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
77
|
+
opts.on("--ami AMI_ID", "AMI id") do |ami_id|
|
78
|
+
@options.ami_id = ami_id
|
79
|
+
end
|
79
80
|
|
80
|
-
|
81
|
-
|
82
|
-
|
81
|
+
opts.on("-z", "--availability-zone ZONE", EC2Launcher::AVAILABILITY_ZONES, "AWS availability zone (#{EC2Launcher::AVAILABILITY_ZONES.join(', ')}).") do |zone|
|
82
|
+
@options.zone = zone
|
83
|
+
end
|
83
84
|
|
84
|
-
|
85
|
-
|
86
|
-
|
85
|
+
opts.on("-i", "--instance-type TYPE", EC2Launcher::INSTANCE_TYPES, "EC2 instance type (#{EC2Launcher::INSTANCE_TYPES.join(', ')}).") do |instance_type|
|
86
|
+
@options.instance_type = instance_type
|
87
|
+
end
|
87
88
|
|
88
|
-
|
89
|
-
|
90
|
-
|
89
|
+
opts.on("-v", "--volume-size SIZE", Integer, "EBS volume size in GB. Defaults to #{EC2Launcher::DEFAULT_VOLUME_SIZE} GB") do |volume_size|
|
90
|
+
@options.volume_size = volume_size
|
91
|
+
end
|
91
92
|
|
92
|
-
|
93
|
-
|
93
|
+
opts.separator ""
|
94
|
+
opts.separator "AWS Security Options:"
|
94
95
|
|
95
|
-
|
96
|
-
|
97
|
-
|
96
|
+
opts.on("--access-key KEY", "Amazon access key. Overrides AWS_ACCESS_KEY environment variable.") do |access_key|
|
97
|
+
@options.access_key = access_key
|
98
|
+
end
|
98
99
|
|
99
|
-
|
100
|
-
|
101
|
-
|
100
|
+
opts.on("--secret SECRET", "Amazon secret access key. Overrides AWS_SECRET_ACCESS_KEY environment variable.") do |secret|
|
101
|
+
@options.secret = secret
|
102
|
+
end
|
102
103
|
|
103
|
-
|
104
|
-
|
104
|
+
opts.separator ""
|
105
|
+
opts.separator "Common options:"
|
105
106
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
107
|
+
# No argument, shows at tail. This will print an options summary.
|
108
|
+
# Try it and see!
|
109
|
+
opts.on_tail("-?", "--help", "Show this message") do
|
110
|
+
puts opts
|
111
|
+
exit
|
112
|
+
end
|
113
|
+
end
|
112
114
|
end
|
113
|
-
end
|
114
115
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
116
|
+
def parse(args)
|
117
|
+
@command = args.shift
|
118
|
+
unless SUB_COMMANDS.include?(@command)
|
119
|
+
puts "Missing command! " if @command.nil?
|
120
|
+
puts "Invalid command: #{@command}" unless @command.nil? || @command == "-?" || @command == "--help"
|
121
|
+
puts @opts
|
122
|
+
exit 1
|
123
|
+
end
|
123
124
|
|
124
|
-
|
125
|
-
|
126
|
-
|
125
|
+
@options = OpenStruct.new
|
126
|
+
@options.list = false
|
127
|
+
@options.show_defaults = false
|
127
128
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
129
|
+
@options.environ = nil
|
130
|
+
@options.application = nil
|
131
|
+
@options.commands = []
|
132
|
+
@options.clone_host = nil
|
132
133
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
134
|
+
@options.ami_id = nil
|
135
|
+
@options.hostname = nil
|
136
|
+
@options.zone = nil
|
137
|
+
@options.instance_type = nil
|
138
|
+
@options.volume_size = nil
|
138
139
|
|
139
|
-
|
140
|
+
@options.directory = "./"
|
140
141
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
142
|
+
if @command == "init"
|
143
|
+
unless args.length >= 1
|
144
|
+
puts "Missing location!"
|
145
|
+
puts
|
146
|
+
help
|
147
|
+
exit 1
|
148
|
+
end
|
149
|
+
@location = args[0]
|
150
|
+
else
|
151
|
+
@opts.parse!(args)
|
152
|
+
|
153
|
+
if (@options.environ.nil? || @options.application.nil?) && ! @options.list
|
154
|
+
puts "Missing a required parameter: #{@options.environ.nil? ? '--environment' : '--application'}"
|
155
|
+
puts
|
156
|
+
help
|
157
|
+
exit 1
|
158
|
+
end
|
157
159
|
end
|
158
|
-
end
|
159
160
|
|
160
|
-
|
161
|
-
|
161
|
+
@options
|
162
|
+
end
|
162
163
|
|
163
|
-
|
164
|
-
|
164
|
+
def help
|
165
|
+
puts @opts
|
166
|
+
end
|
165
167
|
end
|
166
|
-
end
|
168
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2012 Sean Laurent
|
3
|
+
#
|
4
|
+
module EC2Launcher
|
5
|
+
# Helper module for all objects that support EC2 Security Groups.
|
6
|
+
module SecurityGroupHandler
|
7
|
+
|
8
|
+
# Add or retrieve security groups. Defines the @security_groups instance variable,
|
9
|
+
# which contains a Hash of environment names to Arrays of security group names.
|
10
|
+
# May define a "default" environment.
|
11
|
+
#
|
12
|
+
# Can be defined several different ways:
|
13
|
+
# * String - Adds the named security group to the "default" environment.
|
14
|
+
# * Array - Adds the entire array of security groups to the "default" environment.
|
15
|
+
# * Hash - Keys are environment names (Strings) to security groups. Values of the
|
16
|
+
# hash can be either a String or an Array. Both are appended to any
|
17
|
+
# security groups already defined for the environment.
|
18
|
+
#
|
19
|
+
# @param [Array, nil] groups Array of security_group definitions. See above. Returns
|
20
|
+
# the entire Hash of security groups if empty.
|
21
|
+
#
|
22
|
+
# @return [Hash, self] Either returns the Hash of security groups (if groups parameter is empty)
|
23
|
+
# or returns a reference to self.
|
24
|
+
def security_groups(*groups)
|
25
|
+
if groups.empty?
|
26
|
+
@security_groups
|
27
|
+
else
|
28
|
+
@security_groups = Hash.new if @security_groups.nil?
|
29
|
+
if groups[0].kind_of? Array
|
30
|
+
@security_groups[:default] = [] if @security_groups[:default].nil?
|
31
|
+
@security_groups[:default] += groups[0]
|
32
|
+
elsif groups[0].kind_of? Hash
|
33
|
+
groups[0].keys.each do |env_name|
|
34
|
+
@security_groups[env_name] = [] if @security_groups[env_name].nil?
|
35
|
+
if groups[0][env_name].kind_of? Array
|
36
|
+
@security_groups[env_name] += groups[0][env_name]
|
37
|
+
else
|
38
|
+
@security_groups[env_name] << groups[0][env_name]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
else
|
42
|
+
@security_groups[:default] = [] if @security_groups[:default].nil?
|
43
|
+
@security_groups[:default] << groups[0].to_s
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Retrieves the list of security groups for a given environment. Returns the
|
50
|
+
# default set of security groups if the environment isn't defined. Returns
|
51
|
+
# an empty array if the default security group is empty.
|
52
|
+
def security_groups_for_environment(environment)
|
53
|
+
if @security_groups[environment].nil?
|
54
|
+
# Environment not found - check default
|
55
|
+
return @security_groups[:default].nil? ? [] : @security_groups[:default]
|
56
|
+
end
|
57
|
+
@security_groups[environment]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/ec2launcher/version.rb
CHANGED
data/lib/ec2launcher.rb
CHANGED
@@ -9,12 +9,14 @@ require 'aws-sdk'
|
|
9
9
|
require "ec2launcher/version"
|
10
10
|
require "ec2launcher/config"
|
11
11
|
require "ec2launcher/defaults"
|
12
|
+
require "ec2launcher/backoff_runner"
|
12
13
|
|
13
14
|
require 'ec2launcher/application'
|
14
15
|
require 'ec2launcher/environment'
|
15
16
|
require 'ec2launcher/block_device_builder'
|
16
17
|
|
17
18
|
module EC2Launcher
|
19
|
+
|
18
20
|
class AmiDetails
|
19
21
|
attr_reader :ami_name, :ami_id
|
20
22
|
|
@@ -25,28 +27,7 @@ module EC2Launcher
|
|
25
27
|
end
|
26
28
|
|
27
29
|
class Launcher
|
28
|
-
|
29
|
-
# we exceed the allowed AWS RequestLimit.
|
30
|
-
#
|
31
|
-
# @param [Integer] max_time maximum amount of time to sleep before giving up.
|
32
|
-
# @param [Integer] sleep_time the initial amount of time to sleep before retrying.
|
33
|
-
# @param [message] message message to display if we get an exception.
|
34
|
-
# @param [Block] block Ruby code block to execute.
|
35
|
-
def run_with_backoff(max_time, sleep_time, message, &block)
|
36
|
-
if sleep_time > max_time
|
37
|
-
puts "AWS::EC2::Errors::RequestLimitExceeded ... failed #{message}"
|
38
|
-
return false
|
39
|
-
end
|
40
|
-
|
41
|
-
begin
|
42
|
-
yield
|
43
|
-
rescue AWS::EC2::Errors::RequestLimitExceeded
|
44
|
-
puts "AWS::EC2::Errors::RequestLimitExceeded ... retrying #{message} in #{sleep_time} seconds"
|
45
|
-
sleep sleep_time
|
46
|
-
run_with_backoff(max_time, sleep_time * 2, message, &block)
|
47
|
-
end
|
48
|
-
true
|
49
|
-
end
|
30
|
+
include BackoffRunner
|
50
31
|
|
51
32
|
def launch(options)
|
52
33
|
@options = options
|
@@ -79,7 +60,7 @@ module EC2Launcher
|
|
79
60
|
filename = File.join(app_dir, application_name)
|
80
61
|
next if File.directory?(filename)
|
81
62
|
|
82
|
-
apps = ApplicationDSL.execute(File.read(filename)).applications
|
63
|
+
apps = EC2Launcher::ApplicationDSL.execute(File.read(filename)).applications
|
83
64
|
apps.each do |new_application|
|
84
65
|
@applications[new_application.name] = new_application
|
85
66
|
validate_application(filename, new_application)
|
@@ -169,7 +150,7 @@ module EC2Launcher
|
|
169
150
|
# SECURITY GROUPS
|
170
151
|
##############################
|
171
152
|
security_groups = []
|
172
|
-
security_groups += @environment.
|
153
|
+
security_groups += @environment.security_groups_for_environment(@environment.name) unless @environment.security_groups_for_environment(@environment.name).nil?
|
173
154
|
security_groups += @application.security_groups_for_environment(@environment.name)
|
174
155
|
|
175
156
|
# Build mapping of existing security group names to security group objects
|
@@ -673,7 +654,7 @@ rm -f /tmp/runurl"
|
|
673
654
|
# Load configuration file
|
674
655
|
config_filename = File.join(@options.directory, "config.rb")
|
675
656
|
abort("Unable to find 'config.rb' in '#{@options.directory}'") unless File.exists?(config_filename)
|
676
|
-
ConfigDSL.execute(File.read(config_filename)).config
|
657
|
+
EC2Launcher::ConfigDSL.execute(File.read(config_filename)).config
|
677
658
|
end
|
678
659
|
|
679
660
|
# Load and parse an environment file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ec2launcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.10
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-07-
|
12
|
+
date: 2012-07-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: aws-sdk
|
@@ -45,6 +45,7 @@ files:
|
|
45
45
|
- ec2launcher.gemspec
|
46
46
|
- lib/ec2launcher.rb
|
47
47
|
- lib/ec2launcher/application.rb
|
48
|
+
- lib/ec2launcher/backoff_runner.rb
|
48
49
|
- lib/ec2launcher/block_device.rb
|
49
50
|
- lib/ec2launcher/block_device_builder.rb
|
50
51
|
- lib/ec2launcher/config.rb
|
@@ -52,6 +53,7 @@ files:
|
|
52
53
|
- lib/ec2launcher/email_notification.rb
|
53
54
|
- lib/ec2launcher/environment.rb
|
54
55
|
- lib/ec2launcher/init_options.rb
|
56
|
+
- lib/ec2launcher/security_group_handler.rb
|
55
57
|
- lib/ec2launcher/version.rb
|
56
58
|
- startup-scripts/setup.rb
|
57
59
|
- startup-scripts/setup_instance.rb
|