2013-03-20 14:41:21 +00:00
module VagrantPlugins
module Ansible
class Provisioner < Vagrant . plugin ( " 2 " , :provisioner )
2014-02-28 07:50:17 +00:00
def initialize ( machine , config )
super
2014-02-02 21:32:22 +00:00
@logger = Log4r :: Logger . new ( " vagrant::provisioners::ansible " )
2014-02-28 07:50:17 +00:00
end
def provision
2014-03-11 16:03:23 +00:00
@ssh_info = @machine . ssh_info
2013-04-04 20:48:58 +00:00
2014-02-10 15:16:36 +00:00
#
# 1) Default Settings (lowest precedence)
#
# Connect with Vagrant SSH identity
2014-02-28 07:50:17 +00:00
options = %W[ --private-key= #{ @ssh_info [ :private_key_path ] [ 0 ] } --user= #{ @ssh_info [ :username ] } ]
2014-02-10 15:16:36 +00:00
# Multiple SSH keys and/or SSH forwarding can be passed via
# ANSIBLE_SSH_ARGS environment variable, which requires 'ssh' mode.
# Note that multiple keys and ssh-forwarding settings are not supported
# by deprecated 'paramiko' mode.
options << " --connection=ssh " unless ansible_ssh_args . empty?
2013-05-06 20:28:20 +00:00
2014-02-16 10:39:15 +00:00
# By default we limit by the current machine.
# This can be overridden by the limit config option.
options << " --limit= #{ @machine . name } "
2014-02-10 15:16:36 +00:00
#
# 2) Configuration Joker
#
2013-12-16 20:20:10 +00:00
options . concat ( self . as_array ( config . raw_arguments ) ) if config . raw_arguments
2013-05-06 20:28:20 +00:00
2014-02-10 15:16:36 +00:00
#
# 3) Append Provisioner options (highest precedence):
#
2013-09-07 12:32:36 +00:00
options << " --inventory-file= #{ self . setup_inventory_file } "
2013-10-11 22:29:39 +00:00
options << " --extra-vars= #{ self . get_extra_vars_argument } " if config . extra_vars
2013-03-20 14:41:21 +00:00
options << " --sudo " if config . sudo
options << " --sudo-user= #{ config . sudo_user } " if config . sudo_user
2013-10-04 06:58:49 +00:00
options << " #{ self . get_verbosity_argument } " if config . verbose
2013-09-07 12:32:36 +00:00
options << " --ask-sudo-pass " if config . ask_sudo_pass
2013-09-02 22:30:49 +00:00
options << " --tags= #{ as_list_argument ( config . tags ) } " if config . tags
options << " --skip-tags= #{ as_list_argument ( config . skip_tags ) } " if config . skip_tags
2014-02-16 10:39:15 +00:00
options << " --limit= #{ as_list_argument ( config . limit ) } " if config . limit
2013-09-02 22:30:49 +00:00
options << " --start-at-task= #{ config . start_at_task } " if config . start_at_task
2013-04-04 07:07:59 +00:00
# Assemble the full ansible-playbook command
2013-03-20 14:41:21 +00:00
command = ( %w( ansible-playbook ) << options << config . playbook ) . flatten
2013-04-04 20:48:58 +00:00
2014-02-10 09:23:10 +00:00
# Some Ansible options must be passed as environment variables
env = {
" ANSIBLE_FORCE_COLOR " = > " true " ,
" ANSIBLE_HOST_KEY_CHECKING " = > " #{ config . host_key_checking } " ,
# Ensure Ansible output isn't buffered so that we receive ouput
# on a task-by-task basis.
" PYTHONUNBUFFERED " = > 1
}
2014-02-10 09:32:47 +00:00
# Support Multiple SSH keys and SSH forwarding:
2014-02-10 15:16:36 +00:00
env [ " ANSIBLE_SSH_ARGS " ] = ansible_ssh_args unless ansible_ssh_args . empty?
2014-02-10 09:23:10 +00:00
2013-04-04 07:07:59 +00:00
# Write stdout and stderr data, since it's the regular Ansible output
2013-04-04 18:31:27 +00:00
command << {
2014-02-10 09:23:10 +00:00
:env = > env ,
2013-08-29 18:55:58 +00:00
:notify = > [ :stdout , :stderr ] ,
:workdir = > @machine . env . root_path . to_s
2013-04-04 18:31:27 +00:00
}
2013-04-04 20:48:58 +00:00
2013-04-04 20:58:33 +00:00
begin
2013-07-30 12:25:15 +00:00
result = Vagrant :: Util :: Subprocess . execute ( * command ) do | type , data |
2013-04-04 20:58:33 +00:00
if type == :stdout || type == :stderr
2013-07-20 03:38:25 +00:00
@machine . env . ui . info ( data , :new_line = > false , :prefix = > false )
2013-04-04 20:58:33 +00:00
end
2013-04-04 20:48:58 +00:00
end
2013-07-20 04:07:09 +00:00
2013-07-30 12:25:15 +00:00
raise Vagrant :: Errors :: AnsibleFailed if result . exit_code != 0
2013-04-04 20:58:33 +00:00
rescue Vagrant :: Util :: Subprocess :: LaunchError
raise Vagrant :: Errors :: AnsiblePlaybookAppNotFound
2013-04-04 07:07:59 +00:00
end
2013-03-20 14:41:21 +00:00
end
2013-08-28 23:52:13 +00:00
2013-09-07 12:32:36 +00:00
protected
# Auto-generate "safe" inventory file based on Vagrantfile,
# unless inventory_path is explicitly provided
2013-08-28 23:52:13 +00:00
def setup_inventory_file
2013-09-01 04:48:41 +00:00
return config . inventory_path if config . inventory_path
2013-08-28 23:52:13 +00:00
2014-02-02 21:32:22 +00:00
# Managed machines
2014-02-16 10:20:00 +00:00
inventory_machines = { }
2014-02-02 21:32:22 +00:00
2014-02-24 15:52:38 +00:00
generated_inventory_dir = @machine . env . local_data_path . join ( File . join ( %w( provisioners ansible inventory ) ) )
FileUtils . mkdir_p ( generated_inventory_dir ) unless File . directory? ( generated_inventory_dir )
generated_inventory_file = generated_inventory_dir . join ( 'vagrant_ansible_inventory' )
2013-08-28 23:54:44 +00:00
generated_inventory_file . open ( 'w' ) do | file |
2013-07-07 06:47:37 +00:00
file . write ( " # Generated by Vagrant \n \n " )
2013-12-09 07:01:01 +00:00
2014-02-01 21:04:20 +00:00
@machine . env . active_machines . each do | am |
2014-02-02 21:32:22 +00:00
begin
m = @machine . env . machine ( * am )
if ! m . ssh_info . nil?
file . write ( " #{ m . name } ansible_ssh_host= #{ m . ssh_info [ :host ] } ansible_ssh_port= #{ m . ssh_info [ :port ] } \n " )
2014-02-16 10:20:00 +00:00
inventory_machines [ m . name ] = m
2014-02-02 21:32:22 +00:00
else
@logger . error ( " Auto-generated inventory: Impossible to get SSH information for machine ' #{ m . name } ( #{ m . provider_name } )'. This machine should be recreated. " )
# Let a note about this missing machine
file . write ( " # MISSING: ' #{ m . name } ' machine was probably removed without using Vagrant. This machine should be recreated. \n " )
end
rescue Vagrant :: Errors :: MachineNotFound = > e
@logger . info ( " Auto-generated inventory: Skip machine ' #{ am [ 0 ] } ( #{ am [ 1 ] } )', which is not configured for this Vagrant environment. " )
end
2014-02-01 21:04:20 +00:00
end
2014-02-16 10:20:00 +00:00
# Write out groups information.
# All defined groups will be included, but only supported
# machines and defined child groups will be included.
# Group variables are intentionally skipped.
2013-12-09 07:01:01 +00:00
groups_of_groups = { }
2014-02-16 10:20:00 +00:00
defined_groups = [ ]
2013-12-09 07:01:01 +00:00
config . groups . each_pair do | gname , gmembers |
2014-02-02 22:14:07 +00:00
# Require that gmembers be an array
# (easier to be tolerant and avoid error management of few value)
gmembers = [ gmembers ] if ! gmembers . is_a? ( Array )
2013-12-09 07:01:01 +00:00
if gname . end_with? ( " :children " )
groups_of_groups [ gname ] = gmembers
2014-02-16 10:20:00 +00:00
defined_groups << gname . sub ( / :children$ / , '' )
elsif ! gname . include? ( ':vars' )
defined_groups << gname
2013-12-09 07:01:01 +00:00
file . write ( " \n [ #{ gname } ] \n " )
2014-02-01 21:04:20 +00:00
gmembers . each do | gm |
2014-02-16 10:20:00 +00:00
file . write ( " #{ gm } \n " ) if inventory_machines . include? ( gm . to_sym )
2014-02-01 21:04:20 +00:00
end
2013-12-09 07:01:01 +00:00
end
end
2014-02-16 10:20:00 +00:00
defined_groups . uniq!
2013-12-09 07:01:01 +00:00
groups_of_groups . each_pair do | gname , gmembers |
2014-02-16 10:20:00 +00:00
file . write ( " \n [ #{ gname } ] \n " )
gmembers . each do | gm |
file . write ( " #{ gm } \n " ) if defined_groups . include? ( gm )
2013-12-09 07:01:01 +00:00
end
end
2013-07-07 06:47:37 +00:00
end
2013-08-28 23:52:13 +00:00
2013-08-28 23:54:44 +00:00
return generated_inventory_file . to_s
2013-07-07 06:47:37 +00:00
end
2013-09-02 22:30:49 +00:00
2013-10-11 22:29:39 +00:00
def get_extra_vars_argument
if config . extra_vars . kind_of? ( String ) and config . extra_vars =~ / ^@.+$ /
# A JSON or YAML file is referenced (requires Ansible 1.3+)
return config . extra_vars
else
2013-11-25 05:29:04 +00:00
# Expected to be a Hash after config validation. (extra_vars as
# JSON requires Ansible 1.2+, while YAML requires Ansible 1.3+)
2013-10-11 22:29:39 +00:00
return config . extra_vars . to_json
end
end
2013-09-07 13:17:43 +00:00
def get_verbosity_argument
if config . verbose . to_s =~ / ^v+$ /
2013-10-04 06:58:49 +00:00
# ansible-playbook accepts "silly" arguments like '-vvvvv' as '-vvvv' for now
2013-09-17 04:02:22 +00:00
return " - #{ config . verbose } "
2013-10-04 19:32:10 +00:00
else
# safe default, in case input strays
return '-v'
2013-09-07 13:17:43 +00:00
end
end
2014-02-28 07:50:17 +00:00
def ansible_ssh_args
@ansible_ssh_args || = get_ansible_ssh_args
end
2014-02-10 09:23:10 +00:00
def get_ansible_ssh_args
ssh_options = [ ]
# Multiple Private Keys
2014-02-28 07:50:17 +00:00
@ssh_info [ :private_key_path ] . drop ( 1 ) . each do | key |
2014-02-10 09:23:10 +00:00
ssh_options << " -o IdentityFile= #{ key } "
end
2014-02-10 09:32:47 +00:00
# SSH Forwarding
2014-02-28 07:50:17 +00:00
ssh_options << " -o ForwardAgent=yes " if @ssh_info [ :forward_agent ]
2014-02-10 09:32:47 +00:00
2014-03-09 21:47:24 +00:00
# Unchecked SSH Parameters
ssh_options . concat ( self . as_array ( config . raw_ssh_args ) ) if config . raw_ssh_args
2014-03-09 21:41:36 +00:00
# Re-enable ControlPersist Ansible defaults,
# which are lost when ANSIBLE_SSH_ARGS is defined.
unless ssh_options . empty?
ssh_options << " -o ControlMaster=auto "
ssh_options << " -o ControlPersist=60s "
# Intentionally keep ControlPath undefined to let ansible-playbook
# automatically sets this option to Ansible default value
end
2014-02-28 07:50:17 +00:00
ssh_options . join ( ' ' )
2014-02-10 09:23:10 +00:00
end
2013-05-06 19:17:45 +00:00
def as_list_argument ( v )
v . kind_of? ( Array ) ? v . join ( ',' ) : v
end
2013-12-16 20:20:10 +00:00
def as_array ( v )
v . kind_of? ( Array ) ? v : [ v ]
end
2013-03-20 14:41:21 +00:00
end
end
end