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?



Related posts: