Merge branch 'gildegoma/2718-ansible-galaxy' into master

This commit is contained in:
Gilles Cornu 2015-11-21 05:21:41 +01:00
commit 2789ce61e0
9 changed files with 258 additions and 106 deletions

View File

@ -3,7 +3,12 @@ module VagrantPlugins
module Config
class Base < Vagrant.plugin("2", :config)
GALAXY_COMMAND_DEFAULT = "ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force".freeze
attr_accessor :extra_vars
attr_accessor :galaxy_role_file
attr_accessor :galaxy_roles_path
attr_accessor :galaxy_command
attr_accessor :groups
attr_accessor :inventory_path
attr_accessor :limit
@ -19,6 +24,9 @@ module VagrantPlugins
def initialize
@extra_vars = UNSET_VALUE
@galaxy_role_file = UNSET_VALUE
@galaxy_roles_path = UNSET_VALUE
@galaxy_command = UNSET_VALUE
@groups = UNSET_VALUE
@inventory_path = UNSET_VALUE
@limit = UNSET_VALUE
@ -34,19 +42,22 @@ module VagrantPlugins
end
def finalize!
@extra_vars = nil if @extra_vars == UNSET_VALUE
@groups = {} if @groups == UNSET_VALUE
@inventory_path = nil if @inventory_path == UNSET_VALUE
@limit = nil if @limit == UNSET_VALUE
@playbook = nil if @playbook == UNSET_VALUE
@raw_arguments = nil if @raw_arguments == UNSET_VALUE
@skip_tags = nil if @skip_tags == UNSET_VALUE
@start_at_task = nil if @start_at_task == UNSET_VALUE
@sudo = false if @sudo != true
@sudo_user = nil if @sudo_user == UNSET_VALUE
@tags = nil if @tags == UNSET_VALUE
@vault_password_file = nil if @vault_password_file == UNSET_VALUE
@verbose = false if @verbose == UNSET_VALUE
@extra_vars = nil if @extra_vars == UNSET_VALUE
@galaxy_role_file = nil if @galaxy_role_file == UNSET_VALUE
@galaxy_roles_path = nil if @galaxy_roles_path == UNSET_VALUE
@galaxy_command = GALAXY_COMMAND_DEFAULT if @galaxy_command == UNSET_VALUE
@groups = {} if @groups == UNSET_VALUE
@inventory_path = nil if @inventory_path == UNSET_VALUE
@limit = nil if @limit == UNSET_VALUE
@playbook = nil if @playbook == UNSET_VALUE
@raw_arguments = nil if @raw_arguments == UNSET_VALUE
@skip_tags = nil if @skip_tags == UNSET_VALUE
@start_at_task = nil if @start_at_task == UNSET_VALUE
@sudo = false if @sudo != true
@sudo_user = nil if @sudo_user == UNSET_VALUE
@tags = nil if @tags == UNSET_VALUE
@vault_password_file = nil if @vault_password_file == UNSET_VALUE
@verbose = false if @verbose == UNSET_VALUE
end
# Just like the normal configuration "validate" method except that
@ -57,26 +68,26 @@ module VagrantPlugins
# Validate that a playbook path was provided
if !playbook
@errors << I18n.t("vagrant.provisioners.ansible.no_playbook")
@errors << I18n.t("vagrant.provisioners.ansible.errors.no_playbook")
end
# Validate the existence of the playbook
if playbook
check_path_is_a_file(machine, playbook, "vagrant.provisioners.ansible.playbook_path_invalid")
check_path_is_a_file(machine, playbook, "vagrant.provisioners.ansible.errors.playbook_path_invalid")
end
# Validate the existence of the inventory_path, if specified
if inventory_path
check_path_exists(machine, inventory_path, "vagrant.provisioners.ansible.inventory_path_invalid")
check_path_exists(machine, inventory_path, "vagrant.provisioners.ansible.errors.inventory_path_invalid")
end
if galaxy_role_file
check_path_is_a_file(machine, galaxy_role_file, "vagrant.provisioners.ansible.errors.galaxy_role_file_invalid")
end
# Validate the existence of the vault_password_file, if specified
if vault_password_file
check_path_is_a_file(machine, vault_password_file, "vagrant.provisioners.ansible.vault_password_file_invalid")
check_path_is_a_file(machine, vault_password_file, "vagrant.provisioners.ansible.errors.vault_password_file_invalid")
end
# Validate that extra_vars is either a hash, or a path to an
# existing file
# Validate that extra_vars is either a hash, or a path to an existing file
if extra_vars
extra_vars_is_valid = extra_vars.kind_of?(Hash) || extra_vars.kind_of?(String)
if extra_vars.kind_of?(String)
@ -92,7 +103,7 @@ module VagrantPlugins
if !extra_vars_is_valid
@errors << I18n.t(
"vagrant.provisioners.ansible.extra_vars_invalid",
"vagrant.provisioners.ansible.errors.extra_vars_invalid",
type: extra_vars.class.to_s,
value: extra_vars.to_s)
end

View File

@ -7,16 +7,16 @@ module VagrantPlugins
error_namespace("vagrant.provisioners.ansible.errors")
end
class AnsiblePlaybookAppFailed < AnsibleError
error_key(:ansible_playbook_app_failed)
class AnsibleCommandFailed < AnsibleError
error_key(:ansible_command_failed)
end
class AnsiblePlaybookAppNotFoundOnHost < AnsibleError
error_key(:ansible_playbook_app_not_found_on_host)
class AnsibleNotFoundOnHost < AnsibleError
error_key(:ansible_not_found_on_host)
end
class AnsiblePlaybookAppNotFoundOnGuest < AnsibleError
error_key(:ansible_playbook_app_not_found_on_guest)
class AnsibleNotFoundOnGuest < AnsibleError
error_key(:ansible_not_found_on_guest)
end
class AnsibleVersionNotFoundOnGuest < AnsibleError

View File

@ -132,6 +132,26 @@ module VagrantPlugins
end
end
def get_galaxy_role_file(basedir)
File.expand_path(config.galaxy_role_file, basedir)
end
def get_galaxy_roles_path(basedir)
if config.galaxy_roles_path
File.expand_path(config.galaxy_roles_path, basedir)
else
File.join(Pathname.new(config.playbook).expand_path(basedir).parent, 'roles')
end
end
def ui_running_ansible_command(name, command)
@machine.ui.detail I18n.t("vagrant.provisioners.ansible.running_#{name}")
if verbosity_is_enabled?
# Show the ansible command in use
@machine.env.ui.detail command
end
end
def verbosity_is_enabled?
config.verbose && !config.verbose.to_s.empty?
end

View File

@ -14,8 +14,7 @@ module VagrantPlugins
def provision
check_and_install_ansible
prepare_common_command_arguments
prepare_common_environment_variables
execute_ansible_galaxy_on_guest if config.galaxy_role_file
execute_ansible_playbook_on_guest
end
@ -47,15 +46,15 @@ module VagrantPlugins
if config.install &&
(config.version.to_s.to_sym == :latest ||
!@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)
end
# Check for the existence of ansible-playbook binary on the guest,
# Check that ansible binaries are well installed on the guest,
@machine.communicate.execute(
"ansible-playbook --help",
:error_class => Ansible::Errors::AnsibleError,
:error_key => :ansible_playbook_app_not_found_on_guest)
"ansible-galaxy --help && ansible-playbook --help",
:error_class => Ansible::Errors::AnsibleNotFoundOnGuest,
:error_key => :ansible_not_found_on_guest)
# Check if requested ansible version is available
if (!config.version.empty? &&
@ -65,18 +64,31 @@ module VagrantPlugins
end
end
def execute_ansible_galaxy_on_guest
command_values = {
:role_file => get_galaxy_role_file(config.provisioning_path),
:roles_path => get_galaxy_roles_path(config.provisioning_path)
}
remote_command = config.galaxy_command % command_values
execute_ansible_command_on_guest "galaxy", remote_command
end
def execute_ansible_playbook_on_guest
prepare_common_command_arguments
prepare_common_environment_variables
command = (%w(ansible-playbook) << @command_arguments << config.playbook).flatten
remote_command = "cd #{config.provisioning_path} && #{Helpers::stringify_ansible_playbook_command(@environment_variables, command)}"
# TODO: generic HOOK ???
# Show the ansible command in use
if verbosity_is_enabled?
@machine.env.ui.detail(remote_command)
end
execute_ansible_command_on_guest "playbook", remote_command
end
result = execute_on_guest(remote_command)
raise Ansible::Errors::AnsiblePlaybookAppFailed if result != 0
def execute_ansible_command_on_guest(name, command)
ui_running_ansible_command name, command
result = execute_on_guest(command)
raise Ansible::Errors::AnsibleCommandFailed if result != 0
end
def execute_on_guest(command)

View File

@ -19,13 +19,14 @@ module VagrantPlugins
@ssh_info = @machine.ssh_info
warn_for_unsupported_platform
prepare_command_arguments
prepare_environment_variables
execute_ansible_galaxy_from_host if config.galaxy_role_file
execute_ansible_playbook_from_host
end
protected
VAGRANT_ARG_SEPARATOR = 'VAGRANT_ARG_SEP'
def warn_for_unsupported_platform
if Vagrant::Util::Platform.windows?
@machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine"))
@ -72,33 +73,56 @@ module VagrantPlugins
@environment_variables["ANSIBLE_SSH_ARGS"] = ansible_ssh_args unless ansible_ssh_args.empty?
end
def execute_ansible_playbook_from_host
# Assemble the full ansible-playbook command
command = (%w(ansible-playbook) << @command_arguments << config.playbook).flatten
# TODO: generic HOOK ???
# Show the ansible command in use
if verbosity_is_enabled?
@machine.env.ui.detail(Helpers::stringify_ansible_playbook_command(@environment_variables, command))
def execute_command_from_host(command)
begin
result = Vagrant::Util::Subprocess.execute(*command) do |type, data|
if type == :stdout || type == :stderr
@machine.env.ui.detail(data, new_line: false, prefix: false)
end
end
raise Ansible::Errors::AnsibleCommandFailed if result.exit_code != 0
rescue Vagrant::Errors::CommandUnavailable
raise Ansible::Errors::AnsibleNotFoundOnHost
end
end
# Write stdout and stderr data, since it's the regular Ansible output
def execute_ansible_galaxy_from_host
command_values = {
:role_file => get_galaxy_role_file(machine.env.root_path),
:roles_path => get_galaxy_roles_path(machine.env.root_path)
}
command_template = config.galaxy_command.gsub(' ', VAGRANT_ARG_SEPARATOR)
str_command = command_template % command_values
ui_running_ansible_command "galaxy", str_command.gsub(VAGRANT_ARG_SEPARATOR, ' ')
command = str_command.split(VAGRANT_ARG_SEPARATOR)
command << {
env: @environment_variables,
# Write stdout and stderr data, since it's the regular Ansible output
notify: [:stdout, :stderr],
workdir: @machine.env.root_path.to_s
}
begin
result = Vagrant::Util::Subprocess.execute(*command) do |type, data|
if type == :stdout || type == :stderr
@machine.env.ui.info(data, new_line: false, prefix: false)
end
end
raise Ansible::Errors::AnsiblePlaybookAppFailed if result.exit_code != 0
rescue Vagrant::Errors::CommandUnavailable
raise Ansible::Errors::AnsiblePlaybookAppNotFoundOnHost
end
execute_command_from_host command
end
def execute_ansible_playbook_from_host
prepare_command_arguments
prepare_environment_variables
# Assemble the full ansible-playbook command
command = (%w(ansible-playbook) << @command_arguments << config.playbook).flatten
ui_running_ansible_command "playbook", Helpers::stringify_ansible_playbook_command(@environment_variables, command)
command << {
env: @environment_variables,
# Write stdout and stderr data, since it's the regular Ansible output
notify: [:stdout, :stderr],
workdir: @machine.env.root_path.to_s
}
execute_command_from_host command
end
def ship_generated_inventory(inventory_content)

View File

@ -2054,52 +2054,61 @@ en:
interactive_not_elevated: "To be interactive, it must also be privileged."
ansible:
cannot_detect: |-
Vagrant does not support detecting whether Ansible is installed
for the guest OS running in the machine. Vagrant will assume it is
installed and attempt to continue.
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
errors:
ansible_playbook_app_failed: |-
ansible_command_failed: |-
Ansible failed to complete successfully. Any error output should be
visible above. Please fix these errors and try again.
ansible_playbook_app_not_found_on_guest: |-
The "ansible-playbook" program could not be found! Please verify
ansible_not_found_on_guest: |-
The Ansible software could not be found! Please verify
that Ansible is correctly installed on your guest system.
If you haven't installed Ansible yet, please install Ansible
on your Vagrant basebox, or enable the automated setup with the
`install` option of this provisioner. Please check
http://docs.vagrantup.com/v2/provisioning/ansible_local.html
https://docs.vagrantup.com/v2/provisioning/ansible_local.html
for more information.
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,
or adapt the `version` option of this provisioner in your Vagrantfile.
See http://docs.vagrantup.com/v2/provisioning/ansible_local.html
for more information.
ansible_playbook_app_not_found_on_host: |-
The "ansible-playbook" program could not be found! Please verify
ansible_not_found_on_host: |-
The Ansible software could not be found! Please verify
that Ansible is correctly installed on your host system.
If you haven't installed Ansible yet, please install Ansible
on your host system. Vagrant can't do this for you in a safe and
automated way.
Please check http://docs.ansible.com for more information.
extra_vars_invalid: |-
`extra_vars` for the Ansible provisioner must be a hash or a path to an existing file. Received: %{value} (as %{type})
inventory_path_invalid: |-
`inventory_path` for the Ansible provisioner does not exist on the %{system}: %{path}
no_playbook: |-
`playbook` must be set for the Ansible provisioner.
playbook_path_invalid: |-
`playbook` for the Ansible provisioner does not exist on the %{system}: %{path}
vault_password_file_invalid: |-
`vault_password_file` for the Ansible provisioner does not exist on the %{system}: %{path}
cannot_detect: |-
Vagrant does not support detecting whether Ansible is installed
for the guest OS running in the machine. Vagrant will assume it is
installed and attempt to continue.
installing: |-
Installing Ansible...
Please check https://docs.ansible.com for more information.
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,
or adapt the `version` option of this provisioner in your Vagrantfile.
See https://docs.vagrantup.com/v2/provisioning/ansible_local.html
for more information.
extra_vars_invalid: |-
`extra_vars` must be a hash or a path to an existing file. Received: %{value} (as %{type})
galaxy_role_file_invalid: |-
`galaxy_role_file` does not exist on the %{system}: %{path}
inventory_path_invalid: |-
`inventory_path` does not exist on the %{system}: %{path}
no_playbook: |-
`playbook` file path must be set.
playbook_path_invalid: |-
`playbook` does not exist on the %{system}: %{path}
vault_password_file_invalid: |-
`vault_password_file` does not exist on the %{system}: %{path}
installing: "Installing Ansible..."
running_galaxy: "Running ansible-galaxy..."
running_playbook: "Running ansible-playbook..."
windows_not_supported_for_control_machine: |-
Windows is not officially supported for the Ansible Control Machine.
Please check http://docs.ansible.com/intro_installation.html#control-machine-requirements
Please check https://docs.ansible.com/intro_installation.html#control-machine-requirements
docker:
not_running: "Docker is not running on the guest VM."

View File

@ -25,6 +25,9 @@ describe VagrantPlugins::Ansible::Config::Host do
ask_vault_pass
extra_vars
force_remote_user
galaxy_command
galaxy_role_file
galaxy_roles_path
groups
host_key_checking
inventory_path
@ -99,7 +102,7 @@ describe VagrantPlugins::Ansible::Config::Host do
result = subject.validate(machine)
expect(result["ansible remote provisioner"]).to eql([
I18n.t("vagrant.provisioners.ansible.no_playbook")
I18n.t("vagrant.provisioners.ansible.errors.no_playbook")
])
end
@ -109,7 +112,7 @@ describe VagrantPlugins::Ansible::Config::Host do
result = subject.validate(machine)
expect(result["ansible remote provisioner"]).to eql([
I18n.t("vagrant.provisioners.ansible.playbook_path_invalid",
I18n.t("vagrant.provisioners.ansible.errors.playbook_path_invalid",
path: non_existing_file, system: "host")
])
end
@ -136,7 +139,7 @@ describe VagrantPlugins::Ansible::Config::Host do
result = subject.validate(machine)
expect(result["ansible remote provisioner"]).to eql([
I18n.t("vagrant.provisioners.ansible.extra_vars_invalid",
I18n.t("vagrant.provisioners.ansible.errors.extra_vars_invalid",
type: subject.extra_vars.class.to_s,
value: subject.extra_vars.to_s)
])
@ -148,7 +151,7 @@ describe VagrantPlugins::Ansible::Config::Host do
result = subject.validate(machine)
expect(result["ansible remote provisioner"]).to eql([
I18n.t("vagrant.provisioners.ansible.extra_vars_invalid",
I18n.t("vagrant.provisioners.ansible.errors.extra_vars_invalid",
type: subject.extra_vars.class.to_s,
value: subject.extra_vars.to_s)
])
@ -168,7 +171,7 @@ describe VagrantPlugins::Ansible::Config::Host do
result = subject.validate(machine)
expect(result["ansible remote provisioner"]).to eql([
I18n.t("vagrant.provisioners.ansible.inventory_path_invalid",
I18n.t("vagrant.provisioners.ansible.errors.inventory_path_invalid",
path: non_existing_file, system: "host")
])
end
@ -179,7 +182,18 @@ describe VagrantPlugins::Ansible::Config::Host do
result = subject.validate(machine)
expect(result["ansible remote provisioner"]).to eql([
I18n.t("vagrant.provisioners.ansible.vault_password_file_invalid",
I18n.t("vagrant.provisioners.ansible.errors.vault_password_file_invalid",
path: non_existing_file, system: "host")
])
end
it "returns an error if galaxy_role_file is specified, but does not exist" do
subject.galaxy_role_file = non_existing_file
subject.finalize!
result = subject.validate(machine)
expect(result["ansible remote provisioner"]).to eql([
I18n.t("vagrant.provisioners.ansible.errors.galaxy_role_file_invalid",
path: non_existing_file, system: "host")
])
end
@ -192,14 +206,14 @@ describe VagrantPlugins::Ansible::Config::Host do
result = subject.validate(machine)
expect(result["ansible remote provisioner"]).to include(
I18n.t("vagrant.provisioners.ansible.playbook_path_invalid",
I18n.t("vagrant.provisioners.ansible.errors.playbook_path_invalid",
path: non_existing_file, system: "host"))
expect(result["ansible remote provisioner"]).to include(
I18n.t("vagrant.provisioners.ansible.extra_vars_invalid",
I18n.t("vagrant.provisioners.ansible.errors.extra_vars_invalid",
type: subject.extra_vars.class.to_s,
value: subject.extra_vars.to_s))
expect(result["ansible remote provisioner"]).to include(
I18n.t("vagrant.provisioners.ansible.inventory_path_invalid",
I18n.t("vagrant.provisioners.ansible.errors.inventory_path_invalid",
path: non_existing_file, system: "host"))
end

View File

@ -208,7 +208,7 @@ VF
config.finalize!
Vagrant::Util::Subprocess.stub(execute: Vagrant::Util::Subprocess::Result.new(1, "", ""))
expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsiblePlaybookAppFailed)
expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCommandFailed)
end
end
@ -583,6 +583,33 @@ VF
end
end
describe "with galaxy support" do
before do
config.galaxy_role_file = existing_file
end
it "raises an error when ansible-galaxy command fails", skip_before: true, skip_after: true do
config.finalize!
Vagrant::Util::Subprocess.stub(execute: Vagrant::Util::Subprocess::Result.new(1, "", ""))
expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCommandFailed)
end
it "execute ansible-galaxy and ansible-playbook" do
# TODO: to be improved, but I'm currenty facing some issues, maybe only present in RSpec 2.14...
expect(Vagrant::Util::Subprocess).to receive(:execute).twice
end
describe "with verbose option enabled" do
before do
config.verbose = true
end
xit "shows the ansible-galaxy command in use"
end
end
# The Vagrant Ansible provisioner does not validate the coherency of
# argument combinations, and let ansible-playbook complain.
describe "with a maximum of options" do
@ -678,7 +705,20 @@ VF
# Special cases related to the Vagrant Host operating system in use
#
context "with a Solaris-like host" do
context "on a Windows host" do
before do
Vagrant::Util::Platform.stub(windows?: true)
machine.ui.stub(:warn)
end
it "warns that Windows is not officially supported for the Ansible control machine" do
expect(machine.env.ui).to receive(:warn).with { |warning|
expect(warning).to eq(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine"))
}
end
end
context "on a Solaris-like host" do
before do
Vagrant::Util::Platform.stub(solaris?: true)
end

View File

@ -51,6 +51,28 @@ Some of these options are for advanced usage only and should not be used unless
By default, this option is disabled and Vagrant generates an inventory based on the `Vagrantfile` information.
- `galaxy_command` (template string) - The command pattern used to install Galaxy roles when `galaxy_role_file` is set.
The following (optional) placeholders can be used in this command pattern:
- `%{role_file}` is replaced by the absolute path to the `galaxy_role_file` option
- `%{roles_path}` is
- replaced by the absolute path to the `galaxy_roles_path` option when such option is defined, or
- replaced by the absolute path to a `roles` subdirectory sitting in the `playbook` parent directory.
By default, this option is set to
`ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force`
- `galaxy_role_file` (string) - The path to the Ansible Galaxy role file.
By default, this option is set to `nil` and Galaxy support is then disabled.
Note: if an absolute path is given, the `ansible_local` provisioner will assume that it corresponds to the exact location on the guest system.
- `galaxy_roles_path` (string) - The path to the directory where Ansible Galaxy roles must be installed
By default, this option is set to `nil`, which means that the Galaxy roles will be installed in a `roles` subdirectory located in the parent directory of the `playbook` file.
- `limit` (string or array of strings) - Set of machines or groups from the inventory file to further control which hosts [are affected](http://docs.ansible.com/glossary.html#limit-groups).
The default value is set to the machine name (taken from `Vagrantfile`) to ensure that `vagrant provision` command only affect the expected machine.