Ansible - The HowTo's

mail

How to subtract a list from a list ?

As said in the Ansible documentation, different kinds of filters are available to manipulate data (see links in the code below) :

See the code :

---
#	ANSIBLE_LOCALHOST_WARNING=false ansible-playbook test.yml
- hosts: 127.0.0.1
  connection: local
  gather_facts: no
  tasks:

  - set_fact:
      myList: [ 'a', 'b', 'c', '4', 'd', '9' ]
      groceryList: [ 'fruit', 'vegetables', 'milk', 'FAT', 'SALT', 'SUGAR' ]
      unhealthyFood: [ 'FAT', 'SALT', 'SUGAR' ]

  - set_fact:
      rejectSingleItem: "{{ myList | reject('search', '4') | list }}"
      rejectNonMatching: "{{ myList | reject('match', '[^a-z]') | list }}"
      healthyGroceryList_forLoop: "[ {% for item in groceryList if item not in unhealthyFood %}'{{ item }}'{{ ', ' if not loop.last else '' }}{% endfor %} ]"
      healthyGroceryList_reject: "{{ groceryList | reject('in', unhealthyFood) | list }}"

  - debug:
      var: "{{ item }}"
    loop:
      - rejectSingleItem
      - rejectNonMatching
      - healthyGroceryList_forLoop
      - healthyGroceryList_reject
...
TASK [debug] *****************************************************
ok: [127.0.0.1] => (item=rejectSingleItem) => {
    "ansible_loop_var": "item",
    "item": "rejectSingleItem",
    "rejectSingleItem": [
        "a",
        "b",
        "c",
        "d",
        "9"
    ]
}
ok: [127.0.0.1] => (item=rejectNonMatching) => {
    "ansible_loop_var": "item",
    "item": "rejectNonMatching",
    "rejectNonMatching": [
        "a",
        "b",
        "c",
        "d"
    ]
}
ok: [127.0.0.1] => (item=healthyGroceryList_forLoop) => {
    "ansible_loop_var": "item",
    "healthyGroceryList_forLoop": [
        "fruit",
        "vegetables",
        "milk"
    ],
    "item": "healthyGroceryList_forLoop"
}
ok: [127.0.0.1] => (item=healthyGroceryList_reject) => {
    "ansible_loop_var": "item",
    "healthyGroceryList_reject": [
        "fruit",
        "vegetables",
        "milk"
    ],
    "item": "healthyGroceryList_reject"
}

If you get a no test named 'in' error :

  • The in test was added in Jinja2 2.10 (unfold Changelog or see changelog in release notes), so you may not be up-to-date.
  • Check it :
    dpkg -l | grep jinja2
mail

How to send an alert with Ansible ?

Code Output Notes
- fail:
    msg: "ALERT!"
TASK [fail] *********************************************************************************************
fatal: [myHost]: FAILED! => {"changed": false, "msg": "ALERT!"}

PLAY RECAP **********************************************************************************************
myHost	: ok=n	changed=0	unreachable=0	failed=1	skipped=0	rescued=0	ignored=0
  • extremely visible since the execution aborts immediately
  • depending on situations, we _may_ prefer the execution to continue uninterrupted
- fail:
    msg: "ALERT!"
  ignore_errors: true
TASK [fail] *********************************************************************************************
fatal: [myHost]: FAILED! => {"changed": false, "msg": "ALERT!"}
...ignoring
following tasks, then finally :
PLAY RECAP **********************************************************************************************
myHost	: ok=n	changed=0	unreachable=0	failed=0	skipped=0	rescued=0	ignored=1
  • does not interrupt the execution
  • depending on the number of tasks, the alert goes from slightly less visible to almost invisible, lost in the log output
- block:
    - fail:
        msg: "ALERT!"

  rescue:
    - debug:
        msg: "RESCUE"
TASK [fail] *********************************************************************************************
fatal: [myHost]: FAILED! => {"changed": false, "msg": "ALERT!"}

TASK [debug] ********************************************************************************************
ok: [myHost] => {
    "msg": "RESCUE"
}
following tasks, then finally :
PLAY RECAP **********************************************************************************************
myHost	: ok=n	changed=0	unreachable=0	failed=0	skipped=0	rescued=1	ignored=0
  • does not interrupt the execution
  • requires to rescue the error instead of ignoring it
  • appears in the rescued tasks
  • as above, depending on the context : from less visible to almost hidden
- mail:
  
(nothing special, just like any other task)
  • requires a mail server (+ account) that is reachable by the host
  • with time, scripts sending emails may turn into spam machines
mail

How to specify target hosts ?

A few words of warning :

Examples below :

Now let's target hosts :

Target Syntax Comment
by name
a list of hosts ansible host1,host2,host3
a list of hosts matching a regular expression ansible '~server0[12]\.acme\.org'
  • matches server01.acme.org and server02.acme.org
  • make sure you're not limiting the effective target with the ansible-playbook -l flag
