Part of Ansible’s power comes from an easy integration with other systems. In this post I will cover how to look up data from external sources like DNS or Redis.
Background
A tool for automation is only as good as it is capable to integrate it with the already existing environment – thus with other tools. Among various ways Ansible offers the possibility to look up Ansible variables from external stores like DNS, Redis, etcd or even generic INI or CSV files. This enables Ansible to easily access data which are stored – and changed, managed – outside of Ansible.
Setup
Ansible’s lookup feature is already installed by default.
Queries are executed on the host where the playbook is executed – in case of Tower this would be the Tower host itself. Thus the node needs access to the resources which needs to be queried.
Some lookup functions for example for DNS or Redis servers require additional python libraries – on the host actually executing the queries! On Fedora, the python-dns
package is necessary for DNS queries and the package python-redis
for Redis queries.
Generic usage
The lookup function can be used the exact same way variables are used: curly brackets surround the lookup function, the result is placed where the variable would be. That means lookup functions can be used in the head of a playbook, inside the tasks, even in templates.
The lookup command itself has to list the plugin as well as the arguments for the plugin:
{{ lookup('plugin','arguments') }}
Examples
Files
Entire files can be used as content of a variable. This is simply done via:
vars: content: "{{ lookup('file','lorem.txt') }}"
As a result, the variable has the entire content of the file. Note that the lookup of files always searches the files relative to the path of the actual playbook, not relative to the path where the command is executed.
Also, the lookup might fail when the file itself contains quote characters.
CSV
While the file lookup is pretty simple and generic, the CSV lookup module gives the ability to access values of given keys in a CSV file. An optional parameter can identify the appropriate column. For example, if the following CSV file is given:
$ cat gamma.csv daytime,time,meal breakfast,7,soup lunch,12,rice tea,15,cake dinner,18,noodles
Now the lookup function for CSV files can access the lines identified by keys which are compared to the values of the first column. The following example looks up the key dinner
and gives back the entry of the third column: {{ lookup('csvfile','dinner file=gamma.csv delimiter=, col=2') }}
.
Inserted in a playbook, this looks like:
ansible-playbook examples/lookup.yml PLAY [demo lookups] *********************************************************** GATHERING FACTS *************************************************************** ok: [neon] TASK: [lookup of a csv file] ************************************************** ok: [neon] => { "msg": "noodles" } PLAY RECAP ******************************************************************** neon : ok=2 changed=0 unreachable=0 failed=0
The corresponding playbook gives out the variable via the debug module:
--- - name: demo lookups hosts: neon tasks: - name: lookup of a csv file debug: msg="{{ lookup('csvfile','dinner file=gamma.csv delimiter=, col=2') }}"
DNS
The DNS lookup is particularly interesting in cases where the local DNS provides a lot of information like SSH fingerprints or the MX record.
The DNS lookup plugin is called dig
– like the command line client dig. As arguments, the plugin takes a domain name and the DNS type: {{ lookup('dig', 'redhat.com. qtype=MX') }}
. Another way to hand over the type argument is via slash: {{ lookup('dig', 'redhat.com./MX') }}
The result for this example is:
TASK: [lookup of dns dig entries] ********************************************* ok: [neon] => { "msg": "10 int-mx.corp.redhat.com." }
Redis
It gets even more interesting when existing databases are queried. Ansible lookup supports for example Redis databases. The plugin takes as argument the entire URL: redis://$URL:$PORT,$KEY
.
For example, to query a local Redis server for the key dinner
:
--- tasks: - name: lookup of redis entries debug: msg="{{ lookup('redis_kv', 'redis://localhost:6379,dinner') }}"
The result is:
TASK: [lookup of redis entries] *********************************************** ok: [neon] => { "msg": "noodles" }
Template
As already mentioned, lookups can not only be used in Playbooks, but also directly in templates. For example, given the template code:
$ cat templatej2 ... Red Hat MX: {{ lookup('dig', 'redhat.com./MX') }} $ cat template.conf ... Red Hat MX: 10 mx2.redhat.com.,5 mx1.redhat.com.
Conclusion
As shown the lookup plugin of Ansible provides many possibilities to integrate Ansible with existing tools and environments which already contain valuable data about the systems. It is easy to use, integrates well with the existing Ansible concepts and can quickly be integrated. Just drop it where a variable would be dropped, and it already works.
I am looking forward to more lookup modules support in the future – I’d love to see a generic “http” and a generic “SQL” plugin, even with the ability to provide credentials, although these features can be somewhat realized with already existing modules.