hgalvan
hgalvan

Reputation: 59

How can I prompt the user for an inventory group and pass that variable to next plays in the same playbook?

In this playbook, I'm prompting the user for a region. After the user selects a region, I want to run plays against that specific inventory group. I'm also assigning that region to a fact so I can use it on another play on the same playbook.

My inventory file looks like this:

[ams]
device1
device2

[usa]
device1
device2

[jpn]
device1
device2

Playbook looks like this:

---
- name:  Play 1
  hosts: "{{ region }}"
  gather_facts: false
  connection: network_cli
  
  vars_prompt:
    - name: region
      prompt: "Region (ams, usa, jpn)"
      private: no

  tasks:
    - name: Make vars persistent
      set_fact:
        region: "{{ region }}"

    - name: check var
      debug:
        msg: "{{ region }}"


- name:  Play 2
  hosts: "{{ region }}"
  gather_facts: false
  connection: network_cli

  vars:
    region: "{{ hostvars['region'] }}"

  tasks:
    - name: debug
      debug: 
        msg: "{{ region }}"

But I get the following error while starting "Play 2"

ERROR! The field 'hosts' has an invalid value, which includes an undefined variable. The error was: {{ hostvars['region'] }}: "hostvars['region']" is undefined. "hostvars['region']" is undefined. {{ hostvars['region'] }}: "hostvars['region']" is undefined. "hostvars['region']" is undefined

Am I trying to read facts the wrong way?

I also tried with

{{ hostvars['region']['region'] }}"

, but I get the same error.

Upvotes: 1

Views: 423

Answers (1)

Vladimir Botka
Vladimir Botka

Reputation: 68294

{{ hostvars['region']['region'] }}

Q: "Am I trying to read facts the wrong way?"

A: Yes. The first attribute should be the name of a host. Quoting from hostvars:

"A dictionary/map with all the hosts in inventory and variables assigned to them"

{{ hostvars[name_of_host]['region'] }}

You can use for this purpose any of the hosts in the inventory (device1, device2, ...), but generally, it is not a good idea to hardcode in a playbook an inventory name that might be changed. You can use localhost for this purpose, but you'll have to include localhost in the inventory, for example

shell> cat hosts 
localhost

[ams]
device1
device2
...

But this may have side effects on localhost variables if you use ones. The best option for this purpose seems to be a dummy host. Put it into a separate file. Other hosts might be generated dynamically. Create a project for testing

shell> tree .
.
├── ansible.cfg
├── inventory
│   ├── 01-hosts
│   └── 02-hosts
└── pb.yml
shell> cat inventory/01-hosts 
dummy
shell> cat inventory/02-hosts 
[ams]
device1
device2

[usa]
device3
device4

[jpn]
device5
device6

Run the first play on all hosts. Set the variable region once and delegate to dummy. This way, the variable region will be put into the hostvars (instantiated/made persistent) of all hosts

shell> cat pb.yml
- name:  Play 1
  hosts: all
  
  vars_prompt:

    - name: region
      prompt: "Region (ams, usa, jpn)"
      private: no

  tasks:

    - block:
        - name: Make vars persistent
          set_fact:
            region: "{{ region }}"
        - name: Check var
          debug:
            var: dict(hostvars|dict2items|json_query('[].[key, value.region]'))
      delegate_to: dummy
      run_once: true

- name:  Play 2
  hosts: "{{ hostvars.dummy.region }}"

  tasks:

    - debug: 
        var: region

gives

shell> ansible-playbook -i inventory pb.yml
Region (ams, usa, jpn): usa

PLAY [Play 1] *********************************************************************************

TASK [Make vars persistent] *******************************************************************
ok: [dummy]

TASK [Check var] ******************************************************************************
ok: [dummy] => 
  dict(hostvars|dict2items|json_query('[].[key, value.region]')):
    device1: usa
    device2: usa
    device3: usa
    device4: usa
    device5: usa
    device6: usa
    dummy: usa

PLAY [Play 2] *********************************************************************************

TASK [debug] **********************************************************************************
ok: [device3] => 
  region: usa
ok: [device4] => 
  region: usa

PLAY RECAP ************************************************************************************
device3: ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
device4: ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
dummy: ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Ansible module ansible.builtin.add_host

The simpler and more straightforward option is the usage of the module add_host. Create a group, for example, my_region in the first play and use it in the second one. Create a project for testing

shell> tree .
.
├── ansible.cfg
├── hosts
└── pb.yml
shell> cat hosts
[ams]
device1
device2

[usa]
device3
device4

[jpn]
device5
device6
shell> cat pb.yml 
- name:  Play 1
  hosts: localhost
  
  vars_prompt:

    - name: region
      prompt: "Region (ams, usa, jpn)"
      private: no

  tasks:

    - add_host:
        name: "{{ item }}"
        groups: my_region
        region: "{{ region }}"
      loop: "{{ groups[region] }}"

- name:  Play 2
  hosts: my_region

  tasks:

    - debug: 
        var: region

gives

shell> ansible-playbook pb.yml 
Region (ams, usa, jpn): usa

PLAY [Play 1] *********************************************************************************

TASK [add_host] *******************************************************************************
changed: [localhost] => (item=device3)
changed: [localhost] => (item=device4)

PLAY [Play 2] *********************************************************************************

TASK [debug] **********************************************************************************
ok: [device3] => 
  region: usa
ok: [device4] => 
  region: usa

PLAY RECAP ************************************************************************************
device3: ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
device4: ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
localhost: ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Upvotes: 2

Related Questions