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
#
2014-11-30 01:02:29 +00:00
# Ansible provisioner options
2014-02-10 15:16:36 +00:00
#
# 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
provisioners/ansible: force --connection=ssh
When `--connection` argument is not specified, Ansible will use the
'smart' mode, which can either use `ssh` or `paramiko` transports,
depending of the version of OpenSSH available. If OpenSSH version is new
enough to support ControlPersist technology, `ssh` will be used.
See also http://docs.ansible.com/intro_configuration.html#transport.
In order to support some advanced features of Vagrant (e.g. multiple ssh
private key identities or ssh forwarding), the Ansible provisioner
already must force `ssh` connection mode.
Having to deal with the possible fallback to `paramiko` increase the
burden of special cases that Ansible provisioner must handle, without
any added value, as Vagrant is based on OpenSSH and its users are
usually using modern operating systems.
With this change, the Ansible provisioner will officially only support
`ssh`. It will still be possible to switch to another connection mode
via `raw_arguments`, but it will breach the "contract", and no
(community) support can be expected in such use case.
ref #3900, #3396
2014-11-29 22:29:46 +00:00
# Connect with native OpenSSH client
# Other modes (e.g. paramiko) are not officially supported,
# but can be enabled via raw_arguments option.
options << " --connection=ssh "
2013-05-06 20:28:20 +00:00
2014-11-30 01:02:29 +00:00
# By default we limit by the current machine, but
# this can be overridden by the `limit` option.
if config . limit
options << " --limit= #{ as_list_argument ( config . limit ) } "
else
options << " --limit= #{ @machine . name } "
end
2014-02-10 15:16:36 +00:00
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
2014-03-27 07:56:55 +00:00
options << " --ask-vault-pass " if config . ask_vault_pass
options << " --vault-password-file= #{ config . vault_password_file } " if config . vault_password_file
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
options << " --start-at-task= #{ config . start_at_task } " if config . start_at_task
2014-12-07 21:34:19 +00:00
# Finally, add the raw configuration options, which has the highest precedence
# and can therefore potentially override any other options of this provisioner.
2014-11-30 01:02:29 +00:00
options . concat ( self . as_array ( config . raw_arguments ) ) if config . raw_arguments
#
2013-04-04 07:07:59 +00:00
# Assemble the full ansible-playbook command
2014-11-30 01:02:29 +00:00
#
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
env = {
2014-08-25 19:12:25 +00:00
# Ensure Ansible output isn't buffered so that we receive output
2014-02-10 09:23:10 +00:00
# on a task-by-task basis.
provisioners/ansible: force --connection=ssh
When `--connection` argument is not specified, Ansible will use the
'smart' mode, which can either use `ssh` or `paramiko` transports,
depending of the version of OpenSSH available. If OpenSSH version is new
enough to support ControlPersist technology, `ssh` will be used.
See also http://docs.ansible.com/intro_configuration.html#transport.
In order to support some advanced features of Vagrant (e.g. multiple ssh
private key identities or ssh forwarding), the Ansible provisioner
already must force `ssh` connection mode.
Having to deal with the possible fallback to `paramiko` increase the
burden of special cases that Ansible provisioner must handle, without
any added value, as Vagrant is based on OpenSSH and its users are
usually using modern operating systems.
With this change, the Ansible provisioner will officially only support
`ssh`. It will still be possible to switch to another connection mode
via `raw_arguments`, but it will breach the "contract", and no
(community) support can be expected in such use case.
ref #3900, #3396
2014-11-29 22:29:46 +00:00
" PYTHONUNBUFFERED " = > 1 ,
# Some Ansible options must be passed as environment variables,
# as there is no equivalent command line arguments
" ANSIBLE_FORCE_COLOR " = > " true " ,
" ANSIBLE_HOST_KEY_CHECKING " = > " #{ config . host_key_checking } " ,
2014-02-10 09:23:10 +00:00
}
provisioners/ansible: force --connection=ssh
When `--connection` argument is not specified, Ansible will use the
'smart' mode, which can either use `ssh` or `paramiko` transports,
depending of the version of OpenSSH available. If OpenSSH version is new
enough to support ControlPersist technology, `ssh` will be used.
See also http://docs.ansible.com/intro_configuration.html#transport.
In order to support some advanced features of Vagrant (e.g. multiple ssh
private key identities or ssh forwarding), the Ansible provisioner
already must force `ssh` connection mode.
Having to deal with the possible fallback to `paramiko` increase the
burden of special cases that Ansible provisioner must handle, without
any added value, as Vagrant is based on OpenSSH and its users are
usually using modern operating systems.
With this change, the Ansible provisioner will officially only support
`ssh`. It will still be possible to switch to another connection mode
via `raw_arguments`, but it will breach the "contract", and no
(community) support can be expected in such use case.
ref #3900, #3396
2014-11-29 22:29:46 +00:00
# ANSIBLE_SSH_ARGS is required for Multiple SSH keys, SSH forwarding and custom SSH settings
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
2014-11-25 07:12:43 +00:00
show_ansible_playbook_command ( env , command ) if ( config . verbose || @logger . debug? )
2014-03-31 14:25:16 +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-05-22 16:35:12 +00:00
env : env ,
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
2014-05-22 16:35:12 +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 )
2014-10-23 20:58:01 +00:00
m_ssh_info = m . ssh_info
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
2014-04-11 06:20:32 +00:00
return generated_inventory_dir . 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-11-30 05:55:21 +00:00
# Use ANSIBLE_SSH_ARGS to pass some OpenSSH options that are not wrapped by
# an ad-hoc Ansible option. Last update corresponds to Ansible 1.8
2014-02-10 09:23:10 +00:00
def get_ansible_ssh_args
ssh_options = [ ]
2014-12-07 09:40:21 +00:00
# Use an SSH ProxyCommand when using the Docker provider with the intermediate host
if @machine . provider_name == :docker && machine . provider . host_vm?
docker_host_ssh_info = machine . provider . host_vm . ssh_info
proxy_cmd = " ssh #{ docker_host_ssh_info [ :username ] } @ #{ docker_host_ssh_info [ :host ] } " +
" -p #{ docker_host_ssh_info [ :port ] } -i #{ docker_host_ssh_info [ :private_key_path ] [ 0 ] } "
# Use same options than plugins/providers/docker/communicator.rb
# Note: this could be improved (DRY'ed) by sharing these settings.
proxy_cmd += " -o Compression=yes -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
proxy_cmd += " -o ForwardAgent=yes " if @ssh_info [ :forward_agent ]
proxy_cmd += " exec nc %h %p 2>/dev/null "
ssh_options << " -o ProxyCommand=' #{ proxy_cmd } ' "
end
2014-11-30 05:55:21 +00:00
# Don't access user's known_hosts file, except when host_key_checking is enabled.
ssh_options << " -o UserKnownHostsFile=/dev/null " unless config . host_key_checking
2014-02-10 09:23:10 +00:00
# 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
2014-03-31 14:25:16 +00:00
def show_ansible_playbook_command ( env , command )
shell_command = ''
env . each_pair do | k , v |
if k == 'ANSIBLE_SSH_ARGS'
shell_command += " #{ k } =' #{ v } ' "
else
shell_command += " #{ k } = #{ v } "
end
end
shell_arg = [ ]
command . each do | arg |
if arg =~ / (--start-at-task|--limit)=(.+) /
shell_arg << " #{ $1 } =' #{ $2 } ' "
else
shell_arg << arg
end
end
shell_command += shell_arg . join ( ' ' )
@machine . env . ui . detail ( shell_command )
end
2013-03-20 14:41:21 +00:00
end
end
end