Skip to content
Jul 29 / Greg

Ansible Omnibus Examples – Filtering, Loops, Conditionals, Tips


The great sales leader Tony Owens once said “Steal the wheel, don’t reinvent it.” With that in mind, here’s my github repo; feel free to grab and modify when/where you can.

I plan to revisit and update this article on a semi regular basis to keep adding new content, so keep checking back.
Also I welcome any and all feedback.

Loops

Loops give you the ability to iterate through a list which makes task completion much more efficient(or can at any rate).

In this example I use the yum module with a list, to install/update packages on a RHEL based device. Notice the variable “{{ item }}” here. By default when you define a loop, the product of that loop is referred to as item in variable form. This means at first iteration item will equal httpd, then at the next iteration it will equal dovecot.

1
2
3
4
5
6
- yum:
    name: "{{ item }}"
    state: latest
  loop:
    - httpd
    - dovecot

Have a look here at version two of the above loop. This is what you are more likely to encounter when working with other modules to gather information. The variable mail_services is a list(which equates to an array). Here I specify the loop should use mail_services and iterate through each member.

1
2
3
4
5
6
7
8
9
10
vars:
  mail_services:
    - httpd
    - dovecot
 
tasks:
  - yum:
      name: "{{ item }}"
      state: latest
    loop: "{{ mail_services }}"

Efficiency Note
Looping in modules isn’t always the most efficient way of processing information, though. The yum module allows you to specify multiple services in the form of a list like this:

1
2
3
4
5
  - yum:
      name: 
        - httpd
        - dovecot
      state: latest

The difference seems subtle, does it not? When you perform a loop it has to recall the module for each item in the array, which can take quite a while. When you just specify all of the modules in the name section(which can just be an list variable), it processes that block of code at once, making it much more efficient. Having said all of this, not every module allows you to specify multiple items like this, so sometimes a loop is the only option; be sure to check the documentation to see what options are available to you.

Looping through lists of dictionaries:
This adds a little more complication, and you may see it defined in multiple forms. Here’s two version of the same thing:

1
2
3
4
5
6
7
- user:
    name: "{{ item.name }}"
    state: present
    groups: "{{ item.groups }}"
  loop:
    - { name: 'greg', groups: 'tacos' }
    - { name: 'sowell', groups: 'robot' }
1
2
3
4
5
6
7
8
9
10
11
vars:
  sys_users:
    - name: greg
      groups: tacos
    - name: sowell
      groups: robot
- user:
    name: "{{ item.name }}"
    state: present
    groups: "{{ item.groups }}"
  loop: "{{ sys_users }}"

Looking at the above two options I actually think version two makes more sense(this is how you are most likely to encounter this information). Most often you won’t statically define this stuff, rather it will be returned from some other task.
Make note how the name and groups are referenced in the loop as “item.keyvalue”.
That’s one of the tricky bits about Ansible, there are a lot of ways to accomplish the same task 🙂

Nested Loops
This is a bit of a trick question 🙂 Loops can’t be nested, BUT what you can do is have a loop that calls a role or another task yaml file. Take for example the following task

1
2
3
4
5
6
7
8
9
10
11
12
13
---
- name: nested loop test
  hosts: localhost
  gather_facts: faluse
 
  tasks:
  - name: loop through items and include task file for further looping
    include_tasks: tasks/2nd_loop.yml
    loop:
      - Greg
      - Jimmy
    loop_control:
      loop_var: outter_names

Contents of tasks/2nd_loop.yml

1
2
3
4
5
6
7
8
9
10
---
- name: the inner loop
  debug:
    msg: "{{ outter_names }} loves {{ inner_food }}"
  loop:
    - burgers
    - pizza
    - waffle fries
  loop_control:
    loop_var: inner_food

