mirror of https://github.com/ansible/ansible.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
547 lines
18 KiB
ReStructuredText
547 lines
18 KiB
ReStructuredText
11 years ago
|
Intro to Playbooks
|
||
|
==================
|
||
|
|
||
|
.. _about_playbooks:
|
||
|
|
||
|
About Playbooks
|
||
|
```````````````
|
||
|
|
||
|
Playbooks are a completely different way to use ansible than in adhoc task execution mode, and are
|
||
9 years ago
|
particularly powerful.
|
||
11 years ago
|
|
||
|
Simply put, playbooks are the basis for a really simple configuration management and multi-machine deployment system,
|
||
|
unlike any that already exist, and one that is very well suited to deploying complex applications.
|
||
|
|
||
|
Playbooks can declare configurations, but they can also orchestrate steps of
|
||
|
any manual ordered process, even as different steps must bounce back and forth
|
||
|
between sets of machines in particular orders. They can launch tasks
|
||
|
synchronously or asynchronously.
|
||
|
|
||
9 years ago
|
While you might run the main ``/usr/bin/ansible`` program for ad-hoc
|
||
11 years ago
|
tasks, playbooks are more likely to be kept in source control and used
|
||
|
to push out your configuration or assure the configurations of your
|
||
|
remote systems are in spec.
|
||
|
|
||
|
There are also some full sets of playbooks illustrating a lot of these techniques in the
|
||
|
`ansible-examples repository <https://github.com/ansible/ansible-examples>`_. We'd recommend
|
||
|
looking at these in another tab as you go along.
|
||
|
|
||
|
There are also many jumping off points after you learn playbooks, so hop back to the documentation
|
||
|
index after you're done with this section.
|
||
|
|
||
|
.. _playbook_language_example:
|
||
|
|
||
|
Playbook Language Example
|
||
|
`````````````````````````
|
||
|
|
||
|
Playbooks are expressed in YAML format (see :doc:`YAMLSyntax`) and have a minimum of syntax, which intentionally
|
||
|
tries to not be a programming language or script, but rather a model of a configuration or a process.
|
||
|
|
||
|
Each playbook is composed of one or more 'plays' in a list.
|
||
|
|
||
|
The goal of a play is to map a group of hosts to some well defined roles, represented by
|
||
|
things ansible calls tasks. At a basic level, a task is nothing more than a call
|
||
9 years ago
|
to an ansible module (see :doc:`modules`).
|
||
11 years ago
|
|
||
|
By composing a playbook of multiple 'plays', it is possible to
|
||
|
orchestrate multi-machine deployments, running certain steps on all
|
||
|
machines in the webservers group, then certain steps on the database
|
||
|
server group, then more commands back on the webservers group, etc.
|
||
|
|
||
|
"plays" are more or less a sports analogy. You can have quite a lot of plays that affect your systems
|
||
|
to do different things. It's not as if you were just defining one particular state or model, and you
|
||
9 years ago
|
can run different plays at different times.
|
||
11 years ago
|
|
||
|
For starters, here's a playbook that contains just one play::
|
||
|
|
||
|
---
|
||
|
- hosts: webservers
|
||
|
vars:
|
||
|
http_port: 80
|
||
|
max_clients: 200
|
||
|
remote_user: root
|
||
|
tasks:
|
||
|
- name: ensure apache is at the latest version
|
||
7 years ago
|
yum:
|
||
|
name: httpd
|
||
|
state: latest
|
||
11 years ago
|
- name: write the apache config file
|
||
7 years ago
|
template:
|
||
|
src: /srv/httpd.j2
|
||
|
dest: /etc/httpd.conf
|
||
11 years ago
|
notify:
|
||
|
- restart apache
|
||
10 years ago
|
- name: ensure apache is running (and enable it at boot)
|
||
7 years ago
|
service:
|
||
|
name: httpd
|
||
|
state: started
|
||
|
enabled: yes
|
||
11 years ago
|
handlers:
|
||
|
- name: restart apache
|
||
7 years ago
|
service:
|
||
|
name: httpd
|
||
|
state: restarted
|
||
11 years ago
|
|
||
9 years ago
|
When working with tasks that have really long parameters or modules that take
|
||
|
many parameters, you can break tasks items over multiple lines to improve the
|
||
|
structure. Below is another version of the above example but using
|
||
9 years ago
|
YAML dictionaries to supply the modules with their ``key=value`` arguments.::
|
||
10 years ago
|
|
||
|
---
|
||
|
- hosts: webservers
|
||
|
vars:
|
||
|
http_port: 80
|
||
|
max_clients: 200
|
||
|
remote_user: root
|
||
|
tasks:
|
||
|
- name: ensure apache is at the latest version
|
||
|
yum:
|
||
9 years ago
|
name: httpd
|
||
10 years ago
|
state: latest
|
||
|
- name: write the apache config file
|
||
|
template:
|
||
|
src: /srv/httpd.j2
|
||
|
dest: /etc/httpd.conf
|
||
|
notify:
|
||
|
- restart apache
|
||
|
- name: ensure apache is running
|
||
|
service:
|
||
|
name: httpd
|
||
|
state: started
|
||
|
handlers:
|
||
|
- name: restart apache
|
||
|
service:
|
||
|
name: httpd
|
||
|
state: restarted
|
||
|
|
||
10 years ago
|
Playbooks can contain multiple plays. You may have a playbook that targets first
|
||
|
the web servers, and then the database servers. For example::
|
||
|
|
||
|
---
|
||
|
- hosts: webservers
|
||
|
remote_user: root
|
||
|
|
||
|
tasks:
|
||
|
- name: ensure apache is at the latest version
|
||
7 years ago
|
yum:
|
||
|
name: httpd
|
||
|
state: latest
|
||
10 years ago
|
- name: write the apache config file
|
||
7 years ago
|
template:
|
||
|
src: /srv/httpd.j2
|
||
|
dest: /etc/httpd.conf
|
||
10 years ago
|
|
||
|
- hosts: databases
|
||
|
remote_user: root
|
||
|
|
||
|
tasks:
|
||
|
- name: ensure postgresql is at the latest version
|
||
7 years ago
|
yum:
|
||
|
name: postgresql
|
||
|
state: latest
|
||
10 years ago
|
- name: ensure that postgresql is started
|
||
7 years ago
|
service:
|
||
|
name: postgresql
|
||
|
state: started
|
||
10 years ago
|
|
||
|
You can use this method to switch between the host group you're targeting,
|
||
|
the username logging into the remote servers, whether to sudo or not, and so
|
||
|
forth. Plays, like tasks, run in the order specified in the playbook: top to
|
||
|
bottom.
|
||
|
|
||
11 years ago
|
Below, we'll break down what the various features of the playbook language are.
|
||
|
|
||
|
.. _playbook_basics:
|
||
|
|
||
|
Basics
|
||
|
``````
|
||
|
|
||
|
.. _playbook_hosts_and_users:
|
||
|
|
||
|
Hosts and Users
|
||
|
+++++++++++++++
|
||
|
|
||
|
For each play in a playbook, you get to choose which machines in your infrastructure
|
||
|
to target and what remote user to complete the steps (called tasks) as.
|
||
|
|
||
9 years ago
|
The ``hosts`` line is a list of one or more groups or host patterns,
|
||
11 years ago
|
separated by colons, as described in the :doc:`intro_patterns`
|
||
9 years ago
|
documentation. The ``remote_user`` is just the name of the user account::
|
||
11 years ago
|
|
||
|
---
|
||
|
- hosts: webservers
|
||
|
remote_user: root
|
||
|
|
||
11 years ago
|
.. note::
|
||
11 years ago
|
|
||
9 years ago
|
The ``remote_user`` parameter was formerly called just ``user``. It was renamed in Ansible 1.4 to make it more distinguishable from the **user** module (used to create users on remote systems).
|
||
11 years ago
|
|
||
|
Remote users can also be defined per task::
|
||
|
|
||
|
---
|
||
|
- hosts: webservers
|
||
|
remote_user: root
|
||
|
tasks:
|
||
|
- name: test connection
|
||
|
ping:
|
||
|
remote_user: yourname
|
||
|
|
||
10 years ago
|
Support for running things as another user is also available (see :doc:`become`)::
|
||
11 years ago
|
|
||
|
---
|
||
|
- hosts: webservers
|
||
|
remote_user: yourname
|
||
9 years ago
|
become: yes
|
||
11 years ago
|
|
||
9 years ago
|
You can also use become on a particular task instead of the whole play::
|
||
11 years ago
|
|
||
|
---
|
||
|
- hosts: webservers
|
||
|
remote_user: yourname
|
||
|
tasks:
|
||
7 years ago
|
- service:
|
||
|
name: nginx
|
||
|
state: started
|
||
10 years ago
|
become: yes
|
||
|
become_method: sudo
|
||
11 years ago
|
|
||
|
|
||
10 years ago
|
You can also login as you, and then become a user different than root::
|
||
11 years ago
|
|
||
|
---
|
||
|
- hosts: webservers
|
||
|
remote_user: yourname
|
||
10 years ago
|
become: yes
|
||
|
become_user: postgres
|
||
|
|
||
|
You can also use other privilege escalation methods, like su::
|
||
|
|
||
|
---
|
||
|
- hosts: webservers
|
||
|
remote_user: yourname
|
||
|
become: yes
|
||
|
become_method: su
|
||
11 years ago
|
|
||
9 years ago
|
If you need to specify a password to sudo, run ``ansible-playbook`` with ``--ask-become-pass`` or
|
||
|
when using the old sudo syntax ``--ask-sudo-pass`` (``-K``). If you run a become playbook and the
|
||
10 years ago
|
playbook seems to hang, it's probably stuck at the privilege escalation prompt.
|
||
|
Just `Control-C` to kill it and run it again adding the appropriate password.
|
||
11 years ago
|
|
||
|
.. important::
|
||
|
|
||
9 years ago
|
When using ``become_user`` to a user other than root, the module
|
||
|
arguments are briefly written into a random tempfile in ``/tmp``.
|
||
11 years ago
|
These are deleted immediately after the command is executed. This
|
||
10 years ago
|
only occurs when changing privileges from a user like 'bob' to 'timmy',
|
||
|
not when going from 'bob' to 'root', or logging in directly as 'bob' or
|
||
10 years ago
|
'root'. If it concerns you that this data is briefly readable
|
||
10 years ago
|
(not writable), avoid transferring unencrypted passwords with
|
||
9 years ago
|
`become_user` set. In other cases, ``/tmp`` is not used and this does
|
||
11 years ago
|
not come into play. Ansible also takes care to not log password
|
||
|
parameters.
|
||
|
|
||
8 years ago
|
|
||
|
.. _order:
|
||
8 years ago
|
|
||
8 years ago
|
.. versionadded:: 2.4
|
||
8 years ago
|
|
||
8 years ago
|
You can also control the order in which hosts are run. The default is to follow the order supplied by the inventory::
|
||
8 years ago
|
|
||
|
- hosts: all
|
||
|
order: sorted
|
||
|
gather_facts: False
|
||
|
tasks:
|
||
7 years ago
|
- debug:
|
||
|
var: inventory_hostname
|
||
8 years ago
|
|
||
|
Possible values for order are:
|
||
|
|
||
|
inventory:
|
||
8 years ago
|
The default. The order is 'as provided' by the inventory
|
||
8 years ago
|
reverse_inventory:
|
||
|
As the name implies, this reverses the order 'as provided' by the inventory
|
||
|
sorted:
|
||
8 years ago
|
Hosts are alphabetically sorted by name
|
||
8 years ago
|
reverse_sorted:
|
||
8 years ago
|
Hosts are sorted by name in reverse alphabetical order
|
||
8 years ago
|
shuffle:
|
||
|
Hosts are randomly ordered each run
|
||
|
|
||
|
|
||
11 years ago
|
.. _tasks_list:
|
||
|
|
||
|
Tasks list
|
||
|
++++++++++
|
||
|
|
||
|
Each play contains a list of tasks. Tasks are executed in order, one
|
||
|
at a time, against all machines matched by the host pattern,
|
||
|
before moving on to the next task. It is important to understand that, within a play,
|
||
|
all hosts are going to get the same task directives. It is the purpose of a play to map
|
||
|
a selection of hosts to tasks.
|
||
|
|
||
|
When running the playbook, which runs top to bottom, hosts with failed tasks are
|
||
|
taken out of the rotation for the entire playbook. If things fail, simply correct the playbook file and rerun.
|
||
|
|
||
|
The goal of each task is to execute a module, with very specific arguments.
|
||
|
Variables, as mentioned above, can be used in arguments to modules.
|
||
|
|
||
8 years ago
|
Modules should be idempotent, that is, running a module multiple times
|
||
|
in a sequence should have the same effect as running it just once. One
|
||
|
way to achieve idempotency is to have a module check whether its desired
|
||
|
final state has already been achieved, and if that state has been achieved,
|
||
|
to exit without performing any actions. If all the modules a playbook uses
|
||
|
are idempotent, then the playbook itself is likely to be idempotent, so
|
||
|
re-running the playbook should be safe.
|
||
11 years ago
|
|
||
9 years ago
|
The **command** and **shell** modules will typically rerun the same command again,
|
||
11 years ago
|
which is totally ok if the command is something like
|
||
9 years ago
|
``chmod`` or ``setsebool``, etc. Though there is a ``creates`` flag available which can
|
||
11 years ago
|
be used to make these modules also idempotent.
|
||
|
|
||
9 years ago
|
Every task should have a ``name``, which is included in the output from
|
||
9 years ago
|
running the playbook. This is human readable output, and so it is
|
||
8 years ago
|
useful to provide good descriptions of each task step. If the name
|
||
11 years ago
|
is not provided though, the string fed to 'action' will be used for
|
||
|
output.
|
||
|
|
||
9 years ago
|
Tasks can be declared using the legacy ``action: module options`` format, but
|
||
9 years ago
|
it is recommended that you use the more conventional ``module: options`` format.
|
||
11 years ago
|
This recommended format is used throughout the documentation, but you may
|
||
|
encounter the older format in some playbooks.
|
||
|
|
||
10 years ago
|
Here is what a basic task looks like. As with most modules,
|
||
9 years ago
|
the service module takes ``key=value`` arguments::
|
||
11 years ago
|
|
||
|
tasks:
|
||
|
- name: make sure apache is running
|
||
7 years ago
|
service:
|
||
|
name: httpd
|
||
|
state: started
|
||
11 years ago
|
|
||
9 years ago
|
The **command** and **shell** modules are the only modules that just take a list
|
||
9 years ago
|
of arguments and don't use the ``key=value`` form. This makes
|
||
11 years ago
|
them work as simply as you would expect::
|
||
11 years ago
|
|
||
|
tasks:
|
||
8 years ago
|
- name: enable selinux
|
||
|
command: /sbin/setenforce 1
|
||
11 years ago
|
|
||
9 years ago
|
The **command** and **shell** module care about return codes, so if you have a command
|
||
11 years ago
|
whose successful exit code is not zero, you may wish to do this::
|
||
|
|
||
|
tasks:
|
||
|
- name: run this command and ignore the result
|
||
|
shell: /usr/bin/somecommand || /bin/true
|
||
|
|
||
|
Or this::
|
||
|
|
||
|
tasks:
|
||
|
- name: run this command and ignore the result
|
||
|
shell: /usr/bin/somecommand
|
||
|
ignore_errors: True
|
||
|
|
||
|
|
||
|
If the action line is getting too long for comfort you can break it on
|
||
|
a space and indent any continuation lines::
|
||
|
|
||
|
tasks:
|
||
|
- name: Copy ansible inventory file to client
|
||
|
copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts
|
||
|
owner=root group=root mode=0644
|
||
|
|
||
|
Variables can be used in action lines. Suppose you defined
|
||
9 years ago
|
a variable called ``vhost`` in the ``vars`` section, you could do this::
|
||
11 years ago
|
|
||
|
tasks:
|
||
|
- name: create a virtual host file for {{ vhost }}
|
||
7 years ago
|
template:
|
||
|
src: somefile.j2
|
||
|
dest: /etc/httpd/conf.d/{{ vhost }}
|
||
11 years ago
|
|
||
|
Those same variables are usable in templates, which we'll get to later.
|
||
|
|
||
|
Now in a very basic playbook all the tasks will be listed directly in that play, though it will usually
|
||
7 years ago
|
make more sense to break up tasks as described in :doc:`playbooks_reuse`.
|
||
11 years ago
|
|
||
|
.. _action_shorthand:
|
||
|
|
||
|
Action Shorthand
|
||
|
````````````````
|
||
|
|
||
|
.. versionadded:: 0.8
|
||
|
|
||
7 years ago
|
Ansible prefers listing modules like this::
|
||
11 years ago
|
|
||
7 years ago
|
template:
|
||
|
src: templates/foo.j2
|
||
|
dest: /etc/foo.conf
|
||
11 years ago
|
|
||
7 years ago
|
Early versions of Ansible used the following format, which still works::
|
||
11 years ago
|
|
||
|
action: template src=templates/foo.j2 dest=/etc/foo.conf
|
||
|
|
||
|
|
||
|
.. _handlers:
|
||
|
|
||
|
Handlers: Running Operations On Change
|
||
|
``````````````````````````````````````
|
||
|
|
||
8 years ago
|
As we've mentioned, modules should be idempotent and can relay when
|
||
11 years ago
|
they have made a change on the remote system. Playbooks recognize this and
|
||
|
have a basic event system that can be used to respond to change.
|
||
|
|
||
8 years ago
|
These 'notify' actions are triggered at the end of each block of tasks in a play, and will only be
|
||
11 years ago
|
triggered once even if notified by multiple different tasks.
|
||
|
|
||
|
For instance, multiple resources may indicate
|
||
|
that apache needs to be restarted because they have changed a config file,
|
||
|
but apache will only be bounced once to avoid unnecessary restarts.
|
||
|
|
||
|
Here's an example of restarting two services when the contents of a file
|
||
|
change, but only if the file changes::
|
||
|
|
||
|
- name: template configuration file
|
||
7 years ago
|
template:
|
||
|
src: template.j2
|
||
|
dest: /etc/foo.conf
|
||
11 years ago
|
notify:
|
||
|
- restart memcached
|
||
|
- restart apache
|
||
|
|
||
9 years ago
|
The things listed in the ``notify`` section of a task are called
|
||
11 years ago
|
handlers.
|
||
|
|
||
|
Handlers are lists of tasks, not really any different from regular
|
||
9 years ago
|
tasks, that are referenced by a globally unique name, and are notified
|
||
|
by notifiers. If nothing notifies a handler, it will not
|
||
|
run. Regardless of how many tasks notify a handler, it will run only
|
||
9 years ago
|
once, after all of the tasks complete in a particular play.
|
||
11 years ago
|
|
||
|
Here's an example handlers section::
|
||
|
|
||
|
handlers:
|
||
|
- name: restart memcached
|
||
7 years ago
|
service:
|
||
|
name: memcached
|
||
|
state: restarted
|
||
11 years ago
|
- name: restart apache
|
||
7 years ago
|
service:
|
||
|
name: apache
|
||
|
state: restarted
|
||
11 years ago
|
|
||
9 years ago
|
As of Ansible 2.2, handlers can also "listen" to generic topics, and tasks can notify those topics as follows::
|
||
9 years ago
|
|
||
|
handlers:
|
||
|
- name: restart memcached
|
||
7 years ago
|
service:
|
||
|
name: memcached
|
||
|
state: restarted
|
||
9 years ago
|
listen: "restart web services"
|
||
|
- name: restart apache
|
||
7 years ago
|
service:
|
||
|
name: apache
|
||
|
state:restarted
|
||
9 years ago
|
listen: "restart web services"
|
||
|
|
||
|
tasks:
|
||
|
- name: restart everything
|
||
|
command: echo "this task will restart the web services"
|
||
|
notify: "restart web services"
|
||
|
|
||
|
This use makes it much easier to trigger multiple handlers. It also decouples handlers from their names,
|
||
|
making it easier to share handlers among playbooks and roles (especially when using 3rd party roles from
|
||
|
a shared source like Galaxy).
|
||
11 years ago
|
|
||
|
.. note::
|
||
9 years ago
|
* Notify handlers are always run in the same order they are defined, `not` in the order listed in the notify-statement. This is also the case for handlers using `listen`.
|
||
|
* Handler names and `listen` topics live in a global namespace.
|
||
9 years ago
|
* If two handler tasks have the same name, only one will run.
|
||
|
`* <https://github.com/ansible/ansible/issues/4943>`_
|
||
9 years ago
|
* You cannot notify a handler that is defined inside of an include. As of Ansible 2.1, this does work, however the include must be `static`.
|
||
11 years ago
|
|
||
9 years ago
|
Roles are described later on, but it's worthwhile to point out that:
|
||
|
|
||
|
* handlers notified within ``pre_tasks``, ``tasks``, and ``post_tasks`` sections are automatically flushed in the end of section where they were notified;
|
||
|
* handlers notified within ``roles`` section are automatically flushed in the end of ``tasks`` section, but before any ``tasks`` handlers.
|
||
|
|
||
7 years ago
|
If you ever want to flush all the handler commands immediately you can do this::
|
||
11 years ago
|
|
||
|
tasks:
|
||
|
- shell: some tasks go here
|
||
|
- meta: flush_handlers
|
||
|
- shell: some other tasks
|
||
|
|
||
9 years ago
|
In the above example any queued up handlers would be processed early when the ``meta``
|
||
11 years ago
|
statement was reached. This is a bit of a niche case but can come in handy from
|
||
|
time to time.
|
||
|
|
||
|
.. _executing_a_playbook:
|
||
|
|
||
|
Executing A Playbook
|
||
|
````````````````````
|
||
|
|
||
|
Now that you've learned playbook syntax, how do you run a playbook? It's simple.
|
||
|
Let's run a playbook using a parallelism level of 10::
|
||
|
|
||
|
ansible-playbook playbook.yml -f 10
|
||
|
|
||
7 years ago
|
.. _playbook_ansible-pull:
|
||
11 years ago
|
|
||
|
Ansible-Pull
|
||
|
````````````
|
||
|
|
||
|
Should you want to invert the architecture of Ansible, so that nodes check in to a central location, instead
|
||
|
of pushing configuration out to them, you can.
|
||
|
|
||
9 years ago
|
The ``ansible-pull`` is a small script that will checkout a repo of configuration instructions from git, and then
|
||
|
run ``ansible-playbook`` against that content.
|
||
11 years ago
|
|
||
9 years ago
|
Assuming you load balance your checkout location, ``ansible-pull`` scales essentially infinitely.
|
||
11 years ago
|
|
||
11 years ago
|
Run ``ansible-pull --help`` for details.
|
||
11 years ago
|
|
||
9 years ago
|
There's also a `clever playbook <https://github.com/ansible/ansible-examples/blob/master/language_features/ansible_pull.yml>`_ available to configure ``ansible-pull`` via a crontab from push mode.
|
||
11 years ago
|
|
||
11 years ago
|
.. _tips_and_tricks:
|
||
|
|
||
11 years ago
|
Tips and Tricks
|
||
|
```````````````
|
||
|
|
||
8 years ago
|
To check the syntax of a playbook, use ``ansible-playbook`` with the ``--syntax-check`` flag. This will run the
|
||
|
playbook file through the parser to ensure its included files, roles, etc. have no syntax problems.
|
||
|
|
||
11 years ago
|
Look at the bottom of the playbook execution for a summary of the nodes that were targeted
|
||
|
and how they performed. General failures and fatal "unreachable" communication attempts are
|
||
|
kept separate in the counts.
|
||
|
|
||
|
If you ever want to see detailed output from successful modules as well as unsuccessful ones,
|
||
11 years ago
|
use the ``--verbose`` flag. This is available in Ansible 0.5 and later.
|
||
11 years ago
|
|
||
|
|
||
|
To see what hosts would be affected by a playbook before you run it, you
|
||
|
can do this::
|
||
|
|
||
11 years ago
|
ansible-playbook playbook.yml --list-hosts
|
||
11 years ago
|
|
||
|
.. seealso::
|
||
|
|
||
|
:doc:`YAMLSyntax`
|
||
|
Learn about YAML syntax
|
||
|
:doc:`playbooks_best_practices`
|
||
|
Various tips about managing playbooks in the real world
|
||
|
:doc:`index`
|
||
|
Hop back to the documentation index for a lot of special topics about playbooks
|
||
|
:doc:`modules`
|
||
|
Learn about available modules
|
||
8 years ago
|
:doc:`dev_guide/developing_modules`
|
||
11 years ago
|
Learn how to extend Ansible by writing your own modules
|
||
|
:doc:`intro_patterns`
|
||
|
Learn about how to select hosts
|
||
|
`Github examples directory <https://github.com/ansible/ansible-examples>`_
|
||
|
Complete end-to-end playbook examples
|
||
|
`Mailing List <http://groups.google.com/group/ansible-project>`_
|
||
|
Questions? Help? Ideas? Stop by the list on Google Groups
|
||
|
|
||
|
|
||
|
|