by group
all hosts of a single group ansible groupName
all hosts known to Ansible ansible all
hosts of several groups ansible group1:group2:group3 The colon : actually means a logical OR. This command above applies to any host belonging either to group1 or to group2 or to group3
When it comes to complex rules with intersections and exclusions (see examples), it may not be a REAL "logical OR"
hosts belonging to the 2 specified groups ansible 'group1:&group2'
  • aka intersection of groups
by name + group
all hosts except those matching an expression
  • ansible 'all:!expression*'
  • ansible 'groupName:!badHost'
all hosts of a group except several of them ansible 'groupName:!~(host1|host2)'
  • ~ is used to introduce a regular expression
  • you get the idea to exclude as many hosts as necessary
mail

How to loop on a list of items ?

Let's consider this play :
- hosts: 127.0.0.1
  connection: local
  gather_facts: no
  tasks:

  - set_fact:
      fruits: [
        'apple',
        'orange',
        'banana',
        ]

  - debug:
      var: item
    loop:
      - fruits

  - debug:
      var: item
    loop:
      - "{{ fruits }}"

  - debug:
      var: item
    loop:
      "{{ fruits }}"
which outputs :
TASK [debug] ********************************************************************
ok: [127.0.0.1] => (item=fruits) => {
    "item": "fruits"
}

TASK [debug] ********************************************************************
ok: [127.0.0.1] => (item=[u'apple', u'orange', u'banana']) => {
    "item": [
        "apple",
        "orange",
        "banana"
    ]
}

TASK [debug] ********************************************************************
ok: [127.0.0.1] => (item=apple) => {
    "item": "apple"
}
ok: [127.0.0.1] => (item=orange) => {
    "item": "orange"
}
ok: [127.0.0.1] => (item=banana) => {
    "item": "banana"
}

Explanations

- fruits
  • this passes the string fruits, not the fruits variable
  • use "{{ }}" to pass a variable
- "{{ fruits }}"
  • the leading dash - introduces a list item
  • _HERE_ it's a one-item list, and this item happens to be a list
  • Anyway, this is why the loop turns only once and displays all fruits at once
"{{ fruits }}"
this is what we expected
mail

How to stop ansible-playbook execution (i.e. something like --stop-at-task) ?

Situation :

Solution :

This stops the execution abruptly, with a failure status, making this solution mostly suited for debugging. It has the advantage that :

Exceptions ?

So far, I've found no exception to this rule : when using fail, the playbook execution stops. Period.
However, I've been amazed once, when —despite fail— the execution continued (... or at least _seemed_ to continue). Here's what happened then :
  1. RedHat.yml is a role task file having a fail task, everything should go extremely well
  2. the role RedHat.yml belongs to is applied by myPlaybook.yml
  3. I run myPlaybook.yml on a group of hosts (myGroup has MANY hosts) :
    ansible-playbook myPlaybook.yml -l myGroup
  4. the execution starts normally, I can see a series of expected fatal: [hostname]: FAILED! => {"changed": false, "msg": "Execution stopped on purpose for whatever reason"}, then the output continues with the following tasks ()
Explanation (this is highly specific to my context / code) :
  1. when myPlaybook.yml starts running the role having the RedHat.yml task file, it starts executing main.yml
  2. main.yml makes a conditional include_tasks of RedHat.yml, meaning some hosts get it, some don't. This is where the trick happens :
    • RedHat.yml is included by Red Hat servers
    • but there is also Debian.yml, with a similar set of tasks (hence tasks names) for Debian servers
  3. the hosts who actually included RedHat.yml fail as expected (and are removed from the list of the playbook targets)
  4. the other hosts continue normally, which is what I can see
    the tasks running at that time are not from RedHat.yml anymore but from Debian.yml
  5. reasons why I've not been able to see what was happening :
    • large number of hosts in myGroup : the execution lists all of them and scrolls fast
    • cryptic hostnames : pretty difficult to spot that some hosts have disappeared after the fail
    • identical task names in RedHat.yml and in Debian.yml : when the playbook execution showed the name of the task right after the fail, I thought the execution of the task I edited file continued.

Alternate solution :

This _can_ do the job too :
- meta: end_play
But it's fairly different from the fail method : Check it with metaEndPlay.yml.
mail

How to override /etc/ansible/ansible.cfg settings with personal values ?

Create ~/.ansible.cfg and replicate + override the required section / values :
[defaults]
host_key_checking	= False
inventory		= /home/stuart/ansible/hosts
roles_path		= /home/stuart/ansible/roles
vault_password_file	= /var/lib/ansible/.vault_password
mail

How to use with_items ?

Because I can never remember how to use with_items, here's an example :
- name: unmount volume groups
  mount:
    src: "{{ item.device }}"
    name: "{{ item.mountPoint }}"		will be path in Ansible 2.3+
    state: unmounted
  with_items:
    - { device: '/dev/mapper/vg_data-data', mountPoint: '/var/lib/docker/devicemapper' }
    - { device: '/dev/mapper/vg_data-data', mountPoint: '/var/lib/docker' }