Search code examples
ansibleglobal-variablesansible-inventoryansible-facts

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.


Solution

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