Jay
Jay

Reputation: 629

Ansible: Multiple and/or conditionals in when clause

I am having issues when trying to use multiple and/or conditionals in a when statement to decide whether a task needs to be ran or not. Basically I am making a playbook to do automated system patching with options for security patches, kernel only patches and to specify packages in a var file.

I run the playbook with the following commands and define the variables through extended variables option (-e)

ansible-playbook site.yml \
  -i inventory \
  --ask-vault \
  -u (username) \
  -e "security=true restart=true" \
  -k -K

By default the playbook will update every package on the system except kernel but I would like to skip that action if I specify any of a few variables. The code I have is the following:

- name: Update all packages
  yum:
    name: "*"
    state: latest
    exclude: "kernel*"
  when: >
    security is not defined or
    kernel is not defined or
    specified_packages is not defined
    and ansible_os_family == "RedHat"

I've tried all of the following combinations:

when: (ansible_os_family == "RedHat") and (security is defined or kernel is defined or specified_packages is defined)

# this case throws a not defined error because i don't define all variables every time i run the playbook
when: (ansible_os_family == "RedHat") and (security == true or kernel == true or specified_packages == true )

when: ansible_os_family == "RedHat"
when: security is defined or kernel is defined or specified_packages is defined

Note: I am aware and have used an extra variable such as "skip" to skip this task and use the when clause when: ansible_os_family == "RedHat" and skip is not defined but would prefer not have my users need to use an extra variable just to skip this default action.

I also am not using tags as I am gathering a list of packages before and after the upgrade to compare and report in the end so I wont be able to run those as they are local action commands. This is why I'm using one role with multiple tasks turned on and off via extended variables. I am open to any suggestion that rewrites the playbook in a more efficient way as I am sort of a noob.

Upvotes: 16

Views: 101816

Answers (3)

adamt8
adamt8

Reputation: 357

I just had a similar problem needing to test two different variables to see if they were "true", but they don't always exist in the output json. The basic logic needed is:

( a is defined ) and ( a == 'present' or a == 'reinstalled' )

in this case, a is "install_vs2022_status.invocation.module_args.state", and the following three different scenarios produced the correct result:

direct:

when: (install_vs2022_status.invocation.module_args.state is defined) and (install_vs2022_status.invocation.module_args.state == 'reinstalled' or install_vs2022_status.invocation.module_args.state == 'present')

distributed:

when: (install_vs2022_status.invocation.module_args.state is defined and install_vs2022_status.invocation.module_args.state == 'present') or (install_vs2022_status.invocation.module_args.state is defined and install_vs2022_status.invocation.module_args.state == 'reinstalled')

broken into lines that imply 'and' or 'intersection', except it stops if the first item evaluates to false.

when:
  - install_vs2022_status.invocation.module_args.state is defined
  - install_vs2022_status.invocation.module_args.state == 'reinstalled' or install_vs2022_status.invocation.module_args.state == 'present'

The most important factor in each case is that the test for existence happens first, in order to prevent evaluation of a variable that doesn't exist.

EDIT: Just learned how to use multi-line 'or' conditionals:

- name: get process list
  shell: ps -ef
  when: >
    ("passed" in env_status.stdout) or
    ("fixed" in env.status.stdout)

This makes it much more readable when I'm looking for multiple 'or', and parens can be used for multiple combinations of and/or.

EDIT2: Just learned the name for this notation is 'block scalars' and it's worth a google search.

Upvotes: 4

Jay
Jay

Reputation: 629

It was such a simple answer!

The following works:

when: not (security is defined or kernel is defined or specified_packages is defined) and ansible_os_family == "RedHat"

Upvotes: 20

Konstantin Suvorov
Konstantin Suvorov

Reputation: 68269

As @techraf noted in comments, defined/undefined is a nasty test...

Refactor like this:

when:
  - ansible_os_family == "RedHat"
  - security|d('') != '' or kernel|d('') != '' or specified_packages|d('') != ''

Update. Reproducible example:

- hosts: localhost
  gather_facts: no
  tasks:
    - debug:
      msg: hello
      when:
        - '"RedHat" == "RedHat"'
        - security|d('') != '' or kernel|d('') != '' or specified_packages|d('') != ''

execution:

ansible-playbook -e kernel=true playbook.yml

PLAY [localhost] ***************************************************************

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "hello"
}

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0

versions:

$ pip list | grep -iP 'ansible|jinja'
ansible (2.2.1.0)
Jinja2 (2.8)

Upvotes: 9

Related Questions