Merge 'gildegoma/2103-ansible-local-v2'

Resolve conflict in CHANGELOG.md
This commit is contained in:
Gilles Cornu 2015-11-10 23:05:29 +01:00
commit c6ef73a6fa
29 changed files with 1729 additions and 748 deletions

View File

@ -8,16 +8,33 @@ FEATURES:
and restore point-in-time snapshots.
- **IPv6 Private Networks**: Private networking now supports IPv6. This
only works with VirtualBox and VMware at this point. [GH-6342]
- New provisioner: `ansible_local` to execute Ansible from the guest
machine. [GH-2103]
BREAKING CHANGES:
- the `ansible` provisioner now can override the effective ansible remote user
(i.e. `ansible_ssh_user` setting) to always correspond to the vagrant ssh
username. This change is enabled by default, but we expect this to affect
only a tiny number of people as it corresponds to the common usage.
If you however use multiple remote usernames in your Ansible plays, tasks,
or custom inventories, you can simply set the option `force_remote_user` to
false to make Vagrant behave the same as before.
IMPROVEMENTS:
- core: allow removal of all box versions with `--all` flag [GH-3462]
- provisioners/ansible: add new `force_remote_user` option to control whether
`ansible_ssh_user` parameter should be applied or not [GH-6348]
- provisioners/ansible: show a warning when running from a Windows Host [GH-5292]
BUG FIXES:
- communicator/winrm: respect `boot_timeout` setting [GH-6229]
- provisioners/ansible: use quotes for the `ansible_ssh_private_key_file`
value in the generated inventory [GH-6209]
- provisioners/ansible: don't show the `ansible-playbook` command when verbose
option is an empty string
## 1.7.4 (July 17, 2015)

View File

@ -108,14 +108,6 @@ module Vagrant
error_key(:active_machine_with_different_provider)
end
class AnsibleFailed < VagrantError
error_key(:ansible_failed)
end
class AnsiblePlaybookAppNotFound < VagrantError
error_key(:ansible_playbook_app_not_found)
end
class BatchMultiError < VagrantError
error_key(:batch_multi_error)
end

View File

@ -0,0 +1,19 @@
module VagrantPlugins
module Ansible
module Cap
module Guest
module Arch
module AnsibleInstall
def self.ansible_install(machine)
machine.communicate.sudo("pacman -Syy --noconfirm")
machine.communicate.sudo("pacman -S --noconfirm ansible")
end
end
end
end
end
end
end

View File

@ -0,0 +1,28 @@
module VagrantPlugins
module Ansible
module Cap
module Guest
module Debian
module AnsibleInstall
def self.ansible_install(machine)
install_backports_if_wheezy_release = <<INLINE_CRIPT
CODENAME=`lsb_release -cs`
if [ x$CODENAME == 'xwheezy' ]; then
echo 'deb http://http.debian.net/debian wheezy-backports main' > /etc/apt/sources.list.d/wheezy-backports.list
fi
INLINE_CRIPT
machine.communicate.sudo(install_backports_if_wheezy_release)
machine.communicate.sudo("apt-get update -y -qq")
machine.communicate.sudo("apt-get install -y -qq ansible")
end
end
end
end
end
end
end

View File

@ -0,0 +1,39 @@
module VagrantPlugins
module Ansible
module Cap
module Guest
module EPEL # Extra Packages for Enterprise Linux (for RedHat-family distributions)
module AnsibleInstall
# This should work on recent Fedora releases, and any other members of the
# RedHat family which supports YUM and http://fedoraproject.org/wiki/EPEL
def self.ansible_install(machine)
configure_epel = <<INLINE_CRIPT
cat <<EOM >/etc/yum.repos.d/epel-bootstrap.repo
[epel]
name=Bootstrap EPEL
mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=epel-\$releasever&arch=\$basearch
failovermethod=priority
enabled=0
gpgcheck=0
EOM
yum --assumeyes --quiet --enablerepo=epel install epel-release
rm -f /etc/yum.repos.d/epel-bootstrap.repo
INLINE_CRIPT
ansible_is_installable = machine.communicate.execute("yum info ansible", :error_check => false)
if ansible_is_installable != 0
machine.communicate.sudo(configure_epel)
end
machine.communicate.sudo("yum --assumeyes --quiet --enablerepo=epel install ansible")
end
end
end
end
end
end
end

View File

@ -0,0 +1,18 @@
module VagrantPlugins
module Ansible
module Cap
module Guest
module FreeBSD
module AnsibleInstall
def self.ansible_install(machine)
machine.communicate.sudo("yes | pkg install ansible")
end
end
end
end
end
end
end

View File

@ -0,0 +1,25 @@
module VagrantPlugins
module Ansible
module Cap
module Guest
module POSIX
module AnsibleInstalled
# Check if Ansible is installed (at the given version).
# @return [true, false]
def self.ansible_installed(machine, version)
command = 'test -x "$(command -v ansible)"'
if !version.empty?
command << "&& ansible --version | grep 'ansible #{version}'"
end
machine.communicate.test(command, sudo: false)
end
end
end
end
end
end
end

View File

@ -0,0 +1,18 @@
module VagrantPlugins
module Ansible
module Cap
module Guest
module SUSE
module AnsibleInstall
def self.ansible_install(machine)
machine.communicate.sudo("zypper --non-interactive --quiet install ansible")
end
end
end
end
end
end
end

View File

@ -0,0 +1,22 @@
module VagrantPlugins
module Ansible
module Cap
module Guest
module Ubuntu
module AnsibleInstall
def self.ansible_install(machine)
machine.communicate.sudo("apt-get update -y -qq")
machine.communicate.sudo("apt-get install -y -qq software-properties-common python-software-properties")
machine.communicate.sudo("add-apt-repository ppa:ansible/ansible -y")
machine.communicate.sudo("apt-get update -y -qq")
machine.communicate.sudo("apt-get install -y -qq ansible")
end
end
end
end
end
end
end

View File

@ -1,128 +0,0 @@
module VagrantPlugins
module Ansible
class Config < Vagrant.plugin("2", :config)
attr_accessor :playbook
attr_accessor :extra_vars
attr_accessor :inventory_path
attr_accessor :ask_sudo_pass
attr_accessor :ask_vault_pass
attr_accessor :vault_password_file
attr_accessor :limit
attr_accessor :sudo
attr_accessor :sudo_user
attr_accessor :verbose
attr_accessor :tags
attr_accessor :skip_tags
attr_accessor :start_at_task
attr_accessor :groups
attr_accessor :host_key_checking
# Joker attribute, used to pass unsupported arguments to ansible-playbook anyway
attr_accessor :raw_arguments
# Joker attribute, used to set additional SSH parameters for ansible-playbook anyway
attr_accessor :raw_ssh_args
def initialize
@playbook = UNSET_VALUE
@extra_vars = UNSET_VALUE
@inventory_path = UNSET_VALUE
@ask_sudo_pass = UNSET_VALUE
@ask_vault_pass = UNSET_VALUE
@vault_password_file = UNSET_VALUE
@limit = UNSET_VALUE
@sudo = UNSET_VALUE
@sudo_user = UNSET_VALUE
@verbose = UNSET_VALUE
@tags = UNSET_VALUE
@skip_tags = UNSET_VALUE
@start_at_task = UNSET_VALUE
@groups = UNSET_VALUE
@host_key_checking = UNSET_VALUE
@raw_arguments = UNSET_VALUE
@raw_ssh_args = UNSET_VALUE
end
def finalize!
@playbook = nil if @playbook == UNSET_VALUE
@extra_vars = nil if @extra_vars == UNSET_VALUE
@inventory_path = nil if @inventory_path == UNSET_VALUE
@ask_sudo_pass = false unless @ask_sudo_pass == true
@ask_vault_pass = false unless @ask_vault_pass == true
@vault_password_file = nil if @vault_password_file == UNSET_VALUE
@limit = nil if @limit == UNSET_VALUE
@sudo = false unless @sudo == true
@sudo_user = nil if @sudo_user == UNSET_VALUE
@verbose = nil if @verbose == UNSET_VALUE
@tags = nil if @tags == UNSET_VALUE
@skip_tags = nil if @skip_tags == UNSET_VALUE
@start_at_task = nil if @start_at_task == UNSET_VALUE
@groups = {} if @groups == UNSET_VALUE
@host_key_checking = false unless @host_key_checking == true
@raw_arguments = nil if @raw_arguments == UNSET_VALUE
@raw_ssh_args = nil if @raw_ssh_args == UNSET_VALUE
end
def validate(machine)
errors = _detected_errors
# Validate that a playbook path was provided
if !playbook
errors << I18n.t("vagrant.provisioners.ansible.no_playbook")
end
# Validate the existence of said playbook on the host
if playbook
expanded_path = Pathname.new(playbook).expand_path(machine.env.root_path)
if !expanded_path.file?
errors << I18n.t("vagrant.provisioners.ansible.playbook_path_invalid",
path: expanded_path)
end
end
# Validate that extra_vars is either a hash, or a path to an
# existing file
if extra_vars
extra_vars_is_valid = extra_vars.kind_of?(Hash) || extra_vars.kind_of?(String)
if extra_vars.kind_of?(String)
# Accept the usage of '@' prefix in Vagrantfile (e.g. '@vars.yml'
# and 'vars.yml' are both supported)
match_data = /^@?(.+)$/.match(extra_vars)
extra_vars_path = match_data[1].to_s
expanded_path = Pathname.new(extra_vars_path).expand_path(machine.env.root_path)
extra_vars_is_valid = expanded_path.exist?
if extra_vars_is_valid
@extra_vars = '@' + extra_vars_path
end
end
if !extra_vars_is_valid
errors << I18n.t("vagrant.provisioners.ansible.extra_vars_invalid",
type: extra_vars.class.to_s,
value: extra_vars.to_s
)
end
end
# Validate the existence of the inventory_path, if specified
if inventory_path
expanded_path = Pathname.new(inventory_path).expand_path(machine.env.root_path)
if !expanded_path.exist?
errors << I18n.t("vagrant.provisioners.ansible.inventory_path_invalid",
path: expanded_path)
end
end
# Validate the existence of the vault_password_file, if specified
if vault_password_file
expanded_path = Pathname.new(vault_password_file).expand_path(machine.env.root_path)
if !expanded_path.exist?
errors << I18n.t("vagrant.provisioners.ansible.vault_password_file_invalid",
path: expanded_path)
end
end
{ "ansible provisioner" => errors }
end
end
end
end

View File

@ -0,0 +1,105 @@
module VagrantPlugins
module Ansible
module Config
class Base < Vagrant.plugin("2", :config)
attr_accessor :extra_vars
attr_accessor :groups
attr_accessor :inventory_path
attr_accessor :limit
attr_accessor :playbook
attr_accessor :raw_arguments
attr_accessor :skip_tags
attr_accessor :start_at_task
attr_accessor :sudo
attr_accessor :sudo_user
attr_accessor :tags
attr_accessor :vault_password_file
attr_accessor :verbose
def initialize
@extra_vars = UNSET_VALUE
@groups = UNSET_VALUE
@inventory_path = UNSET_VALUE
@limit = UNSET_VALUE
@playbook = UNSET_VALUE
@raw_arguments = UNSET_VALUE
@skip_tags = UNSET_VALUE
@start_at_task = UNSET_VALUE
@sudo = UNSET_VALUE
@sudo_user = UNSET_VALUE
@tags = UNSET_VALUE
@vault_password_file = UNSET_VALUE
@verbose = UNSET_VALUE
end
def finalize!
@extra_vars = nil if @extra_vars == UNSET_VALUE
@groups = {} if @groups == UNSET_VALUE
@inventory_path = nil if @inventory_path == UNSET_VALUE
@limit = nil if @limit == UNSET_VALUE
@playbook = nil if @playbook == UNSET_VALUE
@raw_arguments = nil if @raw_arguments == UNSET_VALUE
@skip_tags = nil if @skip_tags == UNSET_VALUE
@start_at_task = nil if @start_at_task == UNSET_VALUE
@sudo = false if @sudo != true
@sudo_user = nil if @sudo_user == UNSET_VALUE
@tags = nil if @tags == UNSET_VALUE
@vault_password_file = nil if @vault_password_file == UNSET_VALUE
@verbose = false if @verbose == UNSET_VALUE
end
# Just like the normal configuration "validate" method except that
# it returns an array of errors that should be merged into some
# other error accumulator.
def validate(machine)
@errors = _detected_errors
# Validate that a playbook path was provided
if !playbook
@errors << I18n.t("vagrant.provisioners.ansible.no_playbook")
end
# Validate the existence of the playbook
if playbook
check_path_is_a_file(machine, playbook, "vagrant.provisioners.ansible.playbook_path_invalid")
end
# Validate the existence of the inventory_path, if specified
if inventory_path
check_path_exists(machine, inventory_path, "vagrant.provisioners.ansible.inventory_path_invalid")
end
# Validate the existence of the vault_password_file, if specified
if vault_password_file
check_path_is_a_file(machine, vault_password_file, "vagrant.provisioners.ansible.vault_password_file_invalid")
end
# Validate that extra_vars is either a hash, or a path to an
# existing file
if extra_vars
extra_vars_is_valid = extra_vars.kind_of?(Hash) || extra_vars.kind_of?(String)
if extra_vars.kind_of?(String)
# Accept the usage of '@' prefix in Vagrantfile (e.g. '@vars.yml'
# and 'vars.yml' are both supported)
match_data = /^@?(.+)$/.match(extra_vars)
extra_vars_path = match_data[1].to_s
extra_vars_is_valid = check_path_is_a_file(machine, extra_vars_path)
if extra_vars_is_valid
@extra_vars = '@' + extra_vars_path
end
end
if !extra_vars_is_valid
@errors << I18n.t(
"vagrant.provisioners.ansible.extra_vars_invalid",
type: extra_vars.class.to_s,
value: extra_vars.to_s)
end
end
end
end
end
end
end

