diff --git a/README.md b/README.md index 31bbf8cd..3a3bd760 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ This provider exposes quite a few provider-specific configuration options: for credentials. * `block_device_mapping` - Amazon EC2 Block Device Mapping Property * `elb` - The ELB name to attach to the instance. +* `additional_network_interfaces` - An array of the extra network interfaces to create and attach once the machine is up * `unregister_elb_from_az` - Removes the ELB from the AZ on removal of the last instance if true (default). In non default VPC this has to be false. * `terminate_on_shutdown` - Indicates whether an instance stops or terminates when you initiate shutdown from the instance. @@ -298,6 +299,39 @@ Vagrant.configure("2") do |config| end ``` +### Additional Network Adapters + +You can add extra network adapters to your instance after boot. + +```ruby + Vagrant.configure("2") do |config| + # ... other stuff + + config.vm.provider "aws" do |aws| + + # subnet & security groups for primary network interface with device index 0 + aws.subnet_id = 'subnet-caba8084' + aws.security_groups = 'sg-edb6e09b' + + # add additonal interfaces after boot + aws.additional_network_interfaces = [ + { + :device_index => 1, + :subnet_id => 'subnet-2f76b4e7', + :security_groups => ['sg-b2a58ce3', 'sg-008f7950'], + :private_ip_address => '172.16.110.200' #optional + }, + { + :device_index => 2, + :subnet_id => 'subnet-e9725abc', + :security_groups => ['sg-0ded8ff6'] + } + ] + end +end +``` + + ## Development To work on the `vagrant-aws` plugin, clone this repository out, and use diff --git a/lib/vagrant-aws/action.rb b/lib/vagrant-aws/action.rb index 2b5c7cd0..49120d49 100644 --- a/lib/vagrant-aws/action.rb +++ b/lib/vagrant-aws/action.rb @@ -50,11 +50,11 @@ def self.action_destroy b3.use MessageNotCreated next end - b3.use ConnectAWS + b3.use DestroyAdditionalNetworkInterfaces b3.use ElbDeregisterInstance - b3.use ProvisionerCleanup, :before if defined?(ProvisionerCleanup) b3.use TerminateInstance + b3.use ProvisionerCleanup if defined?(ProvisionerCleanup) end else b2.use MessageWillNotDestroy @@ -158,6 +158,7 @@ def self.action_up else b1.use action_prepare_boot b1.use RunInstance # launch a new instance + b1.use RegisterAdditionalNetworkInterfaces end end end @@ -205,6 +206,8 @@ def self.action_reload autoload :WarnNetworks, action_root.join("warn_networks") autoload :ElbRegisterInstance, action_root.join("elb_register_instance") autoload :ElbDeregisterInstance, action_root.join("elb_deregister_instance") + autoload :RegisterAdditionalNetworkInterfaces, action_root.join("network_adapters_register") + autoload :DestroyAdditionalNetworkInterfaces, action_root.join("network_adapters_destroy") end end end diff --git a/lib/vagrant-aws/action/network_adapters_destroy.rb b/lib/vagrant-aws/action/network_adapters_destroy.rb new file mode 100644 index 00000000..bca5b599 --- /dev/null +++ b/lib/vagrant-aws/action/network_adapters_destroy.rb @@ -0,0 +1,31 @@ +require 'vagrant-aws/util/network_adapters' + +module VagrantPlugins + module AWS + module Action + class DestroyAdditionalNetworkInterfaces + include NetworkAdapter + + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant_aws::action::network_adapters_register") + end + + def call(env) + + interfaces = env[:machine].provider_config.additional_network_interfaces + + interfaces.each do |intf| + env[:ui].info(I18n.t("vagrant_aws.destroy_network_interface")) + env[:ui].info(" -- Device Index: #{intf[:device_index]}") + env[:ui].info(" -- Attached To: #{env[:machine].id}") + destroy_adapter env, intf[:device_index], env[:machine].id + end + + @app.call(env) + + end + end + end + end +end diff --git a/lib/vagrant-aws/action/network_adapters_register.rb b/lib/vagrant-aws/action/network_adapters_register.rb new file mode 100644 index 00000000..8454e605 --- /dev/null +++ b/lib/vagrant-aws/action/network_adapters_register.rb @@ -0,0 +1,33 @@ +require 'vagrant-aws/util/network_adapters' + +module VagrantPlugins + module AWS + module Action + class RegisterAdditionalNetworkInterfaces + include NetworkAdapter + + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant_aws::action::network_adapters_register") + end + + def call(env) + + @app.call(env) + + interfaces = env[:machine].provider_config.additional_network_interfaces + + interfaces.each do |intf| + env[:ui].info(I18n.t("vagrant_aws.creating_network_interface")) + env[:ui].info(" -- Device Index: #{intf[:device_index]}") + env[:ui].info(" -- Subnet ID: #{intf[:subnet_id]}") + env[:ui].info(" -- Security Groups: #{intf[:security_groups]}") + env[:ui].info(" -- IP: #{intf[:private_ip_address]}") + register_adapter env, intf[:device_index], intf[:subnet_id], intf[:security_groups], intf[:private_ip_address], env[:machine].id + end + + end + end + end + end +end diff --git a/lib/vagrant-aws/config.rb b/lib/vagrant-aws/config.rb index 30a19cb4..854f16be 100644 --- a/lib/vagrant-aws/config.rb +++ b/lib/vagrant-aws/config.rb @@ -196,6 +196,13 @@ class Config < Vagrant.plugin("2", :config) # @return [String] attr_accessor :aws_profile + # The additional network adapters which should + # be attached to instance + # + # @return [Array] + attr_accessor :additional_network_interfaces + + def initialize(region_specific=false) @access_key_id = UNSET_VALUE @ami = UNSET_VALUE @@ -233,6 +240,7 @@ def initialize(region_specific=false) @tenancy = UNSET_VALUE @aws_dir = UNSET_VALUE @aws_profile = UNSET_VALUE + @additional_network_interfaces = [] # Internal state (prefix with __ so they aren't automatically # merged) diff --git a/lib/vagrant-aws/util/network_adapters.rb b/lib/vagrant-aws/util/network_adapters.rb new file mode 100644 index 00000000..5fc997d0 --- /dev/null +++ b/lib/vagrant-aws/util/network_adapters.rb @@ -0,0 +1,66 @@ +module VagrantPlugins + module AWS + module NetworkAdapter + + def ip_attributes(ips) + return {} if ips.nil? + + attrs = { 'PrivateIpAddresses.0.Primary' => true } + + if ips.kind_of?(Array) + ips.each_with_index do |ip, i| + attrs["PrivateIpAddresses.#{i}.PrivateIpAddress"] = ip + end + else + attrs["PrivateIpAddresses.0.PrivateIpAddress"] = ips + end + + attrs + end + + def security_group_attributes(security_groups) + attrs = {} + + if security_groups.kind_of?(Array) + security_groups.each_with_index do |sid, i| + attrs["SecurityGroupId.#{i + 1}"] = sid + end + else + attrs["SecurityGroupId.1"] = security_groups + end + + attrs + end + + def register_adapter(env, device_index, subnet_id, security_groups, private_ip_address, instance_id) + + options = {} + options.merge! security_group_attributes(security_groups) + options.merge! ip_attributes(private_ip_address) + + interface = env[:aws_compute].create_network_interface( + subnet_id, + options + ).body['networkInterface'] + + env[:aws_compute].attach_network_interface(interface['networkInterfaceId'], instance_id, device_index) + end + + def destroy_adapter(env, device_index, instance_id) + interface = env[:aws_compute].network_interfaces.all('attachment.instance-id' => instance_id, 'attachment.device-index' => device_index ).first + + if interface.nil? + return + end + + if !interface.attachment.nil? && interface.attachment != {} + env[:aws_compute].detach_network_interface(interface.attachment['attachmentId'], true) + interface.wait_for { attachment.nil? || attachment == {} } + end + + interface.destroy + end + + end + end +end diff --git a/locales/en.yml b/locales/en.yml index ddc3d4d6..a6f6ed9c 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -62,6 +62,10 @@ en: will_not_destroy: |- The instance '%{name}' will not be destroyed, since the confirmation was declined. + creating_network_interface: |- + Creating additional network interface with the following settings... + destroy_network_interface: |- + Destroying additional network interface... config: access_key_id_required: |- diff --git a/spec/vagrant-aws/config_spec.rb b/spec/vagrant-aws/config_spec.rb index eb309743..a4a97407 100644 --- a/spec/vagrant-aws/config_spec.rb +++ b/spec/vagrant-aws/config_spec.rb @@ -58,6 +58,7 @@ its("associate_public_ip") { should == false } its("unregister_elb_from_az") { should == true } its("tenancy") { should == "default" } + its("additional_network_interfaces") { should == [] } end describe "overriding defaults" do