ansible

How to dynamically end an include_tasks loop in Ansible


Due to the fact that we cannot run loop on block with Ansible, therefore I have to do include_tasks in the loop where the referenced file contains more than 1 task.

What if I need to dynamically end the loop earlier (lets say 5 iterations instead of 10 as defined by the loop: statements). trying to use when: condition but that only take in pre-defined/statically defined variables, if I have to update the variable that will be evaluated in when: for each iteration, I have to do register: or set_fact in a task before that, which is not possible when the loop task is running...

Also in the included_tasks I can set meta: end_play to end the loop and the play execution but that is not what I want, the play should continue after the loop.


Solution

  • You can't end it. You can only skip the rest. For example,

        - command: "echo {{ item }}"
          with_sequence: end=5
          register: out
          when: out.stdout|d(0)|int < 3
    

    gives (on localhost)

      changed: [localhost] => (item=1)
      changed: [localhost] => (item=2)
      changed: [localhost] => (item=3)
      skipping: [localhost] => (item=4) 
      skipping: [localhost] => (item=5)
    

    See:


    include_tasks

    The include_tasks situation is quite complex. The attribute ignore_conditional says:

    The action is not subject to conditional execution so it will ignore the when: keyword

    You can't use when with include_tasks, but you can apply keywords, including the keyword when, to the tasks within the include. For example, create the file

    shell> cat tasks_1.yml
    - name: Display item
      debug:
        var: item
    - name: Execute command
      command: "echo {{ item }}"
      register: out
    - name: Display stdout
      debug:
        var: out.stdout
    

    When you include this file

        - include_tasks:
            file: tasks_1.yml
            apply:
              when: out.stdout|d(0)|int < 3
          with_sequence: end=5
    

    you apply the condition "to the tasks within the include". Effectively, this will make the included tasks look like below

    - name: Display item
      debug:
        var: item
      when: out.stdout|d(0)|int < 3
    - name: Execute command
      command: "echo {{ item }}"
      register: out
      when: out.stdout|d(0)|int < 3
    - name: Display stdout
      debug:
        var: out.stdout
      when: out.stdout|d(0)|int < 3
    

    Run the play below

    - hosts: localhost
    
      tasks:
    
        - include_tasks:
            file: tasks_1.yml
            apply:
              when: out.stdout|d(0)|int < 3
          with_sequence: end=5
    

    gives (abridged)

    TASK [include_tasks] **************************************************************************
    included: /export/scratch/tmp7/test-389/tasks_1.yml for localhost => (item=1)
    included: /export/scratch/tmp7/test-389/tasks_1.yml for localhost => (item=2)
    included: /export/scratch/tmp7/test-389/tasks_1.yml for localhost => (item=3)
    included: /export/scratch/tmp7/test-389/tasks_1.yml for localhost => (item=4)
    included: /export/scratch/tmp7/test-389/tasks_1.yml for localhost => (item=5)
    
    TASK [Display item] ***************************************************************************
    ok: [localhost] => 
      item: '1'
    
    TASK [Execute command] ************************************************************************
    changed: [localhost]
    
    TASK [Display stdout] *************************************************************************
    ok: [localhost] => 
      out.stdout: '1'
    
    TASK [Display item] ***************************************************************************
    ok: [localhost] => 
      item: '2'
    
    TASK [Execute command] ************************************************************************
    changed: [localhost]
    
    TASK [Display stdout] *************************************************************************
    ok: [localhost] => 
      out.stdout: '2'
    
    TASK [Display item] ***************************************************************************
    ok: [localhost] => 
      item: '3'
    
    TASK [Execute command] ************************************************************************
    changed: [localhost]
    
    TASK [Display stdout] *************************************************************************
    skipping: [localhost]
    
    TASK [Display item] ***************************************************************************
    skipping: [localhost]
    
    TASK [Execute command] ************************************************************************
    skipping: [localhost]
    
    TASK [Display stdout] *************************************************************************
    ok: [localhost] => 
      out.stdout: VARIABLE IS NOT DEFINED!
    
    TASK [Display item] ***************************************************************************
    ok: [localhost] => 
      item: '5'
    
    TASK [Execute command] ************************************************************************
    changed: [localhost]
    
    TASK [Display stdout] *************************************************************************
    skipping: [localhost]
    

    You can see that all iterations are included at once and then executed serially. The results look fine until item: '4'. The explanation always depends on the current value of the variable out.stdout before the execution of a task:

    This is unusable.


    You can improve the framework by creating a variable condition. For example, put the tasks into a block and always set the condition

    shell> cat tasks_2.yml
    - block:
    
        - name: Execute command {{ item }}
          command: "echo {{ item }}"
          register: out
        - debug:
            var: out.stdout
    
      always:
    
        - set_fact:
            condition: "{{ out.stdout|int < 3 }}"
    

    Run the play below

    - hosts: localhost
    
      tasks:
    
        - include_tasks:
            file: tasks_2.yml
            apply:
              when: condition|d(true)
          with_sequence: end=5
    

    gives (abridged)

    TASK [include_tasks] **************************************************************************
    included: /export/scratch/tmp7/test-389/tasks_2.yml for localhost => (item=1)
    included: /export/scratch/tmp7/test-389/tasks_2.yml for localhost => (item=2)
    included: /export/scratch/tmp7/test-389/tasks_2.yml for localhost => (item=3)
    included: /export/scratch/tmp7/test-389/tasks_2.yml for localhost => (item=4)
    included: /export/scratch/tmp7/test-389/tasks_2.yml for localhost => (item=5)
    
    TASK [Execute command 1] **********************************************************************
    changed: [localhost]
    
    TASK [debug] **********************************************************************************
    ok: [localhost] => 
      out.stdout: '1'
    
    TASK [set_fact] *******************************************************************************
    ok: [localhost]
    
    TASK [Execute command 2] **********************************************************************
    changed: [localhost]
    
    TASK [debug] **********************************************************************************
    ok: [localhost] => 
      out.stdout: '2'
    
    TASK [set_fact] *******************************************************************************
    ok: [localhost]
    
    TASK [Execute command 3] **********************************************************************
    changed: [localhost]
    
    TASK [debug] **********************************************************************************
    ok: [localhost] => 
      out.stdout: '3'
    
    TASK [set_fact] *******************************************************************************
    ok: [localhost]
    
    TASK [Execute command 4] **********************************************************************
    skipping: [localhost]
    
    TASK [debug] **********************************************************************************
    skipping: [localhost]
    
    TASK [set_fact] *******************************************************************************
    skipping: [localhost]
    
    TASK [Execute command 5] **********************************************************************
    skipping: [localhost]
    
    TASK [debug] **********************************************************************************
    skipping: [localhost]
    
    TASK [set_fact] *******************************************************************************
    skipping: [localhost]
    

    This seems reasonable.