diff --git a/plugins/provisioners/ansible/config/base.rb b/plugins/provisioners/ansible/config/base.rb index 1b58a60d4..22693122c 100644 --- a/plugins/provisioners/ansible/config/base.rb +++ b/plugins/provisioners/ansible/config/base.rb @@ -46,7 +46,7 @@ module VagrantPlugins def initialize @become = UNSET_VALUE @become_user = UNSET_VALUE - @compatibility_mode = UNSET_VALUE + @compatibility_mode = Ansible::COMPATIBILITY_MODE_AUTO @config_file = UNSET_VALUE @extra_vars = UNSET_VALUE @galaxy_role_file = UNSET_VALUE @@ -95,6 +95,12 @@ module VagrantPlugins def validate(machine) @errors = _detected_errors + # Validate that a compatibility mode was provided + if !compatibility_mode + @errors << I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode", + valid_modes: Ansible::COMPATIBILITY_MODES.map { |s| "'#{s}'" }.join(', ')) + end + # Validate that a playbook path was provided if !playbook @errors << I18n.t("vagrant.provisioners.ansible.errors.no_playbook") diff --git a/plugins/provisioners/ansible/constants.rb b/plugins/provisioners/ansible/constants.rb index 5e1d9e3f8..17f72beb6 100644 --- a/plugins/provisioners/ansible/constants.rb +++ b/plugins/provisioners/ansible/constants.rb @@ -1,9 +1,14 @@ module VagrantPlugins module Ansible + COMPATIBILITY_MODE_AUTO = "auto".freeze COMPATIBILITY_MODE_V1_8 = "1.8".freeze COMPATIBILITY_MODE_V2_0 = "2.0".freeze - DEFAULT_COMPATIBILITY_MODE = COMPATIBILITY_MODE_V1_8 - COMPATIBILITY_MODES = [COMPATIBILITY_MODE_V1_8, COMPATIBILITY_MODE_V2_0].freeze + SAFE_COMPATIBILITY_MODE = COMPATIBILITY_MODE_V1_8 + COMPATIBILITY_MODES = [ + COMPATIBILITY_MODE_AUTO, + COMPATIBILITY_MODE_V1_8, + COMPATIBILITY_MODE_V2_0, + ].freeze end end \ No newline at end of file diff --git a/plugins/provisioners/ansible/provisioner/base.rb b/plugins/provisioners/ansible/provisioner/base.rb index 8d6c8f413..a810409c6 100644 --- a/plugins/provisioners/ansible/provisioner/base.rb +++ b/plugins/provisioners/ansible/provisioner/base.rb @@ -48,11 +48,11 @@ module VagrantPlugins end def set_compatibility_mode - unless config.compatibility_mode + if config.compatibility_mode == Ansible::COMPATIBILITY_MODE_AUTO detect_compatibility_mode(gather_ansible_version) end - unless Ansible::COMPATIBILITY_MODES.include?(config.compatibility_mode) + unless Ansible::COMPATIBILITY_MODES.slice(1..-1).include?(config.compatibility_mode) raise "Programming Error: compatibility_mode must correctly set at this stage!" end @@ -60,7 +60,7 @@ module VagrantPlugins end def detect_compatibility_mode(ansible_version_stdoutput) - if config.compatibility_mode + if config.compatibility_mode != Ansible::COMPATIBILITY_MODE_AUTO raise "Programming Error: detect_compatibility_mode() shouldn't have been called." end @@ -86,8 +86,8 @@ module VagrantPlugins # 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 + if config.compatibility_mode == Ansible::COMPATIBILITY_MODE_AUTO + config.compatibility_mode = Ansible::SAFE_COMPATIBILITY_MODE @machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.compatibility_mode_not_detected", compatibility_mode: config.compatibility_mode, diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 37a21c712..66e297a5d 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -2374,6 +2374,8 @@ en: `%{config_option}` does not exist on the %{system}: %{path} extra_vars_invalid: |- `extra_vars` must be a hash or a path to an existing file. Received: %{value} (as %{type}) + no_compatibility_mode: |- + `compatibility_mode` must be correctly set (possible values: %{valid_modes}). no_playbook: |- `playbook` file path must be set. raw_arguments_invalid: |- diff --git a/test/unit/plugins/provisioners/ansible/config/shared.rb b/test/unit/plugins/provisioners/ansible/config/shared.rb index c30d64f2f..659573af3 100644 --- a/test/unit/plugins/provisioners/ansible/config/shared.rb +++ b/test/unit/plugins/provisioners/ansible/config/shared.rb @@ -5,7 +5,7 @@ shared_examples_for 'options shared by both Ansible provisioners' do expect(subject.become).to be(false) expect(subject.become_user).to be_nil - expect(subject.compatibility_mode).to be_nil + expect(subject.compatibility_mode).to eql(VagrantPlugins::Ansible::COMPATIBILITY_MODE_AUTO) expect(subject.config_file).to be_nil expect(subject.extra_vars).to be_nil expect(subject.galaxy_command).to eql("ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force") @@ -46,23 +46,37 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup | 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 + VagrantPlugins::Ansible::COMPATIBILITY_MODES.each do |valid_mode| + it "supports compatibility mode '#{valid_mode}'" do + subject.compatibility_mode = valid_mode subject.finalize! result = subject.validate(machine) - expect(subject.compatibility_mode).to eql(minimal_version) + expect(subject.compatibility_mode).to eql(valid_mode) end end + it "returns an error if the compatibility mode is not set" do + subject.compatibility_mode = nil + subject.finalize! + + result = subject.validate(machine) + expect(result[provisioner_label]).to eql([ + I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode", + valid_modes: "'auto', '1.8', '2.0'") + ]) + end + %w(invalid 1.9 2.3).each do |invalid_mode| - it "silently forces the compatibility mode detection for invalid mode '#{invalid_mode}'" do + it "returns an error if the compatibility mode is invalid (e.g. '#{invalid_mode}')" do subject.compatibility_mode = invalid_mode subject.finalize! result = subject.validate(machine) - expect(subject.compatibility_mode).to be_nil + expect(result[provisioner_label]).to eql([ + I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode", + valid_modes: "'auto', '1.8', '2.0'") + ]) end end @@ -109,6 +123,7 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup | end it "it collects and returns all detected errors" do + subject.compatibility_mode = nil subject.playbook = nil subject.extra_vars = ["var1", 3, "var2", 5] subject.raw_arguments = { arg1: 1, arg2: "foo" } @@ -116,7 +131,10 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup | result = subject.validate(machine) - expect(result[provisioner_label].size).to eql(3) + expect(result[provisioner_label].size).to eql(4) + expect(result[provisioner_label]).to include( + I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode", + valid_modes: "'auto', '1.8', '2.0'")) expect(result[provisioner_label]).to include( I18n.t("vagrant.provisioners.ansible.errors.no_playbook")) expect(result[provisioner_label]).to include( diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 264a0bb96..a63723a03 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -305,9 +305,9 @@ VF "ask_become_pass" => "--ask-sudo-pass"}) end - context "with no compatibility_mode defined" do + context "with compatibility_mode 'auto'" do before do - config.compatibility_mode = nil + config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_AUTO end valid_versions = { @@ -348,16 +348,16 @@ VF 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 + it "applies the safest compatibility mode ('#{VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE}')" do expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| - expect(config.compatibility_mode).to eq(VagrantPlugins::Ansible::DEFAULT_COMPATIBILITY_MODE) + expect(config.compatibility_mode).to eq(VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE) }.and_return(default_execute_result) end it "warns about not being able to detect the best compatibility mode" do expect(machine.env.ui).to receive(:warn).with( I18n.t("vagrant.provisioners.ansible.compatibility_mode_not_detected", - compatibility_mode: VagrantPlugins::Ansible::DEFAULT_COMPATIBILITY_MODE, + compatibility_mode: VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE, gathered_version: unknown_ansible_version) + "\n") end diff --git a/website/source/docs/provisioning/ansible_common.html.md b/website/source/docs/provisioning/ansible_common.html.md index a8adf2399..fbfa0e96c 100644 --- a/website/source/docs/provisioning/ansible_common.html.md +++ b/website/source/docs/provisioning/ansible_common.html.md @@ -29,10 +29,11 @@ Some of these options are for advanced usage only and should not be used unless 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) + - `"auto"` _(Vagrant will automatically select the optimal compatibilty mode by checking the Ansible version currently available)_ + - `"1.8"` _(Ansible versions prior to 1.8 should mostly work well, but some options might not be supported)_ + - `"2.0"` _(The generated Ansible inventory will be incompatible with Ansible 1.x)_ - By default this option is 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. + By default this option is set to `"auto"`. If Vagrant is not able to detect any supported Ansible version, it will falls back on the compatibility mode `"1.8"` with a warning.
Compatibility Note: