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.
{{ 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