diff --git a/plugins/provisioners/ansible/config/base.rb b/plugins/provisioners/ansible/config/base.rb index 22693122c..201554bdf 100644 --- a/plugins/provisioners/ansible/config/base.rb +++ b/plugins/provisioners/ansible/config/base.rb @@ -28,6 +28,7 @@ module VagrantPlugins attr_accessor :tags attr_accessor :vault_password_file attr_accessor :verbose + attr_accessor :version # # Deprecated options @@ -64,6 +65,7 @@ module VagrantPlugins @tags = UNSET_VALUE @vault_password_file = UNSET_VALUE @verbose = UNSET_VALUE + @version = UNSET_VALUE end def finalize! @@ -87,6 +89,7 @@ module VagrantPlugins @tags = nil if @tags == UNSET_VALUE @vault_password_file = nil if @vault_password_file == UNSET_VALUE @verbose = false if @verbose == UNSET_VALUE + @version = "" if @version == UNSET_VALUE end # Just like the normal configuration "validate" method except that diff --git a/plugins/provisioners/ansible/config/guest.rb b/plugins/provisioners/ansible/config/guest.rb index 16992cb5a..a869779ae 100644 --- a/plugins/provisioners/ansible/config/guest.rb +++ b/plugins/provisioners/ansible/config/guest.rb @@ -11,7 +11,6 @@ module VagrantPlugins attr_accessor :install attr_accessor :install_mode attr_accessor :pip_args - attr_accessor :version def initialize super @@ -21,7 +20,6 @@ module VagrantPlugins @pip_args = UNSET_VALUE @provisioning_path = UNSET_VALUE @tmp_path = UNSET_VALUE - @version = UNSET_VALUE end def finalize! @@ -32,7 +30,6 @@ module VagrantPlugins @pip_args = "" if @pip_args == UNSET_VALUE @provisioning_path = "/vagrant" if provisioning_path == UNSET_VALUE @tmp_path = "/tmp/vagrant-ansible" if tmp_path == UNSET_VALUE - @version = "" if @version == UNSET_VALUE end def validate(machine) diff --git a/plugins/provisioners/ansible/errors.rb b/plugins/provisioners/ansible/errors.rb index be9e55fb9..efc177ef8 100644 --- a/plugins/provisioners/ansible/errors.rb +++ b/plugins/provisioners/ansible/errors.rb @@ -23,8 +23,8 @@ module VagrantPlugins error_key(:cannot_support_pip_install) end - class AnsibleVersionNotFoundOnGuest < AnsibleError - error_key(:ansible_version_not_found_on_guest) + class AnsibleVersionMismatch < AnsibleError + error_key(:ansible_version_mismatch) end end end diff --git a/plugins/provisioners/ansible/provisioner/guest.rb b/plugins/provisioners/ansible/provisioner/guest.rb index 863095dbb..1b8fc38a4 100644 --- a/plugins/provisioners/ansible/provisioner/guest.rb +++ b/plugins/provisioners/ansible/provisioner/guest.rb @@ -65,7 +65,7 @@ module VagrantPlugins if (!config.version.empty? && config.version.to_s.to_sym != :latest && !@machine.guest.capability(:ansible_installed, config.version)) - raise Ansible::Errors::AnsibleVersionNotFoundOnGuest, required_version: config.version.to_s + raise Ansible::Errors::AnsibleVersionMismatch, system: "guest", required_version: config.version.to_s end end diff --git a/plugins/provisioners/ansible/provisioner/host.rb b/plugins/provisioners/ansible/provisioner/host.rb index 7682a2af3..f25178729 100644 --- a/plugins/provisioners/ansible/provisioner/host.rb +++ b/plugins/provisioners/ansible/provisioner/host.rb @@ -19,6 +19,7 @@ module VagrantPlugins @ssh_info = @machine.ssh_info warn_for_unsupported_platform + check_required_ansible_version unless config.version.empty? check_files_existence set_compatibility_mode @@ -36,6 +37,19 @@ module VagrantPlugins end end + def check_required_ansible_version + if config.version.to_s.to_sym == :latest + @logger.debug("The :latest version requirement is not supported (yet) by the host-based provisioner") + return + end + + @logger.info("Checking for Ansible version on Vagrant host...") + found_version = gather_ansible_version + if (!found_version || "ansible #{config.version}\n" != found_version.lines[0]) + raise Ansible::Errors::AnsibleVersionMismatch, system: "host", required_version: config.version.to_s + end + end + def prepare_command_arguments # Connect with native OpenSSH client # Other modes (e.g. paramiko) are not officially supported, diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 66e297a5d..5cb65c0ad 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -2364,11 +2364,11 @@ en: to contribute back support. Thank you! https://github.com/mitchellh/vagrant - ansible_version_not_found_on_guest: |- - The requested Ansible version (%{required_version}) was not found on the guest. - Please check the ansible installation on your guest system, + ansible_version_mismatch: |- + The requested Ansible version (%{required_version}) was not found on the %{system}. + Please check the Ansible installation on your Vagrant %{system} system, or adapt the `version` option of this provisioner in your Vagrantfile. - See https://docs.vagrantup.com/v2/provisioning/ansible_local.html + See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#version for more information. config_file_not_found: |- `%{config_option}` does not exist on the %{system}: %{path} diff --git a/test/unit/plugins/provisioners/ansible/config/guest_test.rb b/test/unit/plugins/provisioners/ansible/config/guest_test.rb index a9ea5d9da..19010ba94 100644 --- a/test/unit/plugins/provisioners/ansible/config/guest_test.rb +++ b/test/unit/plugins/provisioners/ansible/config/guest_test.rb @@ -16,7 +16,8 @@ describe VagrantPlugins::Ansible::Config::Guest do let(:existing_file) { "this/path/is/a/stub" } it "supports a list of options" do - supported_options = %w( become + supported_options = %w( + become become_user compatibility_mode config_file @@ -43,7 +44,8 @@ describe VagrantPlugins::Ansible::Config::Guest do tmp_path vault_password_file verbose - version ) + version + ) expect(get_provisioner_option_names(described_class)).to eql(supported_options) end @@ -58,7 +60,6 @@ describe VagrantPlugins::Ansible::Config::Guest do expect(subject.install_mode).to eql(:default) expect(subject.provisioning_path).to eql("/vagrant") expect(subject.tmp_path).to eql("/tmp/vagrant-ansible") - expect(subject.version).to be_empty end end diff --git a/test/unit/plugins/provisioners/ansible/config/host_test.rb b/test/unit/plugins/provisioners/ansible/config/host_test.rb index 2e18005ee..b868d2648 100644 --- a/test/unit/plugins/provisioners/ansible/config/host_test.rb +++ b/test/unit/plugins/provisioners/ansible/config/host_test.rb @@ -13,7 +13,8 @@ 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_become_pass + supported_options = %w( + ask_become_pass ask_sudo_pass ask_vault_pass become @@ -40,7 +41,9 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do sudo_user tags vault_password_file - verbose ) + verbose + version + ) expect(get_provisioner_option_names(described_class)).to eql(supported_options) end diff --git a/test/unit/plugins/provisioners/ansible/config/shared.rb b/test/unit/plugins/provisioners/ansible/config/shared.rb index 659573af3..16902a138 100644 --- a/test/unit/plugins/provisioners/ansible/config/shared.rb +++ b/test/unit/plugins/provisioners/ansible/config/shared.rb @@ -25,6 +25,7 @@ shared_examples_for 'options shared by both Ansible provisioners' do expect(subject.tags).to be_nil expect(subject.vault_password_file).to be_nil expect(subject.verbose).to be(false) + expect(subject.version).to be_empty end end diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 7e8c3aaaa..eafd8bf79 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -947,6 +947,46 @@ VF end end + + context "with version option set" do + before do + config.version = "2.3.4.5" + end + + describe "and the installed ansible version is correct" do + before do + allow(subject).to receive(:gather_ansible_version).and_return("ansible #{config.version}\n...\n") + end + + it "executes ansible-playbook command" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args).and_return(default_execute_result) + end + end + + describe "and there is an ansible version mismatch" do + before do + allow(subject).to receive(:gather_ansible_version).and_return("ansible 1.9.6\n...\n") + end + + it "raises an error about the ansible version mismatch", skip_before: true, skip_after: true do + config.finalize! + expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleVersionMismatch) + end + end + + describe "and the installed ansible version cannot be detected" do + before do + allow(subject).to receive(:gather_ansible_version).and_return(nil) + end + + it "raises an error about the ansible version mismatch", skip_before: true, skip_after: true do + config.finalize! + expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleVersionMismatch) + end + end + end + + # TODO: add more tests, now that we know how to deal with multiple Subprocess stub executions describe "with galaxy support" do before do diff --git a/website/source/docs/provisioning/ansible_common.html.md b/website/source/docs/provisioning/ansible_common.html.md index fbfa0e96c..17c047992 100644 --- a/website/source/docs/provisioning/ansible_common.html.md +++ b/website/source/docs/provisioning/ansible_common.html.md @@ -42,7 +42,7 @@ Some of these options are for advanced usage only and should not be used unless
Attention: - 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. + Vagrant doesn't perform any validation between the `compatibility_mode` value and the value of the [`version`](#version) option.
- `config_file` (string) - The path to an [Ansible Configuration file](https://docs.ansible.com/intro_configuration.html). @@ -185,3 +185,16 @@ Some of these options are for advanced usage only and should not be used unless Examples: `true` (equivalent to `v`), `-vvv` (equivalent to `vvv`), `vvvv`. Note that when the `verbose` option is enabled, the `ansible-playbook` command used by Vagrant will be displayed. + +- `version` (string) - The expected Ansible version. + + This option is disabled by default. + + When an Ansible version is defined (e.g. `"2.1.6.0"`), the Ansible provisioner will be executed only if Ansible is installed at the requested version. + + When this option is set to `"latest"`, no version check is applied. + +
+ Tip: + With the `ansible_local` provisioner, it is currently possible to use this option to specify which version of Ansible must be automatically installed, but only in combination with the [**`install_mode`**](ansible_local.html#install_mode) set to `:pip`. +
diff --git a/website/source/docs/provisioning/ansible_local.html.md b/website/source/docs/provisioning/ansible_local.html.md index 20b5b42b2..224b2179c 100644 --- a/website/source/docs/provisioning/ansible_local.html.md +++ b/website/source/docs/provisioning/ansible_local.html.md @@ -62,8 +62,8 @@ This section lists the _specific_ options for the Ansible Local provisioner. In Vagrant will try to install (or upgrade) Ansible when one of these conditions are met: - Ansible is not installed (or cannot be found). - - The `version` option is set to `"latest"`. - - The current Ansible version does not correspond to the `version` option. + - The [`version`](/docs/provisioning/ansible_common.html#version) option is set to `"latest"`. + - The current Ansible version does not correspond to the [`version`](/docs/provisioning/ansible_common.html#version) option.
Attention: @@ -76,7 +76,7 @@ This section lists the _specific_ options for the Ansible Local provisioner. In - On Ubuntu-like systems, the latest Ansible release is installed from the `ppa:ansible/ansible` repository. - On RedHat-like systems, the latest Ansible release is installed from the [EPEL](http://fedoraproject.org/wiki/EPEL) repository. - - `:pip`: Ansible is installed from [PyPI](https://pypi.python.org/pypi) with [pip](https://pip.pypa.io) package installer. With this mode, Vagrant will systematically try to [install the latest pip version](https://pip.pypa.io/en/stable/installing/#installing-with-get-pip-py). With the `:pip` mode you can optionally install a specific Ansible release by setting the [`version`](#version) option. + - `:pip`: Ansible is installed from [PyPI](https://pypi.python.org/pypi) with [pip](https://pip.pypa.io) package installer. With this mode, Vagrant will systematically try to [install the latest pip version](https://pip.pypa.io/en/stable/installing/#installing-with-get-pip-py). With the `:pip` mode you can optionally install a specific Ansible release by setting the [`version`](/docs/provisioning/ansible_common.html#version) option. Example: @@ -141,19 +141,6 @@ This section lists the _specific_ options for the Ansible Local provisioner. In The default value is `/tmp/vagrant-ansible` -- `version` (string) - The expected Ansible version. - - This option is disabled by default. - - When an Ansible version is defined (e.g. `"1.8.2"`), the Ansible local provisioner will be executed only if Ansible is installed at the requested version. - - When this option is set to `"latest"`, no version check is applied. - -
- Tip: - It is currently possible to use this option to specify which version of Ansible must be automatically installed, but only in combination with the `install_mode` set to `:pip`. -
- ## Tips and Tricks ### Ansible Parallel Execution from a Guest