ec2launcher 1.0.10 → 1.0.11

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 1.0.11
2
+
3
+ * Support launching multiple instances with one command.
4
+
1
5
  ## 1.0.10
2
6
 
3
7
  * Support multiple "gem" definitions in applications.
@@ -7,7 +7,6 @@ module EC2Launcher
7
7
  # Helper class to build EC2 block device definitions.
8
8
  #
9
9
  class BlockDeviceBuilder
10
- attr_reader :block_device_setup
11
10
  attr_reader :block_device_mappings
12
11
  attr_reader :block_device_tags
13
12
 
@@ -18,11 +17,63 @@ module EC2Launcher
18
17
  @ec2 = ec2
19
18
  @block_size = volume_size
20
19
  @volume_size ||= EC2Launcher::DEFAULT_VOLUME_SIZE
21
-
22
- @block_device_setup = {}
20
+
23
21
  @block_device_mappings = {}
24
22
  @block_device_tags = {}
25
23
  end
24
+
25
+ # Generates the mappings for ephemeral and ebs volumes.
26
+ #
27
+ # @param [String] instance_type type of instance. See EC2Launcher::Defaults::INSTANCE_TYPES.
28
+ # @param [EC2Launcher::Environment] environment current environment
29
+ # @param [EC2Launcher::Application] application current application
30
+ # @param [String, nil] clone_host FQDN of host to clone or nil to skip cloning.
31
+ #
32
+ # @return [Hash<String, Hash] returns a mapping of device names to device details
33
+ #
34
+ def generate_block_devices(instance_type, environment, application, clone_host = nil)
35
+ block_device_mappings = {}
36
+
37
+ build_ephemeral_drives(block_device_mappings, instance_type)
38
+ build_ebs_volumes(block_device_mappings, application.block_devices)
39
+ clone_volumes(block_device_mappings, application, clone_host)
40
+
41
+ block_device_mappings
42
+ end
43
+
44
+ # Generates a mapping of block device names to tags.
45
+ #
46
+ # @param [String] hostname FQDN for new host
47
+ # @param [String] short_hostname short name for host host, without domain name.
48
+ # @param [String] environment_name Name of current environment
49
+ # @param [EC2Launcher::DSL::BlockDevice] block devices for this application
50
+ #
51
+ # @return [Hash<String, Hash<String, String>] returns a mapping of device names to maps tag names and values.
52
+ def generate_device_tags(hostname, short_hostname, environment_name, block_devices)
53
+ block_device_tags = {}
54
+ unless block_devices.nil?
55
+ base_device_name = "sdf"
56
+ block_devices.each do |block_device|
57
+ build_block_devices(block_device.count, base_device_name) do |device_name, index|
58
+ block_device_tags["/dev/#{device_name}"] = {
59
+ "purpose" => block_device.name,
60
+ "host" => hostname,
61
+ "environment" => environment_name
62
+ }
63
+
64
+ if block_device.raid_level.nil?
65
+ block_device_tags["/dev/#{device_name}"]["Name"] = "#{short_hostname} #{block_device.name}"
66
+ else
67
+ block_device_tags["/dev/#{device_name}"]["Name"] = "#{short_hostname} #{block_device.name} raid (#{(index + 1).to_s})"
68
+ block_device_tags["/dev/#{device_name}"]["raid_number"] = (index + 1).to_s
69
+ end
70
+ end
71
+ end
72
+ end
73
+ block_device_tags
74
+ end
75
+
76
+ private
26
77
 
27
78
  # Iterates over a number of block_devices, executing the specified Ruby block.
28
79
  #
@@ -41,12 +92,10 @@ module EC2Launcher
41
92
 
42
93
  # Creates the mappings for the appropriate EBS volumes.
43
94
  #
44
- # @param [String] hostname FQDN for new host
45
- # @param [String] short_hostname short name for host host, without domain name.
46
- # @param [String] environment_name name of the environment
95
+ # @param [Hash<String, Hash>] block_device_mappings Mapping of device names to EBS block device details.
47
96
  # @param [Array<EC2Launcher::BlockDevice>] block_devices list of block devices to create.
48
97
  #
49
- def build_ebs_volumes(hostname, short_hostname, environment_name, block_devices)
98
+ def build_ebs_volumes(block_device_mappings, block_devices)
50
99
  return if block_devices.nil?
51
100
  base_device_name = "sdf"
52
101
  block_devices.each do |block_device|
@@ -58,28 +107,16 @@ module EC2Launcher
58
107
  :volume_size => volume_size,
59
108
  :delete_on_termination => true
60
109
  }
