Search code examples
ansiblehostvars

Ansible - pass fact between plays when one host is a variable


I have a playbook that contains two plays.

  • The first play does some data gathering, creates some facts and finally a dynamic inventory.
  • The second play uses the dynamic inventory from the first and, tries (but fails) to use some facts from the first.

I understand (ish) that the facts created are associated with the hosts of the individual plays and, hostvars could normally be used for this purpose. The trouble i'm having, in my first play, the hosts are defined as a variable that gets passed in as an extra var when calling the playbook. I don't know how to use hostvars in this scenario.

For reproduction of the issue inventory is as follows

[p1]
mgmt_server_01.us.acme.com

[p1:vars]
password = "something"

[p2]
mgmt_server_02.apac.acme.com

[p2:vars]
password = "something_else"

[p3]
mgmt_server_03.emea.acme.com

[p3:vars]
password = "yet_another_something"

[mgmt_servers:children]
p1
p2
p3

[mgmt_servers:vars]
username = "admin"

The "dynamic inventory" is sample.json

{
    "servers": [
        "server01.us.acme.com",
        "server03.us.acme.com",
        "server04.us.acme.com",
        "server02.us.acme.com"
    ]
}

The playbook is dynamic_inv.yml

---
- hosts: "{{ upgrade }}"
  name: Read sample of servers from json and create dynamic inventory. Tasks happen for ALL inventory Servers
  gather_facts: false
  connection: local
  vars:
    json_var: "{{ lookup('file', './sample.json') | from_json }}"

  tasks:
    - name: set json_var to fact
      set_fact:
        my_fact: "{{json_var}}" 

    - name: Read json to get sample servers 
      debug:
        msg: "Server names are {{item}}"
      with_items: "{{json_var.servers}}"

    - name: Create dynamic inventory from json_vars
      add_host:
        name: "{{ item }}"
        groups: sample_group
      with_items: "{{ json_var.servers }}"
      no_log: false

    - name: Print the dynamic inventory
      debug:
        var: groups.sample_group

- hosts: sample_group
  name: tasks iterate sequentially, one server at a time
  gather_facts: false
  connection: local
  order: sorted
  serial: 1

  tasks:
    - name: Print something to show the flow of the playbook
      debug: msg="This line is printed in the beginning for activity on each sample server in the inventory"
    
    - name: Print the Server name
      debug:
        msg: "The Server name is {{inventory_hostname}}"
    
    - name: Print something from my_fact
      debug: 
        msg: "something from my_fact is {{item}}"
      with_items: "{{my_fact}}"

To run the playbook ansible-playbook testing/dynamic_inv.yml -i testing/inventory -e upgrade=mgmt_server_02.apac.acme.com

The error returned is

TASK [Print the dynamic inventory] ********************************************************************************************************************************************************************************
ok: [mgmt_server_02.apac.acme.com] => {
    "groups.sample_group": [
        "server01.us.acme.com",
        "server03.us.acme.com",
        "server04.us.acme.com",
        "server02.us.acme.com"
    ]
}

PLAY [tasks iterate sequentially, one server at a time] ***********************************************************************************************************************************************************

TASK [Print something to show the flow of the playbook] ***********************************************************************************************************************************************************
ok: [server01.us.acme.com] => {
    "msg": "This line is printed in the beginning for activity on each sample server in the inventory"
}

TASK [Print the Server name] **************************************************************************************************************************************************************************************
ok: [server01.us.acme.com] => {
    "msg": "The Server name is server01.us.acme.com"
}

TASK [Print something from my_fact] *******************************************************************************************************************************************************************************
fatal: [server01.us.acme.com]: FAILED! => {"msg": "'my_fact' is undefined"}

PLAY RECAP ********************************************************************************************************************************************************************************************************
mgmt_server_02.apac.acme.com : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
server01.us.acme.com       : ok=2    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

How can i modify the following to use hostvars when the first host is passed as an extra variable ?

    - name: Print something from my_fact
      debug: 
        msg: "something from my_fact is {{item}}"
      with_items: "{{my_fact}}"

Are hostvars the best option or, is there a better solution ?


Solution

  • Instead of a separate set_fact task, set the variable directly on the newly created hosts:

    - hosts: "{{ upgrade }}"
      name: Read sample of servers from json and create dynamic inventory. Tasks happen for ALL inventory Servers
      gather_facts: false
      vars:
        json_var: "{{ lookup('file', 'sample.json') | from_json }}"
      tasks:
        - name: Create dynamic inventory from json_var
          add_host:
            name: "{{ item }}"
            groups: sample_group
            my_fact: "{{ json_var }}"
          loop: "{{ json_var.servers }}"
    
    - hosts: sample_group
      name: tasks iterate sequentially, one server at a time
      gather_facts: false
      order: sorted
      serial: 1
      tasks:
        - name: Print something from my_fact
          debug: 
            msg: "something from my_fact is {{ item }}"
          loop: "{{ my_fact | list }}"
    

    This results in:

    PLAY [Read sample of servers from json and create dynamic inventory. Tasks happen for ALL inventory Servers] ***
    
    TASK [Create dynamic inventory from json_var] **********************************
    changed: [mgmt_server_02.apac.acme.com] => (item=server01.us.acme.com)
    changed: [mgmt_server_02.apac.acme.com] => (item=server03.us.acme.com)
    changed: [mgmt_server_02.apac.acme.com] => (item=server04.us.acme.com)
    changed: [mgmt_server_02.apac.acme.com] => (item=server02.us.acme.com)
    
    PLAY [tasks iterate sequentially, one server at a time] ************************
    
    TASK [Print something from my_fact] ********************************************
    ok: [server01.us.acme.com] => (item=servers) =>
        msg: something from my_fact is servers
    
    PLAY [tasks iterate sequentially, one server at a time] ************************
    
    TASK [Print something from my_fact] ********************************************
    ok: [server02.us.acme.com] => (item=servers) =>
        msg: something from my_fact is servers
    
    PLAY [tasks iterate sequentially, one server at a time] ************************
    
    TASK [Print something from my_fact] ********************************************
    ok: [server03.us.acme.com] => (item=servers) =>
        msg: something from my_fact is servers
    
    PLAY [tasks iterate sequentially, one server at a time] ************************
    
    TASK [Print something from my_fact] ********************************************
    ok: [server04.us.acme.com] => (item=servers) =>
        msg: something from my_fact is servers
    
    PLAY RECAP *********************************************************************
    mgmt_server_02.apac.acme.com : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    server01.us.acme.com       : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    server02.us.acme.com       : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    server03.us.acme.com       : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    server04.us.acme.com       : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    

    I also did some other cleanup. For example, connection: local does nothing when everything you're running is controller-side tasks like debug and add_host. If your full playbook also runs some normal modules that you want to execute on the controller you should use delegate_to: localhost instead of messing with the connection settings, because that has some weird, unintuitive side effects.