2015-02-10 14:28:00 +00:00
|
|
|
require_relative "../errors"
|
|
|
|
require_relative "../helpers"
|
|
|
|
|
|
|
|
module VagrantPlugins
|
|
|
|
module Ansible
|
|
|
|
module Provisioner
|
|
|
|
|
|
|
|
# This class is a base class where the common functionality shared between
|
|
|
|
# both Ansible provisioners are stored.
|
|
|
|
# This is **not an actual provisioner**.
|
|
|
|
# Instead, {Host} (ansible) or {Guest} (ansible_local) should be used.
|
|
|
|
|
|
|
|
class Base < Vagrant.plugin("2", :provisioner)
|
|
|
|
|
2015-12-04 09:05:07 +00:00
|
|
|
RANGE_PATTERN = %r{(?:\[[a-z]:[a-z]\]|\[[0-9]+?:[0-9]+?\])}.freeze
|
|
|
|
|
2015-02-10 14:28:00 +00:00
|
|
|
protected
|
|
|
|
|
|
|
|
def initialize(machine, config)
|
|
|
|
super
|
|
|
|
|
|
|
|
@command_arguments = []
|
|
|
|
@environment_variables = {}
|
|
|
|
@inventory_machines = {}
|
|
|
|
@inventory_path = nil
|
|
|
|
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_files_existence
|
|
|
|
check_path_is_a_file config.playbook, :playbook
|
|
|
|
|
|
|
|
check_path_exists config.inventory_path, :inventory_path if config.inventory_path
|
|
|
|
check_path_is_a_file config.extra_vars[1..-1], :extra_vars if has_an_extra_vars_file_argument
|
|
|
|
check_path_is_a_file config.galaxy_role_file, :galaxy_role_file if config.galaxy_role_file
|
|
|
|
check_path_is_a_file config.vault_password_file, :vault_password if config.vault_password_file
|
|
|
|
end
|
|
|
|
|
2016-04-20 16:05:28 +00:00
|
|
|
def ansible_playbook_command_for_shell_execution
|
|
|
|
shell_command = []
|
|
|
|
@environment_variables.each_pair do |k, v|
|
|
|
|
if k == 'ANSIBLE_SSH_ARGS'
|
|
|
|
shell_command << "#{k}='#{v}'"
|
|
|
|
else
|
|
|
|
shell_command << "#{k}=#{v}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
shell_command << "ansible-playbook"
|
|
|
|
|
|
|
|
shell_args = []
|
|
|
|
@command_arguments.each do |arg|
|
|
|
|
if arg =~ /(--start-at-task|--limit)=(.+)/
|
|
|
|
shell_args << %Q(#{$1}="#{$2}")
|
|
|
|
elsif arg =~ /(--extra-vars)=(.+)/
|
|
|
|
shell_args << %Q(%s="%s") % [$1, $2.gsub('\\', '\\\\\\').gsub('"', %Q(\\"))]
|
|
|
|
else
|
|
|
|
shell_args << arg
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
shell_command << shell_args
|
|
|
|
|
|
|
|
# Add the raw arguments at the end, to give them the highest precedence
|
|
|
|
shell_command << config.raw_arguments if config.raw_arguments
|
|
|
|
|
|
|
|
shell_command << config.playbook
|
|
|
|
|
|
|
|
shell_command.flatten.join(' ')
|
|
|
|
end
|
|
|
|
|
2015-02-10 14:28:00 +00:00
|
|
|
def prepare_common_command_arguments
|
|
|
|
# By default we limit by the current machine,
|
|
|
|
# but this can be overridden by the `limit` option.
|
|
|
|
if config.limit
|
|
|
|
@command_arguments << "--limit=#{Helpers::as_list_argument(config.limit)}"
|
|
|
|
else
|
|
|
|
@command_arguments << "--limit=#{@machine.name}"
|
|
|
|
end
|
|
|
|
|
|
|
|
@command_arguments << "--inventory-file=#{inventory_path}"
|
|
|
|
@command_arguments << "--extra-vars=#{extra_vars_argument}" if config.extra_vars
|
|
|
|
@command_arguments << "--sudo" if config.sudo
|
|
|
|
@command_arguments << "--sudo-user=#{config.sudo_user}" if config.sudo_user
|
|
|
|
@command_arguments << "#{verbosity_argument}" if verbosity_is_enabled?
|
|
|
|
@command_arguments << "--vault-password-file=#{config.vault_password_file}" if config.vault_password_file
|
|
|
|
@command_arguments << "--tags=#{Helpers::as_list_argument(config.tags)}" if config.tags
|
|
|
|
@command_arguments << "--skip-tags=#{Helpers::as_list_argument(config.skip_tags)}" if config.skip_tags
|
|
|
|
@command_arguments << "--start-at-task=#{config.start_at_task}" if config.start_at_task
|
|
|
|
end
|
|
|
|
|
|
|
|
def prepare_common_environment_variables
|
|
|
|
# Ensure Ansible output isn't buffered so that we receive output
|
|
|
|
# on a task-by-task basis.
|
|
|
|
@environment_variables["PYTHONUNBUFFERED"] = 1
|
|
|
|
|
|
|
|
# When Ansible output is piped in Vagrant integration, its default colorization is
|
|
|
|
# automatically disabled and the only way to re-enable colors is to use ANSIBLE_FORCE_COLOR.
|
|
|
|
@environment_variables["ANSIBLE_FORCE_COLOR"] = "true" if @machine.env.ui.color?
|
|
|
|
# Setting ANSIBLE_NOCOLOR is "unnecessary" at the moment, but this could change in the future
|
|
|
|
# (e.g. local provisioner [GH-2103], possible change in vagrant/ansible integration, etc.)
|
|
|
|
@environment_variables["ANSIBLE_NOCOLOR"] = "true" if !@machine.env.ui.color?
|
|
|
|
end
|
|
|
|
|
|
|
|
# Auto-generate "safe" inventory file based on Vagrantfile,
|
|
|
|
# unless inventory_path is explicitly provided
|
|
|
|
def inventory_path
|
|
|
|
if config.inventory_path
|
|
|
|
config.inventory_path
|
|
|
|
else
|
|
|
|
@inventory_path ||= generate_inventory
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-12-01 17:15:40 +00:00
|
|
|
def get_inventory_host_vars_string(machine_name)
|
2015-12-02 07:37:41 +00:00
|
|
|
# In Ruby, Symbol and String values are different, but
|
|
|
|
# Vagrant has to unify them for better user experience.
|
|
|
|
vars = config.host_vars[machine_name.to_sym]
|
|
|
|
if !vars
|
|
|
|
vars = config.host_vars[machine_name.to_s]
|
|
|
|
end
|
2015-12-01 17:15:40 +00:00
|
|
|
s = nil
|
|
|
|
if vars.is_a?(Hash)
|
|
|
|
s = vars.each.collect{ |k, v| "#{k}=#{v}" }.join(" ")
|
|
|
|
elsif vars.is_a?(Array)
|
|
|
|
s = vars.join(" ")
|
|
|
|
elsif vars.is_a?(String)
|
|
|
|
s = vars
|
|
|
|
end
|
|
|
|
if s and !s.empty? then s else nil end
|
|
|
|
end
|
|
|
|
|
2015-02-10 14:28:00 +00:00
|
|
|
def generate_inventory
|
|
|
|
inventory = "# Generated by Vagrant\n\n"
|
|
|
|
|
2015-12-03 08:36:57 +00:00
|
|
|
# This "abstract" step must fill the @inventory_machines list
|
|
|
|
# and return the list of supported host(s)
|
2015-02-10 14:28:00 +00:00
|
|
|
inventory += generate_inventory_machines
|
|
|
|
|
|
|
|
inventory += generate_inventory_groups
|
|
|
|
|
|
|
|
# This "abstract" step must create the inventory file and
|
|
|
|
# return its location path
|
|
|
|
# TODO: explain possible race conditions, etc.
|
|
|
|
@inventory_path = ship_generated_inventory(inventory)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Write out groups information.
|
|
|
|
# All defined groups will be included, but only supported
|
|
|
|
# machines and defined child groups will be included.
|
|
|
|
def generate_inventory_groups
|
|
|
|
groups_of_groups = {}
|
|
|
|
defined_groups = []
|
2015-12-01 13:28:24 +00:00
|
|
|
group_vars = {}
|
2015-02-10 14:28:00 +00:00
|
|
|
inventory_groups = ""
|
|
|
|
|
2015-12-03 19:22:05 +00:00
|
|
|
# Verify if host range patterns exist and warn
|
2015-12-04 09:05:07 +00:00
|
|
|
if config.groups.any? { |gm| gm.to_s[RANGE_PATTERN] }
|
2015-12-03 19:22:05 +00:00
|
|
|
@machine.ui.warn(I18n.t("vagrant.provisioners.ansible.ansible_host_pattern_detected"))
|
|
|
|
end
|
|
|
|
|
2015-02-10 14:28:00 +00:00
|
|
|
config.groups.each_pair do |gname, gmembers|
|
2015-12-03 08:34:53 +00:00
|
|
|
if gname.is_a?(Symbol)
|
|
|
|
gname = gname.to_s
|
|
|
|
end
|
|
|
|
|
2015-12-01 13:28:24 +00:00
|
|
|
if gmembers.is_a?(String)
|
|
|
|
gmembers = gmembers.split(/\s+/)
|
|
|
|
elsif gmembers.is_a?(Hash)
|
|
|
|
gmembers = gmembers.each.collect{ |k, v| "#{k}=#{v}" }
|
|
|
|
elsif !gmembers.is_a?(Array)
|
|
|
|
gmembers = []
|
|
|
|
end
|
2015-02-10 14:28:00 +00:00
|
|
|
|
|
|
|
if gname.end_with?(":children")
|
|
|
|
groups_of_groups[gname] = gmembers
|
|
|
|
defined_groups << gname.sub(/:children$/, '')
|
2015-12-01 13:28:24 +00:00
|
|
|
elsif gname.end_with?(":vars")
|
|
|
|
group_vars[gname] = gmembers
|
|
|
|
else
|
2015-02-10 14:28:00 +00:00
|
|
|
defined_groups << gname
|
|
|
|
inventory_groups += "\n[#{gname}]\n"
|
|
|
|
gmembers.each do |gm|
|
2015-12-04 09:05:07 +00:00
|
|
|
# TODO : Expand and validate host range patterns
|
|
|
|
# against @inventory_machines list before adding them
|
|
|
|
# otherwise abort with an error message
|
|
|
|
if gm[RANGE_PATTERN]
|
2015-12-03 19:22:05 +00:00
|
|
|
inventory_groups += "#{gm}\n"
|
|
|
|
end
|
2015-02-10 14:28:00 +00:00
|
|
|
inventory_groups += "#{gm}\n" if @inventory_machines.include?(gm.to_sym)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defined_groups.uniq!
|
|
|
|
groups_of_groups.each_pair do |gname, gmembers|
|
|
|
|
inventory_groups += "\n[#{gname}]\n"
|
|
|
|
gmembers.each do |gm|
|
|
|
|
inventory_groups += "#{gm}\n" if defined_groups.include?(gm)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-12-01 13:28:24 +00:00
|
|
|
group_vars.each_pair do |gname, gmembers|
|
|
|
|
if defined_groups.include?(gname.sub(/:vars$/, ""))
|
|
|
|
inventory_groups += "\n[#{gname}]\n" + gmembers.join("\n") + "\n"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-02-10 14:28:00 +00:00
|
|
|
return inventory_groups
|
|
|
|
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 has_an_extra_vars_file_argument
|
|
|
|
config.extra_vars && config.extra_vars.kind_of?(String) && config.extra_vars =~ /^@.+$/
|
|
|
|
end
|
|
|
|
|
2015-02-10 14:28:00 +00:00
|
|
|
def extra_vars_argument
|
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
|
|
|
if has_an_extra_vars_file_argument
|
2015-02-10 14:28:00 +00:00
|
|
|
# A JSON or YAML file is referenced.
|
|
|
|
config.extra_vars
|
|
|
|
else
|
|
|
|
# Expected to be a Hash after config validation.
|
|
|
|
config.extra_vars.to_json
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-01-05 07:22:10 +00:00
|
|
|
def get_galaxy_role_file(base_dir)
|
|
|
|
Helpers::expand_path_in_unix_style(config.galaxy_role_file, base_dir)
|
2015-11-17 21:06:06 +00:00
|
|
|
end
|
|
|
|
|
2016-01-05 07:22:10 +00:00
|
|
|
def get_galaxy_roles_path(base_dir)
|
2015-11-17 21:06:06 +00:00
|
|
|
if config.galaxy_roles_path
|
2016-01-05 07:22:10 +00:00
|
|
|
Helpers::expand_path_in_unix_style(config.galaxy_roles_path, base_dir)
|
2015-11-17 21:06:06 +00:00
|
|
|
else
|
2016-01-05 07:22:10 +00:00
|
|
|
playbook_path = Helpers::expand_path_in_unix_style(config.playbook, base_dir)
|
|
|
|
File.join(Pathname.new(playbook_path).parent, 'roles')
|
2015-11-17 21:06:06 +00:00
|
|
|
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
|
2015-11-18 08:37:27 +00:00
|
|
|
@machine.env.ui.detail command
|
2015-11-17 21:06:06 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-02-10 14:28:00 +00:00
|
|
|
def verbosity_is_enabled?
|
|
|
|
config.verbose && !config.verbose.to_s.empty?
|
|
|
|
end
|
|
|
|
|
|
|
|
def verbosity_argument
|
|
|
|
if config.verbose.to_s =~ /^-?(v+)$/
|
|
|
|
"-#{$+}"
|
|
|
|
else
|
|
|
|
# safe default, in case input strays
|
|
|
|
'-v'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|