provisioners/ansible_local: add "pip" install_mode

These changes have been validated against the following guest systems:
- Debian 7 and 8
- Ubuntu 12.04, 14.04 and 16.04
- Fedora 21 and 23
- CentOS 7
- OracleLinux 7
- Scientific Linux 7

At the moment, the pip setup (via get-pip.py script) is not working for
RHEL6-like systems (CentOS 6.6, OracleLinux 6.5, Scientific Linux 6),
because Python 2.6 has been deprecated and is no longer supported by
Python core team. I consider this limitation with low priority in
Vagrant context.

The `:pip` install_mode is currently not implemented for the following
platforms:
- OpenSUSE
- ArchLinux
- FreeBSD

Known Issue: By using get-pip.py script, any previous pip installation
will be most probably overrided. This could be an issue for Python
developers who would prefer to keep their base box setup untouched. In
future iteration, it could be possible to choose to reinstall/upgrade
pip or not. issue for Python developers who would prefer to keep their
base box setup untouched. In future iteration, it could be possible to
choose to reinstall/upgrade pip or not.

Resolve GH-6654

Resolve GH-7167 as the `version` option is now considered to select the
version of Ansible to be installed.
This commit is contained in:
Gilles Cornu 2016-06-08 22:59:47 +02:00
parent 08cf08a110
commit bb9dba56ac
16 changed files with 201 additions and 29 deletions

View File

@ -45,6 +45,8 @@ IMPROVEMENTS:
- commands/login: Print a warning with both the environment variable and - commands/login: Print a warning with both the environment variable and
local login token are present [GH-7206, GH-7219] local login token are present [GH-7206, GH-7219]
- communicators/winrm: Upgrade to latest WinRM gems [GH-6922] - 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 - provisioners/ansible_local: Use `provisioning_path` as working directory
for `ansible-galaxy` execution for `ansible-galaxy` execution
- provisioners/ansible(both provisioners): Add basic config - provisioners/ansible(both provisioners): Add basic config

View File

@ -1,3 +1,4 @@
require_relative "../../../errors"
module VagrantPlugins module VagrantPlugins
module Ansible module Ansible
@ -6,10 +7,14 @@ module VagrantPlugins
module Arch module Arch
module AnsibleInstall module AnsibleInstall
def self.ansible_install(machine) 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 -Syy --noconfirm")
machine.communicate.sudo("pacman -S --noconfirm ansible") machine.communicate.sudo("pacman -S --noconfirm ansible")
end end
end
end end
end end

View File

@ -1,3 +1,4 @@
require_relative "../pip/pip"
module VagrantPlugins module VagrantPlugins
module Ansible module Ansible
@ -6,8 +7,23 @@ module VagrantPlugins
module Debian module Debian
module AnsibleInstall 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 = <<INLINE_CRIPT install_backports_if_wheezy_release = <<INLINE_CRIPT
CODENAME=`lsb_release -cs` CODENAME=`lsb_release -cs`
if [ x$CODENAME == 'xwheezy' ]; then if [ x$CODENAME == 'xwheezy' ]; then
@ -20,6 +36,12 @@ INLINE_CRIPT
machine.communicate.sudo("apt-get install -y -qq ansible") machine.communicate.sudo("apt-get install -y -qq ansible")
end end
def self.pip_setup(machine)
machine.communicate.sudo "apt-get update -y -qq"
machine.communicate.sudo "apt-get install -y -qq build-essential curl git libssl-dev libffi-dev python-dev"
Pip::get_pip machine
end
end end
end end
end end

View File

@ -0,0 +1,24 @@
module VagrantPlugins
module Ansible
module Cap
module Guest
module Facts
def self.dnf?(machine)
machine.communicate.test "/usr/bin/which -s dnf"
end
def self.yum?(machine)
machine.communicate.test "/usr/bin/which -s yum"
end
def self.rpm_package_manager(machine)
dnf?(machine) ? "dnf" : "yum"
end
end
end
end
end
end

View File

@ -1,3 +1,5 @@
require_relative "../facts"
require_relative "../pip/pip"
module VagrantPlugins module VagrantPlugins
module Ansible module Ansible
@ -6,16 +8,20 @@ module VagrantPlugins
module Fedora module Fedora
module AnsibleInstall module AnsibleInstall
def self.ansible_install(machine) def self.ansible_install(machine, install_mode, ansible_version)
if dnf?(machine) if install_mode == :pip
machine.communicate.sudo("dnf -y install ansible") pip_setup machine
Pip::pip_install machine, "ansible", ansible_version
else else
machine.communicate.sudo("yum -y install ansible") machine.communicate.sudo "#{Facts::rpm_package_manager} -y install ansible"
end end
end end
def self.dnf?(machine) private
machine.communicate.test("/usr/bin/which -s dnf")
def self.pip_setup(machine)
machine.communicate.sudo "#{Facts::rpm_package_manager(machine)} install -y curl gcc gmp-devel libffi-devel openssl-devel python-crypto python-devel python-dnf python-setuptools redhat-rpm-config"
Pip::get_pip machine
end end
end end

View File

@ -1,3 +1,4 @@
require_relative "../../../errors"
module VagrantPlugins module VagrantPlugins
module Ansible module Ansible
@ -6,9 +7,13 @@ module VagrantPlugins
module FreeBSD module FreeBSD
module AnsibleInstall module AnsibleInstall
def self.ansible_install(machine) def self.ansible_install(machine, install_mode, ansible_version)
if install_mode == :pip
raise Ansible::Errors::AnsiblePipInstallIsNotSupported
else
machine.communicate.sudo("yes | pkg install ansible") machine.communicate.sudo("yes | pkg install ansible")
end end
end
end end
end end

View File

@ -0,0 +1,28 @@
module VagrantPlugins
module Ansible
module Cap
module Guest
module Pip
def self.pip_install(machine, package, version = "", upgrade = true)
upgrade_arg = "--upgrade " if upgrade
version_arg = ""
if !version.to_s.empty? && version.to_s.to_sym != :latest
version_arg = "==#{version}"
end
machine.communicate.sudo "pip install #{upgrade_arg}#{package}#{version_arg}"
end
def self.get_pip(machine)
machine.ui.detail I18n.t("vagrant.provisioners.ansible.installing_pip")
machine.communicate.execute "curl https://bootstrap.pypa.io/get-pip.py | sudo python"
end
end
end
end
end
end

View File

@ -1,3 +1,5 @@
require_relative "../facts"
require_relative "../pip/pip"
module VagrantPlugins module VagrantPlugins
module Ansible module Ansible
@ -6,17 +8,29 @@ module VagrantPlugins
module RedHat module RedHat
module AnsibleInstall module AnsibleInstall
def self.ansible_install(machine) def self.ansible_install(machine, install_mode, ansible_version)
epel = machine.communicate.execute("#{yum_dnf(machine)} repolist epel | grep -q epel", :error_check => false) if install_mode == :pip
pip_setup machine
Pip::pip_install machine, "ansible", ansible_version
else
ansible_rpm_install machine
end
end
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 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') 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 end
machine.communicate.sudo("#{yum_dnf(machine)} -y --enablerepo=epel install ansible") machine.communicate.sudo "#{Facts::rpm_package_manager} -y --enablerepo=epel install ansible"
end end
def self.yum_dnf(machine) def self.pip_setup(machine)
machine.communicate.test("/usr/bin/which -s dnf") ? "dnf" : "yum" 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
end end

View File

@ -6,9 +6,13 @@ module VagrantPlugins
module SUSE module SUSE
module AnsibleInstall module AnsibleInstall
def self.ansible_install(machine) 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") machine.communicate.sudo("zypper --non-interactive --quiet install ansible")
end end
end
end end
end end

View File

@ -1,3 +1,4 @@
require_relative "../debian/ansible_install"
module VagrantPlugins module VagrantPlugins
module Ansible module Ansible
@ -6,12 +7,22 @@ module VagrantPlugins
module Ubuntu module Ubuntu
module AnsibleInstall module AnsibleInstall
def self.ansible_install(machine) def self.ansible_install(machine, install_mode, ansible_version)
machine.communicate.sudo("apt-get update -y -qq") if install_mode == :pip
machine.communicate.sudo("apt-get install -y -qq software-properties-common python-software-properties") Debian::AnsibleInstall::ansible_pip_install machine, ansible_version
machine.communicate.sudo("add-apt-repository ppa:ansible/ansible -y") else
machine.communicate.sudo("apt-get update -y -qq") ansible_apt_install machine
machine.communicate.sudo("apt-get install -y -qq ansible") 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
end end

View File

@ -9,12 +9,14 @@ module VagrantPlugins
attr_accessor :provisioning_path attr_accessor :provisioning_path
attr_accessor :tmp_path attr_accessor :tmp_path
attr_accessor :install attr_accessor :install
attr_accessor :install_mode
attr_accessor :version attr_accessor :version
def initialize def initialize
super super
@install = UNSET_VALUE @install = UNSET_VALUE
@install_mode = UNSET_VALUE
@provisioning_path = UNSET_VALUE @provisioning_path = UNSET_VALUE
@tmp_path = UNSET_VALUE @tmp_path = UNSET_VALUE
@version = UNSET_VALUE @version = UNSET_VALUE
@ -24,6 +26,7 @@ module VagrantPlugins
super super
@install = true if @install == UNSET_VALUE @install = true if @install == UNSET_VALUE
@install_mode = :default if @install_mode == UNSET_VALUE
@provisioning_path = "/vagrant" if provisioning_path == UNSET_VALUE @provisioning_path = "/vagrant" if provisioning_path == UNSET_VALUE
@tmp_path = "/tmp/vagrant-ansible" if tmp_path == UNSET_VALUE @tmp_path = "/tmp/vagrant-ansible" if tmp_path == UNSET_VALUE
@version = "" if @version == UNSET_VALUE @version = "" if @version == UNSET_VALUE
@ -32,6 +35,12 @@ module VagrantPlugins
def validate(machine) def validate(machine)
super super
if @install_mode.to_s.to_sym == :pip
@install_mode = :pip
else
@install_mode = :default
end
{ "ansible local provisioner" => @errors } { "ansible local provisioner" => @errors }
end end

View File

@ -19,6 +19,10 @@ module VagrantPlugins
error_key(:ansible_not_found_on_guest) error_key(:ansible_not_found_on_guest)
end end
class AnsiblePipInstallIsNotSupported < AnsibleError
error_key(:cannot_support_pip_install)
end
class AnsibleVersionNotFoundOnGuest < AnsibleError class AnsibleVersionNotFoundOnGuest < AnsibleError
error_key(:ansible_version_not_found_on_guest) error_key(:ansible_version_not_found_on_guest)
end end

View File

@ -49,7 +49,7 @@ module VagrantPlugins
(config.version.to_s.to_sym == :latest || (config.version.to_s.to_sym == :latest ||
!@machine.guest.capability(:ansible_installed, config.version)) !@machine.guest.capability(:ansible_installed, config.version))
@machine.ui.detail I18n.t("vagrant.provisioners.ansible.installing") @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 end
# Check that ansible binaries are well installed on the guest, # Check that ansible binaries are well installed on the guest,

View File

@ -2122,6 +2122,15 @@ en:
on your host system. Vagrant can't do this for you in a safe and on your host system. Vagrant can't do this for you in a safe and
automated way. automated way.
Please check https://docs.ansible.com for more information. 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: |- ansible_version_not_found_on_guest: |-
The requested Ansible version (%{required_version}) was not found on the guest. The requested Ansible version (%{required_version}) was not found on the guest.
Please check the ansible installation on your guest system, Please check the ansible installation on your guest system,
@ -2139,6 +2148,7 @@ en:
raw_ssh_args_invalid: |- raw_ssh_args_invalid: |-
`raw_ssh_args` must be an array of strings. Received: %{value} (as %{type}) `raw_ssh_args` must be an array of strings. Received: %{value} (as %{type})
installing: "Installing Ansible..." installing: "Installing Ansible..."
installing_pip: "Installing pip... (for Ansible installation)"
running_galaxy: "Running ansible-galaxy..." running_galaxy: "Running ansible-galaxy..."
running_playbook: "Running ansible-playbook..." running_playbook: "Running ansible-playbook..."
windows_not_supported_for_control_machine: |- windows_not_supported_for_control_machine: |-

View File

@ -23,6 +23,7 @@ describe VagrantPlugins::Ansible::Config::Guest do
groups groups
host_vars host_vars
install install
install_mode
inventory_path inventory_path
limit limit
playbook playbook
@ -48,6 +49,7 @@ describe VagrantPlugins::Ansible::Config::Guest do
subject.finalize! subject.finalize!
expect(subject.install).to be_true expect(subject.install).to be_true
expect(subject.install_mode).to eql(:default)
expect(subject.provisioning_path).to eql("/vagrant") expect(subject.provisioning_path).to eql("/vagrant")
expect(subject.tmp_path).to eql("/tmp/vagrant-ansible") expect(subject.tmp_path).to eql("/tmp/vagrant-ansible")
expect(subject.version).to be_empty expect(subject.version).to be_empty
@ -60,6 +62,22 @@ describe VagrantPlugins::Ansible::Config::Guest do
end end
it_behaves_like "an Ansible provisioner", "/vagrant", "local" 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
end end

View File

@ -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. **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. - `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`. The default value is `/vagrant`.