Search code examples
ansibleansible-inventory

How to use ansible-playbook --limit with an IP address, rather than a hostname?


Our inventory in INI style looks like this:

foo-host ansible_host=1.2.3.4 some_var=bla
bar-host ansible_host=5.6.7.8 some_var=blup

I can limit a playbook run to a single host by using the host alias:

$ ansible-playbook playbook.yml --limit foo-host

But I can't limit the run by mentioning the host's IP address from the ansible_host variable:

$ ansible-playbook playbook.yml --limit 1.2.3.4
ERROR! Specified hosts and/or --limit does not match any hosts

The reason I want to do that is because Ansible is triggered by an external system that only knows the IP address, but not the alias.

Is there a way to make this work? Mangling the IP address (e.g. ip_1_2_3_4) would be acceptable.

Things I've considered:

  • Turn it on its head and identify all hosts by IP address:

    1.2.3.4 some_var=bla
    5.6.7.8 some_var=blup
    

    But now we can't use the nice host aliases anymore, and the inventory file is less readable too.

  • Write a custom inventory script that is run after the regular inventory, and creates a group like ip_1_2_3_4 containing only that single host, so we can use --limit ip_1_2_3_4. But there's no way to access previously loaded inventory from inventory scripts, so I don't know which groups to create.

  • Create the new groups dynamically using the group_by module. But because this is a task, it is run only after --limit has already decided that there are no hosts matching the pattern, and at that point Ansible just gives up and doesn't run the group_by task anymore.


Solution

  • Better solutions still welcome, but currently I'm doing it with a small inventory plugin, which (as opposed to an inventory script) does have access to previously added inventory:

    plugins/inventory/ip_based_groups.py

    import os.path
    import re
    
    from ansible.plugins.inventory import BaseInventoryPlugin
    from ansible.inventory.group import Group
    
    
    PATH_PLACEHOLDER = 'IP_BASED_GROUPS'
    IP_RE = re.compile('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
    
    
    class InventoryModule(BaseInventoryPlugin):
        '''
        This inventory plugin does not create any hosts, but just adds groups based
        on IP addresses. For each host whose ansible_host looks like an IPv4
        address (e.g. 1.2.3.4), a corresponding group is created by prefixing the
        IP address with 'ip_' and replacing dots by underscores (e.g. ip_1_2_3_4).
    
        Use it by putting the literal string IP_BASED_GROUPS at the end of the list
        of inventory sources.
        '''
    
        NAME = 'ip_based_groups'
    
        def verify_file(self, path):
            return self._is_path_placeholder(path)
    
        def parse(self, inventory, loader, path, cache=True):
            if not self._is_path_placeholder(path):
                return
            for host_name, host in inventory.hosts.items():
                ansible_host = host.vars.get('ansible_host', '')
                if self._is_ip_address(ansible_host):
                    group = 'ip_' + ansible_host.replace('.', '_')
                    inventory.add_group(group)
                    inventory.add_host(host_name, group)
    
        def _is_path_placeholder(self, path):
            return os.path.basename(path) == PATH_PLACEHOLDER
    
        def _is_ip_address(self, s):
            return bool(IP_RE.match(s))
    

    ansible.cfg

    [defaults]
    
    # Load plugins from these directories.
    inventory_plugins = plugins/inventory
    
    # Directory that contains all inventory files, and placeholder to create
    # IP-based groups.
    inventory = inventory/,IP_BASED_GROUPS
    
    [inventory]
    
    # Enable our custom inventory plugin.
    enable_plugins = ip_based_groups, host_list, script, auto, yaml, ini, toml