provisioners/ansible(both): Add compatibility mode

With this change, it is now possible to get rid of many deprecation
messages successively introduced in Ansible 1.9, and 2.0. More
interesting, the generated inventory will contain the recommended
variable names (e.g. `ansible_host` instead of `ansible_ssh_host`)
when the compatibility mode is set to '2.0'.

Details:

- Add `compatibility_mode` option to control the Ansible parameters
  format to be used. The value corresponds to the minimal version
  supported. For the moment, possible values are '1.8' (corresponding to
  Vagrant's former behaviour) or '2.0'.
  Note that a dynamic inventory generated in compatibility mode '2.0'
  is not supported by Ansible 1.x. On the other hand, Ansible 2.x so far
  supports inventory format generated by the compatibility mode '1.8'.

- Add compatibility mode auto-detection, based on the available Ansible
  version. This is the default behaviour in order to bring a maximum of
  user friendliness. The drawback of this approach is to let potential
  compatibility breaking risks, for `ansible` provisioner setups that
  already integrate Ansible 2.x **AND** rely on the existence of
  the generated `_ssh` variable names. Thanks to the vagrant warnings
  (and its release notes), I argue that it is worth to offer
  auto-detection by default, which offers a sweet transition to most
  users.

- Add `become`, `become_user` and `ask_become_pass` options and their
  backwards compatible aliases. The legacy options are now deprecated.

Note that we intentionally didn't provide a '1.9' compatibility mode,
as it would add extra-complexity for practically no added-value.
To my knowledge, the Ansible 2.x series haven't introduced yet any major
changes or deprecations that would motivate to introduce a higher
version compatibility mode (to be confirmed/verified).

Resolve GH-6570

Still Pending:

- Optimization: Reduce the number of `ansible` command executions.
  Currently two exec calls will be performed when the compatibility
  mode auto-detection is enabled (i.e. by default). We could make the
  provisioner a little bit smarter to only execute `ansible` only once
  in any situation (by combining "presence" and "version" checks).

- User-friendliness: Add better validator on `compatibility_mode`
  option, and shows a warning or an error instead of the silent
  fallback on the auto-detection modus.

- Test coverage: All the added behaviours are not fully covered yet.
This commit is contained in:
Gilles Cornu 2016-11-13 20:58:26 +01:00
parent e09848ca59
commit 8834afbd8e
No known key found for this signature in database
GPG Key ID: F6BC2CF7E1FE8FFF
13 changed files with 503 additions and 93 deletions

View File

@ -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,28 @@ 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
#
# 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 = UNSET_VALUE
@config_file = UNSET_VALUE
@extra_vars = UNSET_VALUE
@galaxy_role_file = UNSET_VALUE
@ -41,14 +61,15 @@ 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
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,8 +84,6 @@ 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
@ -112,6 +131,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

View File

@ -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

View File

@ -0,0 +1,9 @@
module VagrantPlugins
module Ansible
COMPATIBILITY_MODE_V1_8 = "1.8".freeze
COMPATIBILITY_MODE_V2_0 = "2.0".freeze
DEFAULT_COMPATIBILITY_MODE = COMPATIBILITY_MODE_V1_8
COMPATIBILITY_MODES = [COMPATIBILITY_MODE_V1_8, COMPATIBILITY_MODE_V2_0].freeze
end
end

View File

@ -1,3 +1,4 @@
require_relative "../constants"
require_relative "../errors"
require_relative "../helpers"
@ -14,6 +15,27 @@ 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)
@ -25,6 +47,55 @@ module VagrantPlugins
@inventory_path = nil
end
def set_compatibility_mode
unless config.compatibility_mode
detect_compatibility_mode(gather_ansible_version)
end
unless Ansible::COMPATIBILITY_MODES.include?(config.compatibility_mode)
raise "Programming Error: compatibility_mode must correctly set at this stage!"
end
@lexicon = ANSIBLE_PARAMETER_NAMES[config.compatibility_mode]
end
def detect_compatibility_mode(ansible_version_stdoutput)
if config.compatibility_mode
raise "Programming Error: detect_compatibility_mode() shouldn't have been called."
end
begin
first_line = ansible_version_stdoutput.lines[0]
full_version = first_line.match(/ansible (\d)(\.\d+){1,}/)
if full_version
major_version, _ = full_version.captures
if major_version.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: full_version) +
"\n")
end
rescue
# Nothing to do here, the fallback to default compatibility_mode is done below
end
unless config.compatibility_mode
config.compatibility_mode = Ansible::DEFAULT_COMPATIBILITY_MODE
@machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.compatibility_mode_not_detected",
compatibility_mode: config.compatibility_mode,
gathered_version: ansible_version_stdoutput) +
"\n")
end
end
def check_files_existence
check_path_is_a_file(config.playbook, :playbook)
@ -97,8 +168,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

View File

