Detecting if a virtualbox machine already exists in Vagrant

Posted by: on

Problem

You want to change a VM’s settings depending upon whether the VM is being built or simply being rebooted (for my specific situation, see below).

For example, during the initial provisioning run of the VM you might want to use the host DNS but on subsequent reboots you want to rely on a guest DNS.

  1. vagrant runs when the guest machine does not exist, you want configuration A.

  2. vagrant runs when the guest machine exists (and, we will, assume has been provisioned), you want configuration B.

Solution

Probing virtualbox files

Simply using the following lines ensures that vagrant modifies the virtualbox to boot with different VM settings (or whatever other logic you need to vary).

1
2
3
4
5
6
7
8
9
10
11
12
require 'pathname'

config.vm.define :mysrv do |mysrv|
  mysrv.vm.provider "virtualbox" do |vb|
    machine_id_file=Pathname.new(".vagrant/machines/mysrv/virtualbox/id")
    if machine_id_file.exist?()
      # INSERT YOUR REBOOT LOGIC HERE
    else
      # INSERT YOUR PROVISION LOGIC HERE
    end
  end
end

Discussion

You cannot use a provisioner to vary VM settings (provisioners run on the machine).

The solution offered works by probing the VM file system.

A bit of investigation revealed that a machine that exists has files in the directory under .vagrant/machine/mysrv/virtualbox (obviously, replacing mysrv with the actual machine id).

Line 1 provides the Pathname class.

Line 5 sets up to look for the virtualbox id file for the machine we’re interested in.

Line 6 checks if the file exists. The first time this Vagrantfile is processed the machine will not exist (and so nor will this file), once the machine exists the id file will be present, so the additional modifyvm lines (7-9) will be executed. If you subsequently destroy the machine the id file is removed along with the machine.

Line 7 is replaced by whatever you want to do when the machine is simply being rebooted.

Line 9 is replaced with whatever you want to do when the machine is about to be created.

Motivation

This problem presented itself while I was putting together a dev/test environment for the salt management system on my home system. I needed to disable the virtualbox DNS handling as set up by vagrant. The default makes sense for the general use case. Most of the time having the machine NAT onto the host network and pass DNS queries via the host both makes sense.

In my setup I created a nameserver on one of the VMs and I needed to ensure no interference. To do this I wanted to ensure all the virtualbox DNS facilities were turned off. But, I wanted to maintain this facility during the first provisioning boot (it simplifies installing the salt-minion). Hence this solution.

For the curious, here is the block I added to the Vagrantfile.

1
2
3
4
5
6
7
8
9
require 'pathname'

# The following block is added inside the "virtualbox" provider block
      machine_files=Pathname.new(".vagrant/machines/prsrv002/virtualbox/id" )
      if machine_files.exist?()
        vb.customize ["modifyvm", :id, "--natdnspassdomain1", "off"]
        vb.customize ["modifyvm", :id, "--natdnsproxy1", "off"]
        vb.customize ["modifyvm", :id, "--natdnshostresolver1", "off"]
      end

On the initial boot of the VM the standard vagrant configuration would be in place. My salt system then modifies the machine’s configuration. This works until that machine is rebooted. Under normal circumstances a reboot would result in vagrant/virtualbox updating the VM to use the host DNS. To avoid this the code block above takes over and uses the vb.customize to disable all virtualbox natdns* settings.

One other tweak required on the VM was to ensure the dhclient did not try to change the resolv.conf settings that salt would manage. To this end I added this to the Vagrantfile

1
2
3
4
5
6
7
8
9
10
11
# prime_lan_interface manually configures a static interface so salt can be used
# without vagrant constantly wanting to control the interface subsequently
prime_lan_interface = "cat<<EOF >>/etc/network/interfaces
iface eth1 inet static
  address 192.168.1.254
  netmask 255.255.255.0
EOF
ifup eth1"

# Then invoke this in as a provisioner on any machine needing the dhclient resolv.conf updates disabling
prsrv002.vm.provision "shell", inline: prime_lan_interface

Opinion

Arguably we could change the way virtualbox networks the virtual machine (for example, bridging rather than NAT) but I’m a firm believer in making the smallest changes necessary to achieve a result and it is possible that a more radical change than that shown could have unanticipated effects on the way vagrant interacts with our machine.