Random Ansible "Tricks"
Posted Nov 12 2016, 09:08 by William Shallum [updated Nov 12 2016, 09:09]
Ansible is a thing used at work, it is sometimes rather annoying, but at least it’s somewhat easy to debug. Here are some things I found during experimenting.
No such thing as async include
The unit of reuse is the yml file. What I would like to do is something like this pseudocode:
jobs = []
for (i = 1 to 10) {
jobs += async {
do_some(i)
long_running(i)
tasks(i)
}
}
wait_for_all(jobs)
async applies only to a task. Include is not a task, therefore you cannot do this:
- include: somefile.yml i={{ item }}
with_items:
- 1
- 2
async: 3600
poll: 0
register: jobs
- async_status: jid=item.ansible_job_id
register: job
until: job.finished
retries: 3000
with_items: "{{ jobs.results }}"
so as a workaround, if there is only one long running task in the included file:
- set_fact: jobs: []
- include: somefile.yml i={{ item }}
with_items: "{{ items }}"
- async_status: jid=item.ansible_job_id
register: job
until: job.finished
retries: 3000
with_items: "{{ jobs }}"
somefile.yml:
- quick_pre_task: i={{ i }}
- long_running_task: i={{ i }}
async: 3600
poll: 0
register: long_running
- set_fact:
jobs: "{{ jobs|default([]) + [long_running] }}
This is equivalent to this pseudocode (i.e. quick_pre_task
is still run synchronously and serially):
jobs = []
for (i = 1 to 10) {
quick_pre_task(i)
jobs += async {
long_running_task(i)
}
}
wait_for_all(jobs)
Ugh. Other workarounds may include just running another ansible-playbook command as async - say goodbye to your context.
Using Jinja2 control structures in the playbook
So let’s say you want the private IP address, no matter what interface it is on, as a parameter. Normally to use the value of a variable you would do:
- sometask: someparam={{ somevar }}
{{ somevar }}
is of course evaluated using Jinja2 resulting in the value of the variable being substituted in. All very good, but the Jinja2 engine is used in its full glory here, so you can actually do:
- sometask: someparam="{% for ip in ansible_all_ipv4_addresses if ip.startswith('10.') %}{% if loop.first %}{{ ip }}{% endif %}{% endfor %}"
People will probably hate you for that.
As a sidenote, it seems that the basic structure (command option=argument
) is parsed first before argument is passed to Jinja2 so you cannot do:
- set_fact: stuff: "option=argument"
- sometask: "{{ stuff }}"
Which is probably a good thing, otherwise we’ll be hearing people talk about “parameter injection” vulnerabilities.
Passing complex variable values on the command line
If you have a complex playbook with lots of parameters that have to be validated it may be easier to call it from another script that will check the parameters first to ensure they make sense.
Variables can be set when calling ansible-playbook with -e
, e.g.:
ansible-playbook -e 'somevar=somevalue' playbook.yml
However, the --help
actually states:
-e EXTRA_VARS, --extra-vars=EXTRA_VARS
set additional variables as key=value or YAML/JSON
so this is valid and works as expected:
ansible-playbook -e '{"somearray": [1, 2, 3], "othervar":
{"objects": "too"}}' playbook.yml
JSON serialization is, of course, just an import away in most programming languages.