diff --git a/CHANGELOG.md b/CHANGELOG.md index bb2a1b199..dad1b4a4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,8 @@ IMPROVEMENTS: - commands/login: Print a warning with both the environment variable and local login token are present [GH-7206, GH-7219] - communicators/winrm: Upgrade to latest WinRM gems [GH-6922] + - provisioners/ansible_local: Allow to install Ansible from pip, + with version selection capability [GH-6654, GH-7167] - provisioners/ansible_local: Use `provisioning_path` as working directory for `ansible-galaxy` execution - provisioners/ansible(both provisioners): Add basic config diff --git a/plugins/provisioners/ansible/cap/guest/arch/ansible_install.rb b/plugins/provisioners/ansible/cap/guest/arch/ansible_install.rb index 62e1e2141..062bea01c 100644 --- a/plugins/provisioners/ansible/cap/guest/arch/ansible_install.rb +++ b/plugins/provisioners/ansible/cap/guest/arch/ansible_install.rb @@ -1,3 +1,4 @@ +require_relative "../../../errors" module VagrantPlugins module Ansible @@ -6,9 +7,13 @@ module VagrantPlugins module Arch module AnsibleInstall - def self.ansible_install(machine) - machine.communicate.sudo("pacman -Syy --noconfirm") - machine.communicate.sudo("pacman -S --noconfirm ansible") + def self.ansible_install(machine, install_mode, ansible_version) + if install_mode == :pip + raise Ansible::Errors::AnsiblePipInstallIsNotSupported + else + machine.communicate.sudo("pacman -Syy --noconfirm") + machine.communicate.sudo("pacman -S --noconfirm ansible") + end end end diff --git a/plugins/provisioners/ansible/cap/guest/debian/ansible_install.rb b/plugins/provisioners/ansible/cap/guest/debian/ansible_install.rb index 05308d03d..3ce214bf3 100644 --- a/plugins/provisioners/ansible/cap/guest/debian/ansible_install.rb +++ b/plugins/provisioners/ansible/cap/guest/debian/ansible_install.rb @@ -1,3 +1,4 @@ +require_relative "../pip/pip" module VagrantPlugins module Ansible @@ -6,8 +7,23 @@ module VagrantPlugins module Debian module AnsibleInstall - def self.ansible_install(machine) + def self.ansible_install(machine, install_mode, ansible_version) + if (install_mode == :pip) + ansible_pip_install machine, ansible_version + else + ansible_apt_install machine + end + end + + def self.ansible_pip_install(machine, ansible_version) + pip_setup machine + Pip::pip_install machine, "ansible", ansible_version + end + + private + + def self.ansible_apt_install(machine) install_backports_if_wheezy_release = < false) - if epel != 0 - machine.communicate.sudo('sudo rpm -i https://dl.fedoraproject.org/pub/epel/epel-release-latest-`rpm -E %dist | sed -n \'s/.*el\([0-9]\).*/\1/p\'`.noarch.rpm') + def self.ansible_install(machine, install_mode, ansible_version) + if install_mode == :pip + pip_setup machine + Pip::pip_install machine, "ansible", ansible_version + else + ansible_rpm_install machine end - - machine.communicate.sudo("#{yum_dnf(machine)} -y --enablerepo=epel install ansible") end - def self.yum_dnf(machine) - machine.communicate.test("/usr/bin/which -s dnf") ? "dnf" : "yum" + private + + def self.ansible_rpm_install(machine) + epel = machine.communicate.execute "#{yum_dnf(machine)} repolist epel | grep -q epel", error_check: false + if epel != 0 + machine.communicate.sudo 'sudo rpm -i https://dl.fedoraproject.org/pub/epel/epel-release-latest-`rpm -E %dist | sed -n \'s/.*el\([0-9]\).*/\1/p\'`.noarch.rpm' + end + + machine.communicate.sudo "#{Facts::rpm_package_manager} -y --enablerepo=epel install ansible" + end + + def self.pip_setup(machine) + machine.communicate.sudo("#{Facts::rpm_package_manager(machine)} install -y curl gcc libffi-devel openssl-devel python-crypto python-devel python-setuptools") + Pip::get_pip machine end end diff --git a/plugins/provisioners/ansible/cap/guest/suse/ansible_install.rb b/plugins/provisioners/ansible/cap/guest/suse/ansible_install.rb index 64f3cd41c..08e7a96f4 100644 --- a/plugins/provisioners/ansible/cap/guest/suse/ansible_install.rb +++ b/plugins/provisioners/ansible/cap/guest/suse/ansible_install.rb @@ -6,8 +6,12 @@ module VagrantPlugins module SUSE module AnsibleInstall - def self.ansible_install(machine) - machine.communicate.sudo("zypper --non-interactive --quiet install ansible") + def self.ansible_install(machine, install_mode, ansible_version) + if install_mode == :pip + raise Ansible::Errors::AnsiblePipInstallIsNotSupported + else + machine.communicate.sudo("zypper --non-interactive --quiet install ansible") + end end end diff --git a/plugins/provisioners/ansible/cap/guest/ubuntu/ansible_install.rb b/plugins/provisioners/ansible/cap/guest/ubuntu/ansible_install.rb index 92182fe06..bd9f22295 100644 --- a/plugins/provisioners/ansible/cap/guest/ubuntu/ansible_install.rb +++ b/plugins/provisioners/ansible/cap/guest/ubuntu/ansible_install.rb @@ -1,3 +1,4 @@ +require_relative "../debian/ansible_install" module VagrantPlugins module Ansible @@ -6,12 +7,22 @@ module VagrantPlugins module Ubuntu module AnsibleInstall - def self.ansible_install(machine) - machine.communicate.sudo("apt-get update -y -qq") - machine.communicate.sudo("apt-get install -y -qq software-properties-common python-software-properties") - machine.communicate.sudo("add-apt-repository ppa:ansible/ansible -y") - machine.communicate.sudo("apt-get update -y -qq") - machine.communicate.sudo("apt-get install -y -qq ansible") + def self.ansible_install(machine, install_mode, ansible_version) + if install_mode == :pip + Debian::AnsibleInstall::ansible_pip_install machine, ansible_version + else + ansible_apt_install machine + end + end + + private + + def self.ansible_apt_install(machine) + machine.communicate.sudo "apt-get update -y -qq" + machine.communicate.sudo "apt-get install -y -qq software-properties-common python-software-properties" + machine.communicate.sudo "add-apt-repository ppa:ansible/ansible -y" + machine.communicate.sudo "apt-get update -y -qq" + machine.communicate.sudo "apt-get install -y -qq ansible" end end diff --git a/plugins/provisioners/ansible/config/guest.rb b/plugins/provisioners/ansible/config/guest.rb index 5f0086c4a..c0cbb92d7 100644 --- a/plugins/provisioners/ansible/config/guest.rb +++ b/plugins/provisioners/ansible/config/guest.rb @@ -9,12 +9,14 @@ module VagrantPlugins attr_accessor :provisioning_path attr_accessor :tmp_path attr_accessor :install + attr_accessor :install_mode attr_accessor :version def initialize super @install = UNSET_VALUE + @install_mode = UNSET_VALUE @provisioning_path = UNSET_VALUE @tmp_path = UNSET_VALUE @version = UNSET_VALUE @@ -24,6 +26,7 @@ module VagrantPlugins super @install = true if @install == UNSET_VALUE + @install_mode = :default if @install_mode == 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 @@ -32,6 +35,12 @@ module VagrantPlugins def validate(machine) super + if @install_mode.to_s.to_sym == :pip + @install_mode = :pip + else + @install_mode = :default + end + { "ansible local provisioner" => @errors } end diff --git a/plugins/provisioners/ansible/errors.rb b/plugins/provisioners/ansible/errors.rb index 06a1063cf..be9e55fb9 100644 --- a/plugins/provisioners/ansible/errors.rb +++ b/plugins/provisioners/ansible/errors.rb @@ -19,6 +19,10 @@ module VagrantPlugins error_key(:ansible_not_found_on_guest) end + class AnsiblePipInstallIsNotSupported < AnsibleError + error_key(:cannot_support_pip_install) + end + class AnsibleVersionNotFoundOnGuest < AnsibleError error_key(:ansible_version_not_found_on_guest) end diff --git a/plugins/provisioners/ansible/provisioner/guest.rb b/plugins/provisioners/ansible/provisioner/guest.rb index 298f9cef3..147e212ce 100644 --- a/plugins/provisioners/ansible/provisioner/guest.rb +++ b/plugins/provisioners/ansible/provisioner/guest.rb @@ -49,7 +49,7 @@ module VagrantPlugins (config.version.to_s.to_sym == :latest || !@machine.guest.capability(:ansible_installed, config.version)) @machine.ui.detail I18n.t("vagrant.provisioners.ansible.installing") - @machine.guest.capability(:ansible_install) + @machine.guest.capability(:ansible_install, config.install_mode, config.version) end # Check that ansible binaries are well installed on the guest, diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 1514c7a1f..112ef37d8 100755 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -2122,6 +2122,15 @@ en: on your host system. Vagrant can't do this for you in a safe and automated way. Please check https://docs.ansible.com for more information. + cannot_support_pip_install: |- + Unfortunately Vagrant does not support yet installing Ansible + from pip for the guest OS running in the machine. + + If you'd like this provisioner to be improved, please + take a look at the Vagrant source code linked below and try + 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, @@ -2139,6 +2148,7 @@ en: raw_ssh_args_invalid: |- `raw_ssh_args` must be an array of strings. Received: %{value} (as %{type}) installing: "Installing Ansible..." + installing_pip: "Installing pip... (for Ansible installation)" running_galaxy: "Running ansible-galaxy..." running_playbook: "Running ansible-playbook..." windows_not_supported_for_control_machine: |- diff --git a/test/unit/plugins/provisioners/ansible/config/guest_test.rb b/test/unit/plugins/provisioners/ansible/config/guest_test.rb index 4368c7032..c1c389686 100644 --- a/test/unit/plugins/provisioners/ansible/config/guest_test.rb +++ b/test/unit/plugins/provisioners/ansible/config/guest_test.rb @@ -23,6 +23,7 @@ describe VagrantPlugins::Ansible::Config::Guest do groups host_vars install + install_mode inventory_path limit playbook @@ -48,6 +49,7 @@ describe VagrantPlugins::Ansible::Config::Guest do subject.finalize! expect(subject.install).to be_true + 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 @@ -60,6 +62,22 @@ describe VagrantPlugins::Ansible::Config::Guest do end it_behaves_like "an Ansible provisioner", "/vagrant", "local" + + it "falls back to :default install_mode for any invalid setting" do + subject.install_mode = "from_source" + subject.finalize! + + result = subject.validate(machine) + expect(subject.install_mode).to eql(:default) + end + + it "supports :pip install_mode" do + subject.install_mode = "pip" + subject.finalize! + + result = subject.validate(machine) + expect(subject.install_mode).to eql(:pip) + end end end diff --git a/website/source/docs/provisioning/ansible_local.html.md b/website/source/docs/provisioning/ansible_local.html.md index ba93cc881..a630107bb 100644 --- a/website/source/docs/provisioning/ansible_local.html.md +++ b/website/source/docs/provisioning/ansible_local.html.md @@ -71,6 +71,16 @@ This section lists the specific options for the Ansible Local provisioner. In ad **Attention:** There is no guarantee that this automated installation will replace a custom Ansible setup, that might be already present on the Vagrant box. +- `install_mode` (`:default` or `:pip`) - Select the way to automatically install Ansible on the guest system. + + - `:default`: Ansible is installed from the operating system package manager. This mode doesn't support `version` selection. For many platforms (e.g Debian, FreeBSD, OpenSUSE) the official package repository is used, except for the following Linux distributions: + - 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). The `:pip` mode can install a specific version of Ansible if such information is specified with the `version` option described below. + + The default value is `:default`, and any invalid value for this option will silently fall back to the default value. + - `provisioning_path` (string) - An absolute path on the guest machine where the Ansible files are stored. The `ansible-galaxy` and `ansible-playbook` commands are executed from this directory. This is the location to place an [ansible.cfg](http://docs.ansible.com/ansible/intro_configuration.html) file, in case you need it. The default value is `/vagrant`.