View File

@ -0,0 +1,63 @@
require_relative "base"
module VagrantPlugins
module Ansible
module Config
class Guest < Base
attr_accessor :provisioning_path
attr_accessor :tmp_path
attr_accessor :install
attr_accessor :version
def initialize
super
@install = UNSET_VALUE
@provisioning_path = UNSET_VALUE
@tmp_path = UNSET_VALUE
@version = UNSET_VALUE
end
def finalize!
super
@install = true if @install == UNSET_VALUE
@provisioning_path = "/vagrant" if provisioning_path == UNSET_VALUE
@tmp_path = "/tmp/vagrant-ansible" if tmp_path == UNSET_VALUE
@version = "" if @version == UNSET_VALUE
end
def validate(machine)
super
{ "ansible local provisioner" => @errors }
end
protected
def check_path(machine, path, test_args, error_message_key = nil)
remote_path = Pathname.new(path).expand_path(@provisioning_path)
if machine.communicate.ready? && !machine.communicate.test("test #{test_args} #{remote_path}")
if error_message_key
@errors << I18n.t(error_message_key, path: remote_path, system: "guest")
end
return false
end
# when the machine is not ready for SSH communication,
# the check is "optimistically" by passed.
true
end
def check_path_is_a_file(machine, path, error_message_key = nil)
check_path(machine, path, "-f", error_message_key)
end
def check_path_exists(machine, path, error_message_key = nil)
check_path(machine, path, "-e", error_message_key)
end
end
end
end
end

View File

@ -0,0 +1,64 @@
require_relative "base"
module VagrantPlugins
module Ansible
module Config
class Host < Base
attr_accessor :ask_sudo_pass
attr_accessor :ask_vault_pass
attr_accessor :force_remote_user
attr_accessor :host_key_checking
attr_accessor :raw_ssh_args
def initialize
super
@ask_sudo_pass = false
@ask_vault_pass = false
@force_remote_user = true
@host_key_checking = false
@raw_ssh_args = UNSET_VALUE
end
def finalize!
super
@ask_sudo_pass = false if @ask_sudo_pass != true
@ask_vault_pass = false if @ask_vault_pass != true
@force_remote_user = true if @force_remote_user != false
@host_key_checking = false if @host_key_checking != true
@raw_ssh_args = nil if @raw_ssh_args == UNSET_VALUE
end
def validate(machine)
super
{ "ansible remote provisioner" => @errors }
end
protected
def check_path(machine, path, path_test_method, error_message_key = nil)
expanded_path = Pathname.new(path).expand_path(machine.env.root_path)
if !expanded_path.public_send(path_test_method)
if error_message_key
@errors << I18n.t(error_message_key, path: expanded_path, system: "host")
end
return false
end
true
end
def check_path_is_a_file(machine, path, error_message_key = nil)
check_path(machine, path, "file?", error_message_key)
end
def check_path_exists(machine, path, error_message_key = nil)
check_path(machine, path, "exist?", error_message_key)
end
end
end
end
end

View File

@ -0,0 +1,27 @@
require "vagrant"
module VagrantPlugins
module Ansible
module Errors
class AnsibleError < Vagrant::Errors::VagrantError
error_namespace("vagrant.provisioners.ansible.errors")
end
class AnsiblePlaybookAppFailed < AnsibleError
error_key(:ansible_playbook_app_failed)
end
class AnsiblePlaybookAppNotFoundOnHost < AnsibleError
error_key(:ansible_playbook_app_not_found_on_host)
end
class AnsiblePlaybookAppNotFoundOnGuest < AnsibleError
error_key(:ansible_playbook_app_not_found_on_guest)
end
class AnsibleVersionNotFoundOnGuest < AnsibleError
error_key(:ansible_version_not_found_on_guest)
end
end
end
end

View File

@ -0,0 +1,37 @@
require "vagrant"
module VagrantPlugins
module Ansible
class Helpers
def self.stringify_ansible_playbook_command(env, command)
shell_command = ''
env.each_pair do |k, v|
if k == 'ANSIBLE_SSH_ARGS'
shell_command += "#{k}='#{v}' "
else
shell_command += "#{k}=#{v} "
end
end
shell_arg = []
command.each do |arg|
if arg =~ /(--start-at-task|--limit)=(.+)/
shell_arg << "#{$1}='#{$2}'"
else
shell_arg << arg
end
end
shell_command += shell_arg.join(' ')
end
def self.as_list_argument(v)
v.kind_of?(Array) ? v.join(',') : v
end
def self.as_array(v)
v.kind_of?(Array) ? v : [v]
end
end
end
end

View File

@ -3,21 +3,73 @@ require "vagrant"
module VagrantPlugins
module Ansible
class Plugin < Vagrant.plugin("2")
name "ansible"
description <<-DESC
Provides support for provisioning your virtual machines with
Ansible playbooks.
Provides support for provisioning your virtual machines with Ansible
from the Vagrant host (`ansible`) or from the guests (`ansible_local`).
DESC
config(:ansible, :provisioner) do
require File.expand_path("../config", __FILE__)
Config
config("ansible", :provisioner) do
require_relative "config/host"
Config::Host
end
config("ansible_local", :provisioner) do
require_relative "config/guest"
Config::Guest
end
provisioner("ansible") do
require_relative "provisioner/host"
Provisioner::Host
end
provisioner("ansible_local") do
require_relative "provisioner/guest"
Provisioner::Guest
end
guest_capability(:linux, :ansible_installed) do
require_relative "cap/guest/posix/ansible_installed"
Cap::Guest::POSIX::AnsibleInstalled
end
guest_capability(:freebsd, :ansible_installed) do
require_relative "cap/guest/posix/ansible_installed"
Cap::Guest::POSIX::AnsibleInstalled
end
guest_capability(:arch, :ansible_install) do
require_relative "cap/guest/arch/ansible_install"
Cap::Guest::Arch::AnsibleInstall
end
guest_capability(:debian, :ansible_install) do
require_relative "cap/guest/debian/ansible_install"
Cap::Guest::Debian::AnsibleInstall
end
guest_capability(:ubuntu, :ansible_install) do
require_relative "cap/guest/ubuntu/ansible_install"
Cap::Guest::Ubuntu::AnsibleInstall
end
guest_capability(:redhat, :ansible_install) do
require_relative "cap/guest/epel/ansible_install"
Cap::Guest::EPEL::AnsibleInstall
end
guest_capability(:suse, :ansible_install) do
require_relative "cap/guest/suse/ansible_install"
Cap::Guest::SUSE::AnsibleInstall
end
guest_capability(:freebsd, :ansible_install) do
require_relative "cap/guest/freebsd/ansible_install"
Cap::Guest::FreeBSD::AnsibleInstall
end
provisioner(:ansible) do
require File.expand_path("../provisioner", __FILE__)
Provisioner
end
end
end
end

View File

