Search code examples
regexansibleansible-inventoryansible-bug

Is there a way to use a regular expression to match hosts in ansible?


I am trying to match hosts using a regex pattern with ansible but it is not working as expected. My inventory is as seen below:

[group1]
hello1
world1
hello2
world2

[group2]
hello3     

And my task is:

- debug:
    msg: "{{ item }}"
  with_inventory_hostnames:
    - ~hello*

From their documentation:

Using regexes in patterns
You can specify a pattern as a regular expression by starting the pattern with ~:

~(web|db).*\.example\.com

When I execute the task there is no output. I am a n00b with regex so could it be possible my regex is wrong?


Solution

  • Q: "Could it be possible my regex is wrong?"

    A: It's a bug. See inventory_hostnames lookup doesn't support wildcards in patterns #17268. It will be probably fixed in 2.10. But your pattern wouldn't work, I think, because the doc says: "You can use wildcard patterns with FQDNs or IP addresses, as long as the hosts are named in your inventory by FQDN or IP address". The hosts in your inventory are neither FQDN nor IP.

    Q: "Is there a way to use a regular expression to match hosts in ansible?"

    A: Yes. It is. A very convenient way is to create dynamic groups with the module add_host. For example the playbook below

    - hosts: localhost
      tasks:
        - add_host:
            name: "{{ item }}"
            groups: my_dynamic_group
          loop: "{{ groups.all|select('match', my_pattern)|list }}"
          vars:
            my_pattern: '^hello\d+$'
    
    - hosts: my_dynamic_group
      tasks:
        - debug:
            var: inventory_hostname
    

    gives (abridged)

        "inventory_hostname": "hello2"
        "inventory_hostname": "hello1"
        "inventory_hostname": "hello3"
    

    Update

    The next option is the inventory plugin constructed. See

    shall> ansible-doc -t inventory ansible.builtin.constructed
    
    1. Create the inventory
    shell> tree inventory/
    inventory/
    ├── 01-hosts
    └── 02-constructed.yml
    
    0 directories, 2 files
    
    shell> cat inventory/01-hosts 
    [group1]
    hello1
    world1
    hello2
    world2
    
    [group2]
    hello3
    
    shell> cat inventory/02-constructed.yml 
    plugin: ansible.builtin.constructed
    groups:
      hello_group: inventory_hostname.startswith('hello')
      world_group: inventory_hostname.startswith('world')
    
    1. Test the inventory
    shell> ansible-inventory -i inventory --graph
    @all:
      |--@group1:
      |  |--hello1
      |  |--hello2
      |  |--world1
      |  |--world2
      |--@group2:
      |  |--hello3
      |--@hello_group:
      |  |--hello1
      |  |--hello2
      |  |--hello3
      |--@ungrouped:
      |--@world_group:
      |  |--world1
      |  |--world2
    

    You can see that the plugin created two groups: world_group and hello_group.

    1. Use the groups. For example,
    shell> cat pb.yml
    - hosts: hello_group
      tasks:
        - debug:
            var: ansible_play_hosts_all
          run_once: true
    
    - hosts: world_group
      tasks:
        - debug:
            var: ansible_play_hosts_all
          run_once: true
    

    gives

    shell> ansible-playbook  -i inventory pb.yml 
    
    PLAY [hello_group] ***************************************************************************
    
    TASK [debug] *********************************************************************************
    ok: [hello1] => 
      ansible_play_hosts_all:
      - hello1
      - hello2
      - hello3
    
    PLAY [world_group] ***************************************************************************
    
    TASK [debug] *********************************************************************************
    ok: [world1] => 
      ansible_play_hosts_all:
      - world1
      - world2
    
    PLAY RECAP ***********************************************************************************
    hello1: ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
    world1: ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0