Use Vagrant 1.8's new ansible_local provisioner for Ansible provisioning

I build a lot of local development VMs in a typical week, and need to support Ansible provisioning on Mac, Linux, and Windows workstations (with or without Ansible installed)—Vagrant 1.8.0 was an early Christmas gift for me!

In the past, when I wanted to build a Vagrantfile to provision a VM using an Ansible playbook, I added the following, which used the JJG-Ansible-Windows shell script to install Ansible inside the VM, install role dependencies, and run a given Ansible playbook:

  # Use Ansible provisioner if it's installed on host, JJG-Ansible-Windows if not.
  if which('ansible-playbook')
    config.vm.provision "ansible" do |ansible|
      ansible.playbook = "#{dir}/provisioning/playbook.yml"
      ansible.sudo = true
    end
  else
    config.vm.provision "shell" do |sh|
      sh.path = "#{dir}/provisioning/JJG-Ansible-Windows/windows.sh"
      sh.args = "/provisioning/playbook.yml"
    end
  end

There were a few downsides to this approach: it required inclusion of the JJG-Ansible-Windows script/project in the repository, terminal output was passed through without color (so it's harder to read playbook output), terminal output was had a long delay, and the JJG-Ansible-Windows shell script itself isn't as widely tested as a solution baked into Vagrant.

Vagrant 1.8.0 (and beyond) includes the new <a href="https://docs.vagrantup.com/v2/provisioning/ansible_local.html">ansible_local</a> provisioner. Instead of using a shell script to install Ansible, install Galaxy roles, and run a playbook, you can pass in options like playbook and galaxy_role_file, and Vagrant does all the hard work:

  # Use ansible provisioner if it's installed on host, ansible_local if not.
  if which('ansible-playbook')
    config.vm.provision "ansible" do |ansible|
      ansible.playbook = "#{dir}/provisioning/playbook.yml"
    end
  else
    config.vm.provision "ansible_local" do |ansible|
      ansible.playbook = "provisioning/playbook.yml"
      ansible.galaxy_role_file = "provisioning/requirements.yml"
    end
  end

The above example is taken from Drupal VM, a VM built for easy local Drupal development environments.

Run the playbook on a host without Ansible installed, and you'll notice Vagrant does all the extra work to run the playbook inside the guest:

$ vagrant provision
==> drupalvm: Running provisioner: ansible_local...
    drupalvm: Installing Ansible...
    drupalvm: Running ansible-galaxy...
    [...]
    drupalvm: Running ansible-playbook...

PLAY [all] ********************************************************************

GATHERING FACTS ***************************************************************
ok: [drupalvm]
[...]

One minor downside to this approach is that, because the ansible-galaxy install command isn't idempotent (see this issue), it will re-download all configured roles every time you run vagrant provision, and depending on how many roles your project requires, this could add a bit of time to the project's provisioning!

For more details and options, be sure to read through the main ansible_local documentation page, as well as the common options shared between both Ansible provisioners.