首页 > 解决方案 > How to delegate facts to localhost from a play targeting remote hosts

问题描述

ansible version: 2.9.16 running on RHEL 7.9 python ver = 2.7.5 targeting windows 2016 servers. ( should behave the same for linux target servers too)

EDIT: Switched to using host specific variables in inventory to avoid confusion that Iam just trying to print hostnames of a group. Even here its a gross simplification. Pretend that var1 is obtained dynamically for each server instead of being declared in the inventory file.

My playbook has two plays. One targets 3 remote servers ( Note: serial: 0 i.e Concurrently ) and another just the localhost. In play1 I am trying to delegate facts obtained from each of these hosts to the localhost using delegate_facts and delegate_to. The intent is to have these facts delegated to a single host ( localhost ) so I can use it later in a play2 (using hostvars) that targets the localhost. But strangely thats not working. It only has information from the last host from Play1.

Any help will be greatly appreciated.

my inventory file inventory/test.ini looks like this:

[my_servers]
svr1 var1='abc'
svr2 var1='xyz'
svr3 var1='pqr'

My Code:

## Play1
- name: Main play  that runs against multiple remote servers and builds a list.
  hosts: 'my_servers'  # my inventory group that contains 3 servers svr1,svr2,svr3
  any_errors_fatal: false
  ignore_unreachable: true
  gather_facts: true
  serial: 0

  tasks:

      - name: initialize my_server_list as a list and delegate to localhost 
        set_fact:
          my_server_list: []
        delegate_facts: yes
        delegate_to: localhost

      - command: /root/complex_script.sh
        register: result

      - set_fact:
        my_server_list: "{{ my_server_list + hostvars[inventory_hostname]['result.stdout'] }}"
        # run_once: true  ## Commented as I need to query the hostvars for each host where this executes.
  delegate_to: localhost
  delegate_facts: true





      - name: "Print list - 1"
        debug:
          msg: 
            - "{{ hostvars['localhost']['my_server_list']  | default(['NotFound']) | to_nice_yaml }}" 
        # run_once: true

      - name: "Print list - 2"
        debug:
          msg: 
            - "{{ my_server_list | default(['NA']) }}" 


## Play2
- name: Print my_server_list which was built in Play1
  hosts: localhost
  gather_facts: true
  serial: 0

  tasks:

      - name: "Print my_server_list without hostvars "
        debug:
          msg: 
            - "{{ my_server_list  | to_nice_json }}" 
        # delegate_to: localhost


      - name: "Print my_server_list using hostvars"
        debug:
          msg: 
            - "{{ hostvars['localhost']['my_server_list']  | to_nice_yaml }}" 
        # delegate_to: localhost

###Output###

$ ansible-playbook -i inventory/test.ini delegate_facts.yml

PLAY [Main playbook that runs against multiple remote servers and builds a list.] ***********************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************************************************************************************
ok: [svr3]
ok: [svr1]
ok: [svr2]

TASK [initialize] ***************************************************************************************************************************************************************************
ok: [svr1]
ok: [svr2]
ok: [svr3]

TASK [Build a list of servers] **************************************************************************************************************************************************************
ok: [svr1]
ok: [svr2]
ok: [svr3]

TASK [Print list - 1] ***********************************************************************************************************************************************************************
ok: [svr1] =>
  msg:
  - |-
    - pqr
ok: [svr2] =>
  msg:
  - |-
    - pqr
ok: [svr3] =>
  msg:
  - |-
    - pqr

TASK [Print list - 2] ***********************************************************************************************************************************************************************
ok: [svr1] =>
  msg:
  - - NA
ok: [svr2] =>
  msg:
  - - NA
ok: [svr3] =>
  msg:
  - - NA

PLAY [Print my_server_list] *****************************************************************************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************************************************************************************
ok: [localhost]

TASK [Print my_server_list without hostvars] ************************************************************************************************************************************************
ok: [localhost] =>
  msg:
  - |-
    [
        "pqr"
    ]

TASK [Print my_server_list using hostvars] **************************************************************************************************************************************************
ok: [localhost] =>
  msg:
  - |-
    - pqr

PLAY RECAP **********************************************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
svr1                       : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
svr2                       : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
svr3                       : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Playbook run took 0 days, 0 hours, 0 minutes, 13 seconds

###Expected Output### I was expecting the last two debug statements in Play2 to contain the values of var1 for all the servers something like this:

    TASK [Print my_server_list using hostvars] **************************************************************************************************************************************************
    ok: [localhost] =>
      msg:
      - |-
        - abc
        - xyz
        - pqr
    

标签: ansible

解决方案


Use Special Variables, e.g.

- hosts: all
  gather_facts: false
  tasks:
    - set_fact:
        my_server_list: "{{ ansible_play_hosts_all }}"
      run_once: true
      delegate_to: localhost
      delegate_facts: true

- hosts: localhost
  gather_facts: false
  tasks:
    - debug:
        var: my_server_list

gives

ok: [localhost] => 
  my_server_list:
  - svr1
  - svr2
  - svr3

There are many other ways how to create the list, e.g.

- hosts: all
  gather_facts: false
  tasks:
    - debug:
        msg: "{{ groups.my_servers }}"
      run_once: true
- hosts: all
  gather_facts: false
  tasks:
    - debug:
        msg: "{{ hostvars|json_query('*.inventory_hostname') }}"
      run_once: true

Q: "Fill the list with outputs gathered by running complex commands."

A: Last example above shows how to create a list from hostvars. Register the result from the complex command, e.g.

shell> ssh admin@srv1 cat /root/complex_script.sh
#!/bin/sh
ifconfig wlan0 | grep inet | cut -w -f3

The playbook

- hosts: all
  gather_facts: false
  tasks:
    - command: /root/complex_script.sh
      register: result
    - set_fact:
        my_server_list: "{{ hostvars|json_query('*.result.stdout') }}"
      run_once: true
      delegate_to: localhost
      delegate_facts: true

- hosts: localhost
  gather_facts: false
  tasks:
    - debug:
        var: my_server_list

gives

  my_server_list:
  - 10.1.0.61
  - 10.1.0.62
  - 10.1.0.63

Q: "Why the logic of delegating facts to localhost and keep appending them to that list does not work?"

A: The code below (simplified) can't work because the right-hand-side msl value still comes from the hostvars of the inventory_host despite the fact delegate_facts: true. This merely puts the created variable msl into the localhost's hostvars

- hosts: my_servers
  tasks:
    - set_fact:
        msl: "{{ msl|default([]) + [inventory_hostname] }}"
      delegate_to: localhost
      delegate_facts: true

Quoting from Delegating facts

To assign gathered facts to the delegated host instead of the current host, set delegate_facts to true

As a result of such code, the variable msl will keep the last assigned value only.


推荐阅读