ec2launcher 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 1.3.0
2
+
3
+ * Backward imcompatible change: terminating an instance now requires environment name.
4
+ * Added support for automatically adding A records in Route53 for new instances.
5
+ * Added support for automatically deleting A records from Route53 when terminating an instance.
6
+ * Added new "route53_zone_id" config option to environments to support Route53 integration.
7
+
1
8
  ## 1.2.0
2
9
 
3
10
  * Added support for terminating instances.
data/lib/ec2launcher.rb CHANGED
@@ -19,6 +19,7 @@ require 'ec2launcher/backoff_runner'
19
19
  require 'ec2launcher/instance_paths_config'
20
20
  require 'ec2launcher/block_device_builder'
21
21
  require 'ec2launcher/hostname_generator'
22
+ require 'ec2launcher/route53'
22
23
 
23
24
  require 'ec2launcher/config_wrapper'
24
25
 
@@ -106,6 +107,20 @@ module EC2Launcher
106
107
  initialize_aws(@options.access_key, @options.secret)
107
108
  @ec2 = AWS::EC2.new
108
109
 
110
+ ##############################
111
+ # Create Route53 connection
112
+ ##############################
113
+ @route53 = nil
114
+ @route53_zone_id = nil
115
+ @route53_domain_name = nil
116
+ if @environment.route53_zone_id
117
+ aws_route53 = AWS::Route53.new
118
+ @route53 = EC2Launcher::Route53.new(aws_route53, @environment.route53_zone_id)
119
+ @route53_zone_id = @environment.route53_zone_id
120
+ route53_zone = aws_route53.client.get_hosted_zone({:id => @environment.route53_zone_id})
121
+ @route53_domain_name = route53_zone[:hosted_zone][:name].chop
122
+ end
123
+
109
124
  ##############################
110
125
  # SUBNET
111
126
  ##############################
@@ -203,6 +218,14 @@ module EC2Launcher
203
218
  ami_name_match ||= @environment.ami_name
204
219
  ami = find_ami(instance_architecture, instance_virtualization, ami_name_match, @options.ami_id)
205
220
 
221
+ ##############################
222
+ # DOMAIN NAME
223
+ ##############################
224
+
225
+ # Note: Route53 domain names override domain names specified in the environments
226
+ @domain_name = @route53_domain_name
227
+ @domain_name ||= @environment.domain_name
228
+
206
229
  ##############################
207
230
  # HOSTNAME
208
231
  ##############################
@@ -212,14 +235,14 @@ module EC2Launcher
212
235
  if @options.count > 1
213
236
  1.upto(@options.count).each do |i|
214
237
  short_hostname = hostname_generator.generate_hostname()
215
- long_hostname = hostname_generator.generate_long_name(short_hostname, @environment.domain_name)
238
+ long_hostname = hostname_generator.generate_long_name(short_hostname, @domain_name)
216
239
  short_hostnames << short_hostname
217
240
  fqdn_names << long_hostname
218
241
  end
219
242
  else
220
243
  if @options.hostname.nil?
221
244
  short_hostname = hostname_generator.generate_hostname()
222
- long_hostname = hostname_generator.generate_long_name(short_hostname, @environment.domain_name)
245
+ long_hostname = hostname_generator.generate_long_name(short_hostname, @domain_name)
223
246
  else
224
247
  long_hostname = @options.hostname
225
248
  short_hostname = hostname_generator.generate_short_name(long_hostname, @environment.domain_name)
@@ -285,7 +308,7 @@ module EC2Launcher
285
308
 
286
309
  ##############################
287
310
  @log.info
288
- @log.info "Availability zone: #{availability_zone}"
311
+ @log.info "Availability zone : #{availability_zone}"
289
312
  @log.info "Key name : #{key_name}"
290
313
  @log.info "Security groups : " + security_groups.collect {|name| "#{name} (#{sg_map[name].security_group_id})"}.join(", ")
291
314
  @log.info "IAM profile : #{iam_profile}" if iam_profile
