Consuming REST APIs using Ansible

One of the reasons why I like Ansible is the simplicity in its automation approach. As long as there is a module for what you’re trying to automate, chances are its easy to achieve.

However, not all functionality can be automated using core Ansible modules. If your application or service has a REST API though you can use the Ansible uri module. The uri module allows you to perform REST API requests in a declarative manner. What I like about the module is that you can then combine the response output of the request with supported Ansible jinja2 filters to perform powerful queries.

Lets use a simple example of obtaining the next available IP address from a network defined on an Infoblox grid appliance using the Infoblox REST API.

As per the Infoblox REST API reference guide, firstly you will need to obtain the Infoblox network reference as it will be used in subsequent API requests. The Ansible task for that would something like this:

- name: Obtain Infoblox network reference for 172.25.25.0/24
  uri:
    url: "{{ infoblox_url }}/network?network={{ network_address }}"
    method: GET
    user: "{{ infoblox_username }}"
    password: "{{ infoblox_password }}"
    validate_certs: no
  register: network_ref_response

where {{ infoblox_url }}, {{ network_address }}, {{ infoblox_username }} and {{ infoblox_password}} are Ansible variables defined either earlier in the playbook or passed through as extra_vars on the command line.

The register statement is key here. It tells Ansible to store the output of the task into another variable called network_ref_response. If you want to view the contents of the variable, you can either use debug mode (pass argument -vvv on the command line) or the debug module, which will dump the variable contents in stdout.

Next I will want to extract the Infoblox network reference and the session cookie (so I don’t have to pass through user/password on every request). To do this I use the set_fact module, which I can use to assign values to a ‘fact’ variable for use later on.

- name: Extract the Infoblox reference for the network
  set_fact:
    network_ref: "{{ network_ref_response.json[0]._ref }}"
    infoblox_cookie: "ibapauth={{ network_ref_response.cookies.ibapauth }}"

The Infoblox network reference will look something like the following:

network/ZG5zLm5ldHdvcmskMTcyLjI1LjI1LjAvMjQvMA:172.25.25.0/24/default

Now using the network reference, I can query the Infoblox REST API:

- name: Query Infoblox network for next available IP address
  uri:
    url: "{{ infoblox_url }}/{{ network_ref }}?_function=next_available_ip"
    body:
      num: 1
    body_format: json
    method: POST
    headers:
      Cookie: "{{ infoblox_cookie }}"
    validate_certs: no
  register: network_next_ip
Notice the body section of the task. In YAML it is known as a dictionary. If you specify the body_format as json, Ansible will actually convert the dictionary into json and POST it to the API:
{
  "num": 1
}
Here is the actual API response:
"network_next_ip": { 
       "cache_control": "no-cache, no-store",  
       "changed": false,  
       "connection": "close",  
       "content_type": "application/json",  
       "cookies": {},  
       "cookies_string": "",  
       "date": "Thu, 30 Aug 2018 06:08:43 GMT",  
       "failed": false,  
       "json": { 
           "ips": [ 
               "172.25.25.1" 
           ] 
       },  
       "msg": "OK (unknown bytes)",  
       "pragma": "no-cache",  
       "redirected": false,  
       "status": 200,  
       "transfer_encoding": "chunked",  
       "url": "https://infoblox/wapi/v2.0/network/ZG5zLm5ldHdvcmskMTcyLjI1LjI1LjAvMjQvMA:172.25.25.0/24/default?_function=next_available_ip" 
   }
As you can see in the json object, the API returns a list of IP addresses you can consume. Ansible supports either [] or dot notation for accessing dictionary values. Therefore using the IP address would look something like the following:
- debug:
    msg: "{{ network_next_ip.json.ips[0] }}"
To confirm, here’s the network map in Infoblox:
infoblox_network_view

The next available IP address for that network is indeed 172.25.25.1.

Although you could in theory use a bunch of curl commands to achieve the same outcome,  I think it is much easier to do this using Ansible without convoluted logic.

 

Leave a comment