Merge pull request #8913 from mitchellh/gildegoma/6570-ansible-compatibility-mode
Ansible: Add "compatibility_mode" and "*become*" common options; Move "version" to common options
This commit is contained in:
commit
34addec796
|
@ -1,3 +1,5 @@
|
|||
require_relative "../constants"
|
||||
|
||||
module VagrantPlugins
|
||||
module Ansible
|
||||
module Config
|
||||
|
@ -6,6 +8,9 @@ module VagrantPlugins
|
|||
GALAXY_COMMAND_DEFAULT = "ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force".freeze
|
||||
PLAYBOOK_COMMAND_DEFAULT = "ansible-playbook".freeze
|
||||
|
||||
attr_accessor :become
|
||||
attr_accessor :become_user
|
||||
attr_accessor :compatibility_mode
|
||||
attr_accessor :config_file
|
||||
attr_accessor :extra_vars
|
||||
attr_accessor :galaxy_role_file
|
||||
|
@ -20,13 +25,29 @@ module VagrantPlugins
|
|||
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
|
||||
attr_accessor :version
|
||||
|
||||
#
|
||||
# Deprecated options
|
||||
#
|
||||
alias :sudo :become
|
||||
def sudo=(value)
|
||||
show_deprecation_info 'sudo', 'become'
|
||||
@become = value
|
||||
end
|
||||
alias :sudo_user :become_user
|
||||
def sudo_user=(value)
|
||||
show_deprecation_info 'sudo_user', 'become_user'
|
||||
@become_user = value
|
||||
end
|
||||
|
||||
def initialize
|
||||
@become = UNSET_VALUE
|
||||
@become_user = UNSET_VALUE
|
||||
@compatibility_mode = Ansible::COMPATIBILITY_MODE_AUTO
|
||||
@config_file = UNSET_VALUE
|
||||
@extra_vars = UNSET_VALUE
|
||||
@galaxy_role_file = UNSET_VALUE
|
||||
|
@ -41,14 +62,16 @@ module VagrantPlugins
|
|||
@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
|
||||
@version = UNSET_VALUE
|
||||
end
|
||||
|
||||
def finalize!
|
||||
@become = false if @become != true
|
||||
@become_user = nil if @become_user == UNSET_VALUE
|
||||
@compatibility_mode = nil unless Ansible::COMPATIBILITY_MODES.include?(@compatibility_mode)
|
||||
@config_file = nil if @config_file == UNSET_VALUE
|
||||
@extra_vars = nil if @extra_vars == UNSET_VALUE
|
||||
@galaxy_role_file = nil if @galaxy_role_file == UNSET_VALUE
|
||||
|
@ -63,11 +86,10 @@ module VagrantPlugins
|
|||
@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
|
||||
@version = "" if @version == UNSET_VALUE
|
||||
end
|
||||
|
||||
# Just like the normal configuration "validate" method except that
|
||||
|
@ -76,6 +98,12 @@ module VagrantPlugins
|
|||
def validate(machine)
|
||||
@errors = _detected_errors
|
||||
|
||||
# Validate that a compatibility mode was provided
|
||||
if !compatibility_mode
|
||||
@errors << I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode",
|
||||
valid_modes: Ansible::COMPATIBILITY_MODES.map { |s| "'#{s}'" }.join(', '))
|
||||
end
|
||||
|
||||
# Validate that a playbook path was provided
|
||||
if !playbook
|
||||
@errors << I18n.t("vagrant.provisioners.ansible.errors.no_playbook")
|
||||
|
@ -112,6 +140,14 @@ module VagrantPlugins
|
|||
end
|
||||
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def show_deprecation_info(deprecated_option, new_option)
|
||||
puts "DEPRECATION: The '#{deprecated_option}' option for the Ansible provisioner is deprecated."
|
||||
puts "Please use the '#{new_option}' option instead."
|
||||
puts "The '#{deprecated_option}' option will be removed in a future release of Vagrant.\n\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,6 @@ module VagrantPlugins
|
|||
attr_accessor :install
|
||||
attr_accessor :install_mode
|
||||
attr_accessor :pip_args
|
||||
attr_accessor :version
|
||||
|
||||
def initialize
|
||||
super
|
||||
|
@ -21,7 +20,6 @@ module VagrantPlugins
|
|||
@pip_args = UNSET_VALUE
|
||||
@provisioning_path = UNSET_VALUE
|
||||
@tmp_path = UNSET_VALUE
|
||||
@version = UNSET_VALUE
|
||||
end
|
||||
|
||||
def finalize!
|
||||
|
@ -32,7 +30,6 @@ module VagrantPlugins
|
|||
@pip_args = "" if @pip_args == 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)
|
||||
|
|
|
@ -5,16 +5,25 @@ module VagrantPlugins
|
|||
module Config
|
||||
class Host < Base
|
||||
|
||||
attr_accessor :ask_sudo_pass
|
||||
attr_accessor :ask_become_pass
|
||||
attr_accessor :ask_vault_pass
|
||||
attr_accessor :force_remote_user
|
||||
attr_accessor :host_key_checking
|
||||
attr_accessor :raw_ssh_args
|
||||
|
||||
#
|
||||
# Deprecated options
|
||||
#
|
||||
alias :ask_sudo_pass :ask_become_pass
|
||||
def ask_sudo_pass=(value)
|
||||
show_deprecation_warning 'ask_sudo_pass', 'ask_become_pass'
|
||||
@ask_become_pass = value
|
||||
end
|
||||
|
||||
def initialize
|
||||
super
|
||||
|
||||
@ask_sudo_pass = false
|
||||
@ask_become_pass = false
|
||||
@ask_vault_pass = false
|
||||
@force_remote_user = true
|
||||
@host_key_checking = false
|
||||
|
@ -24,7 +33,7 @@ module VagrantPlugins
|
|||
def finalize!
|
||||
super
|
||||
|
||||
@ask_sudo_pass = false if @ask_sudo_pass != true
|
||||
@ask_become_pass = false if @ask_become_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
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
module VagrantPlugins
|
||||
module Ansible
|
||||
COMPATIBILITY_MODE_AUTO = "auto".freeze
|
||||
COMPATIBILITY_MODE_V1_8 = "1.8".freeze
|
||||
COMPATIBILITY_MODE_V2_0 = "2.0".freeze
|
||||
SAFE_COMPATIBILITY_MODE = COMPATIBILITY_MODE_V1_8
|
||||
COMPATIBILITY_MODES = [
|
||||
COMPATIBILITY_MODE_AUTO,
|
||||
COMPATIBILITY_MODE_V1_8,
|
||||
COMPATIBILITY_MODE_V2_0,
|
||||
].freeze
|
||||
end
|
||||
end
|
|
@ -11,21 +11,30 @@ module VagrantPlugins
|
|||
error_key(:ansible_command_failed)
|
||||
end
|
||||
|
||||
class AnsibleNotFoundOnHost < AnsibleError
|
||||
error_key(:ansible_not_found_on_host)
|
||||
class AnsibleCompatibilityModeConflict < AnsibleError
|
||||
error_key(:ansible_compatibility_mode_conflict)
|
||||
end
|
||||
|
||||
class AnsibleNotFoundOnGuest < AnsibleError
|
||||
error_key(:ansible_not_found_on_guest)
|
||||
end
|
||||
|
||||
class AnsibleNotFoundOnHost < AnsibleError
|
||||
error_key(:ansible_not_found_on_host)
|
||||
end
|
||||
|
||||
class AnsiblePipInstallIsNotSupported < AnsibleError
|
||||
error_key(:cannot_support_pip_install)
|
||||
end
|
||||
|
||||
class AnsibleVersionNotFoundOnGuest < AnsibleError
|
||||
error_key(:ansible_version_not_found_on_guest)
|
||||
class AnsibleProgrammingError < AnsibleError
|
||||
error_key(:ansible_programming_error)
|
||||
end
|
||||
|
||||
class AnsibleVersionMismatch < AnsibleError
|
||||
error_key(:ansible_version_mismatch)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +1,4 @@
|
|||
require_relative "../constants"
|
||||
require_relative "../errors"
|
||||
require_relative "../helpers"
|
||||
|
||||
|
@ -14,15 +15,80 @@ module VagrantPlugins
|
|||
|
||||
RANGE_PATTERN = %r{(?:\[[a-z]:[a-z]\]|\[[0-9]+?:[0-9]+?\])}.freeze
|
||||
|
||||
ANSIBLE_PARAMETER_NAMES = {
|
||||
Ansible::COMPATIBILITY_MODE_V1_8 => {
|
||||
ansible_host: "ansible_ssh_host",
|
||||
ansible_password: "ansible_ssh_pass",
|
||||
ansible_port: "ansible_ssh_port",
|
||||
ansible_user: "ansible_ssh_user",
|
||||
ask_become_pass: "ask-sudo-pass",
|
||||
become: "sudo",
|
||||
become_user: "sudo-user",
|
||||
},
|
||||
Ansible::COMPATIBILITY_MODE_V2_0 => {
|
||||
ansible_host: "ansible_host",
|
||||
ansible_password: "ansible_password",
|
||||
ansible_port: "ansible_port",
|
||||
ansible_user: "ansible_user",
|
||||
ask_become_pass: "ask-become-pass",
|
||||
become: "become",
|
||||
become_user: "become-user",
|
||||
}
|
||||
}
|
||||
|
||||
protected
|
||||
|
||||
def initialize(machine, config)
|
||||
super
|
||||
@control_machine = nil
|
||||
|
||||
@command_arguments = []
|
||||
@environment_variables = {}
|
||||
@inventory_machines = {}
|
||||
@inventory_path = nil
|
||||
|
||||
@gathered_version_stdout = nil
|
||||
@gathered_version_major = nil
|
||||
@gathered_version = nil
|
||||
end
|
||||
|
||||
def set_and_check_compatibility_mode
|
||||
begin
|
||||
set_gathered_ansible_version(gather_ansible_version)
|
||||
rescue StandardError => e
|
||||
# Nothing to do here, as the fallback on safe compatibility_mode is done below
|
||||
@logger.error("Error while gathering the ansible version: #{e.to_s}")
|
||||
end
|
||||
|
||||
if @gathered_version_major
|
||||
if config.compatibility_mode == Ansible::COMPATIBILITY_MODE_AUTO
|
||||
detect_compatibility_mode
|
||||
elsif @gathered_version_major.to_i < 2 && config.compatibility_mode == Ansible::COMPATIBILITY_MODE_V2_0
|
||||
# A better version comparator will be needed
|
||||
# when more compatibility modes come... but so far let's keep it simple!
|
||||
raise Ansible::Errors::AnsibleCompatibilityModeConflict,
|
||||
ansible_version: @gathered_version,
|
||||
system: @control_machine,
|
||||
compatibility_mode: config.compatibility_mode
|
||||
end
|
||||
end
|
||||
|
||||
if config.compatibility_mode == Ansible::COMPATIBILITY_MODE_AUTO
|
||||
config.compatibility_mode = Ansible::SAFE_COMPATIBILITY_MODE
|
||||
|
||||
@machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.compatibility_mode_not_detected",
|
||||
compatibility_mode: config.compatibility_mode,
|
||||
gathered_version: @gathered_version_stdout) +
|
||||
"\n")
|
||||
end
|
||||
|
||||
unless Ansible::COMPATIBILITY_MODES.slice(1..-1).include?(config.compatibility_mode)
|
||||
raise Ansible::Errors::AnsibleProgrammingError,
|
||||
message: "The config.compatibility_mode must be correctly set at this stage!",
|
||||
details: "config.compatibility_mode: '#{config.compatibility_mode}'"
|
||||
end
|
||||
|
||||
@lexicon = ANSIBLE_PARAMETER_NAMES[config.compatibility_mode]
|
||||
end
|
||||
|
||||
def check_files_existence
|
||||
|
@ -97,8 +163,8 @@ module VagrantPlugins
|
|||
|
||||
@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 << "--#{@lexicon[:become]}" if config.become
|
||||
@command_arguments << "--#{@lexicon[:become_user]}=#{config.become_user}" if config.become_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
|
||||
|
@ -291,6 +357,44 @@ module VagrantPlugins
|
|||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def detect_compatibility_mode
|
||||
if !@gathered_version_major || config.compatibility_mode != Ansible::COMPATIBILITY_MODE_AUTO
|
||||
raise Ansible::Errors::AnsibleProgrammingError,
|
||||
message: "The detect_compatibility_mode() function shouldn't have been called!",
|
||||
details: %Q(config.compatibility_mode: '#{config.compatibility_mode}'
|
||||
gathered version major number: '#{@gathered_version_major}'
|
||||
gathered version stdout version:
|
||||
#{@gathered_version_stdout})
|
||||
end
|
||||
|
||||
if @gathered_version_major.to_i <= 1
|
||||
config.compatibility_mode = Ansible::COMPATIBILITY_MODE_V1_8
|
||||
else
|
||||
config.compatibility_mode = Ansible::COMPATIBILITY_MODE_V2_0
|
||||
end
|
||||
|
||||
@machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.compatibility_mode_warning",
|
||||
compatibility_mode: config.compatibility_mode,
|
||||
ansible_version: @gathered_version) +
|
||||
"\n")
|
||||
end
|
||||
|
||||
def set_gathered_ansible_version(stdout_output)
|
||||
@gathered_version_stdout = stdout_output
|
||||
if !@gathered_version_stdout.empty?
|
||||
first_line = @gathered_version_stdout.lines[0]
|
||||
ansible_version_pattern = first_line.match(/(^ansible\s+)(.+)$/)
|
||||
if ansible_version_pattern
|
||||
_, @gathered_version, _ = ansible_version_pattern.captures
|
||||
if @gathered_version
|
||||
@gathered_version_major = @gathered_version.match(/^(\d)\..+$/).captures[0].to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,12 +10,14 @@ module VagrantPlugins
|
|||
|
||||
def initialize(machine, config)
|
||||
super
|
||||
@control_machine = "guest"
|
||||
@logger = Log4r::Logger.new("vagrant::provisioners::ansible_guest")
|
||||
end
|
||||
|
||||
def provision
|
||||
check_files_existence
|
||||
check_and_install_ansible
|
||||
|
||||
execute_ansible_galaxy_on_guest if config.galaxy_role_file
|
||||
execute_ansible_playbook_on_guest
|
||||
end
|
||||
|
@ -25,14 +27,14 @@ module VagrantPlugins
|
|||
#
|
||||
# This handles verifying the Ansible installation, installing it if it was
|
||||
# requested, and so on. This method will raise exceptions if things are wrong.
|
||||
# The compatibility mode checks are also performed here in order to fetch the
|
||||
# Ansible version information only once.
|
||||
#
|
||||
# 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.
|
||||
# - The installation of a specific Ansible version is only supported by
|
||||
# the "pip" install_mode.
|
||||
# - There is no absolute guarantee that the automated installation will replace
|
||||
# a previous Ansible installation (although it works fine in many cases)
|
||||
#
|
||||
def check_and_install_ansible
|
||||
@logger.info("Checking for Ansible installation...")
|
||||
|
@ -52,21 +54,39 @@ module VagrantPlugins
|
|||
@machine.guest.capability(:ansible_install, config.install_mode, config.version, config.pip_args)
|
||||
end
|
||||
|
||||
# Check that Ansible Playbook command is available on the guest
|
||||
@machine.communicate.execute(
|
||||
"test -x \"$(command -v #{config.playbook_command})\"",
|
||||
error_class: Ansible::Errors::AnsibleNotFoundOnGuest,
|
||||
error_key: :ansible_not_found_on_guest
|
||||
)
|
||||
# This step will also fetch the Ansible version data into related instance variables
|
||||
set_and_check_compatibility_mode
|
||||
|
||||
# Check if requested ansible version is available
|
||||
if (!config.version.empty? &&
|
||||
config.version.to_s.to_sym != :latest &&
|
||||
!@machine.guest.capability(:ansible_installed, config.version))
|
||||
raise Ansible::Errors::AnsibleVersionNotFoundOnGuest, required_version: config.version.to_s
|
||||
config.version != @gathered_version)
|
||||
raise Ansible::Errors::AnsibleVersionMismatch,
|
||||
system: @control_machine,
|
||||
required_version: config.version,
|
||||
current_version: @gathered_version
|
||||
end
|
||||
end
|
||||
|
||||
def gather_ansible_version
|
||||
raw_output = ""
|
||||
|
||||
result = @machine.communicate.execute(
|
||||
"ansible --version",
|
||||
error_class: Ansible::Errors::AnsibleNotFoundOnGuest,
|
||||
error_key: :ansible_not_found_on_guest) do |type, output|
|
||||
if type == :stdout && output.lines[0]
|
||||
raw_output = output.lines[0]
|
||||
end
|
||||
end
|
||||
|
||||
if result != 0
|
||||
raw_output = ""
|
||||
end
|
||||
|
||||
raw_output
|
||||
end
|
||||
|
||||
def get_provisioning_working_directory
|
||||
config.provisioning_path
|
||||
end
|
||||
|
@ -159,7 +179,7 @@ module VagrantPlugins
|
|||
error_key: :config_file_not_found,
|
||||
config_option: option_name,
|
||||
path: remote_path,
|
||||
system: "guest"
|
||||
system: @control_machine
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ module VagrantPlugins
|
|||
|
||||
def initialize(machine, config)
|
||||
super
|
||||
@control_machine = "host"
|
||||
@logger = Log4r::Logger.new("vagrant::provisioners::ansible_host")
|
||||
end
|
||||
|
||||
|
@ -18,8 +19,9 @@ module VagrantPlugins
|
|||
# At this stage, the SSH access is guaranteed to be ready
|
||||
@ssh_info = @machine.ssh_info
|
||||
|
||||
check_files_existence
|
||||
warn_for_unsupported_platform
|
||||
check_files_existence
|
||||
check_ansible_version_and_compatibility
|
||||
|
||||
execute_ansible_galaxy_from_host if config.galaxy_role_file
|
||||
execute_ansible_playbook_from_host
|
||||
|
@ -31,7 +33,24 @@ module VagrantPlugins
|
|||
|
||||
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"))
|
||||
@machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine") + "\n")
|
||||
end
|
||||
end
|
||||
|
||||
def check_ansible_version_and_compatibility
|
||||
# This step will also fetch the Ansible version data into related instance variables
|
||||
set_and_check_compatibility_mode
|
||||
|
||||
# Skip this check when not required, nor possible
|
||||
if !@gathered_version || config.version.empty? || config.version.to_s.to_sym == :latest
|
||||
return
|
||||
end
|
||||
|
||||
if config.version != @gathered_version
|
||||
raise Ansible::Errors::AnsibleVersionMismatch,
|
||||
system: @control_machine,
|
||||
required_version: config.version,
|
||||
current_version: @gathered_version
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -49,15 +68,15 @@ module VagrantPlugins
|
|||
|
||||
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.
|
||||
# the ansible_ssh_user/ansible_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]}'"
|
||||
@command_arguments << "--extra-vars=#{@lexicon[:ansible_user]}='#{@ssh_info[:username]}'"
|
||||
end
|
||||
|
||||
@command_arguments << "--ask-sudo-pass" if config.ask_sudo_pass
|
||||
@command_arguments << "--#{@lexicon[:ask_become_pass]}" if config.ask_become_pass
|
||||
@command_arguments << "--ask-vault-pass" if config.ask_vault_pass
|
||||
|
||||
prepare_common_command_arguments
|
||||
|
@ -88,6 +107,30 @@ module VagrantPlugins
|
|||
end
|
||||
end
|
||||
|
||||
def gather_ansible_version
|
||||
raw_output = ""
|
||||
command = %w(ansible --version)
|
||||
|
||||
command << {
|
||||
notify: [:stdout, :stderr]
|
||||
}
|
||||
|
||||
begin
|
||||
result = Vagrant::Util::Subprocess.execute(*command) do |type, output|
|
||||
if type == :stdout && output.lines[0]
|
||||
raw_output = output
|
||||
end
|
||||
end
|
||||
if result.exit_code != 0
|
||||
raw_output = ""
|
||||
end
|
||||
rescue Vagrant::Errors::CommandUnavailable
|
||||
raise Ansible::Errors::AnsibleNotFoundOnHost
|
||||
end
|
||||
|
||||
raw_output
|
||||
end
|
||||
|
||||
def execute_ansible_galaxy_from_host
|
||||
prepare_ansible_config_environment_variable
|
||||
|
||||
|
@ -199,19 +242,19 @@ module VagrantPlugins
|
|||
def get_inventory_ssh_machine(machine, ssh_info)
|
||||
forced_remote_user = ""
|
||||
if config.force_remote_user
|
||||
forced_remote_user = "ansible_ssh_user='#{ssh_info[:username]}' "
|
||||
forced_remote_user = "#{@lexicon[:ansible_user]}='#{ssh_info[:username]}' "
|
||||
end
|
||||
|
||||
"#{machine.name} ansible_ssh_host=#{ssh_info[:host]} ansible_ssh_port=#{ssh_info[:port]} #{forced_remote_user}ansible_ssh_private_key_file='#{ssh_info[:private_key_path][0]}'\n"
|
||||
"#{machine.name} #{@lexicon[:ansible_host]}=#{ssh_info[:host]} #{@lexicon[:ansible_port]}=#{ssh_info[:port]} #{forced_remote_user}ansible_ssh_private_key_file='#{ssh_info[:private_key_path][0]}'\n"
|
||||
end
|
||||
|
||||
def get_inventory_winrm_machine(machine, winrm_net_info)
|
||||
forced_remote_user = ""
|
||||
if config.force_remote_user
|
||||
forced_remote_user = "ansible_ssh_user='#{machine.config.winrm.username}' "
|
||||
forced_remote_user = "#{@lexicon[:ansible_user]}='#{machine.config.winrm.username}' "
|
||||
end
|
||||
|
||||
"#{machine.name} ansible_connection=winrm ansible_ssh_host=#{winrm_net_info[:host]} ansible_ssh_port=#{winrm_net_info[:port]} #{forced_remote_user}ansible_ssh_pass='#{machine.config.winrm.password}'\n"
|
||||
"#{machine.name} ansible_connection=winrm #{@lexicon[:ansible_host]}=#{winrm_net_info[:host]} #{@lexicon[:ansible_port]}=#{winrm_net_info[:port]} #{forced_remote_user}#{@lexicon[:ansible_password]}='#{machine.config.winrm.password}'\n"
|
||||
end
|
||||
|
||||
def ansible_ssh_args
|
||||
|
@ -287,7 +330,7 @@ module VagrantPlugins
|
|||
_key: :config_file_not_found,
|
||||
config_option: option_name,
|
||||
path: expanded_path,
|
||||
system: "host"
|
||||
system: @control_machine
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2341,14 +2341,19 @@ en:
|
|||
ansible_command_failed: |-
|
||||
Ansible failed to complete successfully. Any error output should be
|
||||
visible above. Please fix these errors and try again.
|
||||
ansible_compatibility_mode_conflict: |-
|
||||
The requested Ansible compatibility mode (%{compatibility_mode}) is in conflict with
|
||||
the Ansible installation on your Vagrant %{system} system (currently: %{ansible_version}).
|
||||
See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#compatibility_mode
|
||||
for more information.
|
||||
ansible_not_found_on_guest: |-
|
||||
The Ansible software 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
|
||||
https://docs.vagrantup.com/v2/provisioning/ansible_local.html
|
||||
ansible_local provisioner `install` option. Please check
|
||||
https://docs.vagrantup.com/v2/provisioning/ansible_local.html#install
|
||||
for more information.
|
||||
ansible_not_found_on_host: |-
|
||||
The Ansible software could not be found! Please verify
|
||||
|
@ -2358,6 +2363,18 @@ en:
|
|||
on your host system. Vagrant can't do this for you in a safe and
|
||||
automated way.
|
||||
Please check https://docs.ansible.com for more information.
|
||||
ansible_programming_error: |-
|
||||
Ansible Provisioner Programming Error:
|
||||
|
||||
%{message}
|
||||
|
||||
Internal Details:
|
||||
|
||||
%{details}
|
||||
|
||||
Sorry, but this Vagrant error should never occur.
|
||||
Please check https://github.com/mitchellh/vagrant/issues for any
|
||||
existing bug report. If needed, please create a new issue. Thank you!
|
||||
cannot_support_pip_install: |-
|
||||
Unfortunately Vagrant does not support yet installing Ansible
|
||||
from pip for the guest OS running in the machine.
|
||||
|
@ -2367,16 +2384,18 @@ en:
|
|||
to contribute back support. Thank you!
|
||||
|
||||
https://github.com/mitchellh/vagrant
|
||||
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 https://docs.vagrantup.com/v2/provisioning/ansible_local.html
|
||||
ansible_version_mismatch: |-
|
||||
The requested Ansible version (%{required_version}) was not found on the %{system}.
|
||||
Please check the Ansible installation on your Vagrant %{system} system (currently: %{current_version}),
|
||||
or adapt the provisioner `version` option in your Vagrantfile.
|
||||
See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#version
|
||||
for more information.
|
||||
config_file_not_found: |-
|
||||
`%{config_option}` does not exist on the %{system}: %{path}
|
||||
extra_vars_invalid: |-
|
||||
`extra_vars` must be a hash or a path to an existing file. Received: %{value} (as %{type})
|
||||
no_compatibility_mode: |-
|
||||
`compatibility_mode` must be a valid mode (possible values: %{valid_modes}).
|
||||
no_playbook: |-
|
||||
`playbook` file path must be set.
|
||||
raw_arguments_invalid: |-
|
||||
|
@ -2390,6 +2409,20 @@ en:
|
|||
windows_not_supported_for_control_machine: |-
|
||||
Windows is not officially supported for the Ansible Control Machine.
|
||||
Please check https://docs.ansible.com/intro_installation.html#control-machine-requirements
|
||||
compatibility_mode_not_detected: |-
|
||||
Vagrant gathered an unknown Ansible version:
|
||||
|
||||
%{gathered_version}
|
||||
and falls back on the compatibility mode '%{compatibility_mode}'.
|
||||
|
||||
Alternatively, the compatibility mode can be specified in your Vagrantfile:
|
||||
https://www.vagrantup.com/docs/provisioning/ansible_common.html#compatibility_mode
|
||||
compatibility_mode_warning: |-
|
||||
Vagrant has automatically selected the compatibility mode '%{compatibility_mode}'
|
||||
according to the Ansible version installed (%{ansible_version}).
|
||||
|
||||
Alternatively, the compatibility mode can be specified in your Vagrantfile:
|
||||
https://www.vagrantup.com/docs/provisioning/ansible_common.html#compatibility_mode
|
||||
|
||||
docker:
|
||||
wrong_provisioner: |-
|
||||
|
|
|
@ -16,7 +16,11 @@ describe VagrantPlugins::Ansible::Config::Guest do
|
|||
let(:existing_file) { "this/path/is/a/stub" }
|
||||
|
||||
it "supports a list of options" do
|
||||
supported_options = %w( config_file
|
||||
supported_options = %w(
|
||||
become
|
||||
become_user
|
||||
compatibility_mode
|
||||
config_file
|
||||
extra_vars
|
||||
galaxy_command
|
||||
galaxy_role_file
|
||||
|
@ -40,7 +44,8 @@ describe VagrantPlugins::Ansible::Config::Guest do
|
|||
tmp_path
|
||||
vault_password_file
|
||||
verbose
|
||||
version )
|
||||
version
|
||||
)
|
||||
|
||||
expect(get_provisioner_option_names(described_class)).to eql(supported_options)
|
||||
end
|
||||
|
@ -55,7 +60,6 @@ describe VagrantPlugins::Ansible::Config::Guest do
|
|||
expect(subject.install_mode).to eql(:default)
|
||||
expect(subject.provisioning_path).to eql("/vagrant")
|
||||
expect(subject.tmp_path).to eql("/tmp/vagrant-ansible")
|
||||
expect(subject.version).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -13,8 +13,13 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do
|
|||
let(:existing_file) { File.expand_path(__FILE__) }
|
||||
|
||||
it "supports a list of options" do
|
||||
supported_options = %w( ask_sudo_pass
|
||||
supported_options = %w(
|
||||
ask_become_pass
|
||||
ask_sudo_pass
|
||||
ask_vault_pass
|
||||
become
|
||||
become_user
|
||||
compatibility_mode
|
||||
config_file
|
||||
extra_vars
|
||||
force_remote_user
|
||||
|
@ -36,7 +41,9 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do
|
|||
sudo_user
|
||||
tags
|
||||
vault_password_file
|
||||
verbose )
|
||||
verbose
|
||||
version
|
||||
)
|
||||
|
||||
expect(get_provisioner_option_names(described_class)).to eql(supported_options)
|
||||
end
|
||||
|
@ -47,7 +54,8 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do
|
|||
it "assigns default values to unset host-specific options" do
|
||||
subject.finalize!
|
||||
|
||||
expect(subject.ask_sudo_pass).to be(false)
|
||||
expect(subject.ask_become_pass).to be(false)
|
||||
expect(subject.ask_sudo_pass).to be(false) # deprecated
|
||||
expect(subject.ask_vault_pass).to be(false)
|
||||
expect(subject.force_remote_user).to be(true)
|
||||
expect(subject.host_key_checking).to be(false)
|
||||
|
@ -61,7 +69,14 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do
|
|||
describe "host_key_checking option" do
|
||||
it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :host_key_checking, false
|
||||
end
|
||||
describe "ask_become_pass option" do
|
||||
it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :ask_become_pass, false
|
||||
end
|
||||
describe "ask_sudo_pass option" do
|
||||
before do
|
||||
# Filter the deprecation notice
|
||||
allow($stdout).to receive(:puts)
|
||||
end
|
||||
it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :ask_sudo_pass, false
|
||||
end
|
||||
describe "ask_vault_pass option" do
|
||||
|
|
|
@ -3,6 +3,9 @@ shared_examples_for 'options shared by both Ansible provisioners' do
|
|||
it "assigns default values to unset common options" do
|
||||
subject.finalize!
|
||||
|
||||
expect(subject.become).to be(false)
|
||||
expect(subject.become_user).to be_nil
|
||||
expect(subject.compatibility_mode).to eql(VagrantPlugins::Ansible::COMPATIBILITY_MODE_AUTO)
|
||||
expect(subject.config_file).to be_nil
|
||||
expect(subject.extra_vars).to be_nil
|
||||
expect(subject.galaxy_command).to eql("ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force")
|
||||
|
@ -17,11 +20,12 @@ shared_examples_for 'options shared by both Ansible provisioners' do
|
|||
expect(subject.raw_arguments).to be_nil
|
||||
expect(subject.skip_tags).to be_nil
|
||||
expect(subject.start_at_task).to be_nil
|
||||
expect(subject.sudo).to be(false)
|
||||
expect(subject.sudo_user).to be_nil
|
||||
expect(subject.sudo).to be(false) # deprecated
|
||||
expect(subject.sudo_user).to be_nil # deprecated
|
||||
expect(subject.tags).to be_nil
|
||||
expect(subject.vault_password_file).to be_nil
|
||||
expect(subject.verbose).to be(false)
|
||||
expect(subject.version).to be_empty
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -41,6 +45,44 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup |
|
|||
])
|
||||
end
|
||||
|
||||
describe "compatibility_mode option" do
|
||||
|
||||
VagrantPlugins::Ansible::COMPATIBILITY_MODES.each do |valid_mode|
|
||||
it "supports compatibility mode '#{valid_mode}'" do
|
||||
subject.compatibility_mode = valid_mode
|
||||
subject.finalize!
|
||||
|
||||
result = subject.validate(machine)
|
||||
expect(subject.compatibility_mode).to eql(valid_mode)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns an error if the compatibility mode is not set" do
|
||||
subject.compatibility_mode = nil
|
||||
subject.finalize!
|
||||
|
||||
result = subject.validate(machine)
|
||||
expect(result[provisioner_label]).to eql([
|
||||
I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode",
|
||||
valid_modes: "'auto', '1.8', '2.0'")
|
||||
])
|
||||
end
|
||||
|
||||
%w(invalid 1.9 2.3).each do |invalid_mode|
|
||||
it "returns an error if the compatibility mode is invalid (e.g. '#{invalid_mode}')" do
|
||||
subject.compatibility_mode = invalid_mode
|
||||
subject.finalize!
|
||||
|
||||
result = subject.validate(machine)
|
||||
expect(result[provisioner_label]).to eql([
|
||||
I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode",
|
||||
valid_modes: "'auto', '1.8', '2.0'")
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
it "passes if the extra_vars option is a hash" do
|
||||
subject.extra_vars = { var1: 1, var2: "foo" }
|
||||
subject.finalize!
|
||||
|
@ -82,6 +124,7 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup |
|
|||
end
|
||||
|
||||
it "it collects and returns all detected errors" do
|
||||
subject.compatibility_mode = nil
|
||||
subject.playbook = nil
|
||||
subject.extra_vars = ["var1", 3, "var2", 5]
|
||||
subject.raw_arguments = { arg1: 1, arg2: "foo" }
|
||||
|
@ -89,7 +132,10 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup |
|
|||
|
||||
result = subject.validate(machine)
|
||||
|
||||
expect(result[provisioner_label].size).to eql(3)
|
||||
expect(result[provisioner_label].size).to eql(4)
|
||||
expect(result[provisioner_label]).to include(
|
||||
I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode",
|
||||
valid_modes: "'auto', '1.8', '2.0'"))
|
||||
expect(result[provisioner_label]).to include(
|
||||
I18n.t("vagrant.provisioners.ansible.errors.no_playbook"))
|
||||
expect(result[provisioner_label]).to include(
|
||||
|
@ -102,7 +148,15 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup |
|
|||
value: subject.raw_arguments.to_s))
|
||||
end
|
||||
|
||||
describe "become option" do
|
||||
it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :become, false
|
||||
end
|
||||
|
||||
describe "sudo option" do
|
||||
before do
|
||||
# Filter the deprecation notice
|
||||
allow($stdout).to receive(:puts)
|
||||
end
|
||||
it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :sudo, false
|
||||
end
|
||||
|
||||
|
|
|
@ -60,6 +60,8 @@ VF
|
|||
|
||||
stubbed_ui = Vagrant::UI::Colored.new
|
||||
allow(stubbed_ui).to receive(:detail).and_return("")
|
||||
allow(stubbed_ui).to receive(:warn).and_return("")
|
||||
|
||||
allow(machine.env).to receive(:ui).and_return(stubbed_ui)
|
||||
|
||||
config.playbook = 'playbook.yml'
|
||||
|
@ -69,6 +71,15 @@ VF
|
|||
# Class methods for code reuse across examples
|
||||
#
|
||||
|
||||
def self.it_should_check_ansible_version()
|
||||
it "execute 'ansible --version' before executing 'ansible-playbook'" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).
|
||||
once.with('ansible', '--version', { :notify => [:stdout, :stderr] })
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).
|
||||
once.with('ansible-playbook', any_args)
|
||||
end
|
||||
end
|
||||
|
||||
def self.it_should_set_arguments_and_environment_variables(
|
||||
expected_args_count = 5,
|
||||
expected_vars_count = 4,
|
||||
|
@ -76,9 +87,7 @@ VF
|
|||
expected_transport_mode = "ssh")
|
||||
|
||||
it "sets implicit arguments in a specific order" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
|
||||
expect(args[0]).to eq("ansible-playbook")
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(args[1]).to eq("--connection=ssh")
|
||||
expect(args[2]).to eq("--timeout=30")
|
||||
|
||||
|
@ -90,7 +99,7 @@ VF
|
|||
end
|
||||
|
||||
it "sets --limit argument" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
all_limits = args.select { |x| x =~ /^(--limit=|-l)/ }
|
||||
if config.raw_arguments
|
||||
raw_limits = config.raw_arguments.select { |x| x =~ /^(--limit=|-l)/ }
|
||||
|
@ -108,7 +117,7 @@ VF
|
|||
end
|
||||
|
||||
it "exports environment variables" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
|
||||
if expected_host_key_checking
|
||||
|
@ -116,6 +125,7 @@ VF
|
|||
else
|
||||
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o UserKnownHostsFile=/dev/null")
|
||||
end
|
||||
|
||||
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentitiesOnly=yes")
|
||||
expect(cmd_opts[:env]['ANSIBLE_FORCE_COLOR']).to eql("true")
|
||||
expect(cmd_opts[:env]).to_not include("ANSIBLE_NOCOLOR")
|
||||
|
@ -126,14 +136,14 @@ VF
|
|||
|
||||
# "roughly" verify that only expected args/vars have been defined by the provisioner
|
||||
it "sets the expected number of arguments and environment variables" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(args.length-2).to eq(expected_args_count)
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(args.length - 2).to eq(expected_args_count)
|
||||
expect(args.last[:env].length).to eq(expected_vars_count)
|
||||
}.and_return(default_execute_result)
|
||||
end
|
||||
|
||||
it "enables '#{expected_transport_mode}' as default transport mode" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
index = args.rindex("--connection=#{expected_transport_mode}")
|
||||
expect(index).to be > 0
|
||||
expect(find_last_argument_after(index, args, /--connection=\w+/)).to be(false)
|
||||
|
@ -144,7 +154,7 @@ VF
|
|||
|
||||
def self.it_should_set_optional_arguments(arg_map)
|
||||
it "sets optional arguments" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
arg_map.each_pair do |vagrant_option, ansible_argument|
|
||||
index = args.index(ansible_argument)
|
||||
if config.send(vagrant_option)
|
||||
|
@ -159,7 +169,7 @@ VF
|
|||
|
||||
def self.it_should_explicitly_enable_ansible_ssh_control_persist_defaults
|
||||
it "configures ControlPersist (like Ansible defaults) via ANSIBLE_SSH_ARGS" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ControlMaster=auto")
|
||||
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ControlPersist=60s")
|
||||
|
@ -167,23 +177,24 @@ VF
|
|||
end
|
||||
end
|
||||
|
||||
def self.it_should_create_and_use_generated_inventory(with_ssh_user = true)
|
||||
def self.it_should_create_and_use_generated_inventory(with_user = true)
|
||||
it "generates an inventory with all active machines" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*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")
|
||||
_ssh = config.compatibility_mode == VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0 ? "" : "_ssh"
|
||||
if with_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")
|
||||
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")
|
||||
}.and_return(default_execute_result)
|
||||
end
|
||||
|
||||
it "sets as ansible inventory the directory containing the auto-generated inventory file" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
inventory_index = args.rindex("--inventory-file=#{generated_inventory_dir}")
|
||||
expect(inventory_index).to be > 0
|
||||
expect(find_last_argument_after(inventory_index, args, /--inventory-file=\w+/)).to be(false)
|
||||
|
@ -260,11 +271,12 @@ VF
|
|||
end
|
||||
|
||||
describe "with default options" do
|
||||
it_should_check_ansible_version
|
||||
it_should_set_arguments_and_environment_variables
|
||||
it_should_create_and_use_generated_inventory
|
||||
|
||||
it "does not add any group section to the generated inventory" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) {
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {
|
||||
inventory_content = File.read(generated_inventory_file)
|
||||
expect(inventory_content).to_not match(/^\s*\[^\\+\]\s*$/)
|
||||
}.and_return(default_execute_result)
|
||||
|
@ -275,14 +287,152 @@ VF
|
|||
end
|
||||
end
|
||||
|
||||
describe "deprecated 'sudo' options are aliases for equivalent 'become' options" do
|
||||
before do
|
||||
# Filter the deprecation notices
|
||||
allow($stdout).to receive(:puts)
|
||||
|
||||
config.sudo = true
|
||||
config.sudo_user = 'deployer'
|
||||
config.ask_sudo_pass = true
|
||||
end
|
||||
|
||||
it_should_set_optional_arguments({"sudo" => "--sudo",
|
||||
"sudo_user" => "--sudo-user=deployer",
|
||||
"ask_sudo_pass" => "--ask-sudo-pass",
|
||||
"become" => "--sudo",
|
||||
"become_user" => "--sudo-user=deployer",
|
||||
"ask_become_pass" => "--ask-sudo-pass"})
|
||||
end
|
||||
|
||||
context "with compatibility_mode 'auto'" do
|
||||
before do
|
||||
config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_AUTO
|
||||
end
|
||||
|
||||
valid_versions = {
|
||||
"0.6": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8,
|
||||
"1.9.4": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8,
|
||||
"2.5.0.0-rc1": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0,
|
||||
"2.x.y.z": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0,
|
||||
"4.3.2.1": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0,
|
||||
}
|
||||
valid_versions.each_pair do |ansible_version, mode|
|
||||
describe "and ansible version #{ansible_version}" do
|
||||
before do
|
||||
allow(subject).to receive(:gather_ansible_version).and_return("ansible #{ansible_version}\n...\n")
|
||||
end
|
||||
|
||||
it "detects the compatibility mode #{mode}" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(config.compatibility_mode).to eq(mode)
|
||||
}.and_return(default_execute_result)
|
||||
end
|
||||
|
||||
it "warns about compatibility mode auto-detection being used" do
|
||||
expect(machine.env.ui).to receive(:warn).with(
|
||||
I18n.t("vagrant.provisioners.ansible.compatibility_mode_warning",
|
||||
compatibility_mode: mode, ansible_version: ansible_version) +
|
||||
"\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
invalid_versions = [
|
||||
"ansible devel",
|
||||
"anything 1.2",
|
||||
"2.9.2.1",
|
||||
]
|
||||
invalid_versions.each do |unknown_ansible_version|
|
||||
describe "and `ansible --version` returning '#{unknown_ansible_version}'" do
|
||||
before do
|
||||
allow(subject).to receive(:gather_ansible_version).and_return(unknown_ansible_version)
|
||||
end
|
||||
|
||||
it "applies the safest compatibility mode ('#{VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE}')" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(config.compatibility_mode).to eq(VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE)
|
||||
}.and_return(default_execute_result)
|
||||
end
|
||||
|
||||
it "warns about not being able to detect the best compatibility mode" do
|
||||
expect(machine.env.ui).to receive(:warn).with(
|
||||
I18n.t("vagrant.provisioners.ansible.compatibility_mode_not_detected",
|
||||
compatibility_mode: VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE,
|
||||
gathered_version: unknown_ansible_version) +
|
||||
"\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "with compatibility_mode '#{VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8}'" do
|
||||
before do
|
||||
config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8
|
||||
end
|
||||
|
||||
it_should_check_ansible_version
|
||||
it_should_create_and_use_generated_inventory
|
||||
|
||||
it "doesn't warn about compatibility mode auto-detection" do
|
||||
expect(machine.env.ui).to_not receive(:warn)
|
||||
end
|
||||
end
|
||||
|
||||
context "with compatibility_mode '#{VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0}'" do
|
||||
before do
|
||||
config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0
|
||||
allow(subject).to receive(:gather_ansible_version).and_return("ansible 2.3.0.0\n...\n")
|
||||
end
|
||||
|
||||
it_should_create_and_use_generated_inventory
|
||||
|
||||
it "doesn't warn about compatibility mode auto-detection" do
|
||||
expect(machine.env.ui).to_not receive(:warn)
|
||||
end
|
||||
|
||||
describe "and an incompatible ansible version" do
|
||||
before do
|
||||
allow(subject).to receive(:gather_ansible_version).and_return("ansible 1.9.3\n...\n")
|
||||
end
|
||||
|
||||
it "raises a compatibility conflict error", skip_before: false, skip_after: true do
|
||||
expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCompatibilityModeConflict)
|
||||
end
|
||||
end
|
||||
|
||||
describe "deprecated 'sudo' options are aliases for equivalent 'become' options" do
|
||||
before do
|
||||
# Filter the deprecation notices
|
||||
allow($stdout).to receive(:puts)
|
||||
|
||||
config.sudo = true
|
||||
config.sudo_user = 'deployer'
|
||||
config.ask_sudo_pass = true
|
||||
end
|
||||
|
||||
it_should_set_optional_arguments({"sudo" => "--become",
|
||||
"sudo_user" => "--become-user=deployer",
|
||||
"ask_sudo_pass" => "--ask-become-pass",
|
||||
"become" => "--become",
|
||||
"become_user" => "--become-user=deployer",
|
||||
"ask_become_pass" => "--ask-become-pass"})
|
||||
end
|
||||
end
|
||||
|
||||
describe "with playbook_command option" do
|
||||
before do
|
||||
config.playbook_command = "custom-ansible-playbook"
|
||||
|
||||
# set the compatibility mode to ensure that only ansible-playbook is excuted
|
||||
config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8
|
||||
end
|
||||
|
||||
it "uses custom playbook_command to run playbooks" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute)
|
||||
.with("custom-ansible-playbook", any_args)
|
||||
.and_return(default_execute_result)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -297,7 +447,7 @@ VF
|
|||
"description" => "text with spaces but no quotes",
|
||||
}
|
||||
}
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) {
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {
|
||||
inventory_content = File.read(generated_inventory_file)
|
||||
expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 comments='some text with spaces and quotes' description='text with spaces but no quotes'")
|
||||
}.and_return(default_execute_result)
|
||||
|
@ -307,7 +457,8 @@ VF
|
|||
config.host_vars = {
|
||||
machine1: ["http_port=80", "maxRequestsPerChild=808"]
|
||||
}
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) {
|
||||
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {
|
||||
inventory_content = File.read(generated_inventory_file)
|
||||
expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808")
|
||||
}.and_return(default_execute_result)
|
||||
|
@ -317,7 +468,8 @@ VF
|
|||
config.host_vars = {
|
||||
:machine1 => "http_port=80 maxRequestsPerChild=808"
|
||||
}
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) {
|
||||
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {
|
||||
inventory_content = File.read(generated_inventory_file)
|
||||
expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808")
|
||||
}.and_return(default_execute_result)
|
||||
|
@ -327,7 +479,8 @@ VF
|
|||
config.host_vars = {
|
||||
"machine1" => "http_port=80 maxRequestsPerChild=808"
|
||||
}
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) {
|
||||
inventory_content = File.read(generated_inventory_file)
|
||||
expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808")
|
||||
}.and_return(default_execute_result)
|
||||
|
@ -349,7 +502,7 @@ VF
|
|||
"bar:children" => ["group1", "group2", "group3", "group5"],
|
||||
}
|
||||
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
inventory_content = File.read(generated_inventory_file)
|
||||
|
||||
# Accept String instead of Array for group member list
|
||||
|
@ -387,7 +540,7 @@ VF
|
|||
"group3:vars" => "stringvar1=stringvalue1 stringvar2=stringvalue2",
|
||||
}
|
||||
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
inventory_content = File.read(generated_inventory_file)
|
||||
|
||||
# Hash syntax
|
||||
|
@ -425,18 +578,18 @@ VF
|
|||
|
||||
describe "with boolean (flag) options disabled" do
|
||||
before do
|
||||
config.sudo = false
|
||||
config.ask_sudo_pass = false
|
||||
config.become = false
|
||||
config.ask_become_pass = false
|
||||
config.ask_vault_pass = false
|
||||
|
||||
config.sudo_user = 'root'
|
||||
config.become_user = 'root'
|
||||
end
|
||||
|
||||
it_should_set_arguments_and_environment_variables 6
|
||||
it_should_set_optional_arguments({ "sudo_user" => "--sudo-user=root" })
|
||||
it_should_set_optional_arguments({ "become_user" => "--sudo-user=root" })
|
||||
|
||||
it "it does not set boolean flag when corresponding option is set to false" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(args.index("--sudo")).to be_nil
|
||||
expect(args.index("--ask-sudo-pass")).to be_nil
|
||||
expect(args.index("--ask-vault-pass")).to be_nil
|
||||
|
@ -446,7 +599,7 @@ VF
|
|||
|
||||
describe "with raw_arguments option" do
|
||||
before do
|
||||
config.sudo = false
|
||||
config.become = false
|
||||
config.force_remote_user = false
|
||||
config.skip_tags = %w(foo bar)
|
||||
config.limit = "all"
|
||||
|
@ -465,7 +618,7 @@ VF
|
|||
it_should_set_arguments_and_environment_variables 17, 4, false, "paramiko"
|
||||
|
||||
it "sets all raw arguments" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
config.raw_arguments.each do |raw_arg|
|
||||
expect(args).to include(raw_arg)
|
||||
end
|
||||
|
@ -473,7 +626,7 @@ VF
|
|||
end
|
||||
|
||||
it "sets raw arguments after arguments related to supported options" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(args.index("--user=lion")).to be > args.index("--user=testuser")
|
||||
expect(args.index("--inventory-file=/forget/it/my/friend")).to be > args.index("--inventory-file=#{generated_inventory_dir}")
|
||||
expect(args.index("--limit=bar")).to be > args.index("--limit=all")
|
||||
|
@ -482,7 +635,7 @@ VF
|
|||
end
|
||||
|
||||
it "sets boolean flag (e.g. --sudo) defined in raw_arguments, even if corresponding option is set to false" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(args).to include('--sudo')
|
||||
}.and_return(default_execute_result)
|
||||
end
|
||||
|
@ -507,7 +660,7 @@ VF
|
|||
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(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(args).not_to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'")
|
||||
expect(args).to include("--user=#{machine.ssh_info[:username]}")
|
||||
}.and_return(default_execute_result)
|
||||
|
@ -538,8 +691,7 @@ VF
|
|||
it_should_set_arguments_and_environment_variables
|
||||
|
||||
it "generates an inventory with winrm connection settings" do
|
||||
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(config.inventory_path).to be_nil
|
||||
expect(File.exists?(generated_inventory_file)).to be(true)
|
||||
inventory_content = File.read(generated_inventory_file)
|
||||
|
@ -554,7 +706,7 @@ VF
|
|||
end
|
||||
|
||||
it "doesn't set the ansible remote user in inventory and use '--user' argument with the vagrant ssh username" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
inventory_content = File.read(generated_inventory_file)
|
||||
|
||||
expect(inventory_content).to include("machine1 ansible_connection=winrm ansible_ssh_host=127.0.0.1 ansible_ssh_port=55986 ansible_ssh_pass='winword'\n")
|
||||
|
@ -572,7 +724,7 @@ VF
|
|||
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(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(args).to include("--inventory-file=#{existing_file}")
|
||||
expect(args).not_to include("--inventory-file=#{generated_inventory_file}")
|
||||
expect(File.exists?(generated_inventory_file)).to be(false)
|
||||
|
@ -580,7 +732,7 @@ VF
|
|||
end
|
||||
|
||||
it "uses an --extra-vars argument to force ansible_ssh_user parameter" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(args).not_to include("--user=#{machine.ssh_info[:username]}")
|
||||
expect(args).to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'")
|
||||
}.and_return(default_execute_result)
|
||||
|
@ -592,7 +744,7 @@ VF
|
|||
end
|
||||
|
||||
it "uses a --user argument to set a default remote user" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(args).not_to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'")
|
||||
expect(args).to include("--user=#{machine.ssh_info[:username]}")
|
||||
}.and_return(default_execute_result)
|
||||
|
@ -606,7 +758,7 @@ VF
|
|||
end
|
||||
|
||||
it "sets ANSIBLE_CONFIG environment variable" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
expect(cmd_opts[:env]).to include("ANSIBLE_CONFIG")
|
||||
expect(cmd_opts[:env]['ANSIBLE_CONFIG']).to eql(existing_file)
|
||||
|
@ -622,7 +774,7 @@ VF
|
|||
it_should_set_arguments_and_environment_variables 6
|
||||
|
||||
it "should ask the vault password" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(args).to include("--ask-vault-pass")
|
||||
}.and_return(default_execute_result)
|
||||
end
|
||||
|
@ -636,7 +788,7 @@ VF
|
|||
it_should_set_arguments_and_environment_variables 6
|
||||
|
||||
it "uses the given vault password file" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(args).to include("--vault-password-file=#{existing_file}")
|
||||
}.and_return(default_execute_result)
|
||||
end
|
||||
|
@ -651,7 +803,7 @@ VF
|
|||
it_should_explicitly_enable_ansible_ssh_control_persist_defaults
|
||||
|
||||
it "passes custom SSH options via ANSIBLE_SSH_ARGS with the highest priority" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
raw_opt_index = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ControlMaster=no")
|
||||
default_opt_index = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ControlMaster=auto")
|
||||
|
@ -665,7 +817,7 @@ VF
|
|||
end
|
||||
|
||||
it "sets '-o ForwardAgent=yes' via ANSIBLE_SSH_ARGS with higher priority than raw_ssh_args values" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
forwardAgentYes = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ForwardAgent=yes")
|
||||
forwardAgentNo = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ForwardAgent=no")
|
||||
|
@ -685,7 +837,7 @@ VF
|
|||
it_should_explicitly_enable_ansible_ssh_control_persist_defaults
|
||||
|
||||
it "passes additional Identity Files via ANSIBLE_SSH_ARGS" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/an/other/identity")
|
||||
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/yet/an/other/key")
|
||||
|
@ -699,7 +851,7 @@ VF
|
|||
end
|
||||
|
||||
it "replaces `%` with `%%`" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/foo%%bar/key")
|
||||
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/bar%%%%buz/key")
|
||||
|
@ -716,7 +868,7 @@ VF
|
|||
it_should_explicitly_enable_ansible_ssh_control_persist_defaults
|
||||
|
||||
it "enables SSH-Forwarding via ANSIBLE_SSH_ARGS" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ForwardAgent=yes")
|
||||
}.and_return(default_execute_result)
|
||||
|
@ -729,7 +881,7 @@ VF
|
|||
end
|
||||
|
||||
it "sets '-o ProxyCommand' via ANSIBLE_SSH_ARGS" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ProxyCommand='ssh -W %h:%p -q user@remote_libvirt_host'")
|
||||
}.and_return(default_execute_result)
|
||||
|
@ -799,10 +951,12 @@ VF
|
|||
describe "without colorized output" do
|
||||
before do
|
||||
allow(machine.env).to receive(:ui).and_return(Vagrant::UI::Basic.new)
|
||||
|
||||
allow(machine.env.ui).to receive(:warn).and_return("") # hide the breaking change warning
|
||||
end
|
||||
|
||||
it "disables ansible-playbook colored output" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
expect(cmd_opts[:env]).to_not include("ANSIBLE_FORCE_COLOR")
|
||||
expect(cmd_opts[:env]['ANSIBLE_NOCOLOR']).to eql("true")
|
||||
|
@ -810,6 +964,54 @@ VF
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
context "with version option set" do
|
||||
before do
|
||||
config.version = "2.3.4.5"
|
||||
end
|
||||
|
||||
describe "and the installed ansible version is correct" do
|
||||
before do
|
||||
allow(subject).to receive(:gather_ansible_version).and_return("ansible #{config.version}\n...\n")
|
||||
end
|
||||
|
||||
it "executes ansible-playbook command" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args).and_return(default_execute_result)
|
||||
end
|
||||
end
|
||||
|
||||
describe "and there is an ansible version mismatch" do
|
||||
before do
|
||||
allow(subject).to receive(:gather_ansible_version).and_return("ansible 1.9.6\n...\n")
|
||||
end
|
||||
|
||||
it "raises an error about the ansible version mismatch", skip_before: false, skip_after: true do
|
||||
expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleVersionMismatch)
|
||||
end
|
||||
end
|
||||
|
||||
describe "and the installed ansible version cannot be detected" do
|
||||
before do
|
||||
allow(subject).to receive(:gather_ansible_version).and_return("")
|
||||
end
|
||||
|
||||
it "skips the ansible version check and executes ansible-playbook command" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args).and_return(default_execute_result)
|
||||
end
|
||||
end
|
||||
|
||||
describe "with special value: 'latest'" do
|
||||
before do
|
||||
config.version = :latest
|
||||
allow(subject).to receive(:gather_ansible_version).and_return("ansible 2.2.0.1\n...\n")
|
||||
end
|
||||
|
||||
it "skips the ansible version check and executes ansible-playbook command" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args).and_return(default_execute_result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "with galaxy support" do
|
||||
|
||||
before do
|
||||
|
@ -826,7 +1028,11 @@ VF
|
|||
expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCommandFailed)
|
||||
end
|
||||
|
||||
it "execute ansible-galaxy, and then ansible-playbook" do
|
||||
it "execute three commands: ansible --version, ansible-galaxy, and ansible-playbook" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute)
|
||||
.once
|
||||
.with('ansible', '--version', { :notify => [:stdout, :stderr] })
|
||||
.and_return(default_execute_result)
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute)
|
||||
.once
|
||||
.with('ansible-galaxy', any_args)
|
||||
|
@ -860,7 +1066,7 @@ VF
|
|||
end
|
||||
|
||||
it "sets ANSIBLE_ROLES_PATH with corresponding absolute path" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
expect(cmd_opts[:env]).to include("ANSIBLE_ROLES_PATH")
|
||||
expect(cmd_opts[:env]['ANSIBLE_ROLES_PATH']).to eql(File.join(machine.env.root_path, "my-roles"))
|
||||
|
@ -897,10 +1103,10 @@ VF
|
|||
# command line arguments
|
||||
config.galaxy_roles_path = "/up/to the stars"
|
||||
config.extra_vars = { var1: %Q(string with 'apo$trophe$', \\, " and =), var2: { x: 42 } }
|
||||
config.sudo = true
|
||||
config.sudo_user = 'deployer'
|
||||
config.become = true
|
||||
config.become_user = 'deployer'
|
||||
config.verbose = "vvv"
|
||||
config.ask_sudo_pass = true
|
||||
config.ask_become_pass = true
|
||||
config.ask_vault_pass = true
|
||||
config.vault_password_file = existing_file
|
||||
config.tags = %w(db www)
|
||||
|
@ -918,10 +1124,10 @@ VF
|
|||
it_should_set_arguments_and_environment_variables 21, 6, true
|
||||
it_should_explicitly_enable_ansible_ssh_control_persist_defaults
|
||||
it_should_set_optional_arguments({ "extra_vars" => "--extra-vars={\"var1\":\"string with 'apo$trophe$', \\\\, \\\" and =\",\"var2\":{\"x\":42}}",
|
||||
"sudo" => "--sudo",
|
||||
"sudo_user" => "--sudo-user=deployer",
|
||||
"become" => "--sudo",
|
||||
"become_user" => "--sudo-user=deployer",
|
||||
"verbose" => "-vvv",
|
||||
"ask_sudo_pass" => "--ask-sudo-pass",
|
||||
"ask_become_pass" => "--ask-sudo-pass",
|
||||
"ask_vault_pass" => "--ask-vault-pass",
|
||||
"vault_password_file" => "--vault-password-file=#{File.expand_path(__FILE__)}",
|
||||
"tags" => "--tags=db,www",
|
||||
|
@ -931,7 +1137,7 @@ VF
|
|||
})
|
||||
|
||||
it "also includes given raw arguments" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
expect(args).to include("--why-not")
|
||||
expect(args).to include("--su-user=foot")
|
||||
expect(args).to include("--ask-su-pass")
|
||||
|
@ -971,7 +1177,7 @@ VF
|
|||
end
|
||||
|
||||
it "uses an SSH ProxyCommand to reach the VM" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ProxyCommand='ssh boot9docker@127.0.0.1 -p 2299 -i /path/to/docker/host/key -o Compression=yes -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no exec nc %h %p 2>/dev/null'")
|
||||
}.and_return(default_execute_result)
|
||||
|
@ -986,11 +1192,14 @@ VF
|
|||
before do
|
||||
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)
|
||||
allow(machine.ui).to receive(:warn)
|
||||
|
||||
# Set the compatibility mode to only get the Windows warning
|
||||
config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8
|
||||
end
|
||||
|
||||
it "warns that Windows is not officially supported for the Ansible control machine" do
|
||||
expect(machine.env.ui).to receive(:warn)
|
||||
.with(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine"))
|
||||
.with(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine") + "\n")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1000,7 +1209,7 @@ VF
|
|||
end
|
||||
|
||||
it "does not set IdentitiesOnly=yes in ANSIBLE_SSH_ARGS" do
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to_not include("-o IdentitiesOnly=yes")
|
||||
}.and_return(default_execute_result)
|
||||
|
@ -1010,7 +1219,7 @@ VF
|
|||
it "does not set ANSIBLE_SSH_ARGS environment variable" do
|
||||
config.host_key_checking = true
|
||||
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
expect(cmd_opts[:env]).to_not include('ANSIBLE_SSH_ARGS')
|
||||
}.and_return(Vagrant::Util::Subprocess::Result.new(0, "", ""))
|
||||
|
@ -1023,7 +1232,7 @@ VF
|
|||
it 'does not set IdentitiesOnly=yes in ANSIBLE_SSH_ARGS' do
|
||||
ssh_info[:keys_only] = false
|
||||
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
|
||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
|
||||
cmd_opts = args.last
|
||||
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to_not include("-o IdentitiesOnly=yes")
|
||||
}.and_return(default_execute_result)
|
||||
|
|
|
@ -14,10 +14,8 @@ description: |-
|
|||
The Vagrant 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-warning">
|
||||
<strong>Warning:</strong> If you are not familiar with Ansible and Vagrant already,
|
||||
I recommend starting with the <a href="/docs/provisioning/shell.html">shell
|
||||
provisioner</a>. However, if you are comfortable with Vagrant already, Vagrant
|
||||
is a great way to learn Ansible.
|
||||
<strong>Warning:</strong>
|
||||
If you are not familiar with Ansible and Vagrant already, I recommend starting with the <a href="/docs/provisioning/shell.html">shell provisioner</a>. However, if you are comfortable with Vagrant already, Vagrant is a great way to learn Ansible.
|
||||
</div>
|
||||
|
||||
## Setup Requirements
|
||||
|
@ -53,10 +51,17 @@ end
|
|||
|
||||
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](/docs/provisioning/ansible_common.html).
|
||||
|
||||
- `ask_sudo_pass` (boolean) - require Ansible to [prompt for a sudo password](https://docs.ansible.com/intro_getting_started.html#remote-connection-information).
|
||||
- `ask_become_pass` (boolean) - require Ansible to [prompt for a password](https://docs.ansible.com/intro_getting_started.html#remote-connection-information) when switching to another user with the [become/sudo mechanism](http://docs.ansible.com/ansible/become.html).
|
||||
|
||||
The default value is `false`.
|
||||
|
||||
- `ask_sudo_pass` (boolean) - Backwards compatible alias for the [ask_become_pass](#ask_become_pass) option.
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<strong>Deprecation:</strong>
|
||||
The `ask_sudo_pass` option is deprecated and will be removed in a future release. Please use the [**`ask_become_pass`**](#ask_become_pass) option instead.
|
||||
</div>
|
||||
|
||||
- `ask_vault_pass` (boolean) - require Ansible to [prompt for a vault password](https://docs.ansible.com/playbooks_vault.html#vault).
|
||||
|
||||
The default value is `false`.
|
||||
|
@ -67,7 +72,10 @@ This section lists the _specific_ options for the Ansible (remote) provisioner.
|
|||
|
||||
The default value is `true`.
|
||||
|
||||
**Note:** This option was introduced in Vagrant 1.8.0. Previous Vagrant versions behave like if this option was set to `false`.
|
||||
<div class="alert alert-info">
|
||||
<strong>Compatibility Note:</strong>
|
||||
This option was introduced in Vagrant 1.8.0. Previous Vagrant versions behave like if this option was set to `false`.
|
||||
</div>
|
||||
|
||||
- `host_key_checking` (boolean) - require Ansible to [enable SSH host key checking](https://docs.ansible.com/intro_getting_started.html#host-key-checking).
|
||||
|
||||
|
@ -115,9 +123,10 @@ N = 3
|
|||
end
|
||||
```
|
||||
|
||||
**Caveats:**
|
||||
|
||||
If you apply this parallel provisioning pattern with a static Ansible inventory, you will 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](/docs/vagrantfile/ssh_settings.html)).
|
||||
<div class="alert alert-info">
|
||||
<strong>Tip:</strong>
|
||||
If you apply this parallel provisioning pattern with a static Ansible inventory, you will 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](/docs/vagrantfile/ssh_settings.html)).
|
||||
</div>
|
||||
|
||||
### Force Paramiko Connection Mode
|
||||
|
||||
|
|
|
@ -17,6 +17,36 @@ These options get passed to the `ansible-playbook` command that ships with Ansib
|
|||
|
||||
Some of these options are for advanced usage only and should not be used unless you understand their purpose.
|
||||
|
||||
- `become` (boolean) - Perform all the Ansible playbook tasks [as another user](http://docs.ansible.com/ansible/become.html), different from the user used to log into the guest system.
|
||||
|
||||
The default value is `false`.
|
||||
|
||||
- `become_user` (string) - Set the default username to be used by the Ansible `become` [privilege escalation](http://docs.ansible.com/ansible/become.html) mechanism.
|
||||
|
||||
By default this option is not set, and the Ansible default value (`root`) will be used.
|
||||
|
||||
- `compatibility_mode` (string) - Set the **minimal** version of Ansible to be supported. Vagrant will only use parameters that are compatible with the given version.
|
||||
|
||||
Possible values:
|
||||
|
||||
- `"auto"` _(Vagrant will automatically select the optimal compatibilty mode by checking the Ansible version currently available)_
|
||||
- `"1.8"` _(Ansible versions prior to 1.8 should mostly work well, but some options might not be supported)_
|
||||
- `"2.0"` _(The generated Ansible inventory will be incompatible with Ansible 1.x)_
|
||||
|
||||
By default this option is set to `"auto"`. If Vagrant is not able to detect any supported Ansible version, it will fall back on the compatibility mode `"1.8"` with a warning.
|
||||
|
||||
Vagrant will error if the specified compatibility mode is incompatible with the current Ansible version.
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<strong>Attention:</strong>
|
||||
Vagrant doesn't perform any validation between the `compatibility_mode` value and the value of the [`version`](#version) option.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Compatibility Note:</strong>
|
||||
This option was introduced in Vagrant 2.0. The behavior of previous Vagrant versions can be simulated by setting the `compatibility_mode` to `"1.8"`.
|
||||
</div>
|
||||
|
||||
- `config_file` (string) - The path to an [Ansible Configuration file](https://docs.ansible.com/intro_configuration.html).
|
||||
|
||||
By default, this option is not set, and Ansible will [search for a possible configuration file in some default locations](/docs/provisioning/ansible_intro.html#ANSIBLE_CONFIG).
|
||||
|
@ -123,17 +153,28 @@ Some of these options are for advanced usage only and should not be used unless
|
|||
- `['--check', '-M', '/my/modules']`
|
||||
- `["--connection=paramiko", "--forks=10"]`
|
||||
|
||||
**Caveat:** The `ansible` provisioner does not support whitespace characters in `raw_arguments` elements. Therefore **don't write** something like `["-c paramiko"]`, which will result with an invalid `" parmiko"` parameter value.
|
||||
<div class="alert alert-warn">
|
||||
<strong>Attention:</strong>
|
||||
The `ansible` provisioner does not support whitespace characters in `raw_arguments` elements. Therefore **don't write** something like `["-c paramiko"]`, which will result with an invalid `" parmiko"` parameter value.
|
||||
</div>
|
||||
|
||||
- `skip_tags` (string or array of strings) - Only plays, roles and tasks that [*do not match* these values will be executed](https://docs.ansible.com/playbooks_tags.html).
|
||||
|
||||
- `start_at_task` (string) - The task name where the [playbook execution will start](https://docs.ansible.com/playbooks_startnstep.html#start-at-task).
|
||||
|
||||
- `sudo` (boolean) - Cause Ansible to perform all the playbook tasks [using sudo](https://docs.ansible.com/glossary.html#sudo).
|
||||
- `sudo` (boolean) - Backwards compatible alias for the [`become`](#become) option.
|
||||
|
||||
The default value is `false`.
|
||||
<div class="alert alert-warning">
|
||||
<strong>Deprecation:</strong>
|
||||
The `sudo` option is deprecated and will be removed in a future release. Please use the [**`become`**](#become) option instead.
|
||||
</div>
|
||||
|
||||
- `sudo_user` (string) - set the default username who should be used by the sudo command.
|
||||
- `sudo_user` (string) - Backwards compatible alias for the [`become_user`](#become_user) option.
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<strong>Deprecation:</strong>
|
||||
The `sudo_user` option is deprecated and will be removed in a future release. Please use the [**`become_user`**](#become_user) option instead.
|
||||
</div>
|
||||
|
||||
- `tags` (string or array of strings) - Only plays, roles and tasks [tagged with these values will be executed](https://docs.ansible.com/playbooks_tags.html) .
|
||||
|
||||
|
@ -146,3 +187,16 @@ Some of these options are for advanced usage only and should not be used unless
|
|||
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.
|
||||
|
||||
- `version` (string) - The expected Ansible version.
|
||||
|
||||
This option is disabled by default.
|
||||
|
||||
When an Ansible version is defined (e.g. `"2.1.6.0"`), the Ansible 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.
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Tip:</strong>
|
||||
With the `ansible_local` provisioner, it is currently possible to use this option to specify which version of Ansible must be automatically installed, but <strong>only</strong> in combination with the [**`install_mode`**](/docs/provisioning/ansible_local.html#install_mode) set to <strong>`:pip`</strong>.
|
||||
</div>
|
||||
|
|
|
@ -14,10 +14,8 @@ description: |-
|
|||
The Vagrant 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-warning">
|
||||
<strong>Warning:</strong> If you are not familiar with Ansible and Vagrant already,
|
||||
I recommend starting with the <a href="/docs/provisioning/shell.html">shell
|
||||
provisioner</a>. However, if you are comfortable with Vagrant already, Vagrant
|
||||
is a great way to learn Ansible.
|
||||
<strong>Warning:</strong>
|
||||
If you are not familiar with Ansible and Vagrant already, I recommend starting with the <a href="/docs/provisioning/shell.html">shell provisioner</a>. However, if you are comfortable with Vagrant already, Vagrant is a great way to learn Ansible.
|
||||
</div>
|
||||
|
||||
## Setup Requirements
|
||||
|
@ -64,10 +62,13 @@ This section lists the _specific_ options for the Ansible Local provisioner. In
|
|||
Vagrant will 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.
|
||||
- The [`version`](/docs/provisioning/ansible_common.html#version) option is set to `"latest"`.
|
||||
- The current Ansible version does not correspond to the [`version`](/docs/provisioning/ansible_common.html#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.
|
||||
<div class="alert alert-warning">
|
||||
<strong>Attention:</strong>
|
||||
There is no guarantee that this automated installation will replace a custom Ansible setup, that might be already present on the Vagrant box.
|
||||
</div>
|
||||
|
||||
- `install_mode` (`:default`, `:pip`, or `:pip_args_only`) - Select the way to automatically install Ansible on the guest system.
|
||||
|
||||
|
@ -75,7 +76,7 @@ This section lists the _specific_ options for the Ansible Local provisioner. In
|
|||
- On Ubuntu-like systems, the latest Ansible release is installed from the `ppa:ansible/ansible` repository.
|
||||
- On RedHat-like systems, the latest Ansible release is installed from the [EPEL](http://fedoraproject.org/wiki/EPEL) repository.
|
||||
|
||||
- `:pip`: Ansible is installed from [PyPI](https://pypi.python.org/pypi) with [pip](https://pip.pypa.io) package installer. With this mode, Vagrant will systematically try to [install the latest pip version](https://pip.pypa.io/en/stable/installing/#installing-with-get-pip-py). With the `:pip` mode you can optionally install a specific Ansible release by setting the [`version`](#version) option.
|
||||
- `:pip`: Ansible is installed from [PyPI](https://pypi.python.org/pypi) with [pip](https://pip.pypa.io) package installer. With this mode, Vagrant will systematically try to [install the latest pip version](https://pip.pypa.io/en/stable/installing/#installing-with-get-pip-py). With the `:pip` mode you can optionally install a specific Ansible release by setting the [`version`](/docs/provisioning/ansible_common.html#version) option.
|
||||
|
||||
Example:
|
||||
|
||||
|
@ -140,16 +141,6 @@ This section lists the _specific_ options for the Ansible Local provisioner. In
|
|||
|
||||
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.
|
||||
|
||||
**Warning:** It is currently possible to use this option to specify which version of Ansible must be automatically installed, but only in combination with the `install_mode` set to `:pip`.
|
||||
|
||||
## Tips and Tricks
|
||||
|
||||
### Ansible Parallel Execution from a Guest
|
||||
|
|
Loading…
Reference in New Issue