diff --git a/README.md b/README.md index c0b2734a..1abbd179 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,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 These can be set like typical provider-specific configuration: @@ -265,6 +266,38 @@ 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 @@ -283,10 +316,7 @@ $ bundle exec rake If those pass, you're ready to start developing the plugin. You can test the plugin without installing it into your Vagrant environment by just creating a `Vagrantfile` in the top level of this directory (it is gitignored) -and add the following line to your `Vagrantfile` -```ruby -Vagrant.require_plugin "vagrant-aws" -``` + Use bundler to execute Vagrant: ``` $ bundle exec vagrant up --provider=aws diff --git a/lib/vagrant-aws/action.rb b/lib/vagrant-aws/action.rb index c140ac44..7e83750d 100644 --- a/lib/vagrant-aws/action.rb +++ b/lib/vagrant-aws/action.rb @@ -51,8 +51,9 @@ def self.action_destroy next end b3.use ConnectAWS - b3.use ElbDeregisterInstance - b3.use TerminateInstance + b3.use DestroyAdditionalNetworkInterfaces + b3.use ElbDeregisterInstance + b3.use TerminateInstance b3.use ProvisionerCleanup if defined?(ProvisionerCleanup) end else @@ -157,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 @@ -204,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..3ab309aa --- /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 \ No newline at end of file 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..fe06d757 --- /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 \ No newline at end of file diff --git a/lib/vagrant-aws/action/run_instance.rb b/lib/vagrant-aws/action/run_instance.rb index 225433a2..6e8b079f 100644 --- a/lib/vagrant-aws/action/run_instance.rb +++ b/lib/vagrant-aws/action/run_instance.rb @@ -42,7 +42,7 @@ def call(env) iam_instance_profile_name = region_config.iam_instance_profile_name monitoring = region_config.monitoring ebs_optimized = region_config.ebs_optimized - associate_public_ip = region_config.associate_public_ip + associate_public_ip = region_config.associate_public_ip # If there is no keypair then warn the user if !keypair @@ -90,7 +90,7 @@ def call(env) :instance_initiated_shutdown_behavior => terminate_on_shutdown == true ? "terminate" : nil, :monitoring => monitoring, :ebs_optimized => ebs_optimized, - :associate_public_ip => associate_public_ip + :associate_public_ip => associate_public_ip } if !security_groups.empty? security_group_key = options[:subnet_id].nil? ? :groups : :security_group_ids diff --git a/lib/vagrant-aws/config.rb b/lib/vagrant-aws/config.rb index 86b4be51..c46f0560 100644 --- a/lib/vagrant-aws/config.rb +++ b/lib/vagrant-aws/config.rb @@ -160,6 +160,12 @@ class Config < Vagrant.plugin("2", :config) # @return [String] attr_accessor :elb + # 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 @@ -190,6 +196,7 @@ def initialize(region_specific=false) @ebs_optimized = UNSET_VALUE @associate_public_ip = UNSET_VALUE @elb = UNSET_VALUE + @additional_network_interfaces = [] # Internal state (prefix with __ so they aren't automatically # merged) @@ -389,7 +396,7 @@ def validate(machine) end errors << I18n.interpolate("vagrant_aws.config.ami_required", :region => @region) if config.ami.nil? - end + end { "AWS Provider" => errors } end diff --git a/lib/vagrant-aws/util/network_adapters.rb b/lib/vagrant-aws/util/network_adapters.rb new file mode 100644 index 00000000..55276fed --- /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 \ No newline at end of file diff --git a/locales/en.yml b/locales/en.yml index 00513ba6..3ecf381c 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -59,6 +59,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 b41588f1..54517ee8 100644 --- a/spec/vagrant-aws/config_spec.rb +++ b/spec/vagrant-aws/config_spec.rb @@ -42,6 +42,7 @@ its("monitoring") { should == false } its("ebs_optimized") { should == false } its("associate_public_ip") { should == false } + its("additional_network_interfaces") { should == [] } end describe "overriding defaults" do