44 KiB
Ansible
- TL;DR
- Configuration
- Inventories
- Templating
- Validation
- Error handling
- Output formatting
- Roles
- Create custom filter plugins
- Execution environments
- Secrets management
- Troubleshooting
- Print all known variables
- Force notified handlers to run at a specific point
- Time tasks execution
- Run specific tasks even in check mode
- Dry-run only specific tasks
- Set up recursive permissions on a directory so that directories are set to 755 and files to 644
- Only run a task when another has a specific result
- Define when a task changed or failed
- Set environment variables for a play, role or task
- Set variables to the value of environment variables
- Check if a list contains an item and fail otherwise
- Define different values for
true/false/null - Force a task or play to use a specific Python interpreter
- Provide a template file content inline
- Python breaks in OS X
- Load files' content into variables
- Only run a task when explicitly requested
- Using AWS' SSM with Ansible fails with error Failed to create temporary directory
- Future feature annotations is not defined
- Further readings
TL;DR
# Install.
pip3 install --user 'ansible'
brew install 'ansible' 'sshpass' # darwin
sudo pamac install 'ansible' 'sshpass' # manjaro linux
# Generate example configuration files with entries disabled.
ansible-config init --disabled > 'ansible.cfg'
ansible-config init --disabled -t 'all' > 'ansible.cfg'
# Show the current configuration.
ansible-config dump
# Show hosts' ansible facts.
ansible -i 'path/to/hosts/file' -m 'setup' all
ansible -i 'host1,hostN,' -m 'setup' 'host1' -u 'remote-user'
ansible -i 'localhost,' -c 'local' -km 'setup' 'localhost'
# List hosts.
ansible-inventory -i 'inventory' --list
ansible-playbook -i 'inventory' 'playbook.yml' --list-hosts
ansible -i 'inventory' all --list-hosts
# Check the syntax of a playbook.
# This will *not* execute the plays inside it.
ansible-playbook 'path/to/playbook.yml' --syntax-check
# Execute playbooks.
ansible-playbook 'path/to/playbook.yml' -i 'hosts.list'
ansible-playbook … -i 'host1,host2,hostN,' -l 'hosts,list'
ansible-playbook … -i 'host1,host2,other,' -l 'hosts-pattern' --step
# Show what changes (with details) a play would apply to the local machine.
ansible-playbook 'path/to/playbook.yml' -i 'localhost,' -c 'local' -vvC
# Only execute tasks with specific tags.
ansible-playbook 'path/to/playbook.yml' --tags 'configuration,packages'
ansible-playbook -i 'localhost,' -c 'local' -Dvvv 'playbook.yml' -t 'container_registry' --ask-vault-pass
# Avoid executing tasks with specific tags.
ansible-playbook 'path/to/playbook.yml' --skip-tags 'system,user'
# Check what tasks will be executed.
ansible-playbook 'path/to/playbook.yml' --list-tasks
ansible-playbook … --list-tasks --tags 'configuration,packages'
ansible-playbook … --list-tasks --skip-tags 'system,user'
# Debug playbooks.
ANSIBLE_ENABLE_TASK_DEBUGGER=True ansible-playbook …
# Encrypt data using Vault.
ansible-vault encrypt_string --name 'command_output' 'somethingNobodyShouldKnow'
ansible-vault encrypt '.ssh/id_rsa' --vault-password-file 'password_file.txt'
ansible-vault encrypt --output 'ssh.key' '.ssh/id_rsa'
# Print out decoded contents of files encrypted with Vault.
ansible-vault view 'ssh.key.pub'
ansible-vault view 'ssh.key.pub' --vault-password-file 'password_file.txt'
# Edit decoded contents of files encrypted with Vault.
ansible-vault edit 'ssh.key.pub'
ansible-vault edit 'ssh.key.pub' --vault-password-file 'password_file.txt'
# Decrypt files encrypted with Vault.
ansible-vault decrypt 'ssh.key'
ansible-vault decrypt --output '.ssh/id_rsa' --vault-password-file 'password_file.txt' 'ssh.key'
# List roles installed from Galaxy.
ansible-galaxy list
# Install roles from Galaxy.
ansible-galaxy install 'namespace.role'
ansible-galaxy install --roles-path 'path/to/ansible/roles' 'namespace.role'
ansible-galaxy install 'namespace.role,v1.0.0'
ansible-galaxy install 'git+https://github.com/namespace/role.git,commit-hash'
ansible-galaxy install -r 'requirements.yml'
# Create new roles.
ansible-galaxy init 'role_name'
ansible-galaxy role init --type 'container' --init-path 'path/to/role' 'name'
# Remove roles installed from Galaxy.
ansible-galaxy remove 'namespace.role'
Galaxy collections and roles worth a check:
| ID | Type | Description |
|---|---|---|
| sivel.toiletwater | collection | Extra filters, mostly |
UIs:
| UI | Static inventories | Dynamic inventories |
|---|---|---|
| AWX | ✓ | ✓ |
| Rundeck | ✓ | ? |
| Semaphore | ✓ | ✗ |
| Zuul | ? | ? |
Configuration
Ansible can be configured using INI files named ansible.cfg, environment variables, command-line options, playbook
keywords, and variables.
The ansible-config utility allows to see all the configuration settings available, their defaults, how to set them and
where their current value comes from.
Ansible will process the following list and use the first file found; all the other files are ignored even if existing:
- the
ANSIBLE_CONFIGenvironment variable; - the
ansible.cfgfile in the current directory; - the
~/.ansible.cfgfile in the user's home directory; - the
/etc/ansible/ansible.cfgfile.
Generate a fully commented-out example of the ansible.cfg file:
ansible-config init --disabled > 'ansible.cfg'
# Includes existing plugins.
ansible-config init --disabled -t all > 'ansible.cfg'
Performance tuning
Refer the following:
- 8 ways to speed up your Ansible playbooks
- 6 ways to speed up Ansible playbook execution
- How to speed up Ansible playbooks drastically?
- Easy things you can do to speed up ansible
Suggestions:
-
Optimize fact gathering:
-
Disable fact gathering when not used.
-
Consider using smart fact gathering:
[defaults] gathering = smart fact_caching = jsonfile fact_caching_connection = /tmp/ansible/facts.json ; /tmp/ansible to use the directory and have a file per host fact_caching_timeout = 86400 -
Only gather subsets of facts:
- name: Play with selected facts gather_facts: true gather_subset: - '!all' - '!min' - systemRefer the setup module for more information, and the setup module source code for available keys.
-
-
Consider increasing the number of forks when dealing with lots of managed hosts:
[defaults] forks = 25 -
Set independent tasks as async.
-
Optimize SSH connections:
-
Prefer key-based authentication if used:
[ssh_connection] ssh_args = -o PreferredAuthentications=publickey -
Use pipelining:
[ssh_connection] pipelining = True -
Consider using multiplexing:
[ssh_connection] ssh_args = -o ControlMaster=auto -o ControlPersist=3600s
-
-
Consider installing and using the Mitogen plugin on the controller:
curl -fsLO 'https://github.com/mitogen-hq/mitogen/releases/download/v0.3.7/mitogen-0.3.7.tar.gz' tar -xaf 'mitogen-0.3.7.tar.gz'[defaults] strategy_plugins = mitogen-0.3.7/ansible_mitogen/plugins/strategy strategy = mitogen_linearBe advised that mitogen is not really supported by Ansible and has some issues with privilege escalation (1).
-
Improve the code:
- Bundle up package installations together.
- Beware of expensive calls.
Inventories
saturn ansible_python_interpreter=/usr/bin/python3.12 ansible_connection=local
jupiter.lan ansible_python_interpreter=/usr/bin/python3 ansible_port=4444
[accessed_remotely]
saturn
jupiter.lan
uranus.example.com ansible_port=5987
[swap_resistent]
jupiter.lan
saturn
[workstations]
saturn
; mars.lan ansible_port=4444
AWS
Refer Integrate with AWS SSM.
Templating
Ansible leverages Jinja2 templating, which can be used directly in tasks or through the template module.
All Jinja2's standard filters and tests can be used, with the addition of:
- specialized filters for selecting and transforming data
- tests for evaluating template expressions
- lookup plugins for retrieving data from external sources for use in templating
All templating happens on the Ansible controller, before the task is sent and executed on the target machine.
Updated examples are available.
# Remove empty or false values from a list piping it to 'select()'.
# Returns ["string"].
- vars:
list: ["", "string", 0, false]
ansible.builtin.debug:
var: list | select
# Remove only empty strings from a list 'reject()'ing them.
# Returns ["string", 0, false].
- vars:
list: ["", "string", 0, false]
ansible.builtin.debug:
var: list | reject('match', '^$')
# Merge two lists.
# Returns ["a", "b", "c", "d"].
- vars:
list1: ["a", "b"]
list2: ["c", "d"]
ansible.builtin.debug:
var: list1 + list2
# Dedupe elements in a list.
# Returns ["a", "b"].
- vars:
list: ["a", "b", "b", "a"]
ansible.builtin.debug:
var: list | unique
# Sort a list by version number (not lexicographically).
# Returns ['2.7.0', '2.8.0', '2.9.0', '2.10.0' '2.11.0'].
- vars:
list: ['2.8.0', '2.11.0', '2.7.0', '2.10.0', '2.9.0']
ansible.builtin.debug:
var: list | community.general.version_sort
# Generate a random password.
# Returns a random string following the specifications.
- vars:
password: "{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits,punctuation') }}"
ansible.builtin.debug:
var: password
# Hash a password.
# Returns a hash of the requested type.
- vars:
password: abcd
salt: "{{ lookup('community.general.random_string', special=false) }}"
ansible.builtin.debug:
var: password | password_hash('sha512', salt)
# Get a variable's type.
- ansible.builtin.debug:
var: "'string' | type_debug"
Tests
Return a boolean result.
# Compare semver version numbers.
- ansible.builtin.debug:
var: "'2.0.0-rc.1+build.123' is version('2.1.0-rc.2+build.423', 'ge', version_type='semver')"
# Find specific values in JSON objects.
- ansible.builtin.command: ssm-cli get-diagnostics --output 'json'
become: true
register: diagnostics
failed_when: diagnostics.stdout | to_json | community.general.json_query('DiagnosticsOutput[*].Status=="Failed"')
Loops
# Get the values of some special variables.
# See the 'Further readings' section for the full list.
- ansible.builtin.debug:
var: "{{ item }}"
with_items: ["ansible_local", "playbook_dir", "role_path"]
# Fail when any of the given variables is an empty string.
# Returns the ones which are empty.
- when: lookup('vars', item) == ''
ansible.builtin.fail:
msg: "The {{ item }} variable is an empty string"
loop:
- variable1
- variableN
# Iterate through nested loops.
- vars:
middles:
- 'middle1'
- 'middle2'
ansible.builtin.debug:
msg: "{{ item[0] }}, {{ item[1] }}, {{ item[2] }}"
with_nested:
- ['outer1', 'outer2']
- "{{ middles }}"
- ['inner1', 'inner2']
Validation
Assertions
- ansible.builtin.assert:
that:
- install_method in supported_install_methods
- external_url is ansible.builtin.url
fail_msg: What to say if any of the above conditions fail
success_msg: What to say if all of the above conditions succeed
Error handling
Using blocks
Refer Blocks.
- name: Error handling in blocks
block:
- name: This executes normally
ansible.builtin.debug:
msg: I execute normally
- name: This errors out
ansible.builtin.command: "/bin/false"
- name: This never executes
ansible.builtin.debug:
msg: I never execute due to the above task failing
rescue:
- name: This executes if any errors arose in the block
ansible.builtin.debug:
msg: I caught an error and can do stuff here to fix it
always:
- name: This always executes
ansible.builtin.debug:
msg: I always execute
Output formatting
Introduced in Ansible 2.5
Change Ansible's output setting the stdout callback to json or yaml:
ANSIBLE_STDOUT_CALLBACK='yaml'
# ansible.cfg
[defaults]
stdout_callback = json
yaml will set tasks output only to be in the defined format:
$ ANSIBLE_STDOUT_CALLBACK='yaml' ansible-playbook --inventory='localhost,' 'localhost.configure.yml' -vv --check
PLAY [Configure localhost] *******************************************************************
TASK [Upgrade system packages] ***************************************************************
task path: /home/user/localhost.configure.yml:7
ok: [localhost] => changed=false
cmd:
- /usr/bin/zypper
- --quiet
- --non-interactive
…
update_cache: false
The json output format will be a single, long JSON file:
$ ANSIBLE_STDOUT_CALLBACK='json' ansible-playbook --inventory='localhost,' 'localhost.configure.yml' -vv --check
{
"custom_stats": {},
"global_custom_stats": {},
"plays": [
{
"play": {
…
"name": "Configure localhost"
},
"tasks": [
{
"hosts": {
"localhost": {
"action": "community.general.zypper",
"changed": false,
…
"update_cache": false
}
}
}
]
}
]
}
Roles
Get roles
Roles can be either created:
ansible-galaxy init 'role-name'
or installed from Galaxy:
---
# requirements.yml
collections:
- community.docker
ansible-galaxy install 'mcereda.boinc_client'
ansible-galaxy install --roles-path 'path/to/roles' 'namespace.role'
ansible-galaxy install 'namespace.role,v1.0.0'
ansible-galaxy install 'git+https://github.com/namespace/role.git,commit-hash'
ansible-galaxy install -r 'requirements.yml'
Assign roles
In playbooks:
---
- hosts: all
roles:
- web_server
- geerlingguy.postgresql
- role: /custom/path/to/role
vars:
var1: value1
tags: example
message: some message
Role dependencies
Set them up in role/meta/main.yml:
---
dependencies:
- role: common
vars:
some_parameter: 3
- role: postgres
vars:
dbname: blarg
other_parameter: 12
and/or in role/meta/requirements.yml:
---
collections:
- community.dns
Create custom filter plugins
See Creating your own Ansible filter plugins.
Execution environments
Container images that can be used as Ansible control nodes.
Ansible Builder aids in the creation of Ansible Execution Environments.
Refer Introduction to Ansible Builder for how to build one.
Builders' build command defaults to using:
execution-environment.ymlorexecution-environment.yamlas the definition file.$PWD/contextas the directory to use for the build context.
execution-environment.yml example
Refer Execution environment definition.
---
version: 3
build_arg_defaults:
ANSIBLE_GALAXY_CLI_COLLECTION_OPTS: '--pre'
dependencies:
ansible_core:
package_pip: ansible-core==2.14.4
ansible_runner:
package_pip: ansible-runner
galaxy: requirements.yml
python:
- six
- psutil
system: bindep.txt
exclude:
python:
- docker
system:
- python3-Cython
images:
base_image:
name: docker.io/redhat/ubi9:latest
# Other available base images:
# - quay.io/rockylinux/rockylinux:9
# - quay.io/centos/centos:stream9
# - registry.fedoraproject.org/fedora:38
# - registry.redhat.io/ansible-automation-platform-23/ee-minimal-rhel8:latest
# (needs an account)
# Custom package manager path for the RHEL based images
# options:
# package_manager_path: /usr/bin/microdnf
additional_build_files:
- src: files/ansible.cfg
dest: configs
additional_build_steps:
prepend_base:
- RUN echo This is a prepend base command!
# Enable Non-default stream before packages provided by it can be installed. (optional)
# - RUN $PKGMGR module enable postgresql:15 -y
# - RUN $PKGMGR install -y postgresql
prepend_galaxy:
- COPY _build/configs/ansible.cfg /etc/ansible/ansible.cfg
prepend_final: |
RUN whoami
RUN cat /etc/os-release
append_final:
- RUN echo This is a post-install command!
- RUN ls -la /etc
requirements.yml example
---
collections:
- redhat.openshift
Commands example
pip install 'ansible-builder'
ansible-builder build --container-runtime 'docker' -t 'example-ee:latest' -f 'definition.yml'
ansible-runner -p 'test_play.yml' --container-image 'example-ee:latest'
ansible-navigator run 'test_play.yml' -i 'localhost,' --execution-environment-image 'example-ee:latest' \
--mode 'stdout' --pull-policy 'missing' --container-options='--user=0'
Secrets management
Refer handling secrets in your Ansible playbooks.
Use interactive prompts to ask for values at runtime.
---
- hosts: all
gather_facts: false
vars_prompt:
- name: api_key
prompt: Enter the API key
tasks:
- name: Ensure API key is present in config file
ansible.builtin.lineinfile:
path: /etc/app/configuration.ini
line: "API_KEY={{ api_key }}"
Use Ansible Vault for automated execution when one does not require to use specific secrets or password managers.
Ansible Vault
Refer Protecting sensitive data with Ansible Vault and Ansible Vault tutorial.
Vault encrypts variables and files at rest and allows for their use in playbooks and roles.
It does not prevent tasks to print out data in use. See the
no_log attribute for hiding
sensible values.
Protected data will require one or more passwords to encrypt and decrypt.
If storing vault passwords in third-party tools, one will need them need to allow for non-interactive access.
Create and view protected data by using the ansible-vault command.
Use the ansible.cfg file to specify the location of a password file or to always prompt for the password.
Vault passwords can be any string, and there is currently no special command to create one.
One must provide the/a Vault password every time one encrypts and/or decrypts data with Vault.
If using multiple Vault passwords, one can differentiate between them by means of vault IDs.
By default, Vault IDs only label protected content to remind one which password one used to encrypt it. Ansible will not check that the vault ID in the header of any encrypted content matches the vault ID one provides when using that content, and will try and decrypt the data with the password one provides.
Force this check by setting theDEFAULT_VAULT_ID_MATCHconfig option.
Vault can only encrypt variables and files.
Encrypted content is marked in playbook and roles with the !vault tag. This tells Ansible and YAML that the content
needs to be decrypted. Content created with --vault-id also contains the vault ID's label in the mark.
Encrypted variables allow for mixed plaintext and encrypted content, even inline, in plays or roles.
One cannot rekey encrypted variables.
To encrypt tasks or other content, one must encrypt the entire file.
Input files are encrypted in-place unless one specifies the output files in the command.
Encrypt and use variables
-
Encrypt the variable's value:
$ ansible-vault encrypt_string --name 'command_output' 'somethingNobodyShouldKnow' New Vault password: Confirm New Vault password: Encryption successful command_output: !vault | $ANSIBLE_VAULT;1.1;AES256 34306534613939316131303430653733633961623931363032633933393039373764356464623461 3463353332623466623661363831303836396165323238660a353137363562393161396566386565 35616662336536613365386164353439616232643131306534353264346635373566313630613261 3531373034333830640a353138306463653533366432623438343266623930396238313763643836 66646237336338353866306361316233326535333236363136613263346631633836 $ ansible-vault encrypt_string --name 'command_output' 'somethingNobodyShouldKnow' \ --vault-password-file 'password_file.txt' Encryption successful command_output: !vault | $ANSIBLE_VAULT;1.1;AES256 31373465393164316666663963643163313032623233356634313038333662653061623936383838 6166636433313438613338373438343130633766656535390a353338373261393931316533303837 64363736383163643238336565363936303434393931386131383463336539306466636231633131 6432396337366333350a356338623630626161333666373831313966633038343133316532383562 61303538333031333861313733383363656531613333356364363432343361393636 -
Use the output as the value:
- name: Configure credential 'Gitlab container registry PAT' tags: - container_registry - gitlab awx.awx.credential: organization: Private name: Gitlab container registry PAT credential_type: Container Registry inputs: host: gitlab.example.org:5050 username: awx # or anything, really password: !vault | $ANSIBLE_VAULT;1.1;AES256 34306534613939316131303430653733633961623931363032633933393039373764356464623461 3463353332623466623661363831303836396165323238660a353137363562393161396566386565 35616662336536613365386164353439616232643131306534353264346635373566313630613261 3531373034333830640a353138306463653533366432623438343266623930396238313763643836 66646237336338353866306361316233326535333236363136613263346631633836 verify_ssl: false update_secrets: false -
Require the play execution to ask for the password used during encryption:
ansible-playbook -i 'localhost,' -c 'local' -Dvvv 'playbook.yml' -t 'container_registry' --ask-vault-pass ansible-playbook … --vault-password-file 'password_file.txt'
Encrypt and use existing files
-
Encrypt the file:
# Input files are encrypted in place unless output files are specified $ ansible-vault encrypt 'ssh.key' New Vault password: Confirm New Vault password: Encryption successful $ ansible-vault encrypt --output 'ssh_key.enc' '.ssh/id_rsa' --vault-password-file 'password_file.txt' Encryption successful -
Use the file normally:
- name: Test value is read correctly tags: debug ansible.builtin.debug: msg: "{{ lookup('file', 'ssh_key.enc') }}" -
Require the play execution to ask for the password used during encryption:
ansible-playbook -i 'localhost,' -c 'local' -Dvvv 'playbook.yml' -t 'container_registry' --ask-vault-pass ansible-playbook … --vault-password-file 'password_file.txt'
Decrypt files with ansible-vault decrypt 'path/to/file'.
Input files are decrypted in place unless one specifies the output files in the command.
Decrypt files
$ ansible-vault decrypt 'ssh.key'
New Vault password:
Confirm New Vault password:
Decryption successful
$ ansible-vault decrypt --output '.ssh/id_rsa' --vault-password-file 'password_file.txt' 'ssh.key'
Decryption successful
One can quickly view the content of encrypted files with ansible-vault view 'path/to/file':
View encrypted files' content
$ cat 'ssh.key.pub'
$ANSIBLE_VAULT;1.1;AES256
38623265623763366431646435646634363136373831323464356130383432356266616461323730
6436396161613934356339323731336130383064386464610a373664326235376336333736306563
62366635646565633833336638616434353935313632323733326634356366666439316336353030
6635353335653034340a613330323565366365346638343464623036396134626537643064653437
36653734373839306135306165326464633231383236663735646465643332383332626564643038
64363531383430393834373764633564383537326430303038383661656134383631306336633539
33343166386135663537656262343734383339383363343736633965393262666133623932653732
63613034393964333865626532636332393964396463613131356534623433353065313661383461
37646635336433376132393766333761306162366666346634323166353630633036
$ ansible-vault view 'ssh.key.pub'
Vault password:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFIw4vv6LYg3P7bfgrR5I4k/0123456789abcdefghIL me@example.org
$ ansible-vault view 'ssh.key.pub' --vault-password-file 'password_file.txt'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFIw4vv6LYg3P7bfgrR5I4k/0123456789abcdefghIL me@example.org
Or even edit their content with ansible-vault edit 'path/to/file'.
Troubleshooting
Print all known variables
Print the special variable vars as a task:
- name: Debug all variables
ansible.builtin.debug: var=vars
Force notified handlers to run at a specific point
Use the meta plugin with the flush_handlers option:
- name: Force all notified handlers to run at this point, not waiting for normal sync points
ansible.builtin.meta: flush_handlers
Time tasks execution
Add profile_tasks the list of enable callbacks.
Choose one or more options:
-
Add it to
callbacks_enabledin the[defaults]section of Ansible's configuration file:[defaults] callbacks_enabled = profile_tasks # or ansible.posix.profile_tasks -
Set the
ANSIBLE_CALLBACKS_ENABLEDenvironment variable:export ANSIBLE_CALLBACKS_ENABLED='profile_tasks'
Run specific tasks even in check mode
Add the check_mode: false pair to the task:
- name: this task will make changes to the system even in check mode
check_mode: false
ansible.builtin.command: /something/to/run --even-in-check-mode
Dry-run only specific tasks
Add the check_mode: true pair to the task:
- name: This task will always run under check mode and not change the system
check_mode: true
ansible.builtin.lineinfile:
line: "important file"
dest: /path/to/file.conf
state: present
Set up recursive permissions on a directory so that directories are set to 755 and files to 644
Use the special X mode setting in the file plugin:
- name: Fix files and directories' permissions
ansible.builtin.file:
dest: /path/to/some/dir
mode: u=rwX,g=rX,o=rX
recurse: yes
Only run a task when another has a specific result
When a task executes, it also stores the two special values changed and failed in its results.
One can use those as conditions to execute the next ones:
- name: Trigger task
ansible.builtin.command: any
register: trigger_task
ignore_errors: true
- name: Run only on change
when: trigger_task.changed
ansible.builtin.debug: msg="The trigger task changed"
- name: Run only on failure
when: trigger_task.failed
ansible.builtin.debug: msg="The trigger task failed"
Alternatively, you can use special checks built for this:
- name: Run only on success
when: trigger_task is succeeded
ansible.builtin.debug: msg="The trigger task succeeded"
- name: Run only on change
when: trigger_task is changed
ansible.builtin.debug: msg="The trigger task changed"
- name: Run only on failure
when: trigger_task is failed
ansible.builtin.debug: msg="The trigger task failed"
- name: Run only on skip
when: trigger_task is skipped
ansible.builtin.debug: msg="The trigger task skipped"
Define when a task changed or failed
This lets you avoid using ignore_errors.
Use the changed_when and failed_when attributes to define your own conditions:
- name: Task with custom results
ansible.builtin.command: any
register: result
changed_when:
- result.rc == 2
- result.stderr | regex_search('things changed')
failed_when:
- result.rc != 0
- not (result.stderr | regex_search('all good'))
Set environment variables for a play, role or task
Environment variables can be set at a play, block, or task level using the environment keyword:
- name: Use environment variables for a task
environment:
HTTP_PROXY: http://example.proxy
ansible.builtin.command: curl ifconfig.io
The environment keyword does not affect Ansible itself or its configuration settings, the environment for other
users, or the execution of other plugins like lookups and filters.
Variables set with environment do not automatically become Ansible facts, even when set at the play level.
Set variables to the value of environment variables
Use the lookup() plugin with the env option:
- name: Use a local environment variable
ansible.builtin.debug: msg="HOME={{ lookup('env', 'HOME') }}"
Check if a list contains an item and fail otherwise
- name: Check if a list contains an item and fail otherwise
when: item not in list
ansible.builtin.fail: msg="item not in list"
Define different values for true/false/null
Create a test and define two values: the first will be returned when the test returns true, the second will be
returned when the test returns false (Ansible 1.9+):
{{ (ansible_pkg_mgr == 'zypper') | ternary('gnu_parallel', 'parallel') }}
Since Ansible 2.8 you can define a third value to be returned when the test returns null:
{{ autoscaling_enabled | ternary(true, false, omit) }}
Force a task or play to use a specific Python interpreter
Just set it in the Play's or Task's variables:
vars:
ansible_python_interpreter: /usr/local/bin/python3.9
Provide a template file content inline
Use the ansible.builtin.copy instead of ansible.builtin.template:
- name: Configure knockd
ansible.builtin.copy:
dest: /etc/knockd.conf
content: |
[options]
UseSyslog
Python breaks in OS X
Root Cause:
Mac OS High Sierra and later versions have restricted multithreading for improved security.
Apple has defined some rules on what is allowed and not is not after forking processes, and have also addedasync-signal-safetyto a limited number of APIs.
Solution:
Disable fork initialization safety features as shown in Why Ansible and Python fork break on macOS High Sierra+ and how to solve:
export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
Load files' content into variables
For local files, use lookups:
user_data: "{{ lookup('file', 'path/to/file') }}"
For remote files, use the slurp module:
- ansible.builtin.slurp:
src: "{{ user_data_file }}"
register: slurped_user_data
- ansible.builtin.set_fact:
user_data: "{{ slurped_user_data.content | ansible.builtin.b64decode }}"
The contents are presented as base64 string. The decode is needed.
Only run a task when explicitly requested
Leverage the never tag to never execute the task unless requested by using the
--tags 'never' option:
- tags: never
ansible.builtin.debug:
msg: …
Conversely, one can achieve the opposite by using the always tag and the --skip 'always' option:
- tags: always
ansible.builtin.command: …
Using AWS' SSM with Ansible fails with error Failed to create temporary directory
Message example:
fatal: [i-4ccab452bb7743336]: UNREACHABLE! => { "changed": false, "msg": "Failed to create temporary directory. In some cases, you may have been able to authenticate and did not have permissions on the target directory. Consider changing the remote tmp path in ansible.cfg to a path rooted in \"/tmp\", for more error information use -vvv. Failed command was: ( umask 77 && mkdir -p \"` echo \u001b]0;@ip-192-168-42-42:/usr/bin\u0007/home/centos/.ansible/tmp `\"&& mkdir \"` echo \u001b]0;@ip-192-168-42-42:/usr/bin\u0007/home/centos/.ansible/tmp/ansible-tmp-1708603630.2433128-49665-225488680421418 `\" && echo ansible-tmp-1708603630.2433128-49665-225488680421418=\"` echo \u001b]0;@ip-192-168-42-42:/usr/bin\u0007/home/centos/.ansible/tmp/ansible-tmp-1708603630.2433128-49665-225488680421418 `\" ), exited with result 1, stdout output: \u001b]0;@ip-192-168-42-42:/usr/bin\u0007bash: @ip-192-168-42-42:/usr/bin/home/centos/.ansible/tmp: No such file or directory\r\r\nmkdir: cannot create directory '0': Permission denied\r\r", "unreachable": true }
Root cause:
By default, SSM starts sessions in the /usr/bin directory.
Solution:
Explicitly set Ansible's temporary directory to a folder the remote user can write to.
See Integrate with AWS SSM.
Future feature annotations is not defined
Refer Newer versions of Ansible don't work with RHEL 8.
Error message example:
SyntaxError: future feature annotations is not defined
Solution: use a version of ansible-core lower than 2.17.
Further readings
- Configuration
- Templating
- Examples
- Roles
- Tests
- Special variables
- Collections index
Each also shows the list of connection types, filters, modules, etc it adds. - Automating Helm using Ansible
- Edit .ini file in other servers using Ansible PlayBook
- Yes and No, True and False
- Galaxy
- Ansible Galaxy user guide
- Windows playbook example
- Special tags:
alwaysandnever - Integrate with AWS SSM
- Mitogen for Ansible
- Debugging tasks
- AWX
- Introduction to Ansible Builder
Sources
- Removing empty values from a list and assigning it to a new list
- Human-Readable Output Format
- How to append to lists
- Check if a list contains an item in ansible
- Working with versions
- How to install SSHpass on Mac
- Include task only if file exists
- Unique filter of list in jinja2
- Only do something if another action changed
- How to recursively set directory and file permissions
- Is it possible to use inline templates?
- How to set up and use Python virtual environments for Ansible
- Merging two dictionaries by key in Ansible
- Creating your own Ansible filter plugins
- Why Ansible and Python fork break on macOS High Sierra+ and how to solve
- Ansible: set variable to file content
- How can I hide skipped tasks output in Ansible
- Ansible roles: basics, creating & using
- Developing and Testing Ansible Roles with Molecule and Podman - Part 1
- How to get an arbitrary remote user's home directory in Ansible?
- 6 ways to speed up Ansible playbook execution
- How to speed up Ansible playbooks drastically?
- Easy things you can do to speed up ansible
- What is the exact list of Ansible setup min?
- Setup module source code
- 8 ways to speed up your Ansible playbooks
- Blocks
- How to work with lists and dictionaries in Ansible
- Handling secrets in your Ansible playbooks
- Ansible - how to remove an item from a list?
- Looping over lists inside of a dict
- Newer versions of Ansible don't work with RHEL 8
- Running your Ansible playbooks in parallel and other strategies
- Execution environment definition
- Protecting sensitive data with Ansible vault
- Ansible Vault tutorial