ec2launcher 1.0.0

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.
@@ -0,0 +1,734 @@
1
+ #
2
+ # Copyright (c) 2012 Sean Laurent
3
+ #
4
+ require 'rubygems'
5
+ require 'optparse'
6
+ require 'ostruct'
7
+ require 'aws-sdk'
8
+
9
+ require "ec2launcher/version"
10
+ require "ec2launcher/config"
11
+ require "ec2launcher/defaults"
12
+
13
+ require 'ec2launcher/application'
14
+ require 'ec2launcher/environment'
15
+ require 'ec2launcher/block_device_builder'
16
+
17
+ module EC2Launcher
18
+ class AmiDetails
19
+ attr_reader :ami_name, :ami_id
20
+
21
+ def initialize(name, id)
22
+ @ami_name = name
23
+ @ami_id = id
24
+ end
25
+ end
26
+
27
+ class Launcher
28
+ # Runs an AWS request inside a Ruby block with an exponential backoff in case
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
50
+
51
+ def launch(options)
52
+ @options = options
53
+
54
+ # Load configuration data
55
+ @config = load_config_file
56
+
57
+ environments_directories = process_directory_list(@config.environments, "environments", "Environments", false)
58
+ applications_directories = process_directory_list(@config.applications, "applications", "Applications", true)
59
+
60
+ # Attempt to load default environment data
61
+ @default_environment = nil
62
+ environments_directories.each do |env_dir|
63
+ filename = File.join(env_dir, "default.rb")
64
+ @default_environment = load_environment_file(filename)
65
+ unless @default_environment.nil?
66
+ validate_environment(filename, @default_environment)
67
+ break
68
+ end
69
+ end
70
+ @default_environment ||= EC2Launcher::Environment.new
71
+
72
+ # Load other environments
73
+ @environments = { }
74
+ environments_directories.each do |env_dir|
75
+ Dir.entries(env_dir).each do |env_name|
76
+ filename = File.join(env_dir, env_name)
77
+ next if File.directory?(filename)
78
+ next if filename == "default.rb"
79
+
80
+ new_env = load_environment_file(filename, @default_environment)
81
+ validate_environment(filename, new_env)
82
+
83
+ @environments[new_env.name] = new_env
84
+ new_env.aliases.each {|env_alias| @environments[env_alias] = new_env }
85
+ end
86
+ end
87
+
88
+ # Load applications
89
+ @applications = {}
90
+ applications_directories.each do |app_dir|
91
+ Dir.entries(app_dir).each do |application_name|
92
+ filename = File.join(app_dir, application_name)
93
+ next if File.directory?(filename)
94
+
95
+ apps = ApplicationDSL.execute(File.read(filename)).applications
96
+ apps.each do |new_application|
97
+ @applications[new_application.name] = new_application
98
+ validate_application(filename, new_application)
99
+ end
100
+ end
101
+ end
102
+
103
+ # Process inheritance rules for applications
104
+ @applications.values.each do |app|
105
+ next if app.inherit.nil?
106
+
107
+ # Find base application
108
+ base_app = @applications[app.inherit]
109
+ abort("Invalid inheritance '#{app.inherit}' in #{app.name}") if base_app.nil?
110
+
111
+ # Clone base application
112
+ new_app = Marshal::load(Marshal.dump(base_app))
113
+ new_app.merge(app)
114
+ @applications[new_app.name] = new_app
115
+ end
116
+
117
+ if @options.list
118
+ puts ""
119
+ env_names = @environments.keys.sort.join(", ")
120
+ puts "Environments: #{env_names}"
121
+
122
+ app_names = @applications.keys.sort.join(", ")
123
+ puts "Applications: #{app_names}"
124
+ exit 0
125
+ end
126
+
127
+ ##############################
128
+ # ENVIRONMENT
129
+ ##############################
130
+ unless @environments.has_key? options.environ
131
+ puts "Environment not found: #{options.environ}"
132
+ exit 2
133
+ end
134
+ @environment = @environments[options.environ]
135
+
136
+ ##############################
137
+ # APPLICATION
138
+ ##############################
139
+ unless @applications.has_key? options.application
140
+ puts "Application not found: #{options.application}"
141
+ exit 3
142
+ end
143
+ @application = @applications[options.application]
144
+
145
+ # Initialize AWS and create EC2 connection
146
+ initialize_aws()
147
+ @ec2 = AWS::EC2.new
148
+
149
+ ##############################
150
+ # AVAILABILITY ZONES
151
+ ##############################
152
+ availability_zone = options.zone
153
+ if availability_zone.nil?
154
+ availability_zone = @application.availability_zone
155
+ availability_zone ||= @environment.availability_zone
156
+ availability_zone ||= @default_environment.availability_zone
157
+ availability_zone ||= "us-east-1a"
158
+ end
159
+
160
+ ##############################
161
+ # SSH KEY
162
+ ##############################
163
+ key_name = @environment.key_name
164
+ key_name ||= @default_environment.key_name
165
+ if key_name.nil?
166
+ puts "Unable to determine SSH key name."
167
+ exit 4
168
+ end
169
+
170
+ ##############################
171
+ # SECURITY GROUPS
172
+ ##############################
173
+ security_groups = []
174
+ security_groups += @environment.security_groups unless @environment.security_groups.nil?
175
+ security_groups += @application.security_groups_for_environment(options.environ)
176
+
177
+ ##############################
178
+ # INSTANCE TYPE
179
+ ##############################
180
+ instance_type = options.instance_type
181
+ instance_type ||= @application.instance_type
182
+ instance_type ||= "m1.small"
183
+
184
+ ##############################
185
+ # ARCHITECTURE
186
+ ##############################
187
+ instance_architecture = "x86_64"
188
+
189
+ instance_virtualization = case instance_type
190
+ when "cc1.4xlarge" then "hvm"
191
+ when "cc2.8xlarge" then "hvm"
192
+ when "cg1.4xlarge" then "hvm"
193
+ else "paravirtual"
194
+ end
195
+
196
+ ##############################
197
+ # AMI
198
+ ##############################
199
+ ami_name_match = @application.ami_name
200
+ ami_name_match ||= @environment.ami_name
201
+ ami = find_ami(instance_architecture, instance_virtualization, ami_name_match, @options.ami_id)
202
+
203
+ ##############################
204
+ # HOSTNAME
205
+ ##############################
206
+ hostname = @options.hostname
207
+ if hostname.nil?
208
+ short_hostname = generate_hostname()
209
+ hostname = short_hostname
210
+
211
+ unless @environment.domain_name.nil?
212
+ hostname += ".#{@environment.domain_name}"
213
+ end
214
+ else
215
+ short_hostname = hostname
216
+ unless @environment.domain_name.nil?
217
+ short_hostname = hostname.gsub(/.#{@environment.domain_name}/, '')
218
+ end
219
+ end
220
+
221
+ ##############################
222
+ # Block devices
223
+ ##############################
224
+ builder = EC2Launcher::BlockDeviceBuilder.new(@ec2, @options.volume_size)
225
+ builder.generate_block_devices(hostname, short_hostname, instance_type, @environment, @application, @options.clone_host)
226
+ block_device_mappings = builder.block_device_mappings
227
+ block_device_tags = builder.block_device_tags
228
+
229
+ ##############################
230
+ # ELB
231
+ ##############################
232
+ elb_name = nil
233
+ elb_name = @application.elb_for_environment(@environment.name) unless @application.elb.nil?
234
+
235
+ ##############################
236
+ # Roles
237
+ ##############################
238
+ roles = []
239
+ roles += @environment.roles unless @environment.roles.nil?
240
+ roles += @application.roles_for_environment(@environment.name)
241
+
242
+ ##############################
243
+ # Packages - preinstall
244
+ ##############################
245
+ subnet = nil
246
+ subnet = @application.subnet unless @application.subnet.nil?
247
+ subnet ||= @environment.subnet unless @environment.subnet.nil?
248
+
249
+ ##############################
250
+ # Gems - preinstall
251
+ ##############################
252
+ gems = []
253
+ gems += @environment.gems unless @environment.gems.nil?
254
+ gems += @application.gems unless @application.gems.nil?
255
+
256
+ ##############################
257
+ # Packages - preinstall
258
+ ##############################
259
+ packages = []
260
+ packages += @environment.packages unless @environment.packages.nil?
261
+ packages += @application.packages unless @application.packages.nil?
262
+
263
+ ##############################
264
+ # Email Notification
265
+ ##############################
266
+ email_notifications = nil
267
+ email_notifications = @application.email_notifications
268
+ email_notifications ||= @environment.email_notifications
269
+
270
+ ##############################
271
+ # Chef Validation PEM
272
+ ##############################
273
+ chef_validation_pem_url = nil
274
+ chef_validation_pem_url = @options.chef_validation_url
275
+ chef_validation_pem_url ||= @environment.chef_validation_pem_url
276
+
277
+ ##############################
278
+ # File on new instance containing AWS keys
279
+ ##############################
280
+ aws_keyfile = @environment.aws_keyfile
281
+
282
+ ##############################
283
+ # Build JSON for setup scripts
284
+ ##############################
285
+ setup_json = {
286
+ 'hostname' => hostname,
287
+ 'short_hostname' => short_hostname,
288
+ 'roles' => roles,
289
+ 'chef_server_url' => @environment.chef_server_url,
290
+ 'chef_validation_pem_url' => chef_validation_pem_url,
291
+ 'aws_keyfile' => aws_keyfile,
292
+ 'gems' => gems,
293
+ 'packages' => packages
294
+ }
295
+ unless @application.block_devices.empty?
296
+ setup_json['block_devices'] = @application.block_devices
297
+ end
298
+ unless email_notifications.nil?
299
+ setup_json['email_notifications'] = email_notifications
300
+ end
301
+
302
+ ##############################
303
+ # Build launch command
304
+ user_data = "#!/bin/sh
305
+ export HOME=/root
306
+ echo '#{setup_json.to_json}' > /tmp/setup.json
307
+ curl http://bazaar.launchpad.net/~alestic/runurl/trunk/download/head:/runurl-20090817053347-o2e56z7xwq8m9tt6-1/runurl -o /tmp/runurl
308
+ chmod +x /tmp/runurl
309
+ /tmp/runurl https://s3.amazonaws.com/startup-scripts/setup.rb -e #{options.environ} -a #{options.application} -h #{hostname} /tmp/setup.json > /var/log/cloud-startup.log
310
+ rm -f /tmp/runurl"
311
+ user_data += " -c #{options.clone_host}" unless options.clone_host.nil?
312
+
313
+ # Add extra requested commands to the launch sequence
314
+ options.commands.each {|extra_cmd| user_data += "\n#{extra_cmd}" }
315
+
316
+ ##############################
317
+ puts
318
+ puts "Availability zone: #{availability_zone}"
319
+ puts "Key name : #{key_name}"
320
+ puts "Security groups : #{security_groups.join(", ")}"
321
+ puts "Instance type : #{instance_type}"
322
+ puts "Architecture : #{instance_architecture}"
323
+ puts "AMI name : #{ami.ami_name}"
324
+ puts "AMI id : #{ami.ami_id}"
325
+ puts "Name : #{hostname}"
326
+ puts "ELB : #{elb_name}" if elb_name
327
+ puts "Chef PEM : #{chef_validation_pem_url}"
328
+ puts "AWS key file : #{aws_keyfile}"
329
+ puts "Roles : #{roles.join(', ')}"
330
+ puts "Gems : #{gems.join(', ')}"
331
+ puts "Packages : #{packages.join(', ')}"
332
+ puts "VPC Subnet : #{subnet}" if subnet
333
+
334
+ unless block_device_mappings.empty?
335
+ block_device_mappings.keys.sort.each do |key|
336
+ if block_device_mappings[key] =~ /^ephemeral/
337
+ puts " Block device : #{key}, #{block_device_mappings[key]}"
338
+ else
339
+ puts " Block device : #{key}, #{block_device_mappings[key][:volume_size]}GB, " +
340
+ "#{block_device_mappings[key][:snapshot_id]}, " +
341
+ "(#{block_device_mappings[key][:delete_on_termination] ? 'auto-delete' : 'no delete'})"
342
+ end
343
+ end
344
+ end
345
+
346
+ puts "User data:"
347
+ puts user_data
348
+ puts
349
+
350
+ if chef_validation_pem_url.nil?
351
+ puts "***ERROR*** Missing the URL For the Chef Validation PEM file."
352
+ exit 3
353
+ end
354
+
355
+ # Quit if we're only displaying the defaults
356
+ exit 0 if @options.show_defaults
357
+
358
+ ##############################
359
+ # Launch the new intance
360
+ ##############################
361
+ instance = launch_instance(hostname, ami.ami_id, availability_zone, key_name, security_groups, instance_type, user_data, block_device_mappings, block_device_tags, subnet)
362
+
363
+ ##############################
364
+ # ELB
365
+ ##############################
366
+ attach_to_elb(instance, elb_name) unless elb_name.nil?
367
+
368
+ ##############################
369
+ # COMPLETED
370
+ ##############################
371
+ puts ""
372
+ puts "Hostname : #{hostname}"
373
+ puts "Instance id: #{instance.id}"
374
+ puts "Public dns : #{instance.public_dns_name}"
375
+ puts "Private dns: #{instance.private_dns_name}"
376
+ puts "********************"
377
+ end
378
+
379
+ # Attaches an instance to the specified ELB.
380
+ #
381
+ # @param [AWS::EC2::Instance] instance newly created EC2 instance.
382
+ # @param [String] elb_name name of ELB.
383
+ #
384
+ def attach_to_elb(instance, elb_name)
385
+ begin
386
+ puts ""
387
+ puts "Adding to ELB: #{elb_name}"
388
+ elb = AWS::ELB.new
389
+ AWS.memoize do
390
+ # Build list of availability zones for any existing instances
391
+ zones = { }
392
+ zones[instance.availability_zone] = instance.availability_zone
393
+ elb.load_balancers[elb_name].instances.each do |elb_instance|
394
+ zones[elb_instance.availability_zone] = elb_instance.availability_zone
395
+ end
396
+
397
+ # Build list of existing zones
398
+ existing_zones = { }
399
+ elb.load_balancers[elb_name].availability_zones.each do |zone|
400
+ existing_zones[zone.name] = zone
401
+ end
402
+
403
+ # Enable zones
404
+ zones.keys.each do |zone_name|
405
+ elb.load_balancers[elb_name].availability_zones.enable(zones[zone_name])
406
+ end
407
+
408
+ # Disable zones
409
+ existing_zones.keys.each do |zone_name|
410
+ elb.load_balancers[elb_name].availability_zones.disable(existing_zones[zone_name]) unless zones.has_key?(zone_name)
411
+ end
412
+
413
+ elb.load_balancers[elb_name].instances.register(instance)
414
+ end
415
+ rescue StandardError => bang
416
+ puts "Error adding to load balancers: " + bang.to_s
417
+ end
418
+ end
419
+
420
+ # Given a list of possible directories, build a list of directories that actually exist.
421
+ #
422
+ # @param [Array<String>] directories list of possible directories
423
+ # @return [Array<String>] directories that exist or an empty array if none of the directories exist.
424
+ #
425
+ def build_list_of_valid_directories(directories)
426
+ dirs = []
427
+ unless directories.nil?
428
+ if directories.kind_of? Array
429
+ directories.each {|d| dirs << d if File.directory?(d) }
430
+ else
431
+ dirs << directories if File.directory?(directories)
432
+ end
433
+ end
434
+ dirs
435
+ end
436
+
437
+ # Searches for the most recent AMI matching the criteria.
438
+ #
439
+ # @param [String] arch system archicture, `i386` or `x86_64`
440
+ # @param [String] virtualization virtualization type, `paravirtual` or `hvm`
441
+ # @param [Regex] ami_name_match regular expression that describes the AMI.
442
+ # @param [String, nil] id id of an AMI. If not nil, ami_name_match is ignored.
443
+ #
444
+ # @return [AmiDetails] AMI name and id.
445
+ def find_ami(arch, virtualization, ami_name_match, id = nil)
446
+ puts "Searching for AMI..."
447
+ ami_name = ""
448
+ ami_id = ""
449
+
450
+ # Retrieve list of AMIs
451
+ my_images = @ec2.images.with_owner('self')
452
+
453
+ if id.nil?
454
+ # Search for latest AMI with the right architecture and virtualization
455
+ my_images.each do |ami|
456
+ next if arch != ami.architecture.to_s
457
+ next if virtualization != ami.virtualization_type.to_s
458
+ next unless ami.state == :available
459
+
460
+ next if ! ami.name.match(ami_name_match)
461
+
462
+ if ami.name > ami_name
463
+ ami_name = ami.name
464
+ ami_id = ami.id
465
+ end
466
+ end
467
+ else
468
+ # Look for specified AMI
469
+ ami_arch = nil
470
+ my_images.each do |ami|
471
+ next if ami.id != id
472
+ ami_id = id
473
+ ami_name = ami.name
474
+ ami_arch = ami.architecture
475
+ end
476
+
477
+ # Check that AMI exists
478
+ if ami_id.nil?
479
+ abort("AMI id not found: #{ami_id}")
480
+ end
481
+
482
+ if arch != ami_arch.to_s
483
+ abort("Invalid AMI selection. Architecture for instance type (#{instance_type} - #{instance_architecture} - #{instance_virtualization}) does not match AMI arch (#{ami_arch.to_s}).")
484
+ end
485
+ end
486
+
487
+ AmiDetails.new(ami_name, ami_id)
488
+ end
489
+
490
+ # Initializes connections to the AWS SDK
491
+ #
492
+ def initialize_aws()
493
+ aws_access_key = @options.access_key
494
+ aws_access_key ||= ENV['AWS_ACCESS_KEY']
495
+
496
+ aws_secret_access_key = @options.secret
497
+ aws_secret_access_key ||= ENV['AWS_SECRET_ACCESS_KEY']
498
+
499
+ if aws_access_key.nil? || aws_secret_access_key.nil?
500
+ abort("You MUST either set the AWS_ACCESS_KEY and AWS_SECRET_ACCESS_KEY environment variables or use the command line options.")
501
+ end
502
+
503
+ puts "Initializing AWS connection..."
504
+ AWS.config({
505
+ :access_key_id => aws_access_key,
506
+ :secret_access_key => aws_secret_access_key
507
+ })
508
+ end
509
+
510
+ # Generates a new hostname based on:
511
+ # * application base name
512
+ # * application name
513
+ # * application suffix
514
+ # * environment short name
515
+ # * environment name
516
+ #
517
+ def generate_hostname()
518
+ puts "Calculating host name..."
519
+
520
+ prefix = @application.basename
521
+ prefix ||= @application.name
522
+
523
+ env_suffix = @environment.short_name
524
+ env_suffix ||= @environment.name
525
+
526
+ suffix = env_suffix
527
+ unless @application.name_suffix.nil?
528
+ suffix = "#{@application.name_suffix}.#{env_suffix}"
529
+ end
530
+
531
+ regex = Regexp.new("#{prefix}(\\d+)[.]#{suffix.gsub(/[.]/, "[.]")}.*")
532
+
533
+ server_numbers = []
534
+
535
+ highest_server_number = 0
536
+ lowest_server_number = 32768
537
+ AWS.memoize do
538
+ server_instances = @ec2.instances.filter("tag:Name", "#{prefix}*#{suffix}*")
539
+ server_instances.each do |i|
540
+ next if i.status == :terminated
541
+ server_name = i.tags[:Name]
542
+ unless regex.match(server_name).nil?
543
+ server_num = $1.to_i
544
+ server_numbers << server_num
545
+ end
546
+ end
547
+ highest_server_number = server_numbers.max
548
+ end
549
+
550
+ # If the highest number server is less than 10, just add
551
+ # 1 to it. Otherwise, find the first available
552
+ # server number starting at 1.
553
+ host_number = 0
554
+ if highest_server_number.nil?
555
+ host_number = 1
556
+ elsif highest_server_number < 10
557
+ host_number = highest_server_number + 1
558
+ else
559
+ # Try to start over with 1 and find the
560
+ # first available host number
561
+ server_number_set = Set.new(server_numbers)
562
+ host_number = 1
563
+ while server_number_set.include?(host_number) do
564
+ host_number += 1
565
+ end
566
+ end
567
+
568
+ short_hostname = "#{prefix}#{host_number}.#{suffix}"
569
+ short_hostname
570
+ end
571
+
572
+ # Launches an EC2 instance.
573
+ #
574
+ # @param [String] FQDN for the new host.
575
+ # @param [String] ami_id id for the AMI to use.
576
+ # @param [String] availability_zone EC2 availability zone to use
577
+ # @param [String] key_name EC2 SSH key to use.
578
+ # @param [Array<String>] security_groups list of security groups.
579
+ # @param [String] instance_type EC2 instance type.
580
+ # @param [String] user_data command data to store pass to the instance in the EC2 user-data field.
581
+ # @param [Hash<String,Hash<String, String>, nil] block_device_mappings mapping of device names to block device details.
582
+ # See http://docs.amazonwebservices.com/AWSRubySDK/latest/AWS/EC2/InstanceCollection.html#create-instance_method.
583
+ # @param [Hash<String,Hash<String, String>>, nil] block_device_tags mapping of device names to hash objects with tags for the new EBS block devices.
584
+ #
585
+ # @return [AWS::EC2::Instance] newly created EC2 instance or nil if the launch failed.
586
+ def launch_instance(hostname, ami_id, availability_zone, key_name, security_groups, instance_type, user_data, block_device_mappings = nil, block_device_tags = nil, vpc_subnet = nil)
587
+ puts "Launching instance..."
588
+ new_instance = nil
589
+ run_with_backoff(30, 1, "launching instance") do
590
+ new_instance = @ec2.instances.create(
591
+ :image_id => ami_id,
592
+ :availability_zone => availability_zone,
593
+ :key_name => key_name,
594
+ :security_groups => security_groups,
595
+ :user_data => user_data,
596
+ :instance_type => instance_type,
597
+ :block_device_mappings => block_device_mappings,
598
+ :subnet => vpc_subnet
599
+ )
600
+ end
601
+ sleep 5
602
+
603
+ puts " Waiting for instance to start up..."
604
+ sleep 2
605
+ instance_ready = false
606
+ until instance_ready
607
+ sleep 1
608
+ begin
609
+ instance_ready = new_instance.status != :pending
610
+ rescue
611
+ end
612
+ end
613
+
614
+ unless new_instance.status == :running
615
+ puts "Instance launch failed. Aborting."
616
+ exit 5
617
+ end
618
+
619
+ ##############################
620
+ # Tag instance
621
+ puts "Tagging instance..."
622
+ run_with_backoff(30, 1, "tag #{new_instance.id}, tag: name, value: #{hostname}") { new_instance.add_tag("Name", :value => hostname) }
623
+ run_with_backoff(30, 1, "tag #{new_instance.id}, tag: environment, value: #{@environment.name}") { new_instance.add_tag("environment", :value => @environment.name) }
624
+ run_with_backoff(30, 1, "tag #{new_instance.id}, tag: application, value: #{@application.name}") { new_instance.add_tag("application", :value => @application.name) }
625
+
626
+ ##############################
627
+ # Tag volumes
628
+ unless block_device_tags.empty?
629
+ puts "Tagging volumes..."
630
+ AWS.start_memoizing
631
+ block_device_tags.keys.each do |device|
632
+ v = new_instance.block_device_mappings[device].volume
633
+ block_device_tags[device].keys.each do |tag_name|
634
+ run_with_backoff(30, 1, "tag #{v.id}, tag: #{tag_name}, value: #{block_device_tags[device][tag_name]}") do
635
+ v.add_tag(tag_name, :value => block_device_tags[device][tag_name])
636
+ end
637
+ end
638
+ end
639
+ AWS.stop_memoizing
640
+ end
641
+
642
+ new_instance
643
+ end
644
+
645
+ # Read in the configuration file stored in the workspace directory.
646
+ # By default this will be './config.rb'.
647
+ #
648
+ # @return [EC2Launcher::Config] the parsed configuration object
649
+ def load_config_file()
650
+ # Load configuration file
651
+ config_filename = File.join(@options.directory, "config.rb")
652
+ abort("Unable to find 'config.rb' in '#{@options.directory}'") unless File.exists?(config_filename)
653
+ ConfigDSL.execute(File.read(config_filename)).config
654
+ end
655
+
656
+ # Load and parse an environment file
657
+ #
658
+ # @param [String] name full pathname of the environment file to load
659
+ # @param [EC2Launcher::Environment, nil] default_environment the default environment,
660
+ # which will be used as the base for the new environment. Optional.
661
+ # @param [Boolean] fail_on_missing print an error and exit if the file does not exist.
662
+ #
663
+ # @return [EC2Launcher::Environment] the new environment loaded from the specified file.
664
+ #
665
+ def load_environment_file(name, default_environment = nil, fail_on_missing = false)
666
+ unless File.exists?(name)
667
+ abort("Unable to read environment: #{name}") if fail_on_missing
668
+ return nil
669
+ end
670
+
671
+ new_env = default_environment.clone unless default_environment.nil?
672
+ new_env ||= EC2Launcher::Environment.new
673
+
674
+ new_env.load(File.read(name))
675
+ new_env
676
+ end
677
+
678
+ # Attempts to build a list of valid directories.
679
+ #
680
+ # @param [Array<String>, nil] target_directories list of possible directories
681
+ # @param [String] default_directory directory to use if the target_directories list is empty or nil
682
+ # @param [String] name name of the type of directory. Used only for error messages.
683
+ # @param [Boolean] fail_on_error exit with an error if the list of valid directories is empty
684
+ #
685
+ # @return [Array<String] list of directories that exist
686
+ #
687
+ def process_directory_list(target_directories, default_directory, name, fail_on_error = false)
688
+ dirs = []
689
+ if target_directories.nil?
690
+ dirs << File.join(@options.directory, default_directory)
691
+ else
692
+ target_directories.each {|d| dirs << File.join(@options.directory, d) }
693
+ end
694
+ valid_directories = build_list_of_valid_directories(dirs)
695
+
696
+ if valid_directories.empty?
697
+ temp_dirs = dirs.each {|d| "'#{d}'"}.join(", ")
698
+ if fail_on_error
699
+ abort("ERROR - #{name} directories not found: #{temp_dirs}")
700
+ else
701
+ puts "WARNING - #{name} directories not found: #{temp_dirs}"
702
+ end
703
+ end
704
+
705
+ valid_directories
706
+ end
707
+
708
+ # Validates all settings in an application file
709
+ #
710
+ # @param [String] filename name of the application file
711
+ # @param [EC2Launcher::Application] application application object to validate
712
+ #
713
+ def validate_application(filename, application)
714
+ unless application.availability_zone.nil? || AVAILABILITY_ZONES.include?(application.availability_zone)
715
+ abort("Invalid availability zone '#{application.availability_zone}' in application '#{application.name}' (#{filename})")
716
+ end
717
+
718
+ unless application.instance_type.nil? || INSTANCE_TYPES.include?(application.instance_type)
719
+ abort("Invalid instance type '#{application.instance_type}' in application '#{application.name}' (#{filename})")
720
+ end
721
+ end
722
+
723
+ # Validates all settings in an environment file
724
+ #
725
+ # @param [String] filename name of the environment file
726
+ # @param [EC2Launcher::Environment] environment environment object to validate
727
+ #
728
+ def validate_environment(filename, environment)
729
+ unless environment.availability_zone.nil? || AVAILABILITY_ZONES.include?(environment.availability_zone)
730
+ abort("Invalid availability zone '#{environment.availability_zone}' in environment '#{environment.name}' (#{filename})")
731
+ end
732
+ end
733
+ end
734
+ end