vagrant-hostmanager
vagrant-hostmanager copied to clipboard
Make hostmanager work with DHCP addresses
It seems like vagrant-hostmanager is reading the IP address of a host from the Vagrantfile. However, when using DHCP, that does not work.
I'm currently using the following custom resolver to assign the correct IP address of my eth1 device (I'm using this to assign a private IP in Vagrant):
node.hostmanager.ip_resolver = proc do |vm, resolving_vm|
if hostname = (vm.ssh_info && vm.ssh_info[:host])
`vagrant ssh -c "/sbin/ifconfig eth1" | grep "inet" | tail -n 1 | egrep -o "[0-9\.]*" | head -n 1 2>&1`.split("\n").first[/(\d+\.\d+\.\d+\.\d+)/, 1]
end
end
Made several changes due to ip6 address etc.
config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
if hostname = (vm.ssh_info && vm.ssh_info[:host])
`vagrant ssh -c "/sbin/ifconfig eth1" | grep "inet addr" | tail -n 1 | egrep -o "[0-9\.]+" | head -n 1 2>&1`.split("\n").first[/(\d+\.\d+\.\d+\.\d+)/, 1]
end
end
Sorry for the delay in getting to this. I'd like to implement this capability into hostmanager, pending #99 and #100. If anyone is interested in helping out with those, let me know!
For now, I think using a custom ip_resolver is the only way to go.
Hi, i want to use this plugin to detect the private network ip of vagrant box and update /etc/hosts on my host so i can access the apache webserver over the hostname like www.mydev.com.
After the standard vagrant init, up and ssh
vagrant init ubuntu/trusty64
vagrant up
vagrant ssh
i install apache2 with the common command
sudo apt-get install apache2
after that i leave the box and shutdown it with vagrant halt, until this point i don't touch the Vagrantfile, it was the standard file created by vagrant. Now i want to update /etc/hosts on host but ONLY the /etc/hosts on hosts without modify the hostname on guest. I use private network dhcp like @stucki but the plugin don't update my /etc/hosts. My Vagrantfile looks like:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.hostname = 'www.mydev.com'
config.vm.network "private_network", type: "dhcp"
config.hostmanager.enabled = false
config.hostmanager.manage_host = true
config.hostmanager.ignore_private_ip = false
config.hostmanager.include_offline = true
config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
if hostname = (vm.ssh_info && vm.ssh_info[:host])
`vagrant ssh -c "/sbin/ifconfig eth1" | grep "inet" | tail -n 1 | egrep -o "[0-9\.]*" | head -n 1 2>&1`.split("\n").first[/(\d+\.\d+\.\d+\.\d+)/, 1]
end
end
end
Is this a bug or do i something wrong? And how can i prevent that vagrant change the hostname on guest, i want only set the right private ip with a specific hostname into /etc/hosts on host.
If you know you're using VirtualBox as a provider, this is significantly faster than SSHing in (for me anyway):
config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
if vm.id
`VBoxManage guestproperty get #{vm.id} "/VirtualBox/GuestInfo/Net/1/V4/IP"`.split()[1]
end
end
Updated with @m1keil's suggestion, thanks. Also wrapped in a conditional to handle upping groups of VMs, some of which might not be created yet.
halfninja thx for that config line. just minor fix - it probably needs to be printf in awk and not print because print will insert newline and break hostfile
Another possible edit is to use Ruby's split() instead of AWK, so this will work on Windows as well:
`VBoxManage guestproperty get #{vm.id} "/VirtualBox/GuestInfo/Net/1/V4/IP"`.split()[1]
Yeah, that makes loads more sense - got caught up in the command line and forgot I was in Ruby :)
+1
Custom resolver works, but having DHCP option supported natively would be great!
+1 for support of DHCP-assigned private secondary interfaces.
Also, any general solution should not rely on "vagrant ssh" to gain access into the VM. Be aware that the guests running Windows generally lack ssh access and instead use a winrm communicator. I share this not because I'm a Windows fanboy but because I have to deal with the darn guests and nifty plugins that rely on "vagrant ssh" cannot be used for networks of heterogeneous-os clients.
The VBoxManage trick here avoids the vagrant ssh issue but has a similar issue with expecting all the clients to be provisioned to local VirtualBox.
What's needed is a general solution that works regardless of the chosen communicators and the chosen providers.
In case it's helpful to anyone, this variant doesn't use vagrant ssh, which can break if this is called while another vagrant process has the VM locked (which seems to be the case during vagrant up). Instead it uses Vagrant's API to communicate with the VM. It also caches the results, which if you have more than a trivial number of VMs can be a big win. I use this with EC2 to get at the private IP addresses because my setup requires it an vagrant-aws only exposes the public IP address.
cached_addresses = {}
config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
if cached_addresses[vm.name].nil?
if hostname = (vm.ssh_info && vm.ssh_info[:host])
vm.communicate.execute("/sbin/ifconfig eth0 | grep 'inet addr' | tail -n 1 | egrep -o '[0-9\.]+' | head -n 1 2>&1") do |type, contents|
cached_addresses[vm.name] = contents.split("\n").first[/(\d+\.\d+\.\d+\.\d+)/, 1]
end
end
end
cached_addresses[vm.name]
end
+1
@ewencp awesome thanks. If anyone has trouble with, try changing eth0 to another interface
Is vm.communicate undocumented? It seems so useful. Ideally, the VM should be telling us its IP address once its has booted.
Provider independent and works even with changed locales (german) which response with: inet Adresse
config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
if hostname = (vm.ssh_info && vm.ssh_info[:host])
`vagrant ssh -c "hostname -I"`.split()[1]
end
end
+1 :+1:
@ewencp also works in multi machine set up :100:
@ewencp thanks. I modified your answer to work with CentOS 7 since ifconfig is not included by default. Now, if I can just get this to return the external IPs on the AWS instances only on the host I'll be set.
config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
if cached_addresses[vm.name].nil?
if hostname = (vm.ssh_info && vm.ssh_info[:host])
vm.communicate.execute("/usr/sbin/ip addr show eth0 | grep 'inet ' | xargs | cut -f 2 -d ' '| cut -f 1 -d '/' 2>&1") do |type, contents|
cached_addresses[vm.name] = contents.split("\n").first[/(\d+\.\d+\.\d+\.\d+)/, 1]
end
end
end
cached_addresses[vm.name]
end
The centos7 impl for me was a ifesaver, thanks alot for this... so .. Is this hack still necessary? I think so.
My version, combining @dominikzogg's use of hostname -I with @ewencp's solution of caching the results and not using vagrant ssh. This one should work on centos/rhel 6, centos/rhel 7, and ubuntu 15.10 (and hopefully most linux guests).
config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
if cached_addresses[vm.name].nil?
if hostname = (vm.ssh_info && vm.ssh_info[:host])
vm.communicate.execute("hostname -I | cut -d ' ' -f 2") do |type, contents|
cached_addresses[vm.name] = contents.split("\n").first[/(\d+\.\d+\.\d+\.\d+)/, 1]
end
end
end
cached_addresses[vm.name]
end
Combining all the above as well as handling AWS private/public IP, Linux only but works on Redhat and Debian based system, and does not use vagrant ssh:
$cached_addresses = {}
$ip_resolver = proc do |vm, resolving_vm|
# For aws, we should use private IP on the guests, public IP on the host
if vm.provider_name == :aws
if resolving_vm.nil?
used_name = vm.name.to_s + '--host'
else
used_name = vm.name.to_s + '--guest'
end
else
used_name= vm.name.to_s
end
if $cached_addresses[used_name].nil?
if hostname = (vm.ssh_info && vm.ssh_info[:host])
# getting aws guest ip *for the host*, we want the public IP in that case.
if vm.provider_name == :aws and resolving_vm.nil?
vm.communicate.execute('curl http://169.254.169.254/latest/meta-data/public-ipv4') do |type, pubip|
$cached_addresses[used_name] = pubip
end
else
vm.communicate.execute('uname -o') do |type, uname|
unless uname.downcase.include?('linux')
warn("Guest for #{vm.name} (#{vm.provider_name}) is not Linux, hostmanager might not find an IP.")
end
end
vm.communicate.execute('hostname --all-ip-addresses') do |type, hostname_i|
# much easier (but less fun) to work in ruby than sed'ing or perl'ing from shell
allips = hostname_i.strip().split(' ')
if vm.provider_name == :virtualbox
# 10.0.2.15 is the default virtualbox IP in NAT mode.
allips = allips.select { |x| x != '10.0.2.15'}
end
if allips.size() == 0
warn("Trying to find out ip for #{vm.name} (#{vm.provider_name}), found none useable: #{allips}.")
else
if allips.size() > 1
warn("Trying to find out ip for #{vm.name} (#{vm.provider_name}), found too many: #{allips} and I cannot choose cleverly. Will select the first one.")
end
$cached_addresses[used_name] = allips[0]
end
end
end
end
end
$cached_addresses[used_name]
end
I found that I needed to wrap the communicate.execute in a "communicate.ready?" because otherwise issuing a vagrant destroy would fail because the machine is already down before cleaning up the /etc/hosts file:
vagrant destroy -f
==> hypernode: Forcing shutdown of VM...
==> hypernode: Destroying VM and associated drives...
==> hypernode: Running cleanup tasks for 'shell' provisioner...
==> hypernode: Updating /etc/hosts file on active guest machines...
==> hypernode: Updating /etc/hosts file on host machine (password may be required)...
The provider for this Vagrant-managed machine is reporting that it
is not yet ready for SSH. Depending on your provider this can carry
different meanings. Make sure your machine is created and running and
try again. Additionally, check the output of `vagrant status` to verify
that the machine is in the state that you expect. If you continue to
get this error message, please view the documentation for the provider
you're using.
Custom ip_resolver based on this issue with "if machine.communicate.ready?":
# Get the dynamic hostname from the running box so we know what to put in
# /etc/hosts even though we don't specify a static private ip address
config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
if vm.communicate.ready?
result = ""
vm.communicate.execute("ifconfig eth1") do |type, data|
result << data if type == :stdout
end
end
(ip = /inet addr:(\d+\.\d+\.\d+\.\d+)/.match(result)) && ip[1]
end
vagrant destroy -f
==> hypernode: Forcing shutdown of VM...
Connection to 127.0.0.1 closed by remote host.
==> hypernode: Destroying VM and associated drives...
==> hypernode: Running cleanup tasks for 'shell' provisioner...
==> hypernode: Updating /etc/hosts file on active guest machines...
==> hypernode: Updating /etc/hosts file on host machine (password may be required)...
`Vagrant.configure("2") do |config| #-----------------------------------# # Use this base box # #-----------------------------------# config.vm.box = "ubuntu/trusty64"
#-----------------------------------#
# Port Forwading and networking #
#-----------------------------------#
config.vm.network :private_network, ip: "10.0.240.80"
config.vm.network :private_network, type: :dhcp
config.vm.network :forwarded_port, guest: 80, host: 80 #webserver
config.vm.network :forwarded_port, guest: 443, host: 8443 #webserver (ssl)
config.vm.network :forwarded_port, guest: 3306, host: 3306 #mysql
config.vm.network :forwarded_port, guest: 3819, host: 3819 #phpmyadmin
#-----------------------------------#
# Update hostsfile #
#-----------------------------------#
config.hostmanager.enabled = true
config.hostmanager.manage_host = true
config.hostmanager.manage_guest = true
config.hostmanager.ignore_private_ip = false
config.hostmanager.include_offline = true
config.vm.hostname = "docker-host.vm"
config.hostmanager.aliases = ["docker-host-alias.vm"]
config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
if hostname = (vm.ssh_info && vm.ssh_info[:host])
vagrant ssh -c "hostname -I".split()[1]
end
end
#-----------------------------------#
# VirtualBox Setup and Optimization #
#-----------------------------------#
config.vm.provider :virtualbox do |v|
v.name = "docker-host"
v.customize ["modifyvm", :id, "--name", "docker-host"]
v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
v.customize ["modifyvm", :id, "--memory", 2048]
v.customize ["modifyvm", :id, "--cpus", 2]
v.customize ["modifyvm", :id, "--chipset", "ich9"]
v.customize ["modifyvm", :id, "--ioapic", "on"]
v.customize ["modifyvm", :id, "--rtcuseutc", "on"]
v.customize ["modifyvm", :id, "--pae", "on"]
v.customize ["modifyvm", :id, "--hwvirtex", "on"]
v.customize ["modifyvm", :id, "--nestedpaging", "on"]
# Workaround: stability fix
v.auto_nat_dns_proxy = false
v.customize ["modifyvm", :id, "--natdnsproxy1", "off" ]
v.customize ["modifyvm", :id, "--natdnshostresolver1", "off" ]
# network
v.customize ["modifyvm", :id, "--nictype1", "virtio"]
v.customize ["modifyvm", :id, "--nictype2", "virtio"]
end
config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
puts "==============================================================================================================="
puts "#{`\"#{ENV['VBOX_MSI_INSTALL_PATH']}\\VBoxManage\" guestproperty get #{vm.id} "/VirtualBox/GuestInfo/Net/1/V4/IP"`}"
puts "==============================================================================================================="
puts "=============== VB ID: #{vm.id}"
if vm.id && Vagrant::Util::Platform.windows?
`\"#{ENV['VBOX_MSI_INSTALL_PATH']}\\VBoxManage\" guestproperty get #{vm.id} "/VirtualBox/GuestInfo/Net/1/V4/IP"`.split()[1]
else
`VBoxManage guestproperty get #{vm.id} "/VirtualBox/GuestInfo/Net/1/V4/IP"`.split()[1]
end
end`
This fails as at the time of querying the IP address it seems that there is no value set: Output:
`==> default: Updating /etc/hosts file on active guest machines...
No value set!
=============== VB ID: 03ec23de-18c5-4583-9e86-3406a8c62a22 `
Queryiing manually in a windows cmd after the machine is created reveals the correct IP address:
"C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" guestproperty get 03ec23de-18c5-4583-9e86-3406a8c62a22 "/VirtualBox/GuestInfo/Net/1/V4/IP" Value: 172.28.128.16
Any ideas where it goes wrong?
Hello!
Since this issue seems to SEO pretty well, as of vagrant 1.8.5 and later on ubuntu 16.10 (host) and Centos7 (guest), this line;
cached_addresses[vm.name] = contents.split("\n").first[/(\d+\.\d+\.\d+\.\d+)/, 1]
throws a nasty ruby exception saying,
/Vagrantfile:63:in `block (3 levels) in <top (required)>': undefined method `[]' for nil:NilClass (NoMethodError)`
I found a fix here that fixes the issue and does not rely on vagrant ssh. Here is my updated version of the read_ip_address function that allows it to get the ip address second interface.
def read_ip_address(machine)
command = "ip a | grep 'inet' | grep -v '127.0.0.1' | cut -d: -f2 | awk '{ print $2 }' | cut -f1 -d\"/\""
result = ""
$logger.info "Processing #{ machine.name } ... "
begin
# sudo is needed for ifconfig
machine.communicate.sudo(command) do |type, data|
result << data if type == :stdout
end
$logger.info "Processing #{ machine.name } ... success"
rescue
result = "# NOT-UP"
$logger.info "Processing #{ machine.name } ... not running"
end
# the second inet is more accurate
result.chomp.split("\n").last
end
Hopefully this helps people work around the regression in 1.8.5 and later. For full context here this is a gist of my complete Vagrantfile
@Max-AR I have this error with your complete Vagrantfile in my context:
/Users/stephane/projets/openshift/ta-ansible/vagrant/Vagrantfile:24:in `read_ip_address': undefined method `info' for nil:NilClass (NoMethodError)
full log and Vagrantfile: https://gist.github.com/harobed/f3198e47417b026969fcae99b6cef39d
My mistake: I forget this line
$logger = Log4r::Logger.new('vagrantfile')
Hi, Is this issue still relevant? I just tried to start two Vagrant VM with some basic configuration (DHCP) and this hostmanager config
config.hostmanager.enabled = true
config.hostmanager.manage_host = false
config.hostmanager.manage_guest = true
When starting VM, I see a message about updating /etc/hosts
vm2: Updating /etc/hosts file on active guest machines...
vm1: Updating /etc/hosts file on active guest machines...
And in fact the two VM have a correct /etc/hosts and can communicate with each other.
Without hack, in /etc/hosts on guest machine I have:
## vagrant-hostmanager-start id: 11058adc-e452-4267-8273-a16ce56cc723
127.0.0.1 server-elk
## vagrant-hostmanager-end
with the hack, I don't have 127.0.0.1 but the real ip 172.28.128.27.
Then I think that the bug is always here.
Context:
- vagrant 1.9.2
- debian jessie
$ vagrant ssh -c "sudo ip addr"
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:7a:b8:ad brd ff:ff:ff:ff:ff:ff
inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe7a:b8ad/64 scope link
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:99:35:b5 brd ff:ff:ff:ff:ff:ff
inet 172.28.128.27/24 brd 172.28.128.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe99:35b5/64 scope link
valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:37:99:e4:c5 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 scope global docker0
valid_lft forever preferred_lft forever
Connection to 127.0.0.1 closed.
It's weird because I don't have the hack.
$ vagrant -v
Vagrant 1.9.1
$ vagrant plugin list
vagrant-cachier (1.2.1)
vagrant-env (0.0.3)
vagrant-hostmanager (1.8.5)
vagrant-libvirt (0.0.37)
vagrant-mutate (1.2.0)
vagrant-scp (0.5.7)
vagrant-share (1.1.6, system)
What say vagrant ssh -c "sudo ip addr" ?
I think that your first interface isn't lo:.
I got lo and eth0 in this order, for both machines.
I have same, I have tried many ip_resolver, but no onne works
Vagrant.configure("2") do |config|
config.vm.box = "bento/centos-7.4"
# Manage hosts file https://github.com/devopsgroup-io/vagrant-hostmanager
config.hostmanager.enabled = true
config.hostmanager.manage_host = true
config.vm.hostname = 'uwbox'
config.hostmanager.aliases = %w(uwbox.loc)
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
end
end
➜ vgtest vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'uw172'...
==> default: Matching MAC address for NAT networking...
==> default: Setting the name of the VM: vgtest_default_1509098784527_71154
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
default: Adapter 1: nat
==> default: Forwarding ports...
default: 22 (guest) => 2222 (host) (adapter 1)
==> default: Running 'pre-boot' VM customizations...
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
default: SSH address: 127.0.0.1:2222
default: SSH username: uniworldbox
default: SSH auth method: private key
default: Warning: Remote connection disconnect. Retrying...
default:
default: Vagrant insecure key detected. Vagrant will automatically replace
default: this with a newly generated keypair for better security.
default:
default: Inserting generated public key within guest...
default: Removing insecure key from the guest if it's present...
default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Setting hostname...
==> default: Mounting shared folders...
default: /vagrant => /Users/i_skiridomov/Projects/vgtest
==> default: [vagrant-hostmanager:guests] Updating hosts file on active guest virtual machines...
==> default: [vagrant-hostmanager:host] Updating hosts file on your workstation (password may be required)...
## vagrant-hostmanager-start id: 5300db46-00b5-48f0-a201-52edb2e1cd76
127.0.0.1 uwbox
127.0.0.1 uwbox.loc
## vagrant-hostmanager-end
Update: Only after static_private ip it works
config.vm.network :private_network, ip: "192.168.50.50"