@ -14,8 +14,10 @@ module VagrantPlugins
end
def provision
check_files_existence
check_and_install_ansible
check_files_existence
set_compatibility_mode
execute_ansible_galaxy_on_guest if config.galaxy_role_file
execute_ansible_playbook_on_guest
end
@ -67,6 +69,19 @@ module VagrantPlugins
end
end
def gather_ansible_version
raw_output = nil
result = @machine.communicate.execute("ansible --version", error_check: false) do |type, output|
if type == :stdout && output.lines[0]
raw_output = output.lines[0]
end
end
if result != 0
raw_output = nil
end
raw_output
end
def get_provisioning_working_directory
config.provisioning_path
end

View File

@ -18,8 +18,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
set_compatibility_mode
execute_ansible_galaxy_from_host if config.galaxy_role_file
execute_ansible_playbook_from_host
@ -31,7 +32,7 @@ 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
@ -49,15 +50,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 +89,30 @@ module VagrantPlugins
end
end
def gather_ansible_version
raw_output = nil
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 = nil
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 +224,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

View File

@ -2387,6 +2387,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: |-

View File

@ -16,7 +16,10 @@ 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

View File

@ -13,8 +13,12 @@ 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
@ -47,7 +51,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 +66,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

View File

@ -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 be_nil
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,8 +20,8 @@ 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)
@ -41,6 +44,30 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup |
])
end
describe "compatibility_mode option" do
%w(1.8 2.0).each do |minimal_version|
it "supports compatibility mode '#{minimal_version}'" do
subject.compatibility_mode = minimal_version
subject.finalize!
result = subject.validate(machine)
expect(subject.compatibility_mode).to eql(minimal_version)
end
end
%w(invalid 1.9 2.3).each do |invalid_mode|
it "silently forces the compatibility mode detection for invalid mode '#{invalid_mode}'" do
subject.compatibility_mode = invalid_mode
subject.finalize!
result = subject.validate(machine)
expect(subject.compatibility_mode).to be_nil
end
end
end
it "passes if the extra_vars option is a hash" do
subject.extra_vars = { var1: 1, var2: "foo" }
subject.finalize!
@ -102,7 +129,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

View File

@ -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,139 @@ 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 no compatibility_mode defined" do
before do
config.compatibility_mode = nil
end
valid_versions = {
"0.6": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8,
"1.9.4": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8,
"2.2.1.0": 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}")
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 #{ansible_version}") +
"\n")
end
end
end
invalid_versions = [
"ansible devel",
"ansible 2.x.y.z",
"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 default compatibility mode ('#{VagrantPlugins::Ansible::DEFAULT_COMPATIBILITY_MODE}')" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
expect(config.compatibility_mode).to eq(VagrantPlugins::Ansible::DEFAULT_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::DEFAULT_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_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
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 "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_V2_0
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
@ -293,7 +430,7 @@ VF
config.host_vars = {
machine1: {"http_port" => 80, "comments" => "'some text with spaces'"}
}
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_return(default_execute_result)
@ -303,7 +440,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)
@ -313,7 +451,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)
@ -323,7 +462,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)
@ -345,7 +485,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
@ -383,7 +523,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
@ -421,18 +561,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
@ -442,7 +582,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"
@ -461,7 +601,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
@ -469,7 +609,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")
@ -478,7 +618,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
@ -503,7 +643,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)
@ -534,8 +674,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)
@ -550,7 +689,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")
@ -568,7 +707,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)
@ -576,7 +715,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)
@ -588,7 +727,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)
@ -602,7 +741,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)
@ -618,7 +757,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
@ -632,7 +771,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
@ -647,7 +786,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")
@ -661,7 +800,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")
@ -681,7 +820,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")
@ -695,7 +834,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")
@ -712,7 +851,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)
@ -725,7 +864,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)
@ -795,10 +934,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")
@ -822,7 +963,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)
@ -856,7 +1001,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"))
@ -893,10 +1038,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)
@ -914,10 +1059,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",
@ -927,7 +1072,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")
@ -967,7 +1112,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)
@ -982,11 +1127,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_V2_0
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
@ -996,7 +1144,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)
@ -1006,7 +1154,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, "", ""))
@ -1019,7 +1167,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)

View File

@ -53,10 +53,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`.

View File

@ -17,6 +17,33 @@ 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) - Cause Ansible to perform all the playbook tasks [as another user](http://docs.ansible.com/ansible/become.html), different from the one 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 defined, and the Ansible default value (`root`) will be used.
- `compatibility_mode` (string) - Set the **minimal** version of Ansible to be supported. Vagrant will use some parameters that are only compatible since the given version.
Possible values:
- `"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 not set, and Vagrant will try to automatically set the optimal compatibilty mode by checking the Ansible version currently available. Note that Vagrant doesn't validate this option, and any unsupported value (e.g. "2.3") will also lead Vagrant to auto-detect the compatibility mode.
<div class="alert alert-info">
<strong>Compatibility Note:</strong>
This option was introduced in Vagrant 2.0. Previous Vagrant versions behave like if this option was set to `"1.8"`.
</div>
<div class="alert alert-warning">
<strong>Attention:</strong>
Vagrant doesn't perform any validation between the `compatibility_mode` value and the value of the ansible_local [`version`](/docs/provisioning/ansible_local.html#version) option.
</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).
@ -129,11 +156,19 @@ Some of these options are for advanced usage only and should not be used unless
- `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) .