BEST PRACTICES (source [best_practices](https://www.ansible.com/blog/ansible-best-practices-essentials))
-
Name your plays and Tasks Always name you plays and tasks, for example:
- hosts: web
name: install and start apache
taks:
- name: install apache
yum:
name: httpd
state: latest
-
Use Prefixes and Human Meaningful Names with Variables The recommendation is prefix variables with the source or target of the data it represents. Prefixing variable is particulary vital with developing reusable and portables roles.
apache_max_keepalive: 25
apache_port: 80
ssh_port: 22
-
Use Native YAML Syntax Use native YAML for best readability, the native YAML has more lines but those lines are shorter reducing horizontal scrolling and line wrapping.
Don not abuse of command, shell or raw modules. Use the Ansible modules to perform de tasks because they are idempotent.
In the past, you had to delete or comment out your debug tasks and that was suboptimal approach though.
Starting in 2.1, the Ansible debug moduel supports a verbosity parameter that suppresses output unless the play is being run with a sufficiently high enough verbosity level.
- debug:
msg: "This always is displayed"
- debug:
msg: "This only displays with ansible-playbook -vv+"
verbosity: 2
Execute ansible ad-hoc in several hosts without inventory:
$ ansible all -i host1.org, -m shell -a 'uptime' -u user1 -k
[requirements](http://docs.ansible.com/ansible/latest/intro_installation.html#control-machine-requirements)
Ansible can be run from any machine with Python 2 (versions 2.6 or 2.7) or Python 3 (versions 3.5 and higher). Windows is not supported for control machine.
For this kind of nodes the following requirements are needed:
-
SSH (default uses sftp but you can switch to scp in ansible.cfg)
-
Python 2.6 or later**
-
Ansible raw module and the script module do not even need python, so you can use ansible to install python-simplejson using the raw module, which then allows you to use everything else.
-
-
libselinux-python if you have SELinux enabled on remote nodes (to use copy/file/template functions in Ansible)
[configuration_file.html](http://docs.ansible.com/ansible/latest/intro_configuration.html)
Use in order of preference:
+ Contents of $ANSIBLE_CONFIG
environment variable
+ ./ansible.cfg
+ ~/.ansible.cfg
+ /etc/ansible/ansible.cfg
To see what will be the config used by ansible:
# ansible --version
ansible 2.3.1.0
config file = /tmp/ansible.cfg
[intro_inventory.html](http://docs.ansible.com/ansible/latest/intro_inventory.html), [intro_dynamic_inventory.html](http://docs.ansible.com/ansible/latest/intro_dynamic_inventory.html)
Two kind of definitions for inventory file:
Blocks define groups. Hosts allowed in more than one group.
Hostname ranges: www[01:50].example.com
, db-[a:f].example.com
Per-host variables: thor.example.com ansible_ssh_port=2424 variable1=var1 variable2=var2
-
[foo:children]
: new group foo containing all members if included groups -
[foo:vars]
: variable definition for all members of group foo
Inventory file defaults to /etc/ansible/hosts
. Veritable with -i
or in the configuration file. The file can also be a dynamic inventory script. If a directory, all contained files are processed.
Definition of inventory:
all:
hosts:
mail.example.com
children:
webservers:
hosts:
foo.example.com:
bar.example.com:
dbservers:
hosts:
one.example.com:
two.example.com:
three.example.com:
Definition of variables for a host (or group)
hosts:
jumper:
ansible_port: 5555
ansible_host: 192.0.2.50
[intro_patterns](http://docs.ansible.com/ansible/latest/intro_patterns.html)
Used on the ansible
command line or in playbooks:
-
all
or*
-
hostname:
lab.example.com
-
groupname:
webservers
orwebservers:dbservers
-
exclude:
webservers:!phoneix
-
intersection:
webservers:&staging
You can do combinations:
websersers:dbservers:&staging:!phoneix
You can also user variables if you want to pass some group specifiers via the "-e" argument to any ansible playbook:
webservers:!{{excluded}}:&{{required}}
Also you can use wildcards: .exampe.com
or 192.168.1. and regular expressions: ~(web|db).*\.example\.com
And finally you can refer to hosts within the group by adding a subscript to the group name. For instance if you have the following group:
[webservers]
web1
web2
web3
You can select a host or subset of hosts from a group by their position:
webservers[0] # web1
webservers[0:1] # web1,web2
webservers[1:] # web2,web3
These are my favourite options in .vimrc to edit yaml files for Ansible:
autocmd FileType yaml setlocal ai ts=2 sw=2 et nu cuc
autocmd FileTyep yaml colo desert
-
Jinja templates uses {% EXPR %} for expressions and logic
-
And {{ }} for outputting the results and expressions.
For example:
{{ ansible_facts['default_ipv4']['address'] }} {{ ansible_facts['hostname'] }}
The order when the tasks are running:
-
pre_tasks |__ handler notification (pre_tasks)
-
roles
-
tasks
-
handlers notified by roles and tasks
-
post_tasks |__ handler by post_taks
A sequence of characteres.
my_string_var: This contains a string
For better readability, you can use pipe caracter |
to break the string in several lines, or the operator >
to suppres the break line but used for read beatter.
string_with_break_lines: |
This string
is represented
with several lines
string_whitout_line_breask: >
This string will
shown in just
one line
The date has to be in the ISO-8601 standard, for example:
date_time_ok: 2019-05-30T10:05:25.23+02:00
date_time_short: 2019-05-30
Array is a list of values and the difference with Dictionaries is that the second ara a list of key=values
Two different ways to define an Array in Yaml:
my_own_list: ['Tere', 'Alba', 'Ainhoa']
my_own_list_2:
- Tere
- Alba
- Ainhoa
To access the values of the Array, like follows:
12 - name: Check how to access the element of the array 13 assert: 14 that: 15 - my_own_list['0'] == 'Tere'
Consist of key=values list
This is an example of how to define a dictionary:
my_dictionary: { Emma: Human, Laika: Dog, Alba: Human }
my_dictionary_2:
Emma: Human
Laika: Dog
Alba: Human
An example of how to access the elements of a dictionary:
assert:
that: my_dictionary['Laika'] != 'Human'
Note
|
You can also use the notation 'my_dictionary.Laika' to access the element, but this is not recommended because it can collide with some reserverd names for attributes of methos of Python dict. |
You can use Jinja2 filtering options to convert the given variables. You can check the Jinja offical documentation
For instance, to convert a number variable to string:
{{ my_number | string }}
{{ my_name | capitalize }}
You can also process the information using the unique
and eq
Jinja filters:app-name:
- name: Playing with data processing
assert:
that:
- "{{ [1, 4, 2, 2] | unique | sort }} is eq ( [1, 2, 4] )"
register: output
tags: filter
Providing default values
Examples:
{{ some_variable | default(5) }}
-→ some_variable = 5 when it is not defined
{{ lookup(''env), 'MY_USER') | default('admin', true)
-→ use default value admin when the variable MY_USER is not defined or is empty
Making variables optional
By default Ansibe requires values for all variables in a templated expresion. I you want to make optional some variable you can use special variable omit
:
- name: Create users with optional homedir
user:
name: "{{ item.name }}"
shell: /bin/bash
groups: wheel
append: yes
home: "{{ item.homedir | default(omit) }}"
loop:
- name: james
homedir: /opt/james
- name: toni
- name: pepe
In this example the default home directory for users toni an Pepe will be /home/{{ user.name }} and for james will be /opt/james.
Mandatory values
To setup a variable which is mandatory. If there is no value provided you will get an error.
{{ my_value | mandatory }}
Defining different values for true/false/null(ternary)
{{ (pet_type == "dog") | ternary('Dog','Cat') }}
-→ if sentence is True return Dog and if false cat.
In addition, you can define a one value to use on tru, one value on false and third value on null:
{{ enabled | ternary('no shutdown', 'shutdown', omit) }}
Managing data Types
Discovering data types
- name: Check type from variable
debug:
msg: "{{ my_number | default(42.0) | type_debug }}"
TASK [Check type from variable] ******************************************************************************************************************************
ok: [127.0.0.1] => {
"msg": "float"
}
Transforming dictorionaris into lists
Use dict2items
filter to transform a dictionary into a list of items suitable for looping
:
ansible_check_mode
Boolean that indicates ansible is in check mode
ansible_config_file
: The full path of ansible.cfg
ansible_forks
: integer showing number of maximum forks available in the runing tasks
ansible_inventory_sources
: List of sources used as inventory
inventory_hostname
: the current hosts being iterated over in the play
hostvars
: A dictionary/map with all the hosts in inventory and variables assigned to them
https://docs.google.com/document/d/1NsJSJjO5e6bpNHg0rVzY30fm6poF5VLZCzfiEDWX3KE/editgroup_names
: List of groups the current host is part of
Check all the list of available magic variables
ansible_host
: The ip/name of the target host ot use instead inventory_hostname
ansible_user
: the Ansible user logs in as.
There are many filters available, ones from Jinja2 and others provided by Red Hat Ansible engine.
mandatory
{{ my_variable | mandatory }}
default
You can establish a default value in case there is no value provided, for example:app-name:
{{ my_vaule | default('my_default, True') }}
omit
You can also force to an undefined value in case there is no value provided:
"{{ my_value | default(omit) }}"
You can use the following arithmetic operations:
Operator |
Purpose |
+ |
Add two numbers |
- |
Subsctract two number |
For example, you can use
To sum the values of a List:
tasks:
- name: The sum of a list
debug:
msg: "The sum of a list is [1,2,3,4]: {{ [1, 2, 3, 4] | sum }}"
Merging Lists
You can use flatten
to merge several lists.
- name: Merge several lists
debug:
msg: "{{ [ 2, 3, 4, [ 2, 5, 6]] | flatten }}"
tags: unir
Or sorting a list:
"{{ [2, 3, 9, 1 ,5, 4, 7 ,6 ,8] | sort }}"
Modifying Order
"{{ [2, 4, 5, 8. 9 ] | reverse | list }}"
"{{ [2, 4, 5, 8. 9 ] | sort | list }}"
Find the difference
"{{ [ 2, 3, 5, 8, 9 ] | difference([2, 4, 16]])}}"
Merge dictionaries
- name: Merge dictionaries
debug:
msg: "{{ { 'A': 1, 'B':5 } | combine( {'B':2, 'C':3 }) }}"
Hashing strings and passwords
The hash
filter returns the has value in string format. As an example:
- name: Use hash filter in strings
vars:
# generated by "echo 'Toni' | sha1sum"
expected: 'ee0f3f344826f230ec3581f9924aee07945b0cbb'
tasks:
assert:
that:
- "{{ 'Toni' | hash('sha1') }} is eq( expected )"
Query Lines
Read the files and save it in lines
{{ query('lines','cat /etc/pass') }}
Difference between query
and lookup
plugins are:
-
query
is a Jinja2 function for invoking lookup plugins. The main difference is thatquery
will always return a list. -
lookup
is an Ansible plugin, and the default behaviour oflookup
is to return a string of comma separated values. You can usewantList=True
to have the same list return asquery
.
Resources:
* [https://docs.ansible.com/ansible/latest/user_guide/complex_data_manipulation.html] * http://blog.networktocode.com/post/jinja-map-review/
---
- name: Playing with selectattr
hosts: localhost
become: false
tasks:
- name: Get using selectattr the 'name' elements from the list of dictionaries in 'hosts'
vars:
hosts:
- name: bastion
ip:
- 192.168.1.254
- 192.168.1.1
- name: classroom
ip:
- 192.168.1.254
- 192.168.1.1
ansible.builtin.assert:
that:
# This example verifies the 'name' key is defined and get the value of it
- "{{ hosts | selectattr('name', 'defined') | map (attribute='name') }} is eq( ['bastion','classroom'] ) "
tags: selectattr_example
- name: Get the 'name' elements from the list of dictionaries in 'hosts'
vars:
hosts:
- name: bastion
ip:
- 172.25.250.254
- 172.25.252.1
- name: classroom
ip:
- 172.25.252.254
ansible.builtin.assert:
that:
- "{{ hosts | json_query('[*].ip') }} is eq( ['bastion','classroom'] )"
tags: json_query
Collections are basically how Ansible content is packaged and distributed. A collection provides a set of related modules, roles, and plug-ins that you can download to your ansible control node.
With Collections, you can separate the ansible core development from other ansible’s content vendors.
A collection is organized into namespaces
to make it eaiser to specify different collections.
To list a specify collection, you must use a valid fully qualified collection name (FQCN).
Examples of FQCN:
-
From community namespace:
community.crypto
,community.postgresql
, etc. -
For redhat supported collections:
redhat.satellite
,redhat.insights
, etc.
The name of namespaces are limited to ASCII lowercase letters, numbers, and underscores, must be at least two characters long, and must not start with an underscore.
To access the documentation ansible-navigator collections --eei ee-supported-rhel8:latest
or ansible-navigator doc -l --eei ee-supported-rhel8:latest -m stdout.postgresql
, etc.
- For redhat supported collections: redhat.satellite
, redhat.insights
, etc.
The name of namespaces are limited to ASCII lowercase letters, numbers, and underscores, must be at least two characters long, and must not start with an underscore.
To access the documentation ansible-navigator collections --eei ee-supported-rhel8:latest
or ansible-navigator doc -l --eei ee-supported-rhel8:latest -m stdout`
Formats: - JSON or YAML - ansible-navigator.yml or ansible-navigator.json
Ansible navigator looks for a settings file in the following order and uses the first file that it finds:
- ANSIBLE_NAVIGATOR_CONFIG
env variable.
- ansible-navigator.yml
file in your current Ansible project directory.
- ~/.ansible-navigator.yml
in your home directory
Example of ansible-navigator.yml
config:
--- ansible-navigator: ansible: config: /tmp/ansible.cfg ansible-runner: arfifact-dir: /tmp/test1 logging: level: critical editor: command: /bin/emacs playbook-artifact: enable: false execution-environment: image: ee-supported-rhel8:latest pull-policy: always
-
Ansible Navigator settings[https://ansible-navigator.readthedocs.io/en/latest/settings/]