61
-
62
- block_device_tags["/dev/#{device_name}"] = {
63
- "purpose" => block_device.name,
64
- "host" => hostname,
65
- "environment" => environment_name
66
- }
67
-
68
- if block_device.raid_level.nil?
69
- block_device_tags["/dev/#{device_name}"]["Name"] = "#{short_hostname} #{block_device.name}"
70
- else
71
- block_device_tags["/dev/#{device_name}"]["Name"] = "#{short_hostname} #{block_device.name} raid (#{(index + 1).to_s})"
72
- block_device_tags["/dev/#{device_name}"]["raid_number"] = (index + 1).to_s
73
- end
74
110
  end
75
111
  end
76
112
  end
77
113
 
78
114
  # Creates the mappings for the appropriate ephemeral drives.
79
115
  #
116
+ # @param [Hash<String, Hash>] block_devices Map of device names to EC2 block device details.
80
117
  # @param [String] instance_type type of instance. See EC2Launcher::Defaults::INSTANCE_TYPES.
81
118
  #
82
- def build_ephemeral_drives(instance_type)
119
+ def build_ephemeral_drives(block_devices, instance_type)
83
120
  ephemeral_drive_count = case instance_type
84
121
  when "m1.small" then 1
85
122
  when "m1.medium" then 1
@@ -96,18 +133,18 @@ module EC2Launcher
96
133
  else 0
97
134
  end
98
135
  build_block_devices(ephemeral_drive_count, "sdb") do |device_name, index|
99
- @block_device_mappings["/dev/#{device_name}"] = "ephemeral#{index}"
136
+ block_device_mappings["/dev/#{device_name}"] = "ephemeral#{index}"
100
137
  end
101
138
  end
102
139
 
103
140
  # Finds the EBS snapshots to clone for all appropriate block devices and
104
141
  # updates the block device mapping hash.
105
142
  #
106
- # @param [EC2Launcher::Environment] environment current environment
143
+ # @param [Hash<String, Hash>] block_devices Map of device names to EC2 block device details.
107
144
  # @param [EC2Launcher::Application] application current application
108
145
  # @param [String] clone_host name of host to clone
109
146
  #
110
- def clone_volumes(environment, application, clone_host)
147
+ def clone_volumes(block_device_mappings, application, clone_host = nil)
111
148
  return if clone_host.nil?
112
149
 
113
150
  puts "Retrieving snapshots..."
@@ -117,35 +154,20 @@ module EC2Launcher
117
154
  if block_device.raid_level.nil?
118
155
  latest_snapshot = get_latest_snapshot_by_purpose(clone_host, block_device.name)
119
156
  abort("Unable to find snapshot for #{clone_host} [#{block_device.name}]") if latest_snapshot.nil?
120
- @block_device_mappings["/dev/#{base_device_name}"][:snapshot_id] = latest_snapshot.id
157
+ block_device_mappings["/dev/#{base_device_name}"][:snapshot_id] = latest_snapshot.id
121
158
  base_device_name.next!
122
159
  else
123
160
  snapshots = get_latest_raid_snapshot_mapping(clone_host, block_device.name, block_device.count)
124
161
  abort("Unable to find snapshot for #{clone_host} [#{block_device.name}]") if snapshots.nil?
125
162
  abort("Incorrect snapshot count for #{clone_host} [#{block_device.name}]. Expected: #{block_device.count}, Found: #{snapshots.length}") if snapshots.length != block_device.count
126
163
  build_block_devices(block_device.count, base_device_name) do |device_name, index|
127
- @block_device_mappings["/dev/#{device_name}"][:snapshot_id] = snapshots[(index + 1).to_s].id
164
+ block_device_mappings["/dev/#{device_name}"][:snapshot_id] = snapshots[(index + 1).to_s].id
128
165
  end
129
166
  end
130
167
  end
131
168
  AWS.stop_memoizing
132
169
  end
133
170
 
134
- # Generates the mappings for ephemeral and ebs volumes.
135
- #
136
- # @param [String] hostname FQDN for new host
137
- # @param [String] short_hostname short name for host host, without domain name.
138
- # @param [String] instance_type type of instance. See EC2Launcher::Defaults::INSTANCE_TYPES.
139
- # @param [EC2Launcher::Environment] environment current environment
140
- # @param [EC2Launcher::Application] application current application
141
- # @param [String, nil] clone_host FQDN of host to clone or nil to skip cloning.
142
- #
143
- def generate_block_devices(hostname, short_hostname, instance_type, environment, application, clone_host = nil)
144
- build_ephemeral_drives(instance_type)
145
- build_ebs_volumes(hostname, short_hostname, environment.name, application.block_devices)
146
- clone_volumes(environment, application, clone_host)
147
- end
148
-
149
171
  # Retrieves the latest set of completed snapshots for a RAID array of EBS volumes.
150
172
  #
151
173
  # Volumes must have the following tags:
@@ -0,0 +1,297 @@
1
+ #
2
+ # Copyright (c) 2012 Sean Laurent
3
+ #
4
+ require 'ec2launcher/dsl/block_device'
5
+ require 'ec2launcher/dsl/email_notification'
6
+ require 'ec2launcher/security_group_handler'
7
+
8
+ module EC2Launcher
9
+ module DSL
10
+ # Wrapper class to handle loading Application blocks.
11
+ class ApplicationDSL
12
+ attr_accessor :applications
13
+
14
+ def initialize
15
+ self.applications = []
16
+ end
17
+
18
+ def application(name, &block)
19
+ application = EC2Launcher::DSL::Application.new(name)
20
+ applications << application
21
+ application.instance_eval &block
22
+ application
23
+ end
24
+
25
+ def self.execute(dsl)
26
+ new.tap do |context|
27
+ context.instance_eval(dsl)
28
+ end
29
+ end
30
+ end
31
+
32
+ # Represents a single application stack.
33
+ class Application
34
+ include EC2Launcher::DSL::EmailNotifications
35
+ include EC2Launcher::SecurityGroupHandler
36
+
37
+ attr_reader :name
38
+
39
+ def initialize(name)
40
+ @name = name
41
+ @email_notifications = nil
42
+ end
43
+
44
+ def application(name)
45
+ @name = name
46
+ yield self
47
+ self
48
+ end
49
+
50
+ # Name of the AMI to use for new instances. Optional.
51
+ # Can be either a string or a regular expression.
52
+ #
53
+ # @param [Array, nil] Either an array of parameters or nil to return the AMI name.
54
+ def ami_name(*ami_name)
55
+ if ami_name.empty?
56
+ @ami_name
57
+ else
58
+ if ami_name[0].kind_of? String
59
+ @ami_name = /#{ami_name[0]}/
60
+ else
61
+ @ami_name = ami_name[0]
62
+ end
63
+ self
64
+ end
65
+ end
66
+
67
+ # Name of the availability zone to use for new instances. Optional.
68
+ # Must be one of EC2Launcher::AVAILABILITY_ZONES.
69
+ def availability_zone(*zone)
70
+ if zone.empty?
71
+ @availability_zone
72
+ else
73
+ @availability_zone = zone[0].to_s
74
+ self
75
+ end
76
+ end
77
+
78
+ # Defines a shorter name when building a host name for new instances. Optional.
79
+ # By default, new instances are named using the full application name. If you
80
+ # specify basename, it will be used instead. Typically, this is used if you
81
+ # want a shorter version of the full application name.
82
+ def basename(*name)
83
+ if name.empty?
84
+ @basename
85
+ else
86
+ @basename = name[0]
87
+ self
88
+ end
89
+ end
90
+
91
+ def block_devices(*block_device_data)
92
+ if block_device_data.empty?
93
+ @block_devices
94
+ else
95
+ self
96
+ end
97
+ end
98
+
99
+ def block_device(&block)
100
+ @block_devices = [] if @block_devices.nil?
101
+ device = EC2Launcher::DSL::BlockDevice.new
102
+ device.instance_exec(&block)
103
+ @block_devices << device
104
+ end
105
+
106
+ # Indicates the Amazon Elastic Load Balancer to which new instances should be
107
+ # attached after launch. Optional.
108
+ #
109
+ # The value can be either a String, indicating the name of the ELB, or a Hash
110
+ # that maps environment names to ELB names.
111
+ def elb(*elb)
112
+ if elb.empty?
113
+ @elb
114
+ else
115
+ @elb = Hash.new if @elb.nil?
116
+ if elb[0].kind_of? Hash
117
+ elb[0].keys.each {|key| @elb[key] = elb[0][key]}
118
+ else
119
+ @elb["default"] = elb[0].to_s
120
+ end
121
+ self
122
+ end
123
+ end
124
+
125
+ # Retrieves the ELB name for a given environment.
126
+ def elb_for_environment(environment)
127
+ elb_name = @elb[environment]
128
+ elb_name ||= @elb["default"]
129
+ elb_name
130
+ end
131
+
132
+ # Defines an Array of Chef roles that should be applied to new
133
+ # instances for a specific environment. Can be specified multiple times.
134
+ #
135
+ # Expects two parameters:
136
+ # * Name of an environment
137
+ # * Either the name of a single Chef role or an Array of Chef roles
138
+ def environment_roles(*data)
139
+ if data.empty?
140
+ @environment_roles
141
+ else
142
+ @environment_roles = Hash.new if @environment_roles.nil?
143
+ env_name = data[0]
144
+ env_roles = data[1]
145
+
146
+ environment_data = @environment_roles[env_name]
147
+ environment_data ||= []
148
+
149
+ if env_roles.kind_of? Array
150
+ environment_data += env_roles
151
+ else
152
+ environment_data << env_roles
153
+ end
154
+ @environment_roles[env_name] = environment_data
155
+
156
+ self
157
+ end
158
+ end
159
+
160
+ # Gems to install. Optional.
161
+ #
162
+ # Expects either a single String naming a gem to install or
163
+ # an Array of gems.
164
+ #
165
+ # Can be specified multiple times.
166
+ def gems(*gems)
167
+ if gems.empty?
168
+ @gems
169
+ else
170
+ @gems = [] if @gems.nil?
171
+ if gems[0].kind_of? Array
172
+ @gems += gems[0]
173
+ else
174
+ @gems << gems[0]
175
+ end
176
+ self
177
+ end
178
+ end
179
+
180
+ # Indicates that this application should inherit all of its settings from another named application.
181
+ # Optional.
182
+ def inherit(*inherit_type)
183
+ if inherit_type.empty?
184
+ @inherit_type
185
+ else
186
+ @inherit_type = inherit_type[0]
187
+ end
188
+ end
189
+
190
+ # The Amazon EC2 instance type that should be used for new instances.
191
+ # Must be one of EC2Launcher::INSTANCE_TYPES.
192
+ def instance_type(*type_name)
193
+ if type_name.empty?
194
+ @instance_type
195
+ else
196
+ @instance_type = type_name[0]
197
+ self
198
+ end
199
+ end
200
+
201
+ # Takes values from the other server type and merges them into this one
202
+ def merge(other_server)
203
+ @name = other_server.name
204
+ @ami_name = other_server.ami_name unless other_server.ami_name.nil?
205
+ @availability_zone = other_server.availability_zone unless other_server.availability_zone.nil?
206
+ @basename = other_server.basename unless other_server.basename.nil?
207
+ other_server.block_devices.each {|bd| @block_devices << bd } unless other_server.block_devices.nil?
208
+ other_server.elb.keys.each {|env_name| @elb[env_name] = other_server.elb[env_name] } unless other_server.elb.nil?
209
+ @instance_type = other_server.instance_type unless other_server.instance_type.nil?
210
+ @name_suffix = other_server.name_suffix unless other_server.name_suffix.nil?
211
+ other_server.roles.each {|role| @roles << role } unless other_server.roles.nil?
212
+ unless other_server.security_groups.nil?
213
+ other_server.security_groups.keys.each do |env_name|
214
+ unless @security_groups.has_key? env_name
215
+ @security_groups[env_name] = []
216
+ end
217
+ other_server.security_groups[env_name].each {|sg| @security_groups[env_name] << sg }
218
+ end
219
+ end
220
+ end
221
+
222
+ def name_suffix(*suffix)
223
+ if suffix.empty?
224
+ @name_suffix
225
+ else
226
+ @name_suffix = suffix[0]
227
+ end
228
+ end
229
+
230
+ def packages(*packages)
231
+ if packages.empty?
232
+ @packages
233
+ else
234
+ @packages = packages[0]
235
+ self
236
+ end
237
+ end
238
+
239
+ def roles(*roles)
240
+ if roles.empty?
241
+ @roles
242
+ else
243
+ @roles = [] if @roles.nil?
244
+ if roles[0].kind_of? Array
245
+ @roles += roles[0]
246
+ else
247
+ @roles = []
248
+ @roles << roles[0]
249
+ end
250
+ self
251
+ end
252
+ end
253
+
254
+ def roles_for_environment(environment)
255
+ roles = []
256
+ roles += @roles unless @roles.nil?
257
+
258
+ unless @environment_roles.nil? || @environment_roles[environment].nil?
259
+ roles += @environment_roles[environment]
260
+ end
261
+ roles
262
+ end
263
+
264
+ # Retrieves the list of Security Group names for the specified environment.
265
+ #
266
+ # @return [Array] Returns the list of security groups for the environment. Returns
267
+ # the security groups for the "defaukt" environment if the requested
268
+ # environment is undefined. Returns an empty Array if both the
269
+ # requested environment and "default" environment are undefined.
270
+ def security_groups_for_environment(environment)
271
+ groups = @security_groups[environment]
272
+ groups ||= @security_groups["default"]
273
+ groups ||= []
274
+ groups
275
+ end
276
+
277
+ def subnet(*subnet)
278
+ if subnet.empty?
279
+ @subnet
280
+ else
281
+ @subnet = subnet[0]
282
+ self
283
+ end
284
+ end
285
+
286
+ def load(dsl)
287
+ self.instance_eval(dsl)
288
+ self
289
+ end
290
+
291
+ def self.load(dsl)
292
+ env = Application.new.instance_eval(dsl)
293
+ env
294
+ end
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,96 @@
1
+ #
2
+ # Copyright (c) 2012 Sean Laurent
3
+ #
4
+
5
+ module EC2Launcher
6
+ module DSL
7
+ class BlockDevice
8
+ attr_reader :mount_point
9
+ attr_reader :name
10
+
11
+ def initialize()
12
+ @count = 1
13
+ @group = "root"
14
+ @user = "root"
15
+ end
16
+
17
+ def is_raid?()
18
+ @raid_level.nil?
19
+ end
20
+
21
+ def count(*block_count)
22
+ if block_count.empty?
23
+ @count
24
+ else
25
+ @count = block_count[0]
26
+ self
27
+ end
28
+ end
29
+
30
+ def group(*group)
31
+ if group.empty?
32
+ @group
33
+ else
34
+ @group = group[0]
35
+ self
36
+ end
37
+ end
38
+
39
+ def mount(*mount)
40
+ if mount.empty?
41
+ @mount
42
+ else
43
+ @mount_point = mount[0]
44
+ self
45
+ end
46
+ end
47
+
48
+ def name(*name)
49
+ if name.empty?
50
+ @name
51
+ else
52
+ @name = name[0]
53
+ self
54
+ end
55
+ end
56
+
57
+ def owner(*owner)
58
+ if owner.empty?
59
+ @owner
60
+ else
61
+ @owner = owner[0]
62
+ self
63
+ end
64
+ end
65
+
66
+ def raid_level(*raid_level)
67
+ if raid_level.empty?
68
+ @raid_level
69
+ else
70
+ @raid_level = raid_level[0]
71
+ self
72
+ end
73
+ end
74
+
75
+ def size(*volume_size)
76
+ if volume_size.empty?
77
+ @size
78
+ else
79
+ @size = volume_size[0].to_i
80
+ self
81
+ end
82
+ end
83
+
84
+ def to_json(*a)
85
+ {
86
+ "name" => @name,
87
+ "count" => @count,
88
+ "raid_level" => @raid_level,
89
+ "mount_point" => @mount_point,
90
+ "owner" => @owner,
91
+ "group" => @group
92
+ }.to_json(*a)
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,78 @@
1
+ #
2
+ # Copyright (c) 2012 Sean Laurent
3
+ #
4
+ module EC2Launcher
5
+ module DSL
6
+ class ConfigDSL
7
+ attr_reader :config
8
+
9
+ def config(&block)
10
+ return @config if block.nil?
11
+
12
+ @config = Config.new
13
+ @config.instance_eval &block
14
+ @config
15
+ end
16
+
17
+ def self.execute(dsl)
18
+ new.tap do |context|
19
+ context.instance_eval(dsl)
20
+ end
21
+ end
22
+ end
23
+
24
+ class Config
25
+ DEFAULT_CONFIG_ERB = %q{
26
+ config do
27
+ environments "environments"
28
+ applications "applications"
29
+
30
+ package_manager "apt"
31
+ config_manager "chef"
32
+ end
33
+ }.gsub(/^ /, '')
34
+
35
+ def environments(*environments)
36
+ if environments.empty?
37
+ @environments
38
+ else
39
+ if environments[0].kind_of? Array
40
+ @environments = @environments[0]
41
+ else
42
+ @environments = [ environments[0] ]
43
+ end
44
+ self
45
+ end
46
+ end
47
+
48
+ def applications(*applications)
49
+ if applications.empty?
50
+ @applications
51
+ else
52
+ if applications[0].kind_of? Array
53
+ @applications = @applications[0]
54
+ else
55
+ @applications = [ applications[0] ]
56
+ end
57
+ self
58
+ end
59
+ end
60
+
61
+ def package_manager(*package_manager)
62
+ if package_manager.empty?
63
+ @package_manager
64
+ else
65
+ @package_manager = package_manager[0]
66
+ end
67
+ end
68
+
69
+ def config_manager(*config_manager)
70
+ if config_manager.empty?
71
+ @config_manager
72
+ else
73
+ @config_manager = config_manager[0]
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end