In an earlier post, I wrote about using the OpenStack Ansible inventory helper when calling and Ansible command line tools. However, When developing an playbook, often there is more information pulled from the inventory than just the set of hosts. Often, the inventory also collects variables that are used in common across multiple playbooks. For this reason, and many more, I want to be able to call an Ansible playbook or Ad-Hoc command from the command line, but use the inventory as defined by an Ansible Tower instance. It turns out this is fairly simple to do, using the REST API.
If you have a working Ansible Tower instance, you can find the REST API starting point by editing the URL in your browser. For example, I have a VM running on my machine listening on local IP address 192.168.122.151. If, after authenticating, I edit the URL to https://192.168.122.151/api/ I see the page below:
To navigate to this same page via curl, I run:
curl -s -k https://192.168.122.151/api/
Which returns:
{"available_versions":{"v1":"/api/v1/","v2":"/api/v2/"},"description":"AWX REST API","current_version":"/api/v2/"}
To go much deeper into the API I need to be authenticated. I can do that here using Basic-Auth, and a little jq to make it legible:
export CREDS='admin:password' $ curl -s -k -u $CREDS https://192.168.122.151/api/v2/ | jq ' |
Which returns
{ "authtoken": "/api/v2/authtoken/", "ping": "/api/v2/ping/", "instances": "/api/v2/instances/", "instance_groups": "/api/v2/instance_groups/", "config": "/api/v2/config/", "settings": "/api/v2/settings/", "me": "/api/v2/me/", "dashboard": "/api/v2/dashboard/", "organizations": "/api/v2/organizations/", "users": "/api/v2/users/", "projects": "/api/v2/projects/", "project_updates": "/api/v2/project_updates/", "teams": "/api/v2/teams/", "credentials": "/api/v2/credentials/", "credential_types": "/api/v2/credential_types/", "inventory": "/api/v2/inventories/", "inventory_scripts": "/api/v2/inventory_scripts/", "inventory_sources": "/api/v2/inventory_sources/", "inventory_updates": "/api/v2/inventory_updates/", "groups": "/api/v2/groups/", "hosts": "/api/v2/hosts/", "job_templates": "/api/v2/job_templates/", "jobs": "/api/v2/jobs/", "job_events": "/api/v2/job_events/", "ad_hoc_commands": "/api/v2/ad_hoc_commands/", "system_job_templates": "/api/v2/system_job_templates/", "system_jobs": "/api/v2/system_jobs/", "schedules": "/api/v2/schedules/", "roles": "/api/v2/roles/", "notification_templates": "/api/v2/notification_templates/", "notifications": "/api/v2/notifications/", "labels": "/api/v2/labels/", "unified_job_templates": "/api/v2/unified_job_templates/", "unified_jobs": "/api/v2/unified_jobs/", "activity_stream": "/api/v2/activity_stream/", "workflow_job_templates": "/api/v2/workflow_job_templates/", "workflow_jobs": "/api/v2/workflow_jobs/", "workflow_job_template_nodes": "/api/v2/workflow_job_template_nodes/", "workflow_job_nodes": "/api/v2/workflow_job_nodes/" } |
This call lays out all of the top level objects available in tower. The “inventory” suburl, “/api/v2/inventories/,” enumerates the inventory objects recorded in the system. Because there is a lot of information available on each object, I’ve found it necessary to filter down by name:
$ curl -s -k -u $CREDS https://192.168.122.151/api/v2/inventories/ | jq '.results | .[] | .name' "Demo Inventory" "Generic OCP Inventory" "Ibis" "MOC_RedHatFSI" "RDU-OpenStack" |
And to get access to a specific inventory:
curl -s -k -u $CREDS https://192.168.122.151/api/v2/inventories/ | jq '.results | .[] | select (.name == "MOC_RedHatFSI") ' |
Again, returning a lot of information. What I care about, however, is the URL that I can call to create an inventory json structure consumable by Ansible. And this is in the related collection in the field script.
$ curl -s -k -u $CREDS https://192.168.122.151/api/v2/inventories/ | jq '.results | .[] | select (.name == "MOC_RedHatFSI") |.related ' |
{ "created_by": "/api/v2/users/1/", "job_templates": "/api/v2/inventories/4/job_templates/", "variable_data": "/api/v2/inventories/4/variable_data/", "root_groups": "/api/v2/inventories/4/root_groups/", "object_roles": "/api/v2/inventories/4/object_roles/", "ad_hoc_commands": "/api/v2/inventories/4/ad_hoc_commands/", "script": "/api/v2/inventories/4/script/", "tree": "/api/v2/inventories/4/tree/", "access_list": "/api/v2/inventories/4/access_list/", "activity_stream": "/api/v2/inventories/4/activity_stream/", "instance_groups": "/api/v2/inventories/4/instance_groups/", "hosts": "/api/v2/inventories/4/hosts/", "groups": "/api/v2/inventories/4/groups/", "update_inventory_sources": "/api/v2/inventories/4/update_inventory_sources/", "inventory_sources": "/api/v2/inventories/4/inventory_sources/", "organization": "/api/v2/organizations/1/" } |
That lead me to write a short script that I can call as part of the Ansible command line:
$ cat ~/bin/tower-inventory.sh |
#!/bin/sh export TOWER_CREDS='admin:password' export TOWER_HOST=192.168.122.151 export INVNUM=4 curl -s -k -u $TOWER_CREDS https://$TOWER_HOST/api/v2/inventories/$INVNUM/script/?hostvars=1&towervars=1&all=1 |
Which I can then use as below:
$ ansible --key-file ~/keys/id_rsa --user cloud-user -i ~/bin/tower-inventory.sh idm -m shell -a 'echo $USER' idm | SUCCESS | rc=0 >> cloud-user |
This should be sufficient to get you started. This script can be more flexible. Aside from the obvious changes to consume external values for the variables, it could mimic the work I performed earlier in the article to navigate down, and find an inventory by name. However, that would also slow down the processing, as there would be several more round trip calls to tower.
First of all, Great post! Thanks for sharing. I am facing some issue however, I have got loads of hosts in the inventory (more than 2000) but i keep getting just the first page when using these cURL commands. I guess that has something to do with the results per query. I have written a loop to send an API call for the next page however, can we do this using single API call by tweaking some existing value to give results in one call?
You are ahead of me! I have not had to use this against such a large number of hosts. I have not dug into the filters for the queries, but I suspect that there is some form of Windowing. You might want to ask in the AWX chat or something to get responses from people that actually work on the project to fill in. And, if you are a Red Hat customer, you should be able to get an official answer from support, or even your account Solutions Architect.
Thanks man, best tutorial so far for Ansible API.
Added to my favorites.
Good job, thx for sharing.
hi! what are you doing woth this -i parameter calli g the shell-script and … what is idm?
The -i specified the inventory to use.. the shell script generates it.
IdM is the Productized name of the FreeIPA server.