Arbitrary host name resolution in Ansible
Question:
Is there a way to resolve an arbitrary string as a host name in Ansible group_vars
file or in a Jinja2 template used by Ansible? Let’s say, I want to define a variable in global_vars/all
that would contain one of the several IP addresses that www.google.com
resolves into. In this example, I used www.google.com
just as an example of a string that ca be resolved into multiple IP addresses and yet I cannot use Ansible hostvars
for the address because I cannot ssh into it.
I tried to wire in Pythonic socket.gethostbyname()
but could not get the syntax right. At most, my variable became a literal “socket.gethostbyname(‘my-host-1’)”.
I know I can fall back to a shell script and to take advantage of the tools available in shell but I’d like to see if there is an elegant way to accomplish this in Ansible.
The more gory details of the question are that I need to populate Postgres HBA configuration file with IP addresses of the permitted hosts. I cannot use their host names because the target deployment does not have reverse DNS that is required for host name based HBA.
I really wish Postgres resolved the names in the configuration file and matched it against the client’s IP address instead of doing a reverse lookup of the client’s IP address and then matching the strings of host names. But this is too much to expect and too long to wait. I need a workaround for now, and I’d like to stay within Ansible for that, not having to offload this into an external script.
Thank you for reading this far!
Answers:
You can create a lookup plugin for this:
Ansible 1.x:
import ansible.utils as utils
import ansible.errors as errors
import socket
class LookupModule(object):
def __init__(self, basedir=None, **kwargs):
self.basedir = basedir
def run(self, terms, inject=None, **kwargs):
if not isinstance(terms, basestring):
raise errors.AnsibleError("ip lookup expects a string (hostname)")
return [socket.gethostbyname(terms)]
Ansible 2.x:
import ansible.utils as utils
import ansible.errors as errors
from ansible.plugins.lookup import LookupBase
import socket
class LookupModule(LookupBase):
def __init__(self, basedir=None, **kwargs):
self.basedir = basedir
def run(self, terms, variables=None, **kwargs):
hostname = terms[0]
if not isinstance(hostname, basestring):
raise errors.AnsibleError("ip lookup expects a string (hostname)")
return [socket.gethostbyname(hostname)]
Save this relative to your playbook as lookup_plugins/ip.py
.
Then use it as {{ lookup('ip', 'www.google.com') }}
The lookup plugin udondan mentions is the “right” way to do this (though the API changes for 2.0, so be aware of what version you’re targeting if you go that way). If you just want something quick and dirty though, the following should work:
- hosts: yourhosts
tasks:
- local_action: shell dig +short host-to-lookup-here.com
changed_when: false
register: dig_output
- set_fact:
looked_up_ips: "{{ dig_output.stdout_lines }}"
- debug: msg="found ip {{ item }}"
with_items: looked_up_ips
The fact looked_up_ips would then be a list of the mapped IPs accessible from a template or whatever you need to use it in…
For completeness’ sake, apparently we’ve been shipping a dig lookup plugin in the box since around 1.9 that would wrap this up nicely (h/t Duncan Hutty):
https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/lookup/dig.py
As @nitzmahone suggested, the dig
plugin can be used exactly for this purpose. E.g.:
- name: Simple A record (IPV4 address) lookup for example.com
debug: msg="{{ lookup('dig', 'example.com.')}}"
Prints
MSG:
93.184.216.34
Note: you have to have dnspython
installed in order for this to work. You can install it with
pip install dnspython
Is there a way to resolve an arbitrary string as a host name in Ansible group_vars
file or in a Jinja2 template used by Ansible? Let’s say, I want to define a variable in global_vars/all
that would contain one of the several IP addresses that www.google.com
resolves into. In this example, I used www.google.com
just as an example of a string that ca be resolved into multiple IP addresses and yet I cannot use Ansible hostvars
for the address because I cannot ssh into it.
I tried to wire in Pythonic socket.gethostbyname()
but could not get the syntax right. At most, my variable became a literal “socket.gethostbyname(‘my-host-1’)”.
I know I can fall back to a shell script and to take advantage of the tools available in shell but I’d like to see if there is an elegant way to accomplish this in Ansible.
The more gory details of the question are that I need to populate Postgres HBA configuration file with IP addresses of the permitted hosts. I cannot use their host names because the target deployment does not have reverse DNS that is required for host name based HBA.
I really wish Postgres resolved the names in the configuration file and matched it against the client’s IP address instead of doing a reverse lookup of the client’s IP address and then matching the strings of host names. But this is too much to expect and too long to wait. I need a workaround for now, and I’d like to stay within Ansible for that, not having to offload this into an external script.
Thank you for reading this far!
You can create a lookup plugin for this:
Ansible 1.x:
import ansible.utils as utils
import ansible.errors as errors
import socket
class LookupModule(object):
def __init__(self, basedir=None, **kwargs):
self.basedir = basedir
def run(self, terms, inject=None, **kwargs):
if not isinstance(terms, basestring):
raise errors.AnsibleError("ip lookup expects a string (hostname)")
return [socket.gethostbyname(terms)]
Ansible 2.x:
import ansible.utils as utils
import ansible.errors as errors
from ansible.plugins.lookup import LookupBase
import socket
class LookupModule(LookupBase):
def __init__(self, basedir=None, **kwargs):
self.basedir = basedir
def run(self, terms, variables=None, **kwargs):
hostname = terms[0]
if not isinstance(hostname, basestring):
raise errors.AnsibleError("ip lookup expects a string (hostname)")
return [socket.gethostbyname(hostname)]
Save this relative to your playbook as lookup_plugins/ip.py
.
Then use it as {{ lookup('ip', 'www.google.com') }}
The lookup plugin udondan mentions is the “right” way to do this (though the API changes for 2.0, so be aware of what version you’re targeting if you go that way). If you just want something quick and dirty though, the following should work:
- hosts: yourhosts
tasks:
- local_action: shell dig +short host-to-lookup-here.com
changed_when: false
register: dig_output
- set_fact:
looked_up_ips: "{{ dig_output.stdout_lines }}"
- debug: msg="found ip {{ item }}"
with_items: looked_up_ips
The fact looked_up_ips would then be a list of the mapped IPs accessible from a template or whatever you need to use it in…
For completeness’ sake, apparently we’ve been shipping a dig lookup plugin in the box since around 1.9 that would wrap this up nicely (h/t Duncan Hutty):
https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/lookup/dig.py
As @nitzmahone suggested, the dig
plugin can be used exactly for this purpose. E.g.:
- name: Simple A record (IPV4 address) lookup for example.com
debug: msg="{{ lookup('dig', 'example.com.')}}"
Prints
MSG:
93.184.216.34
Note: you have to have dnspython
installed in order for this to work. You can install it with
pip install dnspython