@@ -294,6 +317,7 @@ module EC2Launcher
294
317
  @log.info "AMI name : #{ami.ami_name}"
295
318
  @log.info "AMI id : #{ami.ami_id}"
296
319
  @log.info "ELB : #{elb_name}" if elb_name
320
+ @log.info "Route53 Zone : #{@route53_domain_name}" if @route53_domain_name
297
321
  @log.info "Chef PEM : #{chef_validation_pem_url}"
298
322
  @log.info "AWS key file : #{aws_keyfile}"
299
323
  @log.info "Roles : #{roles.join(', ')}"
@@ -348,7 +372,7 @@ module EC2Launcher
348
372
  block_device_tags = block_device_builder.generate_device_tags(fqdn_names[i], short_hostnames[i], @environment.name, @application.block_devices)
349
373
  user_data = build_launch_command(fqdn_names[i], short_hostnames[i], roles, chef_validation_pem_url, aws_keyfile, gems, packages, email_notifications)
350
374
 
351
- instance = launch_instance(fqdn_names[i], ami.ami_id, availability_zone, key_name, security_group_ids, iam_profile, instance_type, user_data, block_device_mappings, block_device_tags, subnet)
375
+ instance = launch_instance(fqdn_names[i], short_hostnames[i], ami.ami_id, availability_zone, key_name, security_group_ids, iam_profile, instance_type, user_data, block_device_mappings, block_device_tags, subnet)
352
376
  instances << instance
353
377
 
354
378
  public_dns_name = get_instance_dns(instance, true)
@@ -501,7 +525,7 @@ module EC2Launcher
501
525
  # @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.
502
526
  #
503
527
  # @return [AWS::EC2::Instance] newly created EC2 instance or nil if the launch failed.
504
- def launch_instance(hostname, ami_id, availability_zone, key_name, security_group_ids, iam_profile, instance_type, user_data, block_device_mappings = nil, block_device_tags = nil, vpc_subnet = nil)
528
+ def launch_instance(hostname, short_hostname, ami_id, availability_zone, key_name, security_group_ids, iam_profile, instance_type, user_data, block_device_mappings = nil, block_device_tags = nil, vpc_subnet = nil)
505
529
  @log.warn "Launching instance... #{hostname}"
506
530
  new_instance = nil
507
531
  run_with_backoff(30, 1, "launching instance") do
@@ -544,6 +568,7 @@ module EC2Launcher
544
568
  # Tag instance
545
569
  @log.info "Tagging instance..."
546
570
  run_with_backoff(30, 1, "tag #{new_instance.id}, tag: name, value: #{hostname}") { new_instance.add_tag("Name", :value => hostname) }
571
+ run_with_backoff(30, 1, "tag #{new_instance.id}, tag: short_name, value: #{short_hostname}") { new_instance.add_tag("short_name", :value => short_hostname) }
547
572
  run_with_backoff(30, 1, "tag #{new_instance.id}, tag: environment, value: #{@environment.name}") { new_instance.add_tag("environment", :value => @environment.name) }
548
573
  run_with_backoff(30, 1, "tag #{new_instance.id}, tag: application, value: #{@application.name}") { new_instance.add_tag("application", :value => @application.name) }
549
574
 
@@ -563,6 +588,13 @@ module EC2Launcher
563
588
  AWS.stop_memoizing
564
589
  end
565
590
 
591
+ ##############################
592
+ # Add to Route53
593
+ if @route53
594
+ @log.info "Adding A record to Route53: #{hostname} => #{new_instance.private_ip_address}"
595
+ @route53.create_record(hostname, new_instance.private_ip_address, 'A')
596
+ end
597
+
566
598
  new_instance
567
599
  end
568
600
 
@@ -20,7 +20,7 @@ module EC2Launcher
20
20
  end
21
21
 
22
22
  begin
23
- yield
23
+ block.call
24
24
  rescue AWS::EC2::Errors::RequestLimitExceeded
25
25
  puts "AWS::EC2::Errors::RequestLimitExceeded ... retrying #{message} in #{sleep_time} seconds"
