piercjs
piercjs

Reputation: 317

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

I have a playbook that contains two plays.

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 ?

Upvotes: 0

Views: 169

Answers (1)

flowerysong
flowerysong

Reputation: 2877

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.

Upvotes: 1

Related Questions