You’ll notice some new stuff here; namely “loop_control”. This gives you multiple new abilities on loops, but here you see that I use “loop_var”. This allows me to rename how the loop is referenced in the resulting task(or called tasks). In the original playbook I name the outer loop variables outer_names and on the called task I rename them inner_food. The reason this is so important is that by default loop results are named “item”. If you are nesting loops, then both loops would attempt to use the same name of “item” and that would create a conflict. If I rename my loop variables, then everything keeps working correctly and is also much easier to read.

This will result in:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[[email protected] ansible]# ansible-playbook outerloop.yml
 
PLAY [nested loop test] ***************************************************************************************************************************************************************
 
TASK [loop through items and include task file for further looping] *******************************************************************************************************************
included: /etc/ansible/tasks/2nd_loop.yml for localhost
included: /etc/ansible/tasks/2nd_loop.yml for localhost
 
TASK [the inner loop] *****************************************************************************************************************************************************************
ok: [localhost] => (item=burgers) => {
    "msg": "Greg loves burgers"
}
ok: [localhost] => (item=pizza) => {
    "msg": "Greg loves pizza"
}
ok: [localhost] => (item=waffle fries) => {
    "msg": "Greg loves waffle fries"
}
 
TASK [the inner loop] *****************************************************************************************************************************************************************
ok: [localhost] => (item=burgers) => {
    "msg": "Jimmy loves burgers"
}
ok: [localhost] => (item=pizza) => {
    "msg": "Jimmy loves pizza"
}
ok: [localhost] => (item=waffle fries) => {
    "msg": "Jimmy loves waffle fries"
}
 
PLAY RECAP ****************************************************************************************************************************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Code Block Loops
Here’s another gotcha; code blocks like this can’t be looped. The following fails:

1
2
3
4
5
6
7
8
- name: why can't we loop this?
  block:
    - name: print stuff
      debug:
        msg: "{{ item }}"
  loop:
    - too bad
    - so sad

Lookups

Lookup plugins allow access to outside data sources. This can be pulling info from files on the local system to say pulling the time from your ansible control node. Keep in mind that they run on the control node only(not on the remote host). There are SO MANY lookup plugins. I suppose I’ll add my favorites from time to time here.

Lookup plugins are usually called something like this:

1
2
vars:
  file_contents: "{{lookup('file', 'path/to/file.txt')}}"

In the above notice it is called within the mustaches “{{}}”. This tells ansible to break out into jinja2 for processing. Here the lookup plugin calls for a file, then supplies the file to pull it from.
Lookup plugins also have error handling in the form of the errors option:

1
2
- name: file doesnt exist, but i dont care .. file plugin itself warns anyways ...
  debug: msg="{{ lookup('file', '/idontexist', errors='ignore') }}"

Options are ignore, warn, or strict(strict is the default and will cause the task to fail).

By default a lookup plugin returns a comma separated list of values, which if you want to use it in a loop isn’t great. You can add the option wantlist=True or you can substitute the lookup plugin for the query plugin if you want a list returned. The query plugin returns a list by default. Here are the options side by side:

1
2
3
4
5
lookup('dict', dict_variable, wantlist=True)
 
query('dict', dict_variable)
 
q('dict', dict_variable)

q = query in the above.

Filters

Stuff here soon.

1
 

Conditionals

Stuff here soon.

1
 

Random Tips

Spanning Multiple Lines with Values
You can quickly test what it does with this website(don’t even have to do it within ansible).
The pipe keeps everything just as seen on multiple lines(it puts line breaks after each line).

1
2
3
4
5
6
7
multiple_lines: |
  this line
  will read just
  how you see
  it here.  Where
  these will all be 
  multiple lines.

The greater than will concatenate everything into a single line

1
2
3
4
5
6
7
single_line: >
  all of this
  shows up
  as a single
  line.  This
  allows you to just
  break up long lines.

Both of the above | and > have “\n” new lines at the end of them. You can add the – to the end of them to remove new lines as in >- or |-.

1
2
3
4
5
single_line_no_newline: >-
  this is all 
  one line
  with no
  new line at the end of it.

1
 
1
 
Leave a Comment

 

*