How to Reboot a Vagrant Guest VM During Provisioning
It is very rarely the case that you find yourself in the awkward position of needing to reboot a Vagrant guest VM during the process of provisioning it. Most people use Vagrant to run Linux server distributions, and there is thus very little you can install or change that absolutely requires a restart. Nonetheless, sometimes you find yourself painted into this corner, and that is when you find out that there is in fact no built in method in Vagrant to reboot a machine during the provisioning process. At present to achieve this goal requires a little monkey-patching and unfortunately some reliance on private APIs in Vagrant.
The approach adopted here is to create two new types of provisioner, one for UNIX and one for Windows machines, each of which reboots the VM and then reinstates the synced folder mappings when it comes back up. Since multiple provisioning blocks can be defined for a given VM, it is possible to split provisioning into (a) pre-reboot, (b) reboot, and (c) post-reboot blocks. You can find the code for these reboot provisioners bundled up and documented at GitHub, as well as presented below.
Update 01/25/2015: There is a much better and more robust approach than the one outlined here, which is to create a new provisioner that carries out the standard reload action. That handles all of the awkward issues of synced folder mapping under the hood without tinkering with private APIs. You can find an implementation of the Vagrant Reload Provisioner at GitHub.
The start of a Vagrantfile that uses the reboot provisioner monkey patch will look something like this:
# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" # Require the reboot plugin. require './vagrant-provision-reboot-plugin' Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| # Run your pre-reboot provisioning block. #config.vm.provision :chef_solo do |chef| # ... #end # Run a reboot of a *NIX guest. config.vm.provision :unix_reboot # Run a reboot of a Windows guest, assuming that you are set up with the # relevant plugins and configurations to manage a Windows guest in Vagrant. #config.vm.provision :windows_reboot # Run your post-reboot provisioning block. #config.vm.provision :chef_solo do |chef| # ... #end
The code in vagrant-provision-reboot-plugin.rb is not very complicated in and of itself, and the only trick to it is the remapping of synced folders after the reboot completes. Unfortunately that is the part that requires use of non-public APIs; it will be fragile going forward as Vagrant evolves.
# A quick hack to allow rebooting of a Vagrant VM during provisioning. # # This is tested with Vagrant 1.4.3 and 1.6.1. It may work with slightly earlier # versions, but definitely won't work with 1.3.* or earlier. The code is fragile # with respect to internal changes in Vagrant, as there is no useful public API # that allows a reboot to be engineered. # # Originally adapted from: https://gist.github.com/ukabu/6780121 # # This file should be placed into the same folder as your Vagrantfile. Then in # your Vagrantfile, you'll want to do something like the following: # # ---------------------------------------------------------------------------- # # require './vagrant-provision-reboot-plugin' # # Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| # # # Run your pre-reboot provisioning block. # #config.vm.provision :chef_solo do |chef| # # ... # #end # # # Run a reboot of a *NIX guest. # config.vm.provision :unix_reboot # # Run a reboot of a Windows guest, assuming that you are set up with the # # relevant plugins and configurations to manage a Windows guest in Vagrant. # #config.vm.provision :windows_reboot # # # Run your post-reboot provisioning block. # #config.vm.provision :chef_solo do |chef| # # ... # #end # # ---------------------------------------------------------------------------- # # The provisioner takes care of remounting the synced folders. # # This will work for the VirtualBox provider. For other providers, a # 'remount_synced_folders' action must be added to the provider implementation. require 'vagrant' # Monkey-patch the VirtualBox provider to be able to remap synced folders after # reboot. This is the tricky part. # # This involves pulling out some code fragments from the existing SyncedFolders # class - which is unpleasant, but there are no usefully exposed methods such # that we can run only what we need to. module VagrantPlugins module ProviderVirtualBox module Action class RemountSyncedFolders < SyncedFolders def initialize(app, env) super(app, env) end def call(env) @env = env @app.call(env) # Copied out of /lib/vagrant/action/builtin/synced_folders.rb in # Vagrant 1.4.3, and surprisingly still working in 1.6.1. # # This is going to be fragile with respect to future changes, but # that's just the way the cookie crumbles. # # We can't just run the whole SyncedFolders.call() method because # it undertakes a lot more setup and will error out if invoked twice # during "vagrant up" or "vagrant provision". folders = synced_folders(env[:machine]) folders.each do |impl_name, fs| plugins[impl_name.to_sym][0].new.enable(env[:machine], fs, impl_opts(impl_name, env)) end end end def self.action_remount_synced_folders Vagrant::Action::Builder.new.tap do |b| b.use RemountSyncedFolders end end end end end # Define the plugin. class RebootPlugin < Vagrant.plugin('2') name 'Reboot Plugin' # This plugin provides a provisioner called unix_reboot. provisioner 'unix_reboot' do # Create a provisioner. class RebootProvisioner < Vagrant.plugin('2', :provisioner) # Initialization, define internal state. Nothing needed. def initialize(machine, config) super(machine, config) end # Configuration changes to be done. Nothing needed here either. def configure(root_config) super(root_config) end # Run the provisioning. def provision command = 'shutdown -r now' @machine.ui.info("Issuing command: #{command}") @machine.communicate.sudo(command) do |type, data| if type == :stderr @machine.ui.error(data); end end begin sleep 5 end until @machine.communicate.ready? # Now the machine is up again, perform the necessary tasks. @machine.ui.info("Launching remount_synced_folders action...") @machine.action('remount_synced_folders') end # Nothing needs to be done on cleanup. def cleanup super end end RebootProvisioner end # This plugin provides a provisioner called windows_reboot. provisioner 'windows_reboot' do # Create a provisioner. class RebootProvisioner < Vagrant.plugin('2', :provisioner) # Initialization, define internal state. Nothing needed. def initialize(machine, config) super(machine, config) end # Configuration changes to be done. Nothing needed here either. def configure(root_config) super(root_config) end # Run the provisioning. def provision command = 'shutdown -t 0 -r -f' @machine.ui.info("Issuing command: #{command}") @machine.communicate.execute(command) do if type == :stderr @machine.ui.error(data); end end begin sleep 5 end until @machine.communicate.ready? # Now the machine is up again, perform the necessary tasks. @machine.ui.info("Launching remount_synced_folders action...") @machine.action('remount_synced_folders') end # Nothing needs to be done on cleanup. def cleanup super end end RebootProvisioner end end