diff --git a/plugins/provisioners/ansible/config/base.rb b/plugins/provisioners/ansible/config/base.rb index 36fb5b903..1b58a60d4 100644 --- a/plugins/provisioners/ansible/config/base.rb +++ b/plugins/provisioners/ansible/config/base.rb @@ -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 diff --git a/plugins/provisioners/ansible/config/host.rb b/plugins/provisioners/ansible/config/host.rb index 4e075ae64..06df0d1c4 100644 --- a/plugins/provisioners/ansible/config/host.rb +++ b/plugins/provisioners/ansible/config/host.rb @@ -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 diff --git a/plugins/provisioners/ansible/constants.rb b/plugins/provisioners/ansible/constants.rb new file mode 100644 index 000000000..5e1d9e3f8 --- /dev/null +++ b/plugins/provisioners/ansible/constants.rb @@ -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 \ No newline at end of file diff --git a/plugins/provisioners/ansible/provisioner/base.rb b/plugins/provisioners/ansible/provisioner/base.rb index c2add455a..8d6c8f413 100644 --- a/plugins/provisioners/ansible/provisioner/base.rb +++ b/plugins/provisioners/ansible/provisioner/base.rb @@ -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 diff --git a/plugins/provisioners/ansible/provisioner/guest.rb b/plugins/provisioners/ansible/provisioner/guest.rb index cdf50d9bc..863095dbb 100644 --- a/plugins/provisioners/ansible/provisioner/guest.rb +++ b/plugins/provisioners/ansible/provisioner/guest.rb @@ -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 diff --git a/plugins/provisioners/ansible/provisioner/host.rb b/plugins/provisioners/ansible/provisioner/host.rb index 352a13664..7682a2af3 100644 --- a/plugins/provisioners/ansible/provisioner/host.rb +++ b/plugins/provisioners/ansible/provisioner/host.rb @@ -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 diff --git a/templates/locales/en.yml b/templates/locales/en.yml index f012557d9..37a21c712 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -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: |- diff --git a/test/unit/plugins/provisioners/ansible/config/guest_test.rb b/test/unit/plugins/provisioners/ansible/config/guest_test.rb index 92b7851c5..a9ea5d9da 100644 --- a/test/unit/plugins/provisioners/ansible/config/guest_test.rb +++ b/test/unit/plugins/provisioners/ansible/config/guest_test.rb @@ -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 diff --git a/test/unit/plugins/provisioners/ansible/config/host_test.rb b/test/unit/plugins/provisioners/ansible/config/host_test.rb index 1a23fecfe..2e18005ee 100644 --- a/test/unit/plugins/provisioners/ansible/config/host_test.rb +++ b/test/unit/plugins/provisioners/ansible/config/host_test.rb @@ -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 diff --git a/test/unit/plugins/provisioners/ansible/config/shared.rb b/test/unit/plugins/provisioners/ansible/config/shared.rb index d9bd8cca3..c30d64f2f 100644 --- a/test/unit/plugins/provisioners/ansible/config/shared.rb +++ b/test/unit/plugins/provisioners/ansible/config/shared.rb @@ -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 diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 861a4e1c6..264a0bb96 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -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) diff --git a/website/source/docs/provisioning/ansible.html.md b/website/source/docs/provisioning/ansible.html.md index 3dda49b31..ead77573c 100644 --- a/website/source/docs/provisioning/ansible.html.md +++ b/website/source/docs/provisioning/ansible.html.md @@ -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. + +