ansibleansible-2.xansible-inventory

Ansible delegation: how do I use ansible rescue block in combination with `hosts` keyword


I have an inventory with three host groups, something like:

    [all:vars]
    ansible_user = myuser
    ansible_port = 22

    [A]
    XXX.XXX.XXX.XX ansible_ssh_common_args='-o StrictHostKeyChecking=accept-new' 

    [B]
    XXX.XXX.XXX.XX ansible_ssh_common_args='-o StrictHostKeyChecking=accept-new' 

    [C]
    XXX.XXX.XXX.XX ansible_ssh_common_args='-o StrictHostKeyChecking=accept-new'

Each group has a single host. I have a playlist with multiple task lists, hived off into blocks. I want to run a task list against one host in group A. If that list fails, I want to run a different task list against all the machines in group A and B.

I'm using Ansible v2.16.4.

I thought I might be able to do something like:

- hosts: "[A, B]"
  tags: always
  tasks:
    - name: Execute against A
      block:
        - name: Do something against A
          ansible.builtin.include_tasks:
            file: ./task_lists/a_tasks.yaml
          run_once: true
          delegate_to: "{{ groups['A'][0] }}"

      rescue:
        - name: Do something against A and B
          ansible.builtin.include_tasks:
            file: ./task_lists/ab_tasks.yaml

but this causes my plays to fail. The output seem to show the plays being executed from A onto B, but I'm not entirely sure what [A -> B] means or what it's doing. As far as I can tell from the docs, I would have assumed the output to have been something like [localhost -> A] for the first bit and then [localhost -> A] [localhost -> B] for the second rescue block.

Does anyone have a better approach?

Thanks


Solution

  • Well, it shouldn't work at all because:

    1. [A, B] is not a valid host pattern and it couldn't be parsed. The square brackets define the host ranges, not a list: both A and B groups would be selected as A, B, and the first host in A group would be A[0].
    2. As per your link to the documentation delegate_to cannot be used on include.

    A quick solution to your problem would be to avoid delegation and adding a when condition that checks the host instead (run_once could be omitted as it wouldn't make any sense anymore):

    - hosts: A, B
      tags: always
      tasks:
        - name: Execute against A
          block:
            - name: Do something against A
              ansible.builtin.include_tasks:
                file: ./task_lists/a_tasks.yaml
              when: ansible_host == groups['A'][0]
          rescue:
            - name: Do something against A and B
              ansible.builtin.include_tasks:
                file: ./task_lists/ab_tasks.yaml
    

    Another, a bit more explicit option is to set some fact that would become a trigger for the AB tasks in case of failure in A tasks, and delegate it to the implicit localhost that is available everywhere:

    - hosts: A[0]
      tags: always
      tasks:
        - name: Execute against A
          block:
            - name: Do something against A
              ansible.builtin.include_tasks:
                file: ./task_lists/a_tasks.yaml
          rescue:
            - name: Trigger something against A and B
              ansible.builtin.set_fact:
                run_smth_against_ab: true
              delegate_to: localhost
              delegate_facts: true
    
    - hosts: A,B
      tags: always
      tasks:
        - name: Do something against A and B
          ansible.builtin.include_tasks:
            file: ./task_lists/ab_tasks.yaml
          when:
            - hostvars['localhost'].run_smth_against_ab is defined
            - hostvars['localhost'].run_smth_against_ab