Some time ago I discovered an interesting use of the ternary-filter in Ansible. A ternary-filter in Ansible is a filter that takes three arguments: a condition, a value if the condition is true and an alternative value if the condition is false.
Here’s a simple example straight from Ansible’s documentation:
- name: service-foo, use systemd module unless upstart is present, then use old service module
service:
state: restarted
enabled: yes
use: "{{ (ansible_service_mgr == 'upstart') | ternary('service', 'systemd') }}"
But there are many more interesting use cases for this filter and I decided to take a look what Ansible’s collection authors used it for.
Display command output only if verbosity is greater than 0.
This was the usage that initially got me interested in different use-cases for the filter.
Depending on what verbosity level you use when running a playbook (e.g. how many -v
you add to the command), the command
will run in quiet-mode (with the -quiet
flag) or not.
- name: Validate configuration
become: true
become_user: "{{ consul_user }}"
ansible.builtin.command: >
{{ consul_binary }} validate {{ (ansible_verbosity == 0) | ternary("-quiet", "") }}
{{ consul_config_path }}/config.json {{ consul_configd_path }}
changed_when: false
(Source)
HN-User raziel2p gave an even better example to achieve this:
- name: Validate configuration
become: true
become_user: "{{ consul_user }}"
ansible.builtin.command: >
{{ consul_binary }} validate {{ "-quiet" if ansible_verbosity == 0 }}
{{ consul_config_path }}/config.json {{ consul_configd_path }}
changed_when: false
Testing task idempotency in one run without molecule
This use of the ternary-filter is useful for testing task idempotency in one run.
First, the task-file test_create_scheduler.yml
is imported without a variable set, so the task will change something.
Then, the task-file is imported again, however this time with the variable test_proxysql_scheduler_check_idempotence
set to true
.
- name: "{{ role_name }} | test_create_scheduler | test create scheduler"
import_tasks: test_create_scheduler.yml
- name: "{{ role_name }} | test_create_scheduler | test idempotence of create scheduler"
import_tasks: test_create_scheduler.yml
vars:
test_proxysql_scheduler_check_idempotence: true
When importing the test file test_create_scheduler.yml
without the variable test_proxysql_scheduler_check_idempotence
, the assert
will check for status is changed
, because the ternary-filter evaluated the variable test_proxysql_scheduler_check_idempotence
as false
.
- name: "{{ role_name }} | {{ current_test }} | check if create scheduler reported a change"
assert:
that:
- "status is {{ test_proxysql_scheduler_check_idempotence|ternary('not changed', 'changed') }}"
When importing the test file test_create_scheduler.yml
with the variable test_proxysql_scheduler_check_idempotence
, the assert
will check for status is not changed
, because the ternary-filter evaluated the variable test_proxysql_scheduler_check_idempotence
as true
.
(Source)
Do things based on regex searches
In Ansible you can chain filters using a pipe (|
).
This allows you to filter based on regex searches (which in hindsight is obvious that it works, but I never thought about that).
- name: "{{ role_name }} | {{ current_test }} | are we performing a delete"
set_fact:
test_delete: "{{ current_test | regex_search('^test_delete') | ternary(true, false) }}"
(Source)
Handle older Python versions easily
In the following task, the cassandra-driver is installed. If the used (obsolete!) Python version starts with 2.7, pip should install the cassandra-driver in version 3.26.*. If a recent Python version is used, pip will install the latest version of the cassandra-driver.
- name: Install cassandra-driver
pip:
name: "cassandra-driver{{ ansible_python_version.startswith('2.7') | ternary('==3.26.*', '') }}"
That’s definitely not the most elegant solution, but it works. (I’d probably have tried to install the correct cassandra-driver version according to the operating system and its Python version, where it should be installed)
(Source)
Comment line in template if var is defined
This task will add a line starting with ssl_ciphers
, if the variable zabbix_web_ssl_cipher_suite
is defined and not none
.
Otherwise it will add the same line but commented out.
{{ (zabbix_web_ssl_cipher_suite is defined and zabbix_web_ssl_cipher_suite is not none) | ternary('', '# ') }}ssl_ciphers {{ zabbix_web_ssl_cipher_suite | default('') }}
(Source)
I used another way to add commented out lines in a template (see). I used the comment-filter:
{{ "HostKeyAlgorithms " ~ ssh_host_key_algorithms|join(',') if ssh_host_key_algorithms else "HostKeyAlgorithms" | comment }}
Now that I see my own code, I could probably use the ternary-filter here, too!
{{ ssh_host_key_algorithms | ternary("HostKeyAlgorithms " ~ ssh_host_key_algorithms|join(','), "HostKeyAlgorithms" | comment) }}
But I think I actually like the if-else syntax more.
Do you have any other interesting uses of the ternary-filter?