Merge pull request #2991 from gildegoma/review-pr2926

provisioner/ansible: Ansible single inventory file
This commit is contained in:
Mitchell Hashimoto 2014-02-27 11:30:12 -08:00
commit a1b29ca82b
2 changed files with 165 additions and 47 deletions

View File

@ -2,6 +2,7 @@ module VagrantPlugins
module Ansible
class Provisioner < Vagrant.plugin("2", :provisioner)
def provision
@logger = Log4r::Logger.new("vagrant::provisioners::ansible")
ssh = @machine.ssh_info
# Connect with Vagrant user (unless --user or --private-key are
@ -10,6 +11,10 @@ module VagrantPlugins
# TODO: multiple private key support
options = %W[--private-key=#{ssh[:private_key_path][0]} --user=#{ssh[:username]}]
# By default we limit by the current machine.
# This can be overridden by the limit config option.
options << "--limit=#{@machine.name}"
# Joker! Not (yet) supported arguments can be passed this way.
options.concat(self.as_array(config.raw_arguments)) if config.raw_arguments
@ -63,37 +68,63 @@ module VagrantPlugins
ssh = @machine.ssh_info
generated_inventory_file =
@machine.env.root_path.join("vagrant_ansible_inventory_#{machine.name}")
# Managed machines
inventory_machines = {}
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')
generated_inventory_file.open('w') do |file|
file.write("# Generated by Vagrant\n\n")
file.write("#{machine.name} ansible_ssh_host=#{ssh[:host]} ansible_ssh_port=#{ssh[:port]}\n")
# Write out groups information. Only include current
# machine and its groups to avoid Ansible errors on
# provisioning.
groups_of_groups = {}
included_groups = []
config.groups.each_pair do |gname, gmembers|
if gname.end_with?(":children")
groups_of_groups[gname] = gmembers
elsif gmembers.include?("#{machine.name}")
included_groups << gname
file.write("\n[#{gname}]\n")
file.write("#{machine.name}\n")
@machine.env.active_machines.each do |am|
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")
inventory_machines[m.name] = m
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
end
groups_of_groups.each_pair do |gname, gmembers|
unless (included_groups & gmembers).empty?
# 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.
groups_of_groups = {}
defined_groups = []
config.groups.each_pair do |gname, gmembers|
# Require that gmembers be an array
# (easier to be tolerant and avoid error management of few value)
gmembers = [gmembers] if !gmembers.is_a?(Array)
if gname.end_with?(":children")
groups_of_groups[gname] = gmembers
defined_groups << gname.sub(/:children$/, '')
elsif !gname.include?(':vars')
defined_groups << gname
file.write("\n[#{gname}]\n")
gmembers.each do |gm|
file.write("#{gm}\n") if included_groups.include?(gm)
file.write("#{gm}\n") if inventory_machines.include?(gm.to_sym)
end
end
end
defined_groups.uniq!
groups_of_groups.each_pair do |gname, gmembers|
file.write("\n[#{gname}]\n")
gmembers.each do |gm|
file.write("#{gm}\n") if defined_groups.include?(gm)
end
end
end
return generated_inventory_file.to_s

View File

@ -8,7 +8,7 @@ sidebar_current: "provisioning-ansible"
**Provisioner name: `"ansible"`**
The ansible provisioner allows you to provision the guest using
[Ansible](http://ansible.cc) playbooks.
[Ansible](http://ansible.com) playbooks.
Ansible playbooks are [YAML](http://en.wikipedia.org/wiki/YAML) documents that
comprise the set of steps to be orchestrated on one or more machines. This documentation
@ -28,21 +28,55 @@ a single page of documentation.
## Inventory File
When using Ansible, it needs to know on which machines a given playbook should run. It does
this by way of an inventory file which lists those machines. In the context of Vagrant,
there are two ways to approach working with inventory files. The first and simplest option
is to not provide one to Vagrant at all. Vagrant will generate inventory files for each
virtual machine it manages, and use them for provisioning machines. Generated inventory files
are created adjacent to your Vagrantfile, named using the machine names set in your Vagrantfile.
this by way of an [inventory](http://docs.ansible.com/intro_inventory.html) file which lists those machines. In the context of Vagrant,
there are two ways to approach working with inventory files.
The second option is for situations where you'd like to have more than one virtual machine
in a single inventory file, perhaps leveraging more complex playbooks or inventory grouping.
If you provide the `ansible.inventory_path` option referencing a specific inventory file
dedicated to your Vagrant project, that one will be used instead of generating them.
Such an inventory file for use with Vagrant might look like:
The first and simplest option is to not provide one to Vagrant at all. Vagrant will generate an
inventory file encompassing all of the virtual machine it manages, and use it for provisioning
machines. The generated inventory file is stored as part of your local Vagrant environment in `.vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory`.
The `ansible.groups` option can be used to pass a hash of group
names and group members to be included in the generated inventory file. Group variables
are intentionally not supported, as this practice is not recommended.
For example:
```
[vagrant]
192.168.111.222
ansible.groups = {
"group1" => ["machine1"],
"group2" => ["machine2", "machine3"],
"all_groups:children" => ["group1", "group2", "group3"]
}
```
Note that unmanaged machines and undefined groups are not added to the inventory.
For example, `group3` in the above example would not be added to the inventory file.
A generated inventory might look like:
```
# Generated by Vagrant
machine1 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2200
machine2 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2201
[group1]
machine1
[group2]
machine2
[all_groups:children]
group1
group2
```
The second option is for situations where you'd like to have more control over the inventory management.
With the `ansible.inventory_path` option, you can reference a specific inventory resource (e.g. a static inventory file, a [dynamic inventory script](http://docs.ansible.com/intro_dynamic_inventory.html) or even [multiple inventories stored in the same directory](http://docs.ansible.com/intro_dynamic_inventory.html#using-multiple-inventory-sources)). Vagrant will then use this inventory information instead of generating it.
A very simple inventory file for use with Vagrant might look like:
```
default ansible_ssh_host=192.168.111.222
```
Where the above IP address is one set in your Vagrantfile:
@ -51,13 +85,16 @@ Where the above IP address is one set in your Vagrantfile:
config.vm.network :private_network, ip: "192.168.111.222"
```
Note that machine names in `Vagrantfile` and `ansible.inventory_path` file should correspond,
unless you use `ansible.limit` option to reference the correct machines.
## Playbook
The second component of a successful Ansible provisioner setup is the Ansible playbook
which contains the steps that should be run on the guest. Ansible's
[playbook documentation](http://ansible.cc/docs/playbooks.html) goes into great
[playbook documentation](http://docs.ansible.com/playbooks.html) goes into great
detail on how to author playbooks, and there are a number of
[best practices](http://ansible.cc/docs/bestpractices.html) that can be applied to use
[best practices](http://docs.ansible.com/playbooks_best_practices.html) that can be applied to use
Ansible's powerful features effectively. A playbook that installs and starts (or restarts
if it was updated) the NTP daemon via YUM looks like:
@ -75,7 +112,7 @@ if it was updated) the NTP daemon via YUM looks like:
```
You can of course target other operating systems that don't have YUM by changing the
playbook tasks. Ansible ships with a number of [modules](http://ansible.cc/docs/modules.html)
playbook tasks. Ansible ships with a number of [modules](http://docs.ansible.com/modules.html)
that make running otherwise tedious tasks dead simple.
## Running Ansible
@ -90,7 +127,6 @@ Vagrant.configure("2") do |config|
end
```
This causes Vagrant to run the `playbook.yml` playbook against all hosts in the inventory file.
Since an Ansible playbook can include many files, you may also collect the related files in
a directory structure like this:
@ -114,6 +150,13 @@ Vagrant.configure("2") do |config|
end
```
Vagrant will try to run the `playbook.yml` playbook against all machines defined in your Vagrantfile.
**Backward Compatibility Note**:
Up to Vagrant 1.4, the Ansible provisioner could potentially connect (multiple times) to all hosts from the inventory file.
This behaviour is still possible by setting `ansible.limit = 'all'` (see more details below).
## Additional Options
The Ansible provisioner also includes a number of additional options that can be set,
@ -135,7 +178,9 @@ all of which get passed to the `ansible-playbook` command that ships with Ansibl
* `ansible.sudo_user` can be set to a string containing a username on the guest who should be used
by the sudo command.
* `ansible.ask_sudo_pass` can be set to `true` to require Ansible to prompt for a sudo password.
* `ansible.limit` can be set to a string or an array of machines or groups from the inventory file to further narrow down which hosts are affected.
* `ansible.limit` can be set to a string or an array of machines or groups from the inventory file to further control which hosts are affected. Note that:
* As Vagrant 1.5, the machine name (taken from Vagrantfile) is set as **default limit** to ensure that `vagrant provision` steps only affect the expected machine. Setting `ansible.limit` will override this default.
* Setting `ansible.limit = 'all'` can be used to make Ansible connects to all machines from the inventory file.
* `ansible.verbose` can be set to increase Ansible's verbosity to obtain detailed logging:
* `'v'`, verbose mode
* `'vv'`
@ -147,14 +192,56 @@ by the sudo command.
* `ansible.raw_arguments` can be set to an array of strings corresponding to a list of `ansible-playbook` arguments (e.g. `['--check', '-M /my/modules']`). It is an *unsafe wildcard* that can be used to apply Ansible options that are not (yet) supported by this Vagrant provisioner. Following precedence rules apply:
* Any supported options (described above) will override conflicting `raw_arguments` value (e.g. `--tags` or `--start-at-task`)
* Vagrant default user authentication can be overridden via `raw_arguments` (with custom values for `--user` and `--private-key`)
* `ansible.groups` can be used to pass a hash of group names and group members to be included in the generated inventory file. For example:
```
ansible.groups = {
"group1" => ["machine1"],
"group2" => ["machine2", "machine3"],
"all_groups:children" => ["group1", "group2"]
}
```
Note that only the current machine and its related groups will be added to the inventory file.
* `ansible.host_key_checking` can be set to `false` which will disable host key checking and prevent `"FAILED: (25, 'Inappropriate ioctl for device')"` errors from being reported during provisioner runs. The default value is `true`, which matches the default behavior of Ansible 1.2.1 and later.
## Tips and Tricks
### Ansible Parallel Execution
Vagrant is designed to provision [multi-machine environments](/v2/multi-machine) in sequence, but the following configuration pattern can be used to take advantage of Ansible parallelism:
```
config.vm.define 'machine2' do |machine|
machine.vm.hostname = 'machine2'
machine.vm.network "private_network", ip: "192.168.77.22"
end
config.vm.define 'machine1' do |machine|
machine.vm.hostname = 'machine1'
machine.vm.network "private_network", ip: "192.168.77.21"
machine.vm.provision :ansible do |ansible|
ansible.playbook = "playbook.yml"
# Disable default limit (required with Vagrant 1.5+)
ansible.limit = 'all'
end
end
```
### Provide a local `ansible.cfg` file
Certain settings in Ansible are (only) adjustable via a [configuration file](http://docs.ansible.com/intro_configuration.html), and you might want to ship such a file in your Vagrant project.
As `ansible-playbook` command looks for local [`ansible.cfg`] configuration file in its *current directory* (but not in the directory that contains the main playbook), you have to store this file adjacent to your Vagrantfile.
Note that it is also possible to reference an Ansible configuration file via `ANSIBLE_CONFIG` environment variable, if you want to be flexible about the location of this file.
### Why Ansible provisioner does connect with a wrong user ?
It is good to know that following Ansible settings always override `config.ssh.username` option defined in [Vagrant SSH Settings](/v2/vagrantfile/ssh_settings.html):
* `ansible_ssh_user` variable
* `remote_user` (or `user`) play attribute
* `remote_user` task attribute
Be aware that copying snippets from Ansible documentation might lead to this problem, as `root` is used as remote user in many [examples](http://docs.ansible.com/playbooks_intro.html#hosts-and-users).
Example of SSH error (with `vvv` log level), where an undefined remote user `xyz` has replaced `vagrant`:
```
TASK: [my_role | do something] *****************
<127.0.0.1> ESTABLISH CONNECTION FOR USER: xyz
<127.0.0.1> EXEC ['ssh', '-tt', '-vvv', '-o', 'ControlMaster=auto',...
fatal: [ansible-devbox] => SSH encountered an unknown error. We recommend you re-run the command using -vvvv, which will enable SSH debugging output to help diagnose the issue.
```