26
26
  sleep sleep_time
@@ -144,9 +144,9 @@ module EC2Launcher
144
144
  # Takes values from the other server type and merges them into this one
145
145
  def merge(other_server)
146
146
  @name = other_server.name
147
- @ami_name = other_server.ami_name unless other_server.ami_name.nil?
148
- @availability_zone = other_server.availability_zone unless other_server.availability_zone.nil?
149
- @basename = other_server.basename unless other_server.basename.nil?
147
+ @ami_name = other_server.ami_name if other_server.ami_name
148
+ @availability_zone = other_server.availability_zone if other_server.availability_zone
149
+ @basename = other_server.basename if other_server.basename
150
150
 
151
151
  unless other_server.block_devices.nil?
152
152
  @block_devices = [] if @block_devices.nil?
@@ -158,9 +158,9 @@ module EC2Launcher
158
158
  other_server.elb.keys.each {|env_name| @elb[env_name] = other_server.elb[env_name] }
159
159
  end
160
160
 
161
- @iam_profile = other_server.iam_profile unless other_server.iam_profile.nil?
162
- @instance_type = other_server.instance_type unless other_server.instance_type.nil?
163
- @name_suffix = other_server.name_suffix unless other_server.name_suffix.nil?
161
+ @iam_profile = other_server.iam_profile if other_server.iam_profile
162
+ @instance_type = other_server.instance_type if other_server.instance_type
163
+ @name_suffix = other_server.name_suffix if other_server.name_suffix
164
164
 
165
165
  unless other_server.roles.nil?
166
166
  @roles = [] if @roles.nil?
@@ -177,14 +177,14 @@ module EC2Launcher
177
177
  end
178
178
  end
179
179
 
180
- @use_rvm = other_server.use_rvm unless other_server.use_rvm.nil?
180
+ @use_rvm = other_server.use_rvm if other_server.use_rvm
181
181
  end
182
182
 
183
183
  def roles_for_environment(environment)
184
184
  roles = []
185
185
  roles += @roles unless @roles.nil?
186
186
 
187
- unless @environment_roles.nil? || @environment_roles[environment].nil?
187
+ if @environment_roles && @environment_roles[environment]
188
188
  roles += @environment_roles[environment]
189
189
  end
190
190
  roles
@@ -33,6 +33,9 @@ module EC2Launcher
33
33
  dsl_accessor :subnet
34
34
  dsl_accessor :use_rvm
35
35
 
36
+ # @since 1.3.0
37
+ dsl_accessor :route53_zone_id
38
+
36
39
  dsl_array_accessor :aliases
37
40
  dsl_array_accessor :gems
38
41
  dsl_array_accessor :packages
@@ -54,6 +57,7 @@ module EC2Launcher
54
57
  @precommand = []
55
58
  @postcommand = []
56
59
  @roles = []
60
+ @route53_zone_id = nil
57
61
  @security_groups = {}
58
62
 
59
63
  @use_rvm = true
@@ -69,12 +73,12 @@ module EC2Launcher
69
73
  def merge(other_env)
70
74
  @name =other_env.name
71
75
 
72
- @gems += other_env.gems unless other_env.gems.nil?
73
- @packages += other_env.packages unless other_env.packages.nil?
74
- @roles += other_env.roles unless other_env.roles.nil?
75
- @precommand += other_env.precommand unless other_env.precommand.nil?
76
- @postcommand += other_env.postcommand unless other_env.postcommand.nil?
77
- unless other_env.security_groups.nil?
76
+ @gems += other_env.gems if other_env.gems
77
+ @packages += other_env.packages if other_env.packages
78
+ @roles += other_env.roles if other_env.roles
79
+ @precommand += other_env.precommand if other_env.precommand
80
+ @postcommand += other_env.postcommand if other_env.postcommand
81
+ if other_env.security_groups
78
82
  other_env.security_groups.keys.each do |key|
79
83
  @security_groups[key] = [] if @security_groups[key].nil?
80
84
  @security_groups[key] += other_env.security_groups[key]
@@ -83,22 +87,23 @@ module EC2Launcher
83
87
 
84
88
  @aliases = other_env.aliases.nil? ? nil : other_env.aliases
85
89
 
86
- @ami_name = other_env.ami_name unless other_env.ami_name.nil?
87
- @aws_keyfile = other_env.aws_keyfile unless other_env.aws_keyfile.nil?
88
- @availability_zone = other_env.availability_zone unless other_env.availability_zone.nil?
89
- @chef_path = other_env.chef_path unless other_env.chef_path.nil?
90
- @chef_server_url = other_env.chef_server_url unless other_env.chef_server_url.nil?
91
- @chef_validation_pem_url = other_env.chef_validation_pem_url unless other_env.chef_validation_pem_url.nil?
92
- @domain_name = other_env.domain_name unless other_env.domain_name.nil?
93
- @email_notifications = other_env.email_notifications unless other_env.email_notifications.nil?
94
- @gem_path = other_env.gem_path unless other_env.gem_path.nil?
95
- @iam_profile = other_env.iam_profile unless other_env.iam_profile.nil?
96
- @key_name = other_env.key_name unless other_env.key_name.nil?
97
- @knife_path = other_env.knife_path unless other_env.knife_path.nil?
98
- @ruby_path = other_env.ruby_path unless other_env.ruby_path.nil?
99
- @subnet = other_env.subnet unless other_env.subnet.nil?
100
- @short_name = other_env.short_name unless other_env.short_name.nil?
101
- @use_rvm = other_env.use_rvm unless other_env.use_rvm.nil?
90
+ @ami_name = other_env.ami_name if other_env.ami_name
91
+ @aws_keyfile = other_env.aws_keyfile if other_env.aws_keyfile
92
+ @availability_zone = other_env.availability_zone if other_env.availability_zone
93
+ @chef_path = other_env.chef_path if other_env.chef_path
94
+ @chef_server_url = other_env.chef_server_url if other_env.chef_server_url
95
+ @chef_validation_pem_url = other_env.chef_validation_pem_url if other_env.chef_validation_pem_url
96
+ @domain_name = other_env.domain_name if other_env.domain_name
97
+ @email_notifications = other_env.email_notifications if other_env.email_notifications
98
+ @gem_path = other_env.gem_path if other_env.gem_path
99
+ @iam_profile = other_env.iam_profile if other_env.iam_profile
100
+ @key_name = other_env.key_name if other_env.key_name
101
+ @knife_path = other_env.knife_path if other_env.knife_path
102
+ @route53_zone_id = other_env.route53_zone_id if other_env.route53_zone_id
103
+ @ruby_path = other_env.ruby_path if other_env.ruby_path
104
+ @subnet = other_env.subnet if other_env.subnet
105
+ @short_name = other_env.short_name if other_env.short_name
106
+ @use_rvm = other_env.use_rvm if other_env.use_rvm
102
107
  end
103
108
 
104
109
  def load(dsl)
@@ -175,6 +175,15 @@ module EC2Launcher
175
175
  end
176
176
  @location = args[0]
177
177
  elsif @command =~ /^term/
178
+ @opts.parse!(args)
179
+
180
+ if @options.environ.nil?
181
+ puts "Missing a required parameter: --environment"
182
+ puts
183
+ help
184
+ exit 1
185
+ end
186
+
178
187
  unless args.length >= 1
179
188
  puts "Missing name of server!"
180
189
  puts
@@ -0,0 +1,110 @@
1
+ #
2
+ # Copyright (c) 2012 Sean Laurent
3
+ #
4
+ require 'rubygems'
5
+ require 'aws-sdk'
6
+ require 'log4r'
7
+
8
+ module EC2Launcher
9
+ class Route53
10
+ #
11
+ # @param [AWS::Route53] route53 Initialized Route53 object
12
+ # @param [String] hosted_zone_id Zone ID
13
+ def initialize(route53, hosted_zone_id)
14
+ @log = Logger.new 'route53'
15
+ log_output = Outputter.stdout
16
+ log_output.formatter = PatternFormatter.new :pattern => "%m"
17
+ @log.outputters = log_output
18
+
19
+ @route53 = route53
20
+ @hosted_zone_id = hosted_zone_id
21
+ end
22
+
23
+ # Creates a new DNS record in Route53. Deletes any existing record with the
24
+ # same name and record type.
25
+ #
26
+ # @param [String] name Name of the record
27
+ # @param [String] value Value for the DNS record
28
+ # @param [String] type Type of DNS record: A, CNAME, etc. Defaults to 'A'.
29
+ # @param [Integer] ttl TTL in seconds. Defaults to 300.
30
+ def create_record(name, value, type = "A", ttl = 300)
31
+ # Delete existing record because you can't update records
32
+ delete_record_by_name(name, type)
33
+
34
+ # Create new record
35
+ begin
36
+ @route53.client.change_resource_record_sets({
37
+ :hosted_zone_id => @hosted_zone_id,
38
+ :change_batch => {
39
+ :changes => [
40
+ {
41
+ :action => "CREATE",
42
+ :resource_record_set => {
43
+ :name => name,
44
+ :type => type,
45
+ :ttl => ttl,
46
+ :resource_records => [ { :value => value } ]
47
+ }
48
+ }
49
+ ]
50
+ }
51
+ })
52
+ rescue StandardError => bang
53
+ @log.error "Error creating A record from Route53: #{bang}"
54
+ end
55
+ end
56
+
57
+ # Deletes a record by hostname, if it exists.
58
+ #
59
+ # @param [String] hostname Name of the record
60
+ # @param [String] record_type Type of DNS record: A, CNAME, etc.
61
+ # @param [Boolean] log_errors Log errors or not. False quietly ignores errors.
62
+ #
63
+ def delete_record_by_name(hostname, record_type = "A", log_errors = true)
64
+ # Find the record
65
+ response = @route53.client.list_resource_record_sets({
66
+ :hosted_zone_id => @hosted_zone_id,
67
+ :start_record_name => hostname,
68
+ :start_record_type => record_type,
69
+ :max_items => 1
70
+ })
71
+ if response && response.data
72
+ if response.data[:resource_record_sets] && response.data[:resource_record_sets].size > 0
73
+ record = response.data[:resource_record_sets][0]
74
+ delete_record(record[:name], record[:type], record[:ttl], record[:resource_records][0][:value], log_errors)
75
+ end
76
+ end
77
+ end
78
+
79
+ # Deletes a DNS record from Route53.
80
+ #
81
+ # @param [String] name Name of the record
82
+ # @param [String] type Type of DNS record: A, CNAME, etc.
83
+ # @param [Integer] ttl TTL in seconds
84
+ # @param [String] value Value for the DNS record
85
+ # @param [Boolean] log_errors Log errors or not. False quietly ignores errors.
86
+ #
87
+ def delete_record(name, type, ttl, value, log_errors = true)
88
+ begin
89
+ @route53.client.change_resource_record_sets({
90
+ :hosted_zone_id => @hosted_zone_id,
91
+ :change_batch => {
92
+ :changes => [
93
+ {
94
+ :action => "DELETE",
95
+ :resource_record_set => {
96
+ :name => name,
97
+ :type => type,
98
+ :ttl => ttl,
99
+ :resource_records => [ { :value => value } ]
100
+ }
101
+ }
102
+ ]
103
+ }
104
+ })
105
+ rescue StandardError => bang
106
+ @log.error "Error deleting A record from Route53: #{bang}" if log_errors
107
+ end
108
+ end
109
+ end
110
+ end
@@ -7,6 +7,7 @@ require 'log4r'
7
7
 
8
8
  require 'ec2launcher/aws_initializer'
9
9
  require 'ec2launcher/backoff_runner'
10
+ require 'ec2launcher/route53'
10
11
 
11
12
  module EC2Launcher
12
13
  class Terminator
@@ -21,11 +22,38 @@ module EC2Launcher
21
22
  end
22
23
 
23
24
  def terminate(server_name, options)
25
+ ##############################
26
+ # Load configuration data
27
+ ##############################
28
+ config_wrapper = ConfigWrapper.new(options.directory)
29
+
30
+ @config = config_wrapper.config
31
+ @environments = config_wrapper.environments
32
+
33
+ ##############################
34
+ # ENVIRONMENT
35
+ ##############################
36
+ unless @environments.has_key? options.environ
37
+ @log.fatal "Environment not found: #{options.environ}"
38
+ exit 2
39
+ end
40
+ @environment = @environments[options.environ]
41
+
24
42
  ##############################
25
43
  # Initialize AWS and create EC2 connection
26
44
  ##############################
27
45
  initialize_aws(options.access_key, options.secret)
28
46
  @ec2 = AWS::EC2.new
47
+
48
+ ##############################
49
+ # Create Route53 connection
50
+ ##############################
51
+ aws_route53 = AWS::Route53.new if @environment.route53_zone_id
52
+ @route53 = EC2Launcher::Route53.new(aws_route53, @environment.route53_zone_id)
53
+
54
+ ##############################
55
+ # Find instance
56
+ ##############################
29
57
  instance = nil
30
58
  AWS.memoize do
31
59
  instances = @ec2.instances.filter("tag:Name", server_name)
@@ -40,11 +68,17 @@ module EC2Launcher
40
68
  if instance
41
69
  @log.info("Terminating instance: #{server_name} [#{instance.instance_id}]")
42
70
  instance.terminate
71
+
72
+ if @route53
73
+ @log.info("Deleting A record from Route53: #{server_name} => #{instance.private_ip_address}")
74
+ @route53.delete_record_by_name(server_name, 'A')
75
+ end
76
+
43
77
  @log.info("Deleting node/client from Chef: #{server_name}")
44
78
  node_result = `echo "Y" |knife node delete #{server_name}`
45
79
  client_result = `echo "Y" |knife client delete #{server_name}`
46
- @log.debug("Deleted Chef node: #{result}")
47
- @log.debug("Deleted Chef client: #{result}")
80
+ @log.debug("Deleted Chef node: #{node_result}")
81
+ @log.debug("Deleted Chef client: #{client_result}")
48
82
  else
49
83
  @log.error("Unable to find instance: #{server_name}")
50
84
  end
@@ -2,5 +2,5 @@
2
2
  # Copyright (c) 2012 Sean Laurent
3
3
  #
4
4
  module EC2Launcher
5
- VERSION = "1.2.0"
5
+ VERSION = "1.3.0"
6
6
  end
@@ -0,0 +1,14 @@
1
+ #
2
+ # Copyright (c) 2012 Sean Laurent
3
+ #
4
+
5
+ # Handles YARD documentation for the custom DSL methods
6
+ class DSLAttributeHandler < YARD::Handlers::Ruby::AttributeHandler
7
+ handles_method_call(:dsl_accessor)
8
+ handles_method_call(:dsl_array_accessor)
9
+ namespace_only
10
+
11
+ def process
12
+ push_state(:scope => :class) { super }
13
+ end
14
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ec2launcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-03 00:00:00.000000000 Z
12
+ date: 2012-10-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: aws-sdk
@@ -78,12 +78,14 @@ files:
78
78
  - lib/ec2launcher/hostname_generator.rb
79
79
  - lib/ec2launcher/init_options.rb
80
80
  - lib/ec2launcher/instance_paths_config.rb
81
+ - lib/ec2launcher/route53.rb
81
82
  - lib/ec2launcher/security_group_handler.rb
82
83
  - lib/ec2launcher/terminator.rb
83
84
  - lib/ec2launcher/version.rb
84
85
  - startup-scripts/runurl
85
86
  - startup-scripts/setup.rb
86
87
  - startup-scripts/setup_instance.rb
88
+ - yard_extensions/dsl_attribute_handler.rb
87
89
  homepage: https://github.com/StudyBlue/ec2launcher
88
90
  licenses: []
89
91
  post_install_message: