Merge 'gildegoma/2103-ansible-local-v2'
Resolve conflict in CHANGELOG.md
This commit is contained in:
commit
c6ef73a6fa
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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."
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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:
|
||||
|
||||
```
|
||||
|
|
|
@ -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).
|
|
@ -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`
|
|
@ -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 (`.` → `/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
|
||||
```
|
Loading…
Reference in New Issue