Thomas
Thomas

Reputation: 181715

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:

Upvotes: 1

Views: 6131

Answers (2)

Thomas
Thomas

Reputation: 181715

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

Upvotes: 2

Vladimir Botka
Vladimir Botka

Reputation: 67959

Q: Playbook running a script running a playbook... it would work, but it's a bit hacky

A: FWIW. It's possible to use json_query and avoid the script. For example

- hosts: all
  gather_facts: false
  tasks:
    - set_fact:
        my_host: "{{ hostvars|dict2items|json_query(query)|first }}"
      vars:
        query: "[?value.ansible_host == '{{ my_host_ip }}' ].key"
      run_once: true
    - add_host:
        hostname: "{{ my_host }}"
        groups: my_group
      run_once: true

- hosts: my_group
  gather_facts: false
  tasks:
    - debug:
        var: inventory_hostname

Q: "Unfortunately I'm running ansible-playbook from AWX, so no wrapper scripts allowed."

A: It is possible to run the script from the playbook. For example the playbook below

- hosts: all
  gather_facts: false
  tasks:
    - set_fact:
        my_host: "{{ lookup('pipe', playbook_dir ~ '/script.sh ' ~ my_host_ip) }}"
      delegate_to: localhost
      run_once: true
    - add_host:
        hostname: "{{ my_host }}"
        groups: my_group
      run_once: true

- hosts: my_group
  gather_facts: false
  tasks:
    - debug:
        var: inventory_hostname

gives

$ ansible-playbook -e 'my_host_ip=10.1.0.53' play.yml

PLAY [all] ********************

TASK [set_fact] ***************
ok: [test_01 -> localhost]

TASK [add_host] ***************
changed: [test_01]

PLAY [my_group] ***************

TASK [debug] ************
ok: [test_03] => {
    "inventory_hostname": "test_03"
}

(Fit the script to print the hostname only.)

Q: 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

A: ansible-inventory and jq are able to resolve the host. For example the script

#!/bin/bash
my_host="$(ansible-inventory --list | jq '._meta.hostvars | to_entries[] | select (.value.ansible_host=="'"$1"'") | .key')"
my_host="$(echo $my_host | sed -e 's/^"//' -e 's/"$//')"
echo host: $my_host
ansible-playbook -l $my_host play.yml

with the inventory

test_01 ansible_host=10.1.0.51
test_02 ansible_host=10.1.0.52
test_03 ansible_host=10.1.0.53

and with the playbook.yml

- hosts: all
  gather_facts: false
  tasks:
    - debug:
        var: inventory_hostname

gives

$ ./script.bash 10.1.0.53
host: test_03

PLAY [all] **********

TASK [debug] ************
ok: [test_03] => {
    "inventory_hostname": "test_03"
}

Upvotes: 0

Related Questions