From 94b53444473fe37808fbabe3d786a5808b14193b Mon Sep 17 00:00:00 2001 From: Ben Whaley Date: Wed, 1 May 2013 21:29:22 -0600 Subject: [PATCH 1/5] elastic IP address for VPC instances --- lib/vagrant-aws/action/read_ssh_info.rb | 2 +- lib/vagrant-aws/action/run_instance.rb | 16 ++++++++++++++++ lib/vagrant-aws/config.rb | 9 +++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/vagrant-aws/action/read_ssh_info.rb b/lib/vagrant-aws/action/read_ssh_info.rb index f8452527..91fa17d6 100644 --- a/lib/vagrant-aws/action/read_ssh_info.rb +++ b/lib/vagrant-aws/action/read_ssh_info.rb @@ -31,7 +31,7 @@ def read_ssh_info(aws, machine) # Read the DNS info return { - :host => server.dns_name || server.private_ip_address, + :host => server.public_ip_address || server.dns_name || server.private_ip_address, :port => 22 } end diff --git a/lib/vagrant-aws/action/run_instance.rb b/lib/vagrant-aws/action/run_instance.rb index be72f263..4ca8053b 100644 --- a/lib/vagrant-aws/action/run_instance.rb +++ b/lib/vagrant-aws/action/run_instance.rb @@ -34,6 +34,7 @@ def call(env) subnet_id = region_config.subnet_id tags = region_config.tags user_data = region_config.user_data + elastic_ip = region_config.elastic_ip # If there is no keypair then warn the user if !keypair @@ -54,6 +55,7 @@ def call(env) env[:ui].info(" -- Keypair: #{keypair}") if keypair env[:ui].info(" -- Subnet ID: #{subnet_id}") if subnet_id env[:ui].info(" -- Private IP: #{private_ip_address}") if private_ip_address + env[:ui].info(" -- Elastic IP: #{elastic_ip}") if elastic_ip env[:ui].info(" -- User Data: yes") if user_data env[:ui].info(" -- Security Groups: #{security_groups.inspect}") if !security_groups.empty? env[:ui].info(" -- User Data: #{user_data}") if user_data @@ -117,6 +119,20 @@ def call(env) @logger.info("Time to instance ready: #{env[:metrics]["instance_ready_time"]}") + if elastic_ip + allocation = env[:aws_compute].allocate_address('vpc') + if allocation.body['publicIp'].nil? + @logger.debug("Could not allocate Elastic IP.") + return nil + end + @logger.debug("Public IP #{allocation.body['publicIp']}") + association = env[:aws_compute].associate_address(server.id, nil, nil, allocation.body['allocationId']) + unless association.body['return'] + @logger.debug("Could not associate Elastic IP.") + return nil + end + end + if !env[:interrupted] env[:metrics]["instance_ssh_time"] = Util::Timer.time do # Wait for SSH to be ready. diff --git a/lib/vagrant-aws/config.rb b/lib/vagrant-aws/config.rb index 6199b12a..9b1063b5 100644 --- a/lib/vagrant-aws/config.rb +++ b/lib/vagrant-aws/config.rb @@ -39,6 +39,11 @@ class Config < Vagrant.plugin("2", :config) # @return [String] attr_accessor :private_ip_address + # Acquire and attach an elastic IP address (VPC). + # + # @return [Boolean] + attr_accessor :elastic_ip + # The name of the AWS region in which to create the instance. # # @return [String] @@ -103,6 +108,7 @@ def initialize(region_specific=false) @tags = {} @user_data = UNSET_VALUE @use_iam_profile = UNSET_VALUE + @elastic_ip = UNSET_VALUE # Internal state (prefix with __ so they aren't automatically # merged) @@ -193,6 +199,9 @@ def finalize! # Default the private IP to nil since VPC is not default @private_ip_address = nil if @private_ip_address == UNSET_VALUE + # Acquire an elastic IP if requested + @elastic_ip = nil if @elastic_ip == UNSET_VALUE + # Default region is us-east-1. This is sensible because AWS # generally defaults to this as well. @region = "us-east-1" if @region == UNSET_VALUE From 51b9689baeb8ce355c35e7dfcf3be866a877bfd1 Mon Sep 17 00:00:00 2001 From: Ben Whaley Date: Sun, 5 May 2013 20:26:06 -0600 Subject: [PATCH 2/5] handle EIPs for both VPC and standard EC2 instances --- lib/vagrant-aws/action/run_instance.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/vagrant-aws/action/run_instance.rb b/lib/vagrant-aws/action/run_instance.rb index 4ca8053b..6c061a2d 100644 --- a/lib/vagrant-aws/action/run_instance.rb +++ b/lib/vagrant-aws/action/run_instance.rb @@ -120,13 +120,18 @@ def call(env) @logger.info("Time to instance ready: #{env[:metrics]["instance_ready_time"]}") if elastic_ip - allocation = env[:aws_compute].allocate_address('vpc') + domain = subnet_id ? 'vpc' : 'standard' + allocation = env[:aws_compute].allocate_address(domain) if allocation.body['publicIp'].nil? @logger.debug("Could not allocate Elastic IP.") return nil end @logger.debug("Public IP #{allocation.body['publicIp']}") - association = env[:aws_compute].associate_address(server.id, nil, nil, allocation.body['allocationId']) + if domain == 'vpc' + association = env[:aws_compute].associate_address(server.id, nil, nil, allocation.body['allocationId']) + else + association = env[:aws_compute].associate_address(server.id, allocation.body['publicIp']) + end unless association.body['return'] @logger.debug("Could not associate Elastic IP.") return nil From 0085d167f584036ee65cfacd881cf1029c7c4cde Mon Sep 17 00:00:00 2001 From: Ben Whaley Date: Thu, 23 May 2013 14:49:48 -0600 Subject: [PATCH 3/5] check for elastic_ip before warning about not having one --- lib/vagrant-aws/action/run_instance.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vagrant-aws/action/run_instance.rb b/lib/vagrant-aws/action/run_instance.rb index 6c061a2d..ae747d18 100644 --- a/lib/vagrant-aws/action/run_instance.rb +++ b/lib/vagrant-aws/action/run_instance.rb @@ -42,7 +42,7 @@ def call(env) end # If there is a subnet ID then warn the user - if subnet_id + if subnet_id and !elastic_ip env[:ui].warn(I18n.t("vagrant_aws.launch_vpc_warning")) end From 69a1f4fbef2c6f4a479eafcb14f08767a2ab9987 Mon Sep 17 00:00:00 2001 From: Ben Whaley Date: Mon, 27 May 2013 23:04:49 -0700 Subject: [PATCH 4/5] release elastic ip address upon termination --- lib/vagrant-aws/action/run_instance.rb | 49 ++++++++++++++------ lib/vagrant-aws/action/terminate_instance.rb | 21 +++++++++ 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/lib/vagrant-aws/action/run_instance.rb b/lib/vagrant-aws/action/run_instance.rb index ae747d18..3cd3b12b 100644 --- a/lib/vagrant-aws/action/run_instance.rb +++ b/lib/vagrant-aws/action/run_instance.rb @@ -1,4 +1,5 @@ require "log4r" +require 'json' require 'vagrant/util/retryable' @@ -119,23 +120,10 @@ def call(env) @logger.info("Time to instance ready: #{env[:metrics]["instance_ready_time"]}") + # Allocate and associate an elastic IP if requested if elastic_ip domain = subnet_id ? 'vpc' : 'standard' - allocation = env[:aws_compute].allocate_address(domain) - if allocation.body['publicIp'].nil? - @logger.debug("Could not allocate Elastic IP.") - return nil - end - @logger.debug("Public IP #{allocation.body['publicIp']}") - if domain == 'vpc' - association = env[:aws_compute].associate_address(server.id, nil, nil, allocation.body['allocationId']) - else - association = env[:aws_compute].associate_address(server.id, allocation.body['publicIp']) - end - unless association.body['return'] - @logger.debug("Could not associate Elastic IP.") - return nil - end + do_elastic_ip(env, domain, server) end if !env[:interrupted] @@ -171,6 +159,37 @@ def recover(env) end end + def do_elastic_ip(env, domain, server) + allocation = env[:aws_compute].allocate_address(domain) + if allocation.body['publicIp'].nil? + @logger.debug("Could not allocate Elastic IP.") + return nil + end + @logger.debug("Public IP #{allocation.body['publicIp']}") + + # Associate the address and save the metadata to a hash + if domain == 'vpc' + # VPC requires an allocation ID to assign an IP + association = env[:aws_compute].associate_address(server.id, nil, nil, allocation.body['allocationId']) + h = { :allocation_id => allocation.body['allocationId'], :association_id => association.body['associationId'], :public_ip => allocation.body['publicIp'] } + else + # Standard EC2 instances only need the allocated IP address + association = env[:aws_compute].associate_address(server.id, allocation.body['publicIp']) + h = { :public_ip => allocation.body['publicIp'] } + end + + unless association.body['return'] + @logger.debug("Could not associate Elastic IP.") + return nil + end + + # Save this IP to the data dir so it can be released when the instance is destroyed + ip_file = env[:machine].data_dir.join('elastic_ip') + ip_file.open('w+') do |f| + f.write(h.to_json) + end + end + def terminate(env) destroy_env = env.dup destroy_env.delete(:interrupted) diff --git a/lib/vagrant-aws/action/terminate_instance.rb b/lib/vagrant-aws/action/terminate_instance.rb index 85609685..c2b63c2f 100644 --- a/lib/vagrant-aws/action/terminate_instance.rb +++ b/lib/vagrant-aws/action/terminate_instance.rb @@ -1,4 +1,5 @@ require "log4r" +require "json" module VagrantPlugins module AWS @@ -17,9 +18,29 @@ def call(env) env[:ui].info(I18n.t("vagrant_aws.terminating")) server.destroy env[:machine].id = nil + + # Release the elastic IP + ip_file = env[:machine].data_dir.join('elastic_ip') + if ip_file.file? + release_address(env,ip_file.read) + ip_file.delete + end @app.call(env) end + + # Release an elastic IP address + def release_address(env,eip) + h = JSON.parse(eip) + # Use association_id and allocation_id for VPC, use public IP for EC2 + if h['association_id'] + env[:aws_compute].disassociate_address(nil,h['association_id']) + env[:aws_compute].release_address(h['allocation_id']) + else + env[:aws_compute].disassociate_address(h['public_ip']) + env[:aws_compute].release_address(h['public_ip']) + end + end end end end From a9f634210a6b7a5ff7d7f4307a2b8b5ca57980b8 Mon Sep 17 00:00:00 2001 From: Ben Whaley Date: Sun, 11 Aug 2013 17:40:19 -0700 Subject: [PATCH 5/5] added elastic_ip entry in config spec --- spec/vagrant-aws/config_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/vagrant-aws/config_spec.rb b/spec/vagrant-aws/config_spec.rb index dd6175b1..4881fb6e 100644 --- a/spec/vagrant-aws/config_spec.rb +++ b/spec/vagrant-aws/config_spec.rb @@ -30,6 +30,7 @@ its("user_data") { should be_nil } its("use_iam_profile") { should be_false } its("block_device_mapping") {should == {} } + its("elastic_ip") { should be_nil } end describe "overriding defaults" do @@ -40,7 +41,7 @@ [:access_key_id, :ami, :availability_zone, :instance_ready_timeout, :instance_type, :keypair_name, :region, :secret_access_key, :security_groups, - :subnet_id, :tags, + :subnet_id, :tags, :elastic_ip, :use_iam_profile, :user_data, :block_device_mapping].each do |attribute| it "should not default #{attribute} if overridden" do