2016-05-31 03:17:02 +00:00
|
|
|
require "tempfile"
|
|
|
|
|
2015-02-10 14:28:00 +00:00
|
|
|
require_relative "base"
|
|
|
|
|
|
|
|
module VagrantPlugins
|
|
|
|
module Ansible
|
|
|
|
module Provisioner
|
|
|
|
class Guest < Base
|
2016-05-29 03:17:40 +00:00
|
|
|
include Vagrant::Util
|
2015-02-10 14:28:00 +00:00
|
|
|
|
|
|
|
def initialize(machine, config)
|
|
|
|
super
|
|
|
|
@logger = Log4r::Logger.new("vagrant::provisioners::ansible_guest")
|
|
|
|
end
|
|
|
|
|
|
|
|
def provision
|
provisioners/ansible(both): fix ansible config files presence checks
With this change, the presence of Ansible configuration files (like
playbook file, inventory path, galaxy role file, etc.) is no longer
performed by the `config` classes, but by the `provisioner` classes
(at the beginning of the provision command).
This change fixes several issues:
- Resolve #6984 as `provision` method are only executed when remote
(ssh) communication with the guest machine is possible.
- Resolve #6763 in a better way than 4e451c6 initially did.
- Improve the general provisioner speed since the `config` checks are
actually triggered by many vagrant actions (e.g. `destroy`,...), and
can also be triggered multiple times during a vagrant run (e.g. on
callback request made by the machine provider).
Unlike the former `config`-based checks, the provision action won't
collect all the invalid options, but only report the first invalid
option found and abort the execution.
Some unit tests were not implemented yet to save my scarce "open source
contribution time" for other important issues, but they should be done
at last via GH-6633.
2016-05-31 22:30:07 +00:00
|
|
|
check_files_existence
|
2015-02-10 14:28:00 +00:00
|
|
|
check_and_install_ansible
|
2015-11-17 21:06:06 +00:00
|
|
|
execute_ansible_galaxy_on_guest if config.galaxy_role_file
|
2015-02-10 14:28:00 +00:00
|
|
|
execute_ansible_playbook_on_guest
|
|
|
|
end
|
|
|
|
|
|
|
|
protected
|
|
|
|
|
|
|
|
#
|
|
|
|
# This handles verifying the Ansible installation, installing it if it was
|
|
|
|
# requested, and so on. This method will raise exceptions if things are wrong.
|
|
|
|
#
|
|
|
|
# Current limitations:
|
|
|
|
# - The installation of a specific Ansible version is not supported.
|
|
|
|
# Such feature is difficult to systematically provide via package repositories (apt, yum, ...).
|
|
|
|
# Installing via pip python packaging or directly from github source would be appropriate,
|
|
|
|
# but these approaches require more dependency burden.
|
|
|
|
# - There is no guarantee that the automated installation will replace
|
|
|
|
# a previous Ansible installation.
|
|
|
|
#
|
|
|
|
def check_and_install_ansible
|
|
|
|
@logger.info("Checking for Ansible installation...")
|
|
|
|
|
|
|
|
# If the guest cannot check if Ansible is installed,
|
|
|
|
# print a warning and try to continue without any installation attempt...
|
|
|
|
if !@machine.guest.capability?(:ansible_installed)
|
|
|
|
@machine.ui.warn(I18n.t("vagrant.provisioners.ansible.cannot_detect"))
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
# Try to install Ansible (if needed and requested)
|
|
|
|
if config.install &&
|
2015-11-12 08:09:58 +00:00
|
|
|
(config.version.to_s.to_sym == :latest ||
|
2015-02-10 14:28:00 +00:00
|
|
|
!@machine.guest.capability(:ansible_installed, config.version))
|
2015-11-18 08:37:27 +00:00
|
|
|
@machine.ui.detail I18n.t("vagrant.provisioners.ansible.installing")
|
2016-06-08 20:59:47 +00:00
|
|
|
@machine.guest.capability(:ansible_install, config.install_mode, config.version)
|
2015-02-10 14:28:00 +00:00
|
|
|
end
|
|
|
|
|
2015-11-17 21:06:06 +00:00
|
|
|
# Check that ansible binaries are well installed on the guest,
|
2015-02-10 14:28:00 +00:00
|
|
|
@machine.communicate.execute(
|
2016-01-13 22:05:39 +00:00
|
|
|
"ansible-galaxy info --help && ansible-playbook --help",
|
2016-06-08 21:33:17 +00:00
|
|
|
error_class: Ansible::Errors::AnsibleNotFoundOnGuest,
|
|
|
|
error_key: :ansible_not_found_on_guest
|
|
|
|
)
|
2015-02-10 14:28:00 +00:00
|
|
|
|
|
|
|
# Check if requested ansible version is available
|
|
|
|
if (!config.version.empty? &&
|
2015-11-12 08:09:58 +00:00
|
|
|
config.version.to_s.to_sym != :latest &&
|
2015-02-10 14:28:00 +00:00
|
|
|
!@machine.guest.capability(:ansible_installed, config.version))
|
|
|
|
raise Ansible::Errors::AnsibleVersionNotFoundOnGuest, required_version: config.version.to_s
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-06-11 05:28:05 +00:00
|
|
|
def get_provisioning_working_directory
|
|
|
|
config.provisioning_path
|
|
|
|
end
|
|
|
|
|
2015-11-17 21:06:06 +00:00
|
|
|
def execute_ansible_galaxy_on_guest
|
|
|
|
command_values = {
|
2016-06-11 05:28:05 +00:00
|
|
|
role_file: "'#{get_galaxy_role_file}'",
|
|
|
|
roles_path: "'#{get_galaxy_roles_path}'"
|
2015-11-17 21:06:06 +00:00
|
|
|
}
|
2016-04-23 22:26:59 +00:00
|
|
|
|
2015-11-17 21:06:06 +00:00
|
|
|
remote_command = config.galaxy_command % command_values
|
|
|
|
|
2015-11-19 23:11:44 +00:00
|
|
|
execute_ansible_command_on_guest "galaxy", remote_command
|
2015-11-17 21:06:06 +00:00
|
|
|
end
|
|
|
|
|
2015-02-10 14:28:00 +00:00
|
|
|
def execute_ansible_playbook_on_guest
|
2015-11-17 21:06:06 +00:00
|
|
|
prepare_common_command_arguments
|
|
|
|
prepare_common_environment_variables
|
|
|
|
|
2016-04-23 22:26:59 +00:00
|
|
|
execute_ansible_command_on_guest "playbook", ansible_playbook_command_for_shell_execution
|
2015-11-19 23:11:44 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def execute_ansible_command_on_guest(name, command)
|
2016-04-23 22:26:59 +00:00
|
|
|
remote_command = "cd #{config.provisioning_path} && #{command}"
|
|
|
|
|
|
|
|
ui_running_ansible_command name, remote_command
|
2015-02-10 14:28:00 +00:00
|
|
|
|
2016-04-23 22:26:59 +00:00
|
|
|
result = execute_on_guest(remote_command)
|
2015-11-19 23:07:34 +00:00
|
|
|
raise Ansible::Errors::AnsibleCommandFailed if result != 0
|
2015-02-10 14:28:00 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def execute_on_guest(command)
|
2016-06-08 21:33:17 +00:00
|
|
|
@machine.communicate.execute(command, error_check: false) do |type, data|
|
2015-02-10 14:28:00 +00:00
|
|
|
if [:stderr, :stdout].include?(type)
|
2016-06-08 21:33:17 +00:00
|
|
|
@machine.env.ui.info(data, new_line: false, prefix: false)
|
2015-02-10 14:28:00 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def ship_generated_inventory(inventory_content)
|
|
|
|
inventory_basedir = File.join(config.tmp_path, "inventory")
|
|
|
|
inventory_path = File.join(inventory_basedir, "vagrant_ansible_local_inventory")
|
|
|
|
|
|
|
|
create_and_chown_remote_folder(inventory_basedir)
|
2016-05-29 03:17:40 +00:00
|
|
|
@machine.communicate.sudo("rm -f #{inventory_path}", error_check: false)
|
|
|
|
|
2016-05-31 04:18:16 +00:00
|
|
|
Tempfile.open("vagrant-ansible-local-inventory-#{@machine.name}") do |f|
|
2016-05-31 03:17:02 +00:00
|
|
|
f.binmode
|
2016-05-29 03:17:40 +00:00
|
|
|
f.write(inventory_content)
|
|
|
|
f.fsync
|
|
|
|
f.close
|
|
|
|
@machine.communicate.upload(f.path, inventory_path)
|
2015-02-10 14:28:00 +00:00
|
|
|
end
|
|
|
|
|
2015-12-08 21:59:02 +00:00
|
|
|
return inventory_basedir
|
2015-02-10 14:28:00 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def generate_inventory_machines
|
|
|
|
machines = ""
|
|
|
|
|
|
|
|
# TODO: Instead, why not loop over active_machines and skip missing guests, like in Host?
|
|
|
|
machine.env.machine_names.each do |machine_name|
|
|
|
|
begin
|
|
|
|
@inventory_machines[machine_name] = machine_name
|
|
|
|
if @machine.name == machine_name
|
|
|
|
machines += "#{machine_name} ansible_connection=local\n"
|
|
|
|
else
|
|
|
|
machines += "#{machine_name}\n"
|
|
|
|
end
|
2015-12-01 17:15:40 +00:00
|
|
|
host_vars = get_inventory_host_vars_string(machine_name)
|
|
|
|
machines.sub!(/\n$/, " #{host_vars}\n") if host_vars
|
2015-02-10 14:28:00 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return machines
|
|
|
|
end
|
|
|
|
|
|
|
|
def create_and_chown_remote_folder(path)
|
|
|
|
@machine.communicate.tap do |comm|
|
|
|
|
comm.sudo("mkdir -p #{path}")
|
|
|
|
comm.sudo("chown -h #{@machine.ssh_info[:username]} #{path}")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
provisioners/ansible(both): fix ansible config files presence checks
With this change, the presence of Ansible configuration files (like
playbook file, inventory path, galaxy role file, etc.) is no longer
performed by the `config` classes, but by the `provisioner` classes
(at the beginning of the provision command).
This change fixes several issues:
- Resolve #6984 as `provision` method are only executed when remote
(ssh) communication with the guest machine is possible.
- Resolve #6763 in a better way than 4e451c6 initially did.
- Improve the general provisioner speed since the `config` checks are
actually triggered by many vagrant actions (e.g. `destroy`,...), and
can also be triggered multiple times during a vagrant run (e.g. on
callback request made by the machine provider).
Unlike the former `config`-based checks, the provision action won't
collect all the invalid options, but only report the first invalid
option found and abort the execution.
Some unit tests were not implemented yet to save my scarce "open source
contribution time" for other important issues, but they should be done
at last via GH-6633.
2016-05-31 22:30:07 +00:00
|
|
|
def check_path(path, test_args, option_name)
|
|
|
|
# Checks for the existence of given file (or directory) on the guest system,
|
|
|
|
# and error if it doesn't exist.
|
|
|
|
|
|
|
|
remote_path = Helpers::expand_path_in_unix_style(path, config.provisioning_path)
|
|
|
|
command = "test #{test_args} #{remote_path}"
|
|
|
|
|
|
|
|
@machine.communicate.execute(
|
|
|
|
command,
|
|
|
|
error_class: Ansible::Errors::AnsibleError,
|
|
|
|
error_key: :config_file_not_found,
|
|
|
|
config_option: option_name,
|
|
|
|
path: remote_path,
|
|
|
|
system: "guest"
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_path_is_a_file(path, error_message_key)
|
|
|
|
check_path(path, "-f", error_message_key)
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_path_exists(path, error_message_key)
|
|
|
|
check_path(path, "-e", error_message_key)
|
|
|
|
end
|
|
|
|
|
2015-02-10 14:28:00 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|