Iteration
Loops
- name: add several users
user:
name: "{{ item }}"
state: present
groups: "wheel"
loop:
- testuser1
- testuser2
If you have defined a YAML list in a variables file, or the ‘vars’ section, you can also do:
loop: "{{ somelist }}"
Packages
Some plugins like, the yum and apt modules can take lists directly to their options, this is more optimal than looping over the task. See each action’s documentation for details, for now here is an example:
- name: optimal yum
yum:
name: "{{ list_of_packages }}"
state: present
- name: non optimal yum, not only slower but might cause issues with interdependencies
yum:
name: "{{item}}"
state: present
loop: "{{ list_of_packages }}"
Note that the types of items you iterate over do not have to be simple lists of strings. If you have a list of hashes, you can reference subkeys using things like:
- name: add several users
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
loop:
- { name: 'testuser1', groups: 'wheel' }
- { name: 'testuser2', groups: 'root' }
Also be aware that when combining Conditionals with a loop, the when:
statement is processed separately for each item. See The When Statement for an example.
To loop over a dict, use the dict2items
Dict Filter:
- name: create a tag dictionary of non-empty tags
set_fact:
tags_dict: "{{ (tags_dict | default({})) | combine({item.key: item.value}) }}"
loop: "{{ tags | dict2items }}"
vars:
tags:
Environment: dev
Application: payment
Another: "{{ doesnotexist | default() }}"
when: item.value != ""
Pick host from group:
docker_host: "{{ groups['dockerhosts'][0] }}"
Pass variables to playbook
# short
ansible-playbook release.yml -e "version=1.23.45 other_variable=foo"
# long
ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo"
# as json
ansible-playbook release.yml --extra-vars '{"pacman":"mrs","ghosts":["inky","pinky","clyde","sue"]}'
# from file and from cli
ansible-playbook release.yml --extra-vars "@some_file.json" --extra-vars "other_variable=foo"
Join a list
- name: Concatenate the public keys
set_fact:
my_joined_list: "{{ my_list | join('\n') }}"
List ansible facts
ansible -m setup -i srv-dev-app1b, all
Only gather subset of facts
ansible all -m setup -a 'gather_subset=network,virtual' -i environments/aws-eu-dev/hosts --limit=srv-dev-app1b
List hosts in inventory
ansible-inventory --list --yaml --playbook-dir=environments/development
Reliably get remote user name
ansible_user
is used when we want to specify default SSH user in ansible hosts file whereas remote_user
is used in playbook context: https://github.com/ansible/ansible/blob/c600ab81ee/lib/ansible/playbook/play_context.py#L46-L55
# the magic variable mapping dictionary below is used to translate
# host/inventory variables to fields in the PlayContext
# object. The dictionary values are tuples, to account for aliases
# in variable names.
MAGIC_VARIABLE_MAPPING = dict(
connection = ('ansible_connection',),
remote_addr = ('ansible_ssh_host', 'ansible_host'),
remote_user = ('ansible_ssh_user', 'ansible_user'),
port = ('ansible_ssh_port', 'ansible_port'),
Here is an example of using ansible_user
in ansible hosts
file:
[targets]
localhost ansible_connection=local
other1.example.com ansible_connection=ssh ansible_user=mpdehaan
other2.example.com ansible_connection=ssh ansible_user=mdehaan
Use {{ ansible_user_id }}
to detect the user name on the remote machine that Ansible operates under even if you set become: true
. For the become user, use {{ ansible_become_user }}
instead (default=root)
Become when default sudo is unavailable
# DC privilege workaround:
# executing commands as admin via sudo is not possible, the only way to
# use admin privileges is to become the root user with "sudo su"
ansible_become_method: "su"
ansible_become_exe: "sudo su -"
List all Ansible hostvars
Playbook: hostvars.yml
---
- hosts: all
tasks:
- name: Print all variables for each remote device
debug:
var: hostvars[inventory_hostname]
Run it with:
# Run against all hosts in the inventory
ansible-playbook -i "environments/aws-eu-dev/hosts" hostvars.yml
# Limit to specific hosts (requires "hosts: all" in the play)
ansible-playbook -i "environments/aws-eu-dev/hosts" hostvars.yml --limit=host1,host2
# Specify one or more hosts without an inventory
# Note the comma (,) at the end; this signals that it's a list, not a file.
ansible-playbook -i "host1,host2," hostvars.yml
Helper scripts
Multi vault edit
#/usr/bin/env bash
# https://docs.ansible.com/ansible/latest/user_guide/vault.html#editing-encrypted-files
SCRIPT_DIR=$(dirname "$0")
cd "${SCRIPT_DIR}/../"
vault_files=$(find ./environments -name 'vault.yml' | sort)
# handle "Input is not from a terminal" error that can break the terminal
# Vim expects its stdin to be the same as its controlling terminal but xargs
# sets it to /dev/null
# see: https://superuser.com/questions/336016/invoking-vi-through-find-xargs-breaks-my-terminal-why
case "$(uname -s)" in
Darwin)
echo "$vault_files" | fzf -e -m | xargs -o ansible-vault edit
;;
Linux)
echo "$vault_files" | fzf -e -m | xargs bash -c '</dev/tty ansible-vault edit "$@"' ignoreme
;;
esac
Bad character finder
#!/bin/bash
TARGET_PATH=${1:-$PWD}
# U+00A0 non-breaking space (NBSP)
# U+02002 en space
# U+02003 em space
# U+02004 three-per-em space
# U+02005 four-per-em space
# U+02006 six-per-em space
# U+02007 figure space
# U+02008 punctuation space
# U+02009 thin space
# U+0200A hair space
# U+0202F narrow no-break space
# U+0205F medium mathematical space
# U+03000 ideographic space
case "$(uname -s)" in
Darwin*)
command -v ggrep >/dev/null 2>/dev/null || { echo 'Please run: brew install gnu-grep'; exit 1; }
command -v gsed >/dev/null 2>/dev/null || { echo 'Please run: brew install gnu-sed'; exit 1; }
ggrep --recursive --files-with-matches --binary-files=without-match --perl-regexp "\xa0" "${TARGET_PATH}" | xargs gsed -i 's/\xC2\xA0/ /g'
;;
Linux*)
grep --recursive --files-with-matches --binary-files=without-match --perl-regexp "\xa0" "${TARGET_PATH}" | xargs sed -i 's/\xC2\xA0/ /g'
;;
*)
echo 'unsupported OS'; exit 1;
;;
esac