ec2launcher 1.2.0 → 1.3.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.
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: