'Ansible seems to be ignoring variable in when conditional when it is overridden with "-e" from the command line

I'm working on an Ansible playbook that uses a when condition, and I was trying to test the scenario where there is a default value specified as a play variable, which can then be selectively overridden from the command line using the -e argument to ansible-playbook. But this is not working as I would expect, and I'm not seeing any obvious reason(s) why.

Here's the setup (this is a super stripped-down sample, just to show the point, not my real playbook).

conditional_task.yml

    - name: A playbook to conditionally print some debug messages
      hosts: localhost
      gather_facts: False
      vars:
        doomed: False
      tasks:
        - name: print the value of doomed
          debug: var=doomed
        - name: print the message if we're doomed
          debug:
            msg: "We're f*****g doomed!"
          when: doomed == True
        - name: print the message if we're not doomed
          debug:
            msg: "Glory be to FSM, we are saved!"
          when: doomed == False

If I run this with the ansible-playbook command with no arguments, I get exactly the expected output.

    $ ansible-playbook conditional_task.yml
    
    PLAY [A playbook to conditionally print some debug messages] ************************************************************************************
    
    TASK [print the value of doomed] ****************************************************************************************************************
    ok: [localhost] => {
        "doomed": true
    }
    
    TASK [print the message if we're doomed] ********************************************************************************************************
    ok: [localhost] => {
        "msg": "We're f*****g doomed!"
    }
    
    TASK [print the message if we're not doomed] ****************************************************************************************************
    skipping: [localhost]
    
    PLAY RECAP **************************************************************************************************************************************
    localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0  

And if I edit the playbook to change the value of the doomed var to False, and re-run this, I get the other message.

$ ansible-playbook conditional_task.yml

PLAY [A playbook to conditionally print some debug messages] ************************************************************************************

TASK [print the value of doomed] ****************************************************************************************************************
ok: [localhost] => {
    "doomed": false
}

TASK [print the message if we're doomed] ********************************************************************************************************
skipping: [localhost]

TASK [print the message if we're not doomed] ****************************************************************************************************
ok: [localhost] => {
    "msg": "Glory be to FSM, we are saved!"
}

PLAY RECAP **************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

Now, given all that, I would expect to be able to do something like this:

$ ansible-playbook -e doomed=False conditional_task.yml

and get the "not doomed" message. What I get instead is this:

$ ansible-playbook -e doomed=False conditional_task.yml

PLAY [A playbook to conditionally print some debug messages] ************************************************************************************

TASK [print the value of doomed] ****************************************************************************************************************
ok: [localhost] => {
    "doomed": false
}

TASK [print the message if we're doomed] ********************************************************************************************************
skipping: [localhost]

TASK [print the message if we're not doomed] ****************************************************************************************************
skipping: [localhost]

PLAY RECAP **************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0

As you can see, neither when condition fires in this case. It's almost like the variable isn't there at all, or has some unexpected value, or something. But as the debug output shows, the variable is defined, and does have value "false" as expected. And yet the "when" conditionals ignore this.

It also doesn't matter if I change the command line to

$ ansible-playbook -e doomed=True conditional_task.yml

In that case I still get neither conditional task executing. But again, the debug statement shows that doomed is present and has value of "true".

I also tried re-writing my conditions as "doomed is true" and "doomed is false" respectively, instead of using the == operator and that didn't make any difference. At this point I'm stuck. If anybody can explain this, it would be greatly appreciated.

Here's my Ansible version information:

$ ansible-playbook --version
ansible-playbook 2.10.8
  config file = /extradrive1/development/experimental/ansible/ansible_experimental/ansible.cfg
  configured module search path = ['/home/prhodes/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  executable location = /usr/bin/ansible-playbook
  python version = 3.9.7 (default, Sep 10 2021, 14:59:43) [GCC 11.2.0]

This is all running on Pop! OS:

NAME="Pop!_OS"
VERSION="21.10"


Solution 1:[1]

The problem is caused by the fact that the type of the extra variables passed to Ansible from the command line is always string. For example, the playbook below

shell> cat pb.yml
- hosts: localhost
  vars:
    doomed: false
  tasks:
    - debug:
        var: doomed|type_debug
    - debug:
        msg: We are doomed
      when: doomed

works as expected. The type of the variable doomed is Boolean. Then, simply use the variable in the condition. No comparison is needed. The debug task will be skipped

shell> ansible-playbook pb.yml

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

TASK [debug] **********************************************************************************
ok: [localhost] => 
  doomed|type_debug: bool

TASK [debug] **********************************************************************************
skipping: [localhost]

The thing will change when you pass the variable from the command line. Now, the type of the variable doomed is a string. A non-empty string will evaluate to True in the condition and the debug task will be executed

shell> ansible-playbook pb.yml -e doomed=false

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

TASK [debug] **********************************************************************************
ok: [localhost] => 
  doomed|type_debug: str

TASK [debug] **********************************************************************************
ok: [localhost] => 
  msg: We are doomed

There is a simple universal solution. Always cast such variables to bool. In addition to this, to simplify the code, default to false or true, depending on the use-case, instead of an explicit declaration. This way you write the code fast and the condition will always do what you want

shell> cat pb.yml
- hosts: localhost
  tasks:
    - debug:
        msg: We are doomed
      when: doomed|d(false)|bool

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Vladimir Botka