@ -1,302 +0,0 @@
require "vagrant/util/platform"
require "thread"
module VagrantPlugins
module Ansible
class Provisioner < Vagrant.plugin("2", :provisioner)
@@lock = Mutex.new
def initialize(machine, config)
super
@logger = Log4r::Logger.new("vagrant::provisioners::ansible")
end
def provision
@ssh_info = @machine.ssh_info
#
# Ansible provisioner options
#
# By default, connect with Vagrant SSH username
options = %W[--user=#{@ssh_info[:username]}]
# Connect with native OpenSSH client
# Other modes (e.g. paramiko) are not officially supported,
# but can be enabled via raw_arguments option.
options << "--connection=ssh"
# Increase the SSH connection timeout, as the Ansible default value (10 seconds)
# is a bit demanding for some overloaded developer boxes. This is particularly
# helpful when additional virtual networks are configured, as their availability
# is not controlled during vagrant boot process.
options << "--timeout=30"
# By default we limit by the current machine, but
# this can be overridden by the `limit` option.
if config.limit
options << "--limit=#{as_list_argument(config.limit)}"
else
options << "--limit=#{@machine.name}"
end
options << "--inventory-file=#{self.setup_inventory_file}"
options << "--extra-vars=#{self.get_extra_vars_argument}" if config.extra_vars
options << "--sudo" if config.sudo
options << "--sudo-user=#{config.sudo_user}" if config.sudo_user
options << "#{self.get_verbosity_argument}" if config.verbose
options << "--ask-sudo-pass" if config.ask_sudo_pass
options << "--ask-vault-pass" if config.ask_vault_pass
options << "--vault-password-file=#{config.vault_password_file}" if config.vault_password_file
options << "--tags=#{as_list_argument(config.tags)}" if config.tags
options << "--skip-tags=#{as_list_argument(config.skip_tags)}" if config.skip_tags
options << "--start-at-task=#{config.start_at_task}" if config.start_at_task
# Finally, add the raw configuration options, which has the highest precedence
# and can therefore potentially override any other options of this provisioner.
options.concat(self.as_array(config.raw_arguments)) if config.raw_arguments
#
# Assemble the full ansible-playbook command
#
command = (%w(ansible-playbook) << options << config.playbook).flatten
env = {
# Ensure Ansible output isn't buffered so that we receive output
# on a task-by-task basis.
"PYTHONUNBUFFERED" => 1,
# Some Ansible options must be passed as environment variables,
# as there is no equivalent command line arguments
"ANSIBLE_HOST_KEY_CHECKING" => "#{config.host_key_checking}",
}
# When Ansible output is piped in Vagrant integration, its default colorization is
# automatically disabled and the only way to re-enable colors is to use ANSIBLE_FORCE_COLOR.
env["ANSIBLE_FORCE_COLOR"] = "true" if @machine.env.ui.color?
# Setting ANSIBLE_NOCOLOR is "unnecessary" at the moment, but this could change in the future
# (e.g. local provisioner [GH-2103], possible change in vagrant/ansible integration, etc.)
env["ANSIBLE_NOCOLOR"] = "true" if !@machine.env.ui.color?
# ANSIBLE_SSH_ARGS is required for Multiple SSH keys, SSH forwarding and custom SSH settings
env["ANSIBLE_SSH_ARGS"] = ansible_ssh_args unless ansible_ssh_args.empty?
show_ansible_playbook_command(env, command) if config.verbose
# Write stdout and stderr data, since it's the regular Ansible output
command << {
env: env,
notify: [:stdout, :stderr],
workdir: @machine.env.root_path.to_s
}
begin
result = Vagrant::Util::Subprocess.execute(*command) do |type, data|
if type == :stdout || type == :stderr
@machine.env.ui.info(data, new_line: false, prefix: false)
end
end
raise Vagrant::Errors::AnsibleFailed if result.exit_code != 0
rescue Vagrant::Util::Subprocess::LaunchError
raise Vagrant::Errors::AnsiblePlaybookAppNotFound
end
end
protected
# Auto-generate "safe" inventory file based on Vagrantfile,
# unless inventory_path is explicitly provided
def setup_inventory_file
return config.inventory_path if config.inventory_path
# Managed machines
inventory_machines = {}
generated_inventory_dir = @machine.env.local_data_path.join(File.join(%w(provisioners ansible inventory)))
FileUtils.mkdir_p(generated_inventory_dir) unless File.directory?(generated_inventory_dir)
generated_inventory_file = generated_inventory_dir.join('vagrant_ansible_inventory')
inventory = "# Generated by Vagrant\n\n"
@machine.env.active_machines.each do |am|
begin
m = @machine.env.machine(*am)
m_ssh_info = m.ssh_info
if !m_ssh_info.nil?
inventory += "#{m.name} ansible_ssh_host=#{m_ssh_info[:host]} ansible_ssh_port=#{m_ssh_info[:port]} ansible_ssh_private_key_file='#{m_ssh_info[:private_key_path][0]}'\n"
inventory_machines[m.name] = m
else
@logger.error("Auto-generated inventory: Impossible to get SSH information for machine '#{m.name} (#{m.provider_name})'. This machine should be recreated.")
# Let a note about this missing machine
inventory += "# MISSING: '#{m.name}' machine was probably removed without using Vagrant. This machine should be recreated.\n"
end
rescue Vagrant::Errors::MachineNotFound => e
@logger.info("Auto-generated inventory: Skip machine '#{am[0]} (#{am[1]})', which is not configured for this Vagrant environment.")
end
end
# Write out groups information.
# All defined groups will be included, but only supported
# machines and defined child groups will be included.
# Group variables are intentionally skipped.
groups_of_groups = {}
defined_groups = []
config.groups.each_pair do |gname, gmembers|
# Require that gmembers be an array
# (easier to be tolerant and avoid error management of few value)
gmembers = [gmembers] if !gmembers.is_a?(Array)
if gname.end_with?(":children")
groups_of_groups[gname] = gmembers
defined_groups << gname.sub(/:children$/, '')
elsif !gname.include?(':vars')
defined_groups << gname
inventory += "\n[#{gname}]\n"
gmembers.each do |gm|
inventory += "#{gm}\n" if inventory_machines.include?(gm.to_sym)
end
end
end
defined_groups.uniq!
groups_of_groups.each_pair do |gname, gmembers|
inventory += "\n[#{gname}]\n"
gmembers.each do |gm|
inventory += "#{gm}\n" if defined_groups.include?(gm)
end
end
@@lock.synchronize do
if ! File.exists?(generated_inventory_file) or
inventory != File.read(generated_inventory_file)
generated_inventory_file.open('w') do |file|
file.write(inventory)
end
end
end
return generated_inventory_dir.to_s
end
def get_extra_vars_argument
if config.extra_vars.kind_of?(String) and config.extra_vars =~ /^@.+$/
# A JSON or YAML file is referenced (requires Ansible 1.3+)
return config.extra_vars
else
# Expected to be a Hash after config validation. (extra_vars as
# JSON requires Ansible 1.2+, while YAML requires Ansible 1.3+)
return config.extra_vars.to_json
end
end
def get_verbosity_argument
if config.verbose.to_s =~ /^v+$/
# ansible-playbook accepts "silly" arguments like '-vvvvv' as '-vvvv' for now
return "-#{config.verbose}"
else
# safe default, in case input strays
return '-v'
end
end
def ansible_ssh_args
@ansible_ssh_args ||= get_ansible_ssh_args
end
# Use ANSIBLE_SSH_ARGS to pass some OpenSSH options that are not wrapped by
# an ad-hoc Ansible option. Last update corresponds to Ansible 1.8
def get_ansible_ssh_args
ssh_options = []
# Use an SSH ProxyCommand when using the Docker provider with the intermediate host
if @machine.provider_name == :docker && machine.provider.host_vm?
docker_host_ssh_info = machine.provider.host_vm.ssh_info
proxy_cmd = "ssh #{docker_host_ssh_info[:username]}@#{docker_host_ssh_info[:host]}" +
" -p #{docker_host_ssh_info[:port]} -i #{docker_host_ssh_info[:private_key_path][0]}"
# Use same options than plugins/providers/docker/communicator.rb
# Note: this could be improved (DRY'ed) by sharing these settings.
proxy_cmd += " -o Compression=yes -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
proxy_cmd += " -o ForwardAgent=yes" if @ssh_info[:forward_agent]
proxy_cmd += " exec nc %h %p 2>/dev/null"
ssh_options << "-o ProxyCommand='#{ proxy_cmd }'"
end
# Don't access user's known_hosts file, except when host_key_checking is enabled.
ssh_options << "-o UserKnownHostsFile=/dev/null" unless config.host_key_checking
# Set IdentitiesOnly=yes to avoid authentication errors when the host has more than 5 ssh keys.
# Notes:
# - Solaris/OpenSolaris/Illumos uses SunSSH which doesn't support the IdentitiesOnly option.
# - this could be improved by sharing logic with lib/vagrant/util/ssh.rb
ssh_options << "-o IdentitiesOnly=yes" unless Vagrant::Util::Platform.solaris?
# Multiple Private Keys
unless !config.inventory_path && @ssh_info[:private_key_path].size == 1
@ssh_info[:private_key_path].each do |key|
ssh_options << "-o IdentityFile=#{key}"
end
end
# SSH Forwarding
ssh_options << "-o ForwardAgent=yes" if @ssh_info[:forward_agent]
# Unchecked SSH Parameters
ssh_options.concat(self.as_array(config.raw_ssh_args)) if config.raw_ssh_args
# Re-enable ControlPersist Ansible defaults,
# which are lost when ANSIBLE_SSH_ARGS is defined.
unless ssh_options.empty?
ssh_options << "-o ControlMaster=auto"
ssh_options << "-o ControlPersist=60s"
# Intentionally keep ControlPath undefined to let ansible-playbook
# automatically sets this option to Ansible default value
end
ssh_options.join(' ')
end
def as_list_argument(v)
v.kind_of?(Array) ? v.join(',') : v
end
def as_array(v)
v.kind_of?(Array) ? v : [v]
end
def show_ansible_playbook_command(env, command)
shell_command = ''
env.each_pair do |k, v|
if k == 'ANSIBLE_SSH_ARGS'
shell_command += "#{k}='#{v}' "
else
shell_command += "#{k}=#{v} "
end
end
shell_arg = []
command.each do |arg|
if arg =~ /(--start-at-task|--limit)=(.+)/
shell_arg << "#{$1}='#{$2}'"
else
shell_arg << arg
end
end
shell_command += shell_arg.join(' ')
@machine.env.ui.detail(shell_command)
end
end
end
end

View File

@ -0,0 +1,151 @@
require_relative "../errors"
require_relative "../helpers"
module VagrantPlugins
module Ansible
module Provisioner
# This class is a base class where the common functionality shared between
# both Ansible provisioners are stored.
# This is **not an actual provisioner**.
# Instead, {Host} (ansible) or {Guest} (ansible_local) should be used.
class Base < Vagrant.plugin("2", :provisioner)
protected
def initialize(machine, config)
super
@command_arguments = []
@environment_variables = {}
@inventory_machines = {}
@inventory_path = nil
end
def prepare_common_command_arguments
# By default we limit by the current machine,
# but this can be overridden by the `limit` option.
if config.limit
@command_arguments << "--limit=#{Helpers::as_list_argument(config.limit)}"
else
@command_arguments << "--limit=#{@machine.name}"
end
@command_arguments << "--inventory-file=#{inventory_path}"
@command_arguments << "--extra-vars=#{extra_vars_argument}" if config.extra_vars
@command_arguments << "--sudo" if config.sudo
@command_arguments << "--sudo-user=#{config.sudo_user}" if config.sudo_user
@command_arguments << "#{verbosity_argument}" if verbosity_is_enabled?
@command_arguments << "--vault-password-file=#{config.vault_password_file}" if config.vault_password_file
@command_arguments << "--tags=#{Helpers::as_list_argument(config.tags)}" if config.tags
@command_arguments << "--skip-tags=#{Helpers::as_list_argument(config.skip_tags)}" if config.skip_tags
@command_arguments << "--start-at-task=#{config.start_at_task}" if config.start_at_task
# Finally, add the raw configuration options, which has the highest precedence
# and can therefore potentially override any other options of this provisioner.
@command_arguments.concat(Helpers::as_array(config.raw_arguments)) if config.raw_arguments
end
def prepare_common_environment_variables
# Ensure Ansible output isn't buffered so that we receive output
# on a task-by-task basis.
@environment_variables["PYTHONUNBUFFERED"] = 1
# When Ansible output is piped in Vagrant integration, its default colorization is
# automatically disabled and the only way to re-enable colors is to use ANSIBLE_FORCE_COLOR.
@environment_variables["ANSIBLE_FORCE_COLOR"] = "true" if @machine.env.ui.color?
# Setting ANSIBLE_NOCOLOR is "unnecessary" at the moment, but this could change in the future
# (e.g. local provisioner [GH-2103], possible change in vagrant/ansible integration, etc.)
@environment_variables["ANSIBLE_NOCOLOR"] = "true" if !@machine.env.ui.color?
end
# Auto-generate "safe" inventory file based on Vagrantfile,
# unless inventory_path is explicitly provided
def inventory_path
if config.inventory_path
config.inventory_path
else
@inventory_path ||= generate_inventory
end
end
def generate_inventory
inventory = "# Generated by Vagrant\n\n"
# This "abstract" step must fill the @inventory_machines list
# and return the list of supported host(s)
inventory += generate_inventory_machines
inventory += generate_inventory_groups
# This "abstract" step must create the inventory file and
# return its location path
# TODO: explain possible race conditions, etc.
@inventory_path = ship_generated_inventory(inventory)
end
# Write out groups information.
# All defined groups will be included, but only supported
# machines and defined child groups will be included.
# Group variables are intentionally skipped.
def generate_inventory_groups
groups_of_groups = {}
defined_groups = []
inventory_groups = ""
config.groups.each_pair do |gname, gmembers|
# Require that gmembers be an array
# (easier to be tolerant and avoid error management of few value)
gmembers = [gmembers] if !gmembers.is_a?(Array)
if gname.end_with?(":children")
groups_of_groups[gname] = gmembers
defined_groups << gname.sub(/:children$/, '')
elsif !gname.include?(':vars')
defined_groups << gname
inventory_groups += "\n[#{gname}]\n"
gmembers.each do |gm|
inventory_groups += "#{gm}\n" if @inventory_machines.include?(gm.to_sym)
end
end
end
defined_groups.uniq!
groups_of_groups.each_pair do |gname, gmembers|
inventory_groups += "\n[#{gname}]\n"
gmembers.each do |gm|
inventory_groups += "#{gm}\n" if defined_groups.include?(gm)
end
end
return inventory_groups
end
def extra_vars_argument
if config.extra_vars.kind_of?(String) and config.extra_vars =~ /^@.+$/
# A JSON or YAML file is referenced.
config.extra_vars
else
# Expected to be a Hash after config validation.
config.extra_vars.to_json
end
end
def verbosity_is_enabled?
config.verbose && !config.verbose.to_s.empty?
end
def verbosity_argument
if config.verbose.to_s =~ /^-?(v+)$/
"-#{$+}"
else
# safe default, in case input strays
'-v'
end
end
end
end
end
end

View File

@ -0,0 +1,135 @@
require 'tempfile'
require_relative "base"
module VagrantPlugins
module Ansible
module Provisioner
class Guest < Base
def initialize(machine, config)
super
@logger = Log4r::Logger.new("vagrant::provisioners::ansible_guest")
end
def provision
check_and_install_ansible
prepare_common_command_arguments
prepare_common_environment_variables
execute_ansible_playbook_on_guest
end
protected
#
# This handles verifying the Ansible installation, installing it if it was
# requested, and so on. This method will raise exceptions if things are wrong.
#
# Current limitations:
# - The installation of a specific Ansible version is not supported.
# Such feature is difficult to systematically provide via package repositories (apt, yum, ...).
# Installing via pip python packaging or directly from github source would be appropriate,
# but these approaches require more dependency burden.
# - There is no guarantee that the automated installation will replace
# a previous Ansible installation.
#
def check_and_install_ansible
@logger.info("Checking for Ansible installation...")
# If the guest cannot check if Ansible is installed,
# print a warning and try to continue without any installation attempt...
if !@machine.guest.capability?(:ansible_installed)
@machine.ui.warn(I18n.t("vagrant.provisioners.ansible.cannot_detect"))
return
end
# Try to install Ansible (if needed and requested)
if config.install &&
(config.version == :latest ||
!@machine.guest.capability(:ansible_installed, config.version))
@machine.ui.detail(I18n.t("vagrant.provisioners.ansible.installing"))
@machine.guest.capability(:ansible_install)
end
# Check for the existence of ansible-playbook binary on the guest,
@machine.communicate.execute(
"ansible-playbook --help",
:error_class => Ansible::Errors::AnsibleError,
:error_key => :ansible_playbook_app_not_found_on_guest)
# Check if requested ansible version is available
if (!config.version.empty? &&
config.version != :latest &&
!@machine.guest.capability(:ansible_installed, config.version))
raise Ansible::Errors::AnsibleVersionNotFoundOnGuest, required_version: config.version.to_s
end
end
def execute_ansible_playbook_on_guest
command = (%w(ansible-playbook) << @command_arguments << config.playbook).flatten
remote_command = "cd #{config.provisioning_path} && #{Helpers::stringify_ansible_playbook_command(@environment_variables, command)}"
# TODO: generic HOOK ???
# Show the ansible command in use
if verbosity_is_enabled?
@machine.env.ui.detail(remote_command)
end
result = execute_on_guest(remote_command)
raise Ansible::Errors::AnsiblePlaybookAppFailed if result != 0
end
def execute_on_guest(command)
@machine.communicate.execute(command, :error_check => false) do |type, data|
if [:stderr, :stdout].include?(type)
@machine.env.ui.info(data, :new_line => false, :prefix => false)
end
end
end
def ship_generated_inventory(inventory_content)
inventory_basedir = File.join(config.tmp_path, "inventory")
inventory_path = File.join(inventory_basedir, "vagrant_ansible_local_inventory")
temp_inventory = Tempfile.new("vagrant_ansible_local_inventory_#{@machine.name}")
temp_inventory.write(inventory_content)
temp_inventory.close
create_and_chown_remote_folder(inventory_basedir)
@machine.communicate.tap do |comm|
comm.sudo("rm -f #{inventory_path}", error_check: false)
comm.upload(temp_inventory.path, inventory_path)
end
return inventory_path
end
def generate_inventory_machines
machines = ""
# TODO: Instead, why not loop over active_machines and skip missing guests, like in Host?
machine.env.machine_names.each do |machine_name|
begin
@inventory_machines[machine_name] = machine_name
if @machine.name == machine_name
machines += "#{machine_name} ansible_connection=local\n"
else
machines += "#{machine_name}\n"
end
end
end
return machines
end
def create_and_chown_remote_folder(path)
@machine.communicate.tap do |comm|
comm.sudo("mkdir -p #{path}")
comm.sudo("chown -h #{@machine.ssh_info[:username]} #{path}")
end
end
end
end
end
end

View File

@ -0,0 +1,209 @@
require "thread"
require_relative "base"
module VagrantPlugins
module Ansible
module Provisioner
class Host < Base
@@lock = Mutex.new
def initialize(machine, config)
super
@logger = Log4r::Logger.new("vagrant::provisioners::ansible_host")
end
def provision
# At this stage, the SSH access is guaranteed to be ready
@ssh_info = @machine.ssh_info
warn_for_unsupported_platform
prepare_command_arguments
prepare_environment_variables
execute_ansible_playbook_from_host
end
protected
def warn_for_unsupported_platform
if Vagrant::Util::Platform.windows?
@machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine"))
end
end
def prepare_command_arguments
# Connect with native OpenSSH client
# Other modes (e.g. paramiko) are not officially supported,
# but can be enabled via raw_arguments option.
@command_arguments << "--connection=ssh"
# Increase the SSH connection timeout, as the Ansible default value (10 seconds)
# is a bit demanding for some overloaded developer boxes. This is particularly
# helpful when additional virtual networks are configured, as their availability
# is not controlled during vagrant boot process.
@command_arguments << "--timeout=30"
if !config.force_remote_user
# Pass the vagrant ssh username as Ansible default remote user, because
# the ansible_ssh_user parameter won't be added to the auto-generated inventory.
@command_arguments << "--user=#{@ssh_info[:username]}"
elsif config.inventory_path
# Using an extra variable is the only way to ensure that the Ansible remote user
# is overridden (as the ansible inventory is not under vagrant control)
@command_arguments << "--extra-vars=ansible_ssh_user='#{@ssh_info[:username]}'"
end
@command_arguments << "--ask-sudo-pass" if config.ask_sudo_pass
@command_arguments << "--ask-vault-pass" if config.ask_vault_pass
prepare_common_command_arguments
end
def prepare_environment_variables
prepare_common_environment_variables
# Some Ansible options must be passed as environment variables,
# as there is no equivalent command line arguments
@environment_variables["ANSIBLE_HOST_KEY_CHECKING"] = "#{config.host_key_checking}"
# ANSIBLE_SSH_ARGS is required for Multiple SSH keys, SSH forwarding and custom SSH settings
@environment_variables["ANSIBLE_SSH_ARGS"] = ansible_ssh_args unless ansible_ssh_args.empty?
end
def execute_ansible_playbook_from_host
# Assemble the full ansible-playbook command
command = (%w(ansible-playbook) << @command_arguments << config.playbook).flatten
# TODO: generic HOOK ???
# Show the ansible command in use
if verbosity_is_enabled?
@machine.env.ui.detail(Helpers::stringify_ansible_playbook_command(@environment_variables, command))
end
# Write stdout and stderr data, since it's the regular Ansible output
command << {
env: @environment_variables,
notify: [:stdout, :stderr],
workdir: @machine.env.root_path.to_s
}
begin
result = Vagrant::Util::Subprocess.execute(*command) do |type, data|
if type == :stdout || type == :stderr
@machine.env.ui.info(data, new_line: false, prefix: false)
end
end
raise Ansible::Errors::AnsiblePlaybookAppFailed if result.exit_code != 0
rescue Vagrant::Errors::CommandUnavailable
raise Ansible::Errors::AnsiblePlaybookAppNotFoundOnHost
end
end
def ship_generated_inventory(inventory_content)
inventory_path = Pathname.new(File.join(@machine.env.local_data_path.join, %w(provisioners ansible inventory)))
FileUtils.mkdir_p(inventory_path) unless File.directory?(inventory_path)
inventory_file = Pathname.new(File.join(inventory_path, 'vagrant_ansible_inventory'))
@@lock.synchronize do
if !File.exists?(inventory_file) or inventory_content != File.read(inventory_file)
inventory_file.open('w') do |file|
file.write(inventory_content)
end
end
end
return inventory_path
end
def generate_inventory_machines
machines = ""
@machine.env.active_machines.each do |am|
begin
m = @machine.env.machine(*am)
m_ssh_info = m.ssh_info
if !m_ssh_info.nil?
forced_ssh_user = ""
if config.force_remote_user
forced_ssh_user = "ansible_ssh_user='#{m_ssh_info[:username]}' "
end
machines += "#{m.name} ansible_ssh_host=#{m_ssh_info[:host]} ansible_ssh_port=#{m_ssh_info[:port]} #{forced_ssh_user}ansible_ssh_private_key_file='#{m_ssh_info[:private_key_path][0]}'\n"
@inventory_machines[m.name] = m
else
@logger.error("Auto-generated inventory: Impossible to get SSH information for machine '#{m.name} (#{m.provider_name})'. This machine should be recreated.")
# Let a note about this missing machine
machines += "# MISSING: '#{m.name}' machine was probably removed without using Vagrant. This machine should be recreated.\n"
end
rescue Vagrant::Errors::MachineNotFound => e
@logger.info("Auto-generated inventory: Skip machine '#{am[0]} (#{am[1]})', which is not configured for this Vagrant environment.")
end
end
return machines
end
def ansible_ssh_args
@ansible_ssh_args ||= prepare_ansible_ssh_args
end
def prepare_ansible_ssh_args
ssh_options = []
# Use an SSH ProxyCommand when using the Docker provider with the intermediate host
if @machine.provider_name == :docker && machine.provider.host_vm?
docker_host_ssh_info = machine.provider.host_vm.ssh_info
proxy_cmd = "ssh #{docker_host_ssh_info[:username]}@#{docker_host_ssh_info[:host]}" +
" -p #{docker_host_ssh_info[:port]} -i #{docker_host_ssh_info[:private_key_path][0]}"
# Use same options than plugins/providers/docker/communicator.rb
# Note: this could be improved (DRY'ed) by sharing these settings.
proxy_cmd += " -o Compression=yes -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
proxy_cmd += " -o ForwardAgent=yes" if @ssh_info[:forward_agent]
proxy_cmd += " exec nc %h %p 2>/dev/null"
ssh_options << "-o ProxyCommand='#{ proxy_cmd }'"
end
# Don't access user's known_hosts file, except when host_key_checking is enabled.
ssh_options << "-o UserKnownHostsFile=/dev/null" unless config.host_key_checking
# Set IdentitiesOnly=yes to avoid authentication errors when the host has more than 5 ssh keys.
# Notes:
# - Solaris/OpenSolaris/Illumos uses SunSSH which doesn't support the IdentitiesOnly option.
# - this could be improved by sharing logic with lib/vagrant/util/ssh.rb
ssh_options << "-o IdentitiesOnly=yes" unless Vagrant::Util::Platform.solaris?
# Multiple Private Keys
unless !config.inventory_path && @ssh_info[:private_key_path].size == 1
@ssh_info[:private_key_path].each do |key|
ssh_options << "-o IdentityFile=#{key}"
end
end
# SSH Forwarding
ssh_options << "-o ForwardAgent=yes" if @ssh_info[:forward_agent]
# Unchecked SSH Parameters
ssh_options.concat(Helpers::as_array(config.raw_ssh_args)) if config.raw_ssh_args
# Re-enable ControlPersist Ansible defaults,
# which are lost when ANSIBLE_SSH_ARGS is defined.
unless ssh_options.empty?
ssh_options << "-o ControlMaster=auto"
ssh_options << "-o ControlPersist=60s"
# Intentionally keep ControlPath undefined to let ansible-playbook
# automatically sets this option to Ansible default value
end
ssh_options.join(' ')
end
end
end
end
end

View File

@ -371,17 +371,6 @@ en:
Machine name: %{name}
Active provider: %{active_provider}
Requested provider: %{requested_provider}
ansible_failed: |-
Ansible failed to complete successfully. Any error output should be
visible above. Please fix these errors and try again.
ansible_playbook_app_not_found: |-
The "ansible-playbook" program could not be found! Please verify
that "ansible-playbook" is available on the PATH of your host
system, and try again.
If you haven't installed Ansible yet, please install Ansible
on your system. Vagrant can't do this for you in a safe, automated
way. Please see ansible.cc for more info.
batch_multi_error: |-
An error occurred while executing multiple actions in parallel.
Any errors that occurred are shown below.
@ -2050,11 +2039,52 @@ en:
upload_path_not_set: "`upload_path` must be set for the shell provisioner."
ansible:
no_playbook: "`playbook` must be set for the Ansible provisioner."
playbook_path_invalid: "`playbook` for the Ansible provisioner does not exist on the host system: %{path}"
inventory_path_invalid: "`inventory_path` for the Ansible provisioner does not exist on the host system: %{path}"
vault_password_file_invalid: "`vault_password_file` for the Ansible provisioner does not exist on the host system: %{path}"
extra_vars_invalid: "`extra_vars` for the Ansible provisioner must be a hash or a path to an existing file. Received: %{value} (as %{type})"
errors:
ansible_playbook_app_failed: |-
Ansible failed to complete successfully. Any error output should be
visible above. Please fix these errors and try again.
ansible_playbook_app_not_found_on_guest: |-
The "ansible-playbook" program could not be found! Please verify
that Ansible is correctly installed on your guest system.
If you haven't installed Ansible yet, please install Ansible
on your Vagrant basebox, or enable the automated setup with the
`install` option of this provisioner. Please check
http://docs.vagrantup.com/v2/provisioning/ansible_local.html
for more information.
ansible_version_not_found_on_guest: |-
The requested Ansible version (%{required_version}) was not found on the guest.
Please check the ansible installation on your guest system,
or adapt the `version` option of this provisioner in your Vagrantfile.
See http://docs.vagrantup.com/v2/provisioning/ansible_local.html
for more information.
ansible_playbook_app_not_found_on_host: |-
The "ansible-playbook" program could not be found! Please verify
that Ansible is correctly installed on your host system.
If you haven't installed Ansible yet, please install Ansible
on your host system. Vagrant can't do this for you in a safe and
automated way.
Please check http://docs.ansible.com for more information.
extra_vars_invalid: |-
`extra_vars` for the Ansible provisioner must be a hash or a path to an existing file. Received: %{value} (as %{type})
inventory_path_invalid: |-
`inventory_path` for the Ansible provisioner does not exist on the %{system}: %{path}
no_playbook: |-
`playbook` must be set for the Ansible provisioner.
playbook_path_invalid: |-
`playbook` for the Ansible provisioner does not exist on the %{system}: %{path}
vault_password_file_invalid: |-
`vault_password_file` for the Ansible provisioner does not exist on the %{system}: %{path}
cannot_detect: |-
Vagrant does not support detecting whether Ansible is installed
for the guest OS running in the machine. Vagrant will assume it is
installed and attempt to continue.
installing: |-
Installing Ansible...
windows_not_supported_for_control_machine: |-
Windows is not officially supported for the Ansible Control Machine.
Please check http://docs.ansible.com/intro_installation.html#control-machine-requirements
docker:
not_running: "Docker is not running on the guest VM."

View File

@ -1,9 +1,9 @@
require_relative "../../../base"
require_relative "../support/shared/config"
require Vagrant.source_root.join("plugins/provisioners/ansible/config")
require Vagrant.source_root.join("plugins/provisioners/ansible/config/host")
describe VagrantPlugins::Ansible::Config do
describe VagrantPlugins::Ansible::Config::Host do
include_context "unit"
subject { described_class.new }
@ -15,9 +15,11 @@ describe VagrantPlugins::Ansible::Config do
it "supports a list of options" do
config_options = subject.public_methods(false).find_all { |i| i.to_s.end_with?('=') }
config_options.map! { |i| i.to_s.sub('=', '') }
supported_options = %w( ask_sudo_pass
ask_vault_pass
extra_vars
force_remote_user
groups
host_key_checking
inventory_path
@ -33,7 +35,7 @@ describe VagrantPlugins::Ansible::Config do
vault_password_file
verbose )
expect(config_options.sort).to eql(supported_options)
expect(get_provisioner_option_names(described_class)).to eql(supported_options)
end
it "assigns default values to unset options" do
@ -41,13 +43,14 @@ describe VagrantPlugins::Ansible::Config do
expect(subject.playbook).to be_nil
expect(subject.extra_vars).to be_nil
expect(subject.force_remote_user).to be_true
expect(subject.ask_sudo_pass).to be_false
expect(subject.ask_vault_pass).to be_false
expect(subject.vault_password_file).to be_nil
expect(subject.limit).to be_nil
expect(subject.sudo).to be_false
expect(subject.sudo_user).to be_nil
expect(subject.verbose).to be_nil
expect(subject.verbose).to be_false
expect(subject.tags).to be_nil
expect(subject.skip_tags).to be_nil
expect(subject.start_at_task).to be_nil
@ -57,6 +60,9 @@ describe VagrantPlugins::Ansible::Config do
expect(subject.raw_ssh_args).to be_nil
end
describe "force_remote_user option" do
it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :force_remote_user, true
end
describe "host_key_checking option" do
it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :host_key_checking, false
end
@ -79,7 +85,7 @@ describe VagrantPlugins::Ansible::Config do
subject.finalize!
result = subject.validate(machine)
expect(result["ansible provisioner"]).to eql([])
expect(result["ansible remote provisioner"]).to eql([])
end
it "returns an error if the playbook option is undefined" do
@ -87,7 +93,7 @@ describe VagrantPlugins::Ansible::Config do
subject.finalize!
result = subject.validate(machine)
expect(result["ansible provisioner"]).to eql([
expect(result["ansible remote provisioner"]).to eql([
I18n.t("vagrant.provisioners.ansible.no_playbook")
])
end
@ -97,9 +103,9 @@ describe VagrantPlugins::Ansible::Config do
subject.finalize!
result = subject.validate(machine)
expect(result["ansible provisioner"]).to eql([
expect(result["ansible remote provisioner"]).to eql([
I18n.t("vagrant.provisioners.ansible.playbook_path_invalid",
path: non_existing_file)
path: non_existing_file, system: "host")
])
end
@ -108,7 +114,7 @@ describe VagrantPlugins::Ansible::Config do
subject.finalize!
result = subject.validate(machine)
expect(result["ansible provisioner"]).to eql([])
expect(result["ansible remote provisioner"]).to eql([])
end
it "passes if the extra_vars option is a hash" do
@ -116,7 +122,7 @@ describe VagrantPlugins::Ansible::Config do
subject.finalize!
result = subject.validate(machine)
expect(result["ansible provisioner"]).to eql([])
expect(result["ansible remote provisioner"]).to eql([])
end
it "returns an error if the extra_vars option refers to a file that does not exist" do
@ -124,7 +130,7 @@ describe VagrantPlugins::Ansible::Config do
subject.finalize!
result = subject.validate(machine)
expect(result["ansible provisioner"]).to eql([
expect(result["ansible remote provisioner"]).to eql([
I18n.t("vagrant.provisioners.ansible.extra_vars_invalid",
type: subject.extra_vars.class.to_s,
value: subject.extra_vars.to_s)
@ -136,7 +142,7 @@ describe VagrantPlugins::Ansible::Config do
subject.finalize!
result = subject.validate(machine)
expect(result["ansible provisioner"]).to eql([
expect(result["ansible remote provisioner"]).to eql([
I18n.t("vagrant.provisioners.ansible.extra_vars_invalid",
type: subject.extra_vars.class.to_s,
value: subject.extra_vars.to_s)
@ -148,7 +154,7 @@ describe VagrantPlugins::Ansible::Config do
subject.finalize!
result = subject.validate(machine)
expect(result["ansible provisioner"]).to eql([])
expect(result["ansible remote provisioner"]).to eql([])
end
it "returns an error if inventory_path is specified, but does not exist" do
@ -156,9 +162,9 @@ describe VagrantPlugins::Ansible::Config do
subject.finalize!
result = subject.validate(machine)
expect(result["ansible provisioner"]).to eql([
expect(result["ansible remote provisioner"]).to eql([
I18n.t("vagrant.provisioners.ansible.inventory_path_invalid",
path: non_existing_file)
path: non_existing_file, system: "host")
])
end
@ -167,9 +173,9 @@ describe VagrantPlugins::Ansible::Config do
subject.finalize!
result = subject.validate(machine)
expect(result["ansible provisioner"]).to eql([
expect(result["ansible remote provisioner"]).to eql([
I18n.t("vagrant.provisioners.ansible.vault_password_file_invalid",
path: non_existing_file)
path: non_existing_file, system: "host")
])
end
@ -180,16 +186,16 @@ describe VagrantPlugins::Ansible::Config do
subject.finalize!
result = subject.validate(machine)
expect(result["ansible provisioner"]).to include(
expect(result["ansible remote provisioner"]).to include(
I18n.t("vagrant.provisioners.ansible.playbook_path_invalid",
path: non_existing_file))
expect(result["ansible provisioner"]).to include(
path: non_existing_file, system: "host"))
expect(result["ansible remote provisioner"]).to include(
I18n.t("vagrant.provisioners.ansible.extra_vars_invalid",
type: subject.extra_vars.class.to_s,
value: subject.extra_vars.to_s))
expect(result["ansible provisioner"]).to include(
expect(result["ansible remote provisioner"]).to include(
I18n.t("vagrant.provisioners.ansible.inventory_path_invalid",
path: non_existing_file))
path: non_existing_file, system: "host"))
end
end

View File

@ -1,7 +1,7 @@
require_relative "../../../base"
require Vagrant.source_root.join("plugins/provisioners/ansible/config")
require Vagrant.source_root.join("plugins/provisioners/ansible/provisioner")
require Vagrant.source_root.join("plugins/provisioners/ansible/config/host")
require Vagrant.source_root.join("plugins/provisioners/ansible/provisioner/host")
#
# Helper Functions
@ -15,7 +15,7 @@ def find_last_argument_after(ref_index, ansible_playbook_args, arg_pattern)
return false
end
describe VagrantPlugins::Ansible::Provisioner do
describe VagrantPlugins::Ansible::Provisioner::Host do
include_context "unit"
subject { described_class.new(machine, config) }
@ -37,7 +37,7 @@ VF
end
let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }
let(:config) { VagrantPlugins::Ansible::Config.new }
let(:config) { VagrantPlugins::Ansible::Config::Host.new }
let(:ssh_info) {{
private_key_path: ['/path/to/my/key'],
username: 'testuser',
@ -67,15 +67,17 @@ VF
#
def self.it_should_set_arguments_and_environment_variables(
expected_args_count = 6, expected_vars_count = 4, expected_host_key_checking = false, expected_transport_mode = "ssh")
expected_args_count = 5,
expected_vars_count = 4,
expected_host_key_checking = false,
expected_transport_mode = "ssh")
it "sets implicit arguments in a specific order" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
expect(args[0]).to eq("ansible-playbook")
expect(args[1]).to eq("--user=#{machine.ssh_info[:username]}")
expect(args[2]).to eq("--connection=ssh")
expect(args[3]).to eq("--timeout=30")
expect(args[1]).to eq("--connection=ssh")
expect(args[2]).to eq("--timeout=30")
inventory_count = args.count { |x| x =~ /^--inventory-file=.+$/ }
expect(inventory_count).to be > 0
@ -162,13 +164,17 @@ VF
end
end
def self.it_should_create_and_use_generated_inventory
def self.it_should_create_and_use_generated_inventory(with_ssh_user = true)
it "generates an inventory with all active machines" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
expect(config.inventory_path).to be_nil
expect(File.exists?(generated_inventory_file)).to be_true
inventory_content = File.read(generated_inventory_file)
if with_ssh_user
expect(inventory_content).to include("#{machine.name} ansible_ssh_host=#{machine.ssh_info[:host]} ansible_ssh_port=#{machine.ssh_info[:port]} ansible_ssh_user='#{machine.ssh_info[:username]}' ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\n")
else
expect(inventory_content).to include("#{machine.name} ansible_ssh_host=#{machine.ssh_info[:host]} ansible_ssh_port=#{machine.ssh_info[:port]} ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\n")
end
expect(inventory_content).to include("# MISSING: '#{iso_env.machine_names[1]}' machine was probably removed without using Vagrant. This machine should be recreated.\n")
}
end
@ -202,7 +208,7 @@ VF
config.finalize!
Vagrant::Util::Subprocess.stub(execute: Vagrant::Util::Subprocess::Result.new(1, "", ""))
expect {subject.provision}.to raise_error(Vagrant::Errors::AnsibleFailed)
expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsiblePlaybookAppFailed)
end
end
@ -215,16 +221,13 @@ VF
inventory_content = File.read(generated_inventory_file)
expect(inventory_content).to_not match(/^\s*\[^\\+\]\s*$/)
# Note:
# The expectation below is a workaround to a possible misuse (or bug) in RSpec/Ruby stack.
# If 'args' variable is not required by in this block, the "Vagrant::Util::Subprocess).to receive"
# surprisingly expects to receive "no args".
# This problem can be "solved" by using args the "unnecessary" (but harmless) expectation below:
expect(args.length).to be > 0
# Ending this block with a negative expectation (to_not / not_to)
# would lead to a failure of the above expectation.
true
}
end
it "does not show the ansible-playbook command" do
it "doesn't show the ansible-playbook command" do
expect(machine.env.ui).not_to receive(:detail).with { |full_command|
expect(full_command).to include("ansible-playbook")
}
@ -276,7 +279,7 @@ VF
config.host_key_checking = true
end
it_should_set_arguments_and_environment_variables 6, 4, true
it_should_set_arguments_and_environment_variables 5, 4, true
end
describe "with boolean (flag) options disabled" do
@ -288,7 +291,7 @@ VF
config.sudo_user = 'root'
end
it_should_set_arguments_and_environment_variables 7
it_should_set_arguments_and_environment_variables 6
it_should_set_optional_arguments({ "sudo_user" => "--sudo-user=root" })
it "it does not set boolean flag when corresponding option is set to false" do
@ -303,6 +306,7 @@ VF
describe "with raw_arguments option" do
before do
config.sudo = false
config.force_remote_user = false
config.skip_tags = %w(foo bar)
config.limit = "all"
config.raw_arguments = ["--connection=paramiko",
@ -352,12 +356,29 @@ VF
it_should_set_arguments_and_environment_variables
end
context "with force_remote_user option disabled" do
before do
config.force_remote_user = false
end
it_should_create_and_use_generated_inventory false # i.e. without setting ansible_ssh_user in inventory
it_should_set_arguments_and_environment_variables 6
it "uses a --user argument to set a default remote user" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
expect(args).not_to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'")
expect(args).to include("--user=#{machine.ssh_info[:username]}")
}
end
end
describe "with inventory_path option" do
before do
config.inventory_path = existing_file
end
it_should_set_arguments_and_environment_variables
it_should_set_arguments_and_environment_variables 6
it "does not generate the inventory and uses given inventory path instead" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
@ -366,6 +387,26 @@ VF
expect(File.exists?(generated_inventory_file)).to be_false
}
end
it "uses an --extra-vars argument to force ansible_ssh_user parameter" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
expect(args).not_to include("--user=#{machine.ssh_info[:username]}")
expect(args).to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'")
}
end
describe "with force_remote_user option disabled" do
before do
config.force_remote_user = false
end
it "uses a --user argument to set a default remote user" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
expect(args).not_to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'")
expect(args).to include("--user=#{machine.ssh_info[:username]}")
}
end
end
end
describe "with ask_vault_pass option" do
@ -373,7 +414,7 @@ VF
config.ask_vault_pass = true
end
it_should_set_arguments_and_environment_variables 7
it_should_set_arguments_and_environment_variables 6
it "should ask the vault password" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
@ -387,7 +428,7 @@ VF
config.vault_password_file = existing_file
end
it_should_set_arguments_and_environment_variables 7
it_should_set_arguments_and_environment_variables 6
it "uses the given vault password file" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
@ -401,7 +442,7 @@ VF
config.raw_ssh_args = ['-o ControlMaster=no', '-o ForwardAgent=no']
end
it_should_set_arguments_and_environment_variables 6, 4
it_should_set_arguments_and_environment_variables
it_should_explicitly_enable_ansible_ssh_control_persist_defaults
it "passes custom SSH options via ANSIBLE_SSH_ARGS with the highest priority" do
@ -435,7 +476,7 @@ VF
ssh_info[:private_key_path] = ['/path/to/my/key', '/an/other/identity', '/yet/an/other/key']
end
it_should_set_arguments_and_environment_variables 6, 4
it_should_set_arguments_and_environment_variables
it_should_explicitly_enable_ansible_ssh_control_persist_defaults
it "passes additional Identity Files via ANSIBLE_SSH_ARGS" do
@ -452,7 +493,7 @@ VF
ssh_info[:forward_agent] = true
end
it_should_set_arguments_and_environment_variables 6, 4
it_should_set_arguments_and_environment_variables
it_should_explicitly_enable_ansible_ssh_control_persist_defaults
it "enables SSH-Forwarding via ANSIBLE_SSH_ARGS" do
@ -463,21 +504,71 @@ VF
end
end
describe "with verbose option" do
context "with verbose option defined" do
%w(vv vvvv).each do |verbose_option|
describe "with a value of '#{verbose_option}'" do
before do
config.verbose = 'v'
config.verbose = verbose_option
end
it_should_set_arguments_and_environment_variables 7
it_should_set_optional_arguments({ "verbose" => "-v" })
it_should_set_arguments_and_environment_variables 6
it_should_set_optional_arguments({ "verbose" => "-#{verbose_option}" })
it "shows the ansible-playbook command" do
it "shows the ansible-playbook command and set verbosity to '-#{verbose_option}' level" do
expect(machine.env.ui).to receive(:detail).with { |full_command|
expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_FORCE_COLOR=true ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --user=testuser --connection=ssh --timeout=30 --limit='machine1' --inventory-file=#{generated_inventory_dir} -v playbook.yml")
expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit='machine1' --inventory-file=#{generated_inventory_dir} -#{verbose_option} playbook.yml")
}
end
end
describe "with a value of '-#{verbose_option}'" do
before do
config.verbose = "-#{verbose_option}"
end
it_should_set_arguments_and_environment_variables 6
it_should_set_optional_arguments({ "verbose" => "-#{verbose_option}" })
it "shows the ansible-playbook command and set verbosity to '-#{verbose_option}' level" do
expect(machine.env.ui).to receive(:detail).with { |full_command|
expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit='machine1' --inventory-file=#{generated_inventory_dir} -#{verbose_option} playbook.yml")
}
end
end
end
describe "with an invalid string" do
before do
config.verbose = "wrong"
end
it_should_set_arguments_and_environment_variables 6
it_should_set_optional_arguments({ "verbose" => "-v" })
it "shows the ansible-playbook command and set verbosity to '-v' level" do
expect(machine.env.ui).to receive(:detail).with { |full_command|
expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --limit='machine1' --inventory-file=#{generated_inventory_dir} -v playbook.yml")
}
end
end
describe "with an empty string" do
before do
config.verbose = ""
end
it_should_set_arguments_and_environment_variables
it "doesn't show the ansible-playbook command" do
expect(machine.env.ui).not_to receive(:detail).with { |full_command|
expect(full_command).to include("ansible-playbook")
}
end
end
end
describe "without colorized output" do
before do
machine.env.stub(ui: Vagrant::UI::Basic.new)
@ -492,9 +583,8 @@ VF
end
end
# Note:
# The Vagrant Ansible provisioner does not validate the coherency of argument combinations,
# and let ansible-playbook complain.
# The Vagrant Ansible provisioner does not validate the coherency of
# argument combinations, and let ansible-playbook complain.
describe "with a maximum of options" do
before do
# vagrant general options
@ -520,7 +610,7 @@ VF
config.raw_ssh_args = ['-o ControlMaster=no']
end
it_should_set_arguments_and_environment_variables 21, 4, true
it_should_set_arguments_and_environment_variables 20, 4, true
it_should_explicitly_enable_ansible_ssh_control_persist_defaults
it_should_set_optional_arguments({ "extra_vars" => "--extra-vars=@#{File.expand_path(__FILE__)}",
"sudo" => "--sudo",
@ -547,7 +637,7 @@ VF
it "shows the ansible-playbook command, with additional quotes when required" do
expect(machine.env.ui).to receive(:detail).with { |full_command|
expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_FORCE_COLOR=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -o IdentityFile=/my/key1 -o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --user=testuser --connection=ssh --timeout=30 --limit='machine*:&vagrant:!that_one' --inventory-file=#{generated_inventory_dir} --extra-vars=@#{File.expand_path(__FILE__)} --sudo --sudo-user=deployer -vvv --ask-sudo-pass --ask-vault-pass --vault-password-file=#{File.expand_path(__FILE__)} --tags=db,www --skip-tags=foo,bar --start-at-task='an awesome task' --why-not --su-user=foot --ask-su-pass --limit='all' --private-key=./myself.key playbook.yml")
expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -o IdentityFile=/my/key1 -o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --ask-sudo-pass --ask-vault-pass --limit='machine*:&vagrant:!that_one' --inventory-file=#{generated_inventory_dir} --extra-vars=@#{File.expand_path(__FILE__)} --sudo --sudo-user=deployer -vvv --vault-password-file=#{File.expand_path(__FILE__)} --tags=db,www --skip-tags=foo,bar --start-at-task='an awesome task' --why-not --su-user=foot --ask-su-pass --limit='all' --private-key=./myself.key playbook.yml")
}
end
end
@ -598,12 +688,9 @@ VF
cmd_opts = args.last
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to_not include("-o IdentitiesOnly=yes")
# Note:
# The expectation below is a workaround to a possible misuse (or bug) in RSpec/Ruby stack.
# If 'args' variable is not required by in this block, the "Vagrant::Util::Subprocess).to receive"
# surprisingly expects to receive "no args".
# This problem can be "solved" by using args the "unnecessary" (but harmless) expectation below:
expect(true).to be_true
# Ending this block with a negative expectation (to_not / not_to)
# would lead to a failure of the above expectation.
true
}
end
@ -615,12 +702,9 @@ VF
cmd_opts = args.last
expect(cmd_opts[:env]).to_not include('ANSIBLE_SSH_ARGS')
# Note:
# The expectation below is a workaround to a possible misuse (or bug) in RSpec/Ruby stack.
# If 'args' variable is not required by in this block, the "Vagrant::Util::Subprocess).to receive"
# surprisingly expects to receive "no args".
# This problem can be "solved" by using args the "unnecessary" (but harmless) expectation below:
expect(true).to be_true
# Ending this block with a negative expectation (to_not / not_to)
# would lead to a failure of the above expectation.
true
}
end
end

View File

@ -1,3 +1,9 @@
def get_provisioner_option_names(provisioner_class)
config_options = provisioner_class.instance_methods(true).find_all { |i| i.to_s.end_with?('=') }
config_options.map! { |i| i.to_s.sub('=', '') }
(config_options - ["!", "=", "=="]).sort
end
shared_examples_for 'any VagrantConfigProvisioner strict boolean attribute' do |attr_name, attr_default_value|
[true, false].each do |bool|

View File

@ -160,6 +160,7 @@
<li<%= sidebar_current("provisioning-file") %>><a href="/v2/provisioning/file.html">File</a></li>
<li<%= sidebar_current("provisioning-shell") %>><a href="/v2/provisioning/shell.html">Shell</a></li>
<li<%= sidebar_current("provisioning-ansible") %>><a href="/v2/provisioning/ansible.html">Ansible</a></li>
<li<%= sidebar_current("provisioning-ansible-local") %>><a href="/v2/provisioning/ansible_local.html">Ansible Local</a></li>
<li<%= sidebar_current("provisioning-cfengine") %>><a href="/v2/provisioning/cfengine.html">CFEngine</a></li>
<li<%= sidebar_current("provisioning-chefsolo") %>><a href="/v2/provisioning/chef_solo.html">Chef Solo</a></li>
<li<%= sidebar_current("provisioning-chefzero") %>><a href="/v2/provisioning/chef_zero.html">Chef Zero</a></li>

View File

@ -5,16 +5,9 @@ sidebar_current: "provisioning-ansible"
# Ansible Provisioner
**Provisioner name: `"ansible"`**
**Provisioner name: `ansible`**
The ansible provisioner allows you to provision the guest using
[Ansible](http://ansible.com) playbooks by executing `ansible-playbook` from the Vagrant host.
Ansible playbooks are [YAML](http://en.wikipedia.org/wiki/YAML) documents that
comprise the set of steps to be orchestrated on one or more machines. This documentation
page will not go into how to use Ansible or how to write Ansible playbooks, since Ansible
is a complete deployment and configuration management system that is beyond the scope of
a single page of documentation.
The Ansible provisioner allows you to provision the guest using [Ansible](http://ansible.com) playbooks by executing **`ansible-playbook` from the Vagrant host**.
<div class="alert alert-warn">
<p>
@ -27,199 +20,62 @@ a single page of documentation.
## Setup Requirements
* [Install Ansible](http://docs.ansible.com/intro_installation.html#installing-the-control-machine) on your Vagrant host.
* Your Vagrant host should ideally provide a recent version of OpenSSH that [supports ControlPersist](http://docs.ansible.com/faq.html#how-do-i-get-ansible-to-reuse-connections-enable-kerberized-ssh-or-have-ansible-pay-attention-to-my-local-ssh-config-file)
- **[Install Ansible](http://docs.ansible.com/intro_installation.html#installing-the-control-machine) on your Vagrant host**.
## Inventory File
- Your Vagrant host should ideally provide a recent version of OpenSSH that [supports ControlPersist](http://docs.ansible.com/faq.html#how-do-i-get-ansible-to-reuse-connections-enable-kerberized-ssh-or-have-ansible-pay-attention-to-my-local-ssh-config-file).
When using Ansible, it needs to know on which machines a given playbook should run. It does
this by way of an [inventory](http://docs.ansible.com/intro_inventory.html) file which lists those machines.
In the context of Vagrant, there are two ways to approach working with inventory files.
If installing Ansible directly on the Vagrant host is not an option in your development environment, you might be looking for the <a href="/v2/provisioning/ansible_local.html">Ansible Local provisioner</a> alternative.
### Auto-Generated Inventory
## Usage
The first and simplest option is to not provide one to Vagrant at all. Vagrant will generate an
inventory file encompassing all of the virtual machines it manages, and use it for provisioning
machines. The generated inventory file is stored as part of your local Vagrant environment in `.vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory`.
This page only documents the specific parts of the `ansible` (remote) provisioner. General Ansible concepts like Playbook or Inventory are shortly explained in the [introduction to Ansible and Vagrant](/v2/provisioning/ansible_intro.html).
**Groups of Hosts**
### Simplest Configuration
The `ansible.groups` option can be used to pass a hash of group names and group members to be included in the generated inventory file.
With this configuration example:
```
ansible.groups = {
"group1" => ["machine1"],
"group2" => ["machine2"],
"all_groups:children" => ["group1", "group2"]
}
```
Vagrant would generate an inventory file that might look like:
```
# Generated by Vagrant
machine1 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2200 ansible_ssh_private_key_file=/home/.../.vagrant/machines/machine1/virtualbox/private_key
machine2 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2201 ansible_ssh_private_key_file=/home/.../.vagrant/machines/machine2/virtualbox/private_key
[group1]
machine1
[group2]
machine2
[all_groups:children]
group1
group2
```
**Notes**
* The generation of group variables blocks (e.g. `[group1:vars]`) are intentionally not supported, as it is [not recommended to store group variables in the main inventory file](http://docs.ansible.com/intro_inventory.html#splitting-out-host-and-group-specific-data). A good practice is to store these group (or host) variables in `YAML` files stored in `group_vars/` or `host_vars/` directories in the playbook (or inventory) directory.
* Unmanaged machines and undefined groups are not added to the inventory, to avoid useless Ansible errors (e.g. *unreachable host* or *undefined child group*)
* Prior to Vagrant 1.7.3, the `ansible_ssh_private_key_file` variable was not set in generated inventory, but passed as command line argument to `ansible-playbook` command.
For example, `machine3`, `group3` and `group1:vars` in the example below would not be added to the generated inventory file:
```
ansible.groups = {
"group1" => ["machine1"],
"group2" => ["machine2", "machine3"],
"all_groups:children" => ["group1", "group2", "group3"],
"group1:vars" => { "variable1" => 9, "variable2" => "example" }
}
```
### Static Inventory
The second option is for situations where you'd like to have more control over the inventory management.
With the `ansible.inventory_path` option, you can reference a specific inventory resource (e.g. a static inventory file, a [dynamic inventory script](http://docs.ansible.com/intro_dynamic_inventory.html) or even [multiple inventories stored in the same directory](http://docs.ansible.com/intro_dynamic_inventory.html#using-multiple-inventory-sources)). Vagrant will then use this inventory information instead of generating it.
A very simple inventory file for use with Vagrant might look like:
```
default ansible_ssh_host=192.168.111.222
```
Where the above IP address is one set in your Vagrantfile:
```
config.vm.network :private_network, ip: "192.168.111.222"
```
**Notes:**
* The machine names in `Vagrantfile` and `ansible.inventory_path` files should correspond, unless you use `ansible.limit` option to reference the correct machines.
* The SSH host addresses (and ports) must obviously be specified twice, in `Vagrantfile` and `ansible.inventory_path` files.
## Playbook
The second component of a successful Ansible provisioner setup is the Ansible playbook
which contains the steps that should be run on the guest. Ansible's
[playbook documentation](http://docs.ansible.com/playbooks.html) goes into great
detail on how to author playbooks, and there are a number of
[best practices](http://docs.ansible.com/playbooks_best_practices.html) that can be applied to use
Ansible's powerful features effectively. A playbook that installs and starts (or restarts
if it was updated) the NTP daemon via YUM looks like:
```
---
- hosts: all
tasks:
- name: ensure ntpd is at the latest version
yum: pkg=ntp state=latest
notify:
- restart ntpd
handlers:
- name: restart ntpd
service: name=ntpd state=restarted
```
You can of course target other operating systems that don't have YUM by changing the
playbook tasks. Ansible ships with a number of [modules](http://docs.ansible.com/modules.html)
that make running otherwise tedious tasks dead simple.
## Running Ansible
To run Ansible against your Vagrant guest, the basic Vagrantfile configuration looks like:
To run Ansible against your Vagrant guest, the basic `Vagrantfile` configuration looks like:
```ruby
Vagrant.configure("2") do |config|
Vagrant.configure(2) do |config|
#
# Run Ansible from the Vagrant Host
#
config.vm.provision "ansible" do |ansible|
ansible.playbook = "playbook.yml"
end
end
```
Since an Ansible playbook can include many files, you may also collect the related files in
a directory structure like this:
## Options
```
$ tree
.
|-- Vagrantfile
|-- provisioning
| |-- group_vars
| |-- all
| |-- playbook.yml
```
This section lists the specific options for the Ansible (remote) provisioner. In addition to the options listed below, this provisioner supports the [common options for both Ansible provisioners](/v2/provisioning/ansible_common.html).
In such an arrangement, the `ansible.playbook` path should be adjusted accordingly:
- `ask_sudo_pass` (boolean) - require Ansible to [prompt for a sudo password](http://docs.ansible.com/intro_getting_started.html#remote-connection-information).
```ruby
Vagrant.configure("2") do |config|
config.vm.provision "ansible" do |ansible|
ansible.playbook = "provisioning/playbook.yml"
end
end
```
The default value is `false`.
Vagrant will try to run the `playbook.yml` playbook against all machines defined in your Vagrantfile.
- `ask_vault_pass` (boolean) - require Ansible to [prompt for a vault password](http://docs.ansible.com/playbooks_vault.html#vault).
**Backward Compatibility Note**:
The default value is `false`.
Up to Vagrant 1.4, the Ansible provisioner could potentially connect (multiple times) to all hosts from the inventory file.
This behaviour is still possible by setting `ansible.limit = 'all'` (see more details below).
- `force_remote_user` (boolean) - require Vagrant to set the `ansible_ssh_user` setting in the generated inventory, or as an extra variable when a static inventory is used. All the Ansible `remote_user` parameters will then be overridden by the value of `config.ssh.username` of the [Vagrant SSH Settings](/v2/vagrantfile/ssh_settings.html).
## Additional Options
If this option is set to `false` Vagrant will set the Vagrant SSH username as a default Ansible remote user, but `remote_user` parameters of your Ansible plays or tasks will still be taken into account and thus override the Vagrant configuration.
The Ansible provisioner also includes a number of additional options that can be set,
all of which get passed to the `ansible-playbook` command that ships with Ansible.
The default value is `true`.
* `ansible.extra_vars` can be used to pass additional variables (with highest priority) to the playbook. This parameter can be a path to a JSON or YAML file, or a hash. For example:
**Note:** This option was introduced in Vagrant 1.8.0. Previous Vagrant versions behave like if this option was set to `false`.
```
ansible.extra_vars = {
ntp_server: "pool.ntp.org",
nginx: {
port: 8008,
workers: 4
}
}
```
These variables take the highest precedence over any other variables.
* `ansible.sudo` can be set to `true` to cause Ansible to perform commands using sudo.
* `ansible.sudo_user` can be set to a string containing a username on the guest who should be used
by the sudo command.
* `ansible.ask_sudo_pass` can be set to `true` to require Ansible to prompt for a sudo password.
* `ansible.ask_vault_pass` can be set to `true` to require Ansible to prompt for a vault password.
* `ansible.vault_password_file` can be set to a string containing the path of a file containing the password used by Ansible Vault.
* `ansible.limit` can be set to a string or an array of machines or groups from the inventory file to further control which hosts are affected. Note that:
* As of Vagrant 1.5, the machine name (taken from Vagrantfile) is set as **default limit** to ensure that `vagrant provision` steps only affect the expected machine. Setting `ansible.limit` will override this default.
* Setting `ansible.limit = 'all'` can be used to make Ansible connect to all machines from the inventory file.
* `ansible.verbose` can be set to increase Ansible's verbosity to obtain detailed logging:
* `'v'`, verbose mode
* `'vv'`
* `'vvv'`, more
* `'vvvv'`, connection debugging
* `ansible.tags` can be set to a string or an array of tags. Only plays, roles and tasks tagged with these values will be executed.
* `ansible.skip_tags` can be set to a string or an array of tags. Only plays, roles and tasks that *do not match* these values will be executed.
* `ansible.start_at_task` can be set to a string corresponding to the task name where the playbook provision will start.
* `ansible.raw_arguments` can be set to an array of strings corresponding to a list of `ansible-playbook` arguments (e.g. `['--check', '-M /my/modules']`). It is an *unsafe wildcard* that can be used to apply Ansible options that are not (yet) supported by this Vagrant provisioner. As of Vagrant 1.7, `raw_arguments` has the highest priority and its values can potentially override or break other Vagrant settings.
* `ansible.raw_ssh_args` can be set to an array of strings corresponding to a list of OpenSSH client parameters (e.g. `['-o ControlMaster=no']`). It is an *unsafe wildcard* that can be used to pass additional SSH settings to Ansible via `ANSIBLE_SSH_ARGS` environment variable.
* `ansible.host_key_checking` can be set to `true` which will enable host key checking. As of Vagrant 1.5, the default value is `false` and as of Vagrant 1.7 the user known host file (e.g. `~/.ssh/known_hosts`) is no longer read nor modified. In other words: by default, the Ansible provisioner behaves the same as Vagrant native commands (e.g `vagrant ssh`).
- `host_key_checking` (boolean) - require Ansible to [enable SSH host key checking](http://docs.ansible.com/intro_getting_started.html#host-key-checking).
The default value is `false`.
- `raw_ssh_args` (array of strings) - require Ansible to apply a list of OpenSSH client options.
Example: `['-o ControlMaster=no']`.
It is an *unsafe wildcard* that can be used to pass additional SSH settings to Ansible via `ANSIBLE_SSH_ARGS` environment variable, overriding any other SSH arguments (e.g. defined in an [`ansible.cfg` configuration file](http://docs.ansible.com/intro_configuration.html#ssh-args)).
## Tips and Tricks
@ -265,46 +121,12 @@ end
If you apply this parallel provisioning pattern with a static Ansible inventory, you'll have to organize the things so that [all the relevant private keys are provided to the `ansible-playbook` command](https://github.com/mitchellh/vagrant/pull/5765#issuecomment-120247738). The same kind of considerations applies if you are using multiple private keys for a same machine (see [`config.ssh.private_key_path` SSH setting](/v2/vagrantfile/ssh_settings.html)).
### Provide a local `ansible.cfg` file
Certain settings in Ansible are (only) adjustable via a [configuration file](http://docs.ansible.com/intro_configuration.html), and you might want to ship such a file in your Vagrant project.
As `ansible-playbook` command looks for local `ansible.cfg` configuration file in its *current directory* (but not in the directory that contains the main playbook), you have to store this file adjacent to your Vagrantfile.
Note that it is also possible to reference an Ansible configuration file via `ANSIBLE_CONFIG` environment variable, if you want to be flexible about the location of this file.
### Why does the Ansible provisioner connect as the wrong user?
It is good to know that the following Ansible settings always override the `config.ssh.username` option defined in [Vagrant SSH Settings](/v2/vagrantfile/ssh_settings.html):
* `ansible_ssh_user` variable
* `remote_user` (or `user`) play attribute
* `remote_user` task attribute
Be aware that copying snippets from the Ansible documentation might lead to this problem, as `root` is used as the remote user in many [examples](http://docs.ansible.com/playbooks_intro.html#hosts-and-users).
Example of an SSH error (with `vvv` log level), where an undefined remote user `xyz` has replaced `vagrant`:
```
TASK: [my_role | do something] *****************
<127.0.0.1> ESTABLISH CONNECTION FOR USER: xyz
<127.0.0.1> EXEC ['ssh', '-tt', '-vvv', '-o', 'ControlMaster=auto',...
fatal: [ansible-devbox] => SSH encountered an unknown error. We recommend you re-run the command using -vvvv, which will enable SSH debugging output to help diagnose the issue.
```
In a situation like the above, to override the `remote_user` specified in a play you can use the following line in your Vagrantfile `vm.provision` block:
```
ansible.extra_vars = { ansible_ssh_user: 'vagrant' }
```
### Force Paramiko Connection Mode
The Ansible provisioner is implemented with native OpenSSH support in mind, and there is no official support for [paramiko](https://github.com/paramiko/paramiko/) (A native Python SSHv2 protocol library).
If you really need to use this connection mode, it is though possible to enable paramiko as illustrated in the following configuration examples:
With auto-generated inventory:
```

View File

@ -0,0 +1,86 @@
---
page_title: "Common Ansible Options - Provisioning"
sidebar_current: "provisioning-ansible-common"
---
# Shared Ansible Options
The following options are available to both Ansible provisioners:
- [`ansible`](/v2/provisioning/ansible.html)
- [`ansible_local`](/v2/provisioning/ansible_local.html)
These options get passed to the `ansible-playbook` command that ships with Ansible, either via command line arguments or environment variables, depending on Ansible own capabilities.
Some of these options are for advanced usage only and should not be used unless you understand their purpose.
- `extra_vars` (string or hash) - Pass additional variables (with highest priority) to the playbook.
This parameter can be a path to a JSON or YAML file, or a hash.
Example:
```ruby
ansible.extra_vars = {
ntp_server: "pool.ntp.org",
nginx: {
port: 8008,
workers: 4
}
}
```
These variables take the highest precedence over any other variables.
- `groups` (hash) - Set of inventory groups to be included in the [auto-generated inventory file](/v2/provisioning/ansible_intro.html).
Example:
```ruby
ansible.groups = {
"web" => ["vm1", "vm2"],
"db" => ["vm3"]
}
```
Notes:
- Alphanumeric patterns are not supported (e.g. `db-[a:f]`, `vm[01:10]`).
- This option has no effect when the `inventory_path` option is defined.
- `inventory_path` (string) - The path to an Ansible inventory resource (e.g. a [static inventory file](http://docs.ansible.com/intro_inventory.html), a [dynamic inventory script](http://docs.ansible.com/intro_dynamic_inventory.html) or even [multiple inventories stored in the same directory](http://docs.ansible.com/intro_dynamic_inventory.html#using-multiple-inventory-sources)).
By default, this option is disabled and Vagrant generates an inventory based on the `Vagrantfile` information.
- `limit` (string or array of strings) - Set of machines or groups from the inventory file to further control which hosts [are affected](http://docs.ansible.com/glossary.html#limit-groups).
The default value is set to the machine name (taken from `Vagrantfile`) to ensure that `vagrant provision` command only affect the expected machine.
Setting `limit = "all"` can be used to make Ansible connect to all machines from the inventory file.
- `raw_arguments` (array of strings) - a list of additional `ansible-playbook` arguments.
It is an *unsafe wildcard* that can be used to apply Ansible options that are not (yet) supported by this Vagrant provisioner. As of Vagrant 1.7, `raw_arguments` has the highest priority and its values can potentially override or break other Vagrant settings.
Example: `['--check', '-M /my/modules']`).
- `skip_tags` (string or array of strings) - Only plays, roles and tasks that [*do not match* these values will be executed](http://docs.ansible.com/playbooks_tags.html).
- `start_at_task` (string) - The task name where the [playbook execution will start](http://docs.ansible.com/playbooks_startnstep.html#start-at-task).
- `sudo` (boolean) - Cause Ansible to perform all the playbook tasks [using sudo](http://docs.ansible.com/glossary.html#sudo).
The default value is `false`.
- `sudo_user` (string) - set the default username who should be used by the sudo command.
- `tags` (string or array of strings) - Only plays, roles and tasks [tagged with these values will be executed](http://docs.ansible.com/playbooks_tags.html) .
- `verbose` (boolean or string) - Set Ansible's verbosity to obtain detailed logging
Default value is `false` (minimal verbosity).
Examples: `true` (equivalent to `v`), `-vvv` (equivalent to `vvv`), `vvvv`.
Note that when the `verbose` option is enabled, the `ansible-playbook` command used by Vagrant will be displayed.
- `vault_password_file` (string) - The path of a file containing the password used by [Ansible Vault](http://docs.ansible.com/playbooks_vault.html#vault).

View File

@ -0,0 +1,208 @@
---
page_title: "Ansible - Short Introduction"
sidebar_current: "provisioning-ansible-intro"
---
# Ansible and Vagrant
The information below is applicable to both Ansible provisioners:
- [`ansible`](/v2/provisioning/ansible.html), where Ansible is executed on the **Vagrant host**
- [`ansible_local`](/v2/provisioning/ansible_local.html), where Ansible is executed on the **Vagrant guest**
The list of common options for these two provisioners is documented in a [separate documentation page](/v2/provisioning/ansible_common.html).
This documentation page will not go into how to use Ansible or how to write Ansible playbooks, since Ansible is a complete deployment and configuration management system that is beyond the scope of Vagrant documentation.
To learn more about Ansible, please consult the [Ansible Documentation Site](http://docs.ansible.com/).
## The Playbook File
The first component of a successful Ansible provisioner setup is the Ansible playbook which contains the steps that should be run on the guest. Ansible's
[playbook documentation](http://docs.ansible.com/playbooks.html) goes into great detail on how to author playbooks, and there are a number of [best practices](http://docs.ansible.com/playbooks_best_practices.html) that can be applied to use Ansible's powerful features effectively.
A playbook that installs and starts (or restarts) the NTP daemon via YUM looks like:
```
---
- hosts: all
tasks:
- name: ensure ntpd is at the latest version
yum: pkg=ntp state=latest
notify:
- restart ntpd
handlers:
- name: restart ntpd
service: name=ntpd state=restarted
```
You can of course target other operating systems that don't have YUM by changing the playbook tasks. Ansible ships with a number of [modules](http://docs.ansible.com/modules.html) that make running otherwise tedious tasks dead simple.
### Running Ansible
The `playbook` option is strictly required by both Ansible provisioners ([`ansible`](/v2/provisioning/ansible.html) and [`ansible_local`](/v2/provisioning/ansible_local.html)), as illustrated in this basic Vagrantfile` configuration:
```ruby
Vagrant.configure(2) do |config|
# Use :ansible or :ansible_local to
# select the provisioner of your choice
config.vm.provision :ansible do |ansible|
ansible.playbook = "playbook.yml"
end
end
```
Since an Ansible playbook can include many files, you may also collect the related files in a [directory structure](http://docs.ansible.com/playbooks_best_practices.html#directory-layout) like this:
```
.
|-- Vagrantfile
|-- provisioning
| |-- group_vars
| |-- all
| |-- roles
| |-- bar
| |-- foo
| |-- playbook.yml
```
In such an arrangement, the `ansible.playbook` path should be adjusted accordingly:
```ruby
Vagrant.configure(2) do |config|
config.vm.provision "ansible" do |ansible|
ansible.playbook = "provisioning/playbook.yml"
end
end
```
## The Inventory File
When using Ansible, it needs to know on which machines a given playbook should run. It does this by way of an [inventory](http://docs.ansible.com/intro_inventory.html) file which lists those machines. In the context of Vagrant, there are two ways to approach working with inventory files.
### Auto-Generated Inventory
The first and simplest option is to not provide one to Vagrant at all. Vagrant will generate an inventory file encompassing all of the virtual machines it manages, and use it for provisioning machines.
**Example with the [`ansible`](/v2/provisioning/ansible.html) provisioner:**
```
# Generated by Vagrant
default ansible_ssh_host=127.0.0.1 ansible_ssh_port=2200 ansible_ssh_user='vagrant' ansible_ssh_private_key_file='/home/.../.vagrant/machines/default/virtualbox/private_key'
```
Note that the generated inventory file is stored as part of your local Vagrant environment in
`.vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory`.
**Example with the [`ansible_local`](/v2/provisioning/ansible_local.html) provisioner:**
```
# Generated by Vagrant
default ansible_connection=local
```
Note that the generated inventory file is uploaded to the guest VM in a subdirectory of [`tmp_path`](/v2/provisioning/ansible_local.html), e.g. `/tmp/vagrant-ansible/inventory/vagrant_ansible_local_inventory`.
**How to generate Inventory Groups:**
The [`groups`](/v2/provisioning/ansible_common.html) option can be used to pass a hash of group names and group members to be included in the generated inventory file.
With this configuration example:
```
Vagrant.configure(2) do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.define "machine1"
config.vm.define "machine2"
config.vm.provision "ansible" do |ansible|
ansible.playbook = "playbook.yml"
ansible.groups = {
"group1" => ["machine1"],
"group2" => ["machine2"],
"all_groups:children" => ["group1", "group2"]
}
end
end
```
Vagrant would generate an inventory file that might look like:
```
# Generated by Vagrant
machine1 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2200 ansible_ssh_user='vagrant' ansible_ssh_private_key_file='/home/.../.vagrant/machines/machine1/virtualbox/private_key'
machine2 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2222 ansible_ssh_user='vagrant' ansible_ssh_private_key_file='/home/.../.vagrant/machines/machine2/virtualbox/private_key'
[group1]
machine1
[group2]
machine2
[all_groups:children]
group1
group2
```
**Notes:**
- Prior to Vagrant 1.7.3, the `ansible_ssh_private_key_file` variable was not set in generated inventory, but passed as command line argument to `ansible-playbook` command.
- The generation of group variables blocks (e.g. `[group1:vars]`) are intentionally not supported, as it is [not recommended to store group variables in the main inventory file](http://docs.ansible.com/intro_inventory.html#splitting-out-host-and-group-specific-data). A good practice is to store these group (or host) variables in `YAML` files stored in `group_vars/` or `host_vars/` directories in the playbook (or inventory) directory.
- Unmanaged machines and undefined groups are not added to the inventory, to avoid useless Ansible errors (e.g. *unreachable host* or *undefined child group*)
For example, `machine3`, `group3` and `group1:vars` in the example below would not be added to the generated inventory file:
```
ansible.groups = {
"group1" => ["machine1"],
"group2" => ["machine2", "machine3"],
"all_groups:children" => ["group1", "group2", "group3"],
"group1:vars" => { "variable1" => 9, "variable2" => "example" }
}
```
### Static Inventory
The second option is for situations where you'd like to have more control over the inventory management.
With the `inventory_path` option, you can reference a specific inventory resource (e.g. a static inventory file, a [dynamic inventory script](http://docs.ansible.com/intro_dynamic_inventory.html) or even [multiple inventories stored in the same directory](http://docs.ansible.com/intro_dynamic_inventory.html#using-multiple-inventory-sources)). Vagrant will then use this inventory information instead of generating it.
A very simple inventory file for use with Vagrant might look like:
```
default ansible_ssh_host=192.168.111.222
```
Where the above IP address is one set in your Vagrantfile:
```
config.vm.network :private_network, ip: "192.168.111.222"
```
**Notes:**
- The machine names in `Vagrantfile` and `ansible.inventory_path` files should correspond, unless you use `ansible.limit` option to reference the correct machines.
- The SSH host addresses (and ports) must obviously be specified twice, in `Vagrantfile` and `ansible.inventory_path` files.
- Sharing hostnames across Vagrant host and guests might be a good idea (e.g. with some Ansible configuration task, or with a plugin like [`vagrant-hostmanager`](https://github.com/smdahlen/vagrant-hostmanager)).
### The Ansible Configuration File
Certain settings in Ansible are (only) adjustable via a [configuration file](http://docs.ansible.com/intro_configuration.html), and you might want to ship such a file in your Vagrant project.
When shipping an Ansible configuration file it is good to know that:
- it is possible to reference an Ansible configuration file via `ANSIBLE_CONFIG` environment variable, if you want to be flexible about the location of this file.
- `ansible-playbook` **never** looks for `ansible.cfg` in the directory that contains the main playbook file.
- As of Ansible 1.5, the lookup order is the following:
- `ANSIBLE_CONFIG` an environment variable
- `ansible.cfg` in the runtime current directory
- `.ansible.cfg` in the user home directory
- `/etc/ansible/ansible.cfg`

View File

@ -0,0 +1,147 @@
---
page_title: "Ansible Local - Provisioning"
sidebar_current: "provisioning-ansible-local"
---
# Ansible Local Provisioner
**Provisioner name: `ansible_local`**
The Ansible Local provisioner allows you to provision the guest using [Ansible](http://ansible.com) playbooks by executing **`ansible-playbook` directly on the guest machine**.
<div class="alert alert-warn">
<p>
<strong>Warning:</strong> If you're not familiar with Ansible and Vagrant already,
I recommend starting with the <a href="/v2/provisioning/shell.html">shell
provisioner</a>. However, if you're comfortable with Vagrant already, Vagrant
is a great way to learn Ansible.
</p>
</div>
## Setup Requirements
The main advantage of the Ansible Local provisioner in comparison to the [Ansible (remote) provisioner](/v2/provisioning/ansible.html) is that it does not require any additional software on your Vagrant host.
On the other hand, [Ansible must obviously be installed](http://docs.ansible.com/intro_installation.html#installing-the-control-machine) on your guest machine(s).
**Note:** By default, Vagrant will *try* to automatically install Ansible if it is not yet present on the guest machine (see the `install` option below for more details).
## Usage
This page only documents the specific parts of the `ansible_local` provisioner. General Ansible concepts like Playbook or Inventory are shortly explained in the [introduction to Ansible and Vagrant](/v2/provisioning/ansible_intro.html).
The Ansible Local provisioner requires that all the Ansible Playbook files are available on the guest machine, at the location referred by the `provisioning_path` option. Usually these files are initially present on the host machine (as part of your Vagrant projet), and it is quite easy to share them with a Vagrant [Synced Folder](/v2/synced-folders/index.html).
### Simplest Configuration
To run Ansible from your Vagrant guest, the basic `Vagrantfile` configuration looks like:
```ruby
Vagrant.configure(2) do |config|
#
# Run Ansible from the Vagrant VM
#
config.vm.provision "ansible_local" do |ansible|
ansible.playbook = "playbook.yml"
end
end
```
**Requirements:**
- The `playbook.yml` file is stored in your Vagrant's project home directory.
- The [default shared directory](/v2/synced-folders/basic_usage.html) is enabled (`.` &rarr; `/vagrant`).
## Options
This section lists the specific options for the Ansible Local provisioner. In addition to the options listed below, this provisioner supports the [common options for both Ansible provisioners](/v2/provisioning/ansible_common.html).
- `install` (boolean) - Try to automatically install Ansible on the guest system.
This option is enabled by default.
Vagrant will to try to install (or upgrade) Ansible when one of these conditions are met:
- Ansible is not installed (or cannot be found).
- The `version` option is set to `"latest"`.
- The current Ansible version does not correspond to the `version` option.
**Attention:** There is no guarantee that this automated installation will replace a custom Ansible setup, that might be already present on the Vagrant box.
- `provisioning_path` (string) - An absolute path on the guest machine where the Ansible files are stored. The `ansible-playbook` command is executed from this directory.
The default value is `/vagrant`.
- `tmp_path` (string) - An absolute path on the guest machine where temporary files are stored by the Ansible Local provisioner.
The default value is `/tmp/vagrant-ansible`
- `version` (string) - The expected Ansible version.
This option is disabled by default.
When an Ansible version is defined (e.g. `"1.8.2"`), the Ansible local provisioner will be executed only if Ansible is installed at the requested version.
When this option is set to `"latest"`, no version check is applied.
**Attention:** It is currently not possible to use this option to specify which version of Ansible must be automatically installed. With the `install` option enabled, the latest version packaged for the target operating system will always be installed.
## Tips and Tricks
### Ansible Parallel Execution from a Guest
With the following configuration pattern, you can install and execute Ansible only on a single guest machine (the `"controller"`) to provision all your machines.
```ruby
Vagrant.configure(2) do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.define "node1" do |machine|
machine.vm.network "private_network", ip: "172.17.177.21"
end
config.vm.define "node2" do |machine|
machine.vm.network "private_network", ip: "172.17.177.22"
end
config.vm.define 'controller' do |machine|
machine.vm.network "private_network", ip: "172.17.177.11"
machine.vm.provision :ansible_local do |ansible|
ansible.playbook = "example.yml"
ansible.verbose = true
ansible.install = true
ansible.limit = "all" # or only "nodes" group, etc.
ansible.inventory_path = "inventory"
end
end
end
```
You need to create a static `inventory` file that corresponds to your `Vagrantfile` machine definitions:
```
controller ansible_connection=local
node1 ansible_ssh_host=172.17.177.21 ansible_ssh_private_key_file=/vagrant/.vagrant/machines/node1/virtualbox/private_key
node2 ansible_ssh_host=172.17.177.22 ansible_ssh_private_key_file=/vagrant/.vagrant/machines/node2/virtualbox/private_key
[nodes]
node[1:2]
```
And finally, you also have to create an [`ansible.cfg` file](http://docs.ansible.com/intro_configuration.html#openssh-specific-settings) to fully disable SSH host key checking. More SSH configurations can be added to the `ssh_args` parameter (e.g. agent forwarding, etc.)
```
[defaults]
host_key_checking = no
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes
```