ec2launcher 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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