Search code examples
ansible

Loop is not working with data type test as conditional in Ansible


When I execute ansible playbook with this command and extra vars as

ansible-playbook controller_config/windows_domain_names.yml -e "target_win_host=testserver.testdomain.com"

with one task inside it

- name: Find if the variable target_win_host is a list
  debug:
    msg: "{{ item }} is list data type"
  loop: "{{ target_win_host}}"
  when: target_win_host | type_debug == 'list'

I got the error as

"Invalid data passed to 'loop', it requires a list, got this instead: testserver.testdomain.com. Hint: If you passed a list/dict of just one element, try adding wantlist=True to your lookup invocation or use q/query instead of lookup."

I expect the above block should be skipped as this does not meet the condition that variable is a list and that its a scalar (string).

I tried with different condition as below

- name: Find if the variable target_win_host is a list
  debug:
    msg: "{{ item }} is list data type"
  loop: "{{ target_win_host}}"
  when: target_win_host is not string and target_win_host is iterable and target_win_host is sequence

but same error as above.


Solution

  • You approach won't work because for the given task, the loop expression is first evaluted, the first item is extracted and only then the when: condition is evaluated (once for each loop iteration).

    In other words, for your approach to work, you need to somehow embed the condition in the loop: expression and make it result to a zero length list.
    The following task reaches that goal.

    - name: Loop over target_win_host if it is a list
      debug:
        msg: "{{ item }} is part of a list of hosts"
      loop: "{{ target_win_host if target_win_host | type_debug == 'list' else [] }}"
    

    Meanwhile if you'd rather play the same task for all hosts whether they are given as a list or as a single host string, this is probably better:

    - name: Loop over hosts in target_win_host whether it is a list or a single host string
      debug:
        msg: "{{ item }} was given as one the hosts"
      loop: "{{ [target_win_host] | flatten }}"
    

    This is a full playbook to test both options:

    ---
    - name: Conditionnal and loops
      hosts: localhost
      gather_facts: false
    
      vars:
        # default value as a list. Override with extra vars
        target_win_host:
          - host1.example.com
          - host2.example.com
    
      tasks:
        - name: Loop over target_win_host if it is a list
          debug:
              msg: "{{ item }} is part of a list of hosts"
          loop: "{{ target_win_host if target_win_host | type_debug == 'list' else [] }}"
    
    
        - name: Loop over hosts in target_win_host whether it is a list or a single host string
          debug:
            msg: "{{ item }} was given as one of the hosts"
          loop: "{{ [target_win_host] | flatten }}"
    

    Which gives:

    $ ansible-playbook test.yml 
    
    PLAY [Conditionnal and loops] *************************************************************************************************************************************************************************************
    
    TASK [Loop over target_win_host if it is a list] *********************************************************************************************************************************************************************
    ok: [localhost] => (item=host1.example.com) => {
        "msg": "host1.example.com is part of a list of hosts"
    }
    ok: [localhost] => (item=host2.example.com) => {
        "msg": "host2.example.com is part of a list of hosts"
    }
    
    TASK [Loop over hosts in target_win_host whether it is a list or a single host string] *****************************************************************************************************************************
    ok: [localhost] => (item=host1.example.com) => {
        "msg": "host1.example.com was given as one of the hosts"
    }
    ok: [localhost] => (item=host2.example.com) => {
        "msg": "host2.example.com was given as one of the hosts"
    }
    
    PLAY RECAP ********************************************************************************************************************************************************************************************************
    localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    
    $ ansible-playbook test.yml -e target_win_host=a.single.host
    
    PLAY [Conditionnal and loops] *************************************************************************************************************************************************************************************
    
    TASK [Loop over target_win_host if it is a list] *********************************************************************************************************************************************************************
    skipping: [localhost]
    
    TASK [Loop over hosts in target_win_host whether it is a list or a single host string] *****************************************************************************************************************************
    ok: [localhost] => (item=a.single.host) => {
        "msg": "a.single.host was given as one of the hosts"
    }
    
    PLAY RECAP ********************************************************************************************************************************************************************************************************
    localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0