Search code examples
ansibleansible-runner

How to get unreachable nodes after ansible playbook execution in python?


After executing an ansible playbook, I need the node names that were not reached. I thought about using AnsibleRunner for running the ansible-playbook and I know of the special variables ansible_play_hosts and ansible_play_hosts_all which you can diff to get the unreachable nodes. But those special variables only exist in the context of ansible.

So how do I get the unreachable nodes from AnsibleRunner (or a similar utility), without parsing ansible's output.

On Runner Objects

Currently I am creating a runner object and evaluating its variables after execution. I see that in the events I can theoretically look for unreachable nodes, but that seems to be quite sketchy.

One might also be able to write an event handler that specifically catches those events that indicate that a node is unreachable that handles those cases differently.

I also wrote a small fact registration that registers which nodes are unreachable which will be executed as a last task, but I couldn't access it yet:

- set_fact:
    unreachable: "{{ ansible_play_hosts_all|difference(ansible_play_hosts) }}"

On ansible API

The ansible API has in its example a dictionary called host_unreachable. While that sounds very promising I have two issues with it: I) I couldn't run the example II) The API will not be downwards compatible and therefore shouldn't be used externally.


Solution

  • What you are looking for is actually available out of the box in the Runner.stats property once your playbook has run.

    Example implementation

    Given:

    1. the inv.yml inventory
     ---
     local_test:
       vars:
         ansible_connection: local
         failling_hosts:
           - test2
           - test3
       hosts:
         test1:
         test2:
         test3:
         test4:
     
     unreachable:
       hosts:
         test5:
    
    1. the test.yml playbook
    ---
    - hosts: all
    
      tasks:
        - name: dummy task for all
          debug:
            msg: task1 for all hosts
    
        - name: blindly fail some hosts for test
          assert:
            that: inventory_hostname not in (failling_hosts | d([]))
    
        - name: dummy task for survivors
          debug:
            msg: task2 for survivors
    
    1. the test.py python script
    import ansible_runner
    
    runner = ansible_runner.run(
        private_data_dir='.',
        inventory='inv.yml',
        playbook='test.yml',
        quiet=False if len(sys.argv) > 1 and sys.argv[1] == '-v' else True
    )
    
    overall_failed = {
        'unreachable': runner.stats['dark'].keys(),
        'failed': runner.stats['failures'].keys()
    }
    
    for elem_type, elements in overall_failed.items():
        for i, host in enumerate(elements):
            if i == 0:
                print(f'\nList of {elem_type} hosts:')
            print(f'\t{host}')
    

    We get (run with -v if you want the playbook output too):

    $ python test.py
    
    List of unreachable hosts:
        test5
    
    List of failed hosts:
        test2
        test3