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
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

View File

@ -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

View File

@ -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 = <<INLINE_CRIPT
CODENAME=`lsb_release -cs`
if [ x$CODENAME == 'xwheezy' ]; then
@ -20,6 +36,12 @@ INLINE_CRIPT
machine.communicate.sudo("apt-get install -y -qq ansible")
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

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 Ansible
@ -6,16 +8,20 @@ module VagrantPlugins
module Fedora
module AnsibleInstall
def self.ansible_install(machine)
if dnf?(machine)
machine.communicate.sudo("dnf -y install ansible")
def self.ansible_install(machine, install_mode, ansible_version)
if install_mode == :pip
pip_setup machine
Pip::pip_install machine, "ansible", ansible_version
else
machine.communicate.sudo("yum -y install ansible")
machine.communicate.sudo "#{Facts::rpm_package_manager} -y install ansible"
end
end
def self.dnf?(machine)
machine.communicate.test("/usr/bin/which -s dnf")
private
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

View File

@ -1,3 +1,4 @@
require_relative "../../../errors"
module VagrantPlugins
module Ansible
@ -6,8 +7,12 @@ module VagrantPlugins
module FreeBSD
module AnsibleInstall
def self.ansible_install(machine)
machine.communicate.sudo("yes | pkg install ansible")
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")
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 Ansible
@ -6,17 +8,29 @@ module VagrantPlugins
module RedHat
module AnsibleInstall
def self.ansible_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')
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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: |-

View File

@ -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

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.
- `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`.