ec2launcher 1.0.10 → 1.0.11

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/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