Using Ansible with REST APIs

You may have queried APIs with a web browser or curl, but one of the overlooked capabilities of Ansible is how well it can leverage APIs as part of any playbook.
53 readers like this.
Looking at a map

opensource.com

Ansible is a top open source project which, on the surface, looks to provide a simple way to standardize your existing automation and allow it to run in parallel across multiple hosts, and it does this very successfully. Yet, in reality, Ansible has the capabilities to extend what your existing automation does to incorporate other systems and really simplify tasks across all aspects of your daily routine.

This capability starts with the collections and roles that are included with Ansible and all the third-party utilities distributed through Ansible Galaxy. You may have queried APIs with a web browser or curl, but one of the overlooked capabilities of Ansible is how well it can leverage APIs as part of any playbook. This is extremely useful because the number of REST APIs being built and deployed both internally and across the global internet is increasing exponentially. There's even a public-apis GitHub repo listing hundreds of free APIs across over a dozen categories just for a sense of scale.

 

 

A basic API playbook

Well, it really comes down to a few key core capabilities within Ansible, which are exposed nicely with one specific built-in task, uri. In this post, I'll go through a fairly simple example of how to call a REST API and use the data from that call to decide what to do next. This works with Ansible 2.9 and higher. In later versions (specifically v4), the modules we use need to be prepended with ansible.builtin like ansible.builtin.set_fact instead of just set_fact.

To get started, you need a basic playbook to build on. In this case, you're only using local calls, so you don't need to be a superuser.

First, create this YAML file to establish a working baseline:

---
- name: Using a REST API
  become: false
  hosts: localhost
  gather_facts: false
  tasks:
    - debug:
        msg: “Let’s call an API”

Here's the output after running it:

% ansible-playbook using-a-rest-api.yml

PLAY [Using a REST API] *********************************************************************************************

TASK [debug] ********************************************************************************************************
ok: [localhost] => {
    "msg": "“Let’s call an API”"
}

PLAY RECAP **********************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Calling an API

To call an actual API, you can use the uri module. Here are two examples. The first is just a GET and the second is a POST with parameters to show the different available options.

---
- name: Everyone loves a good Chuck Norris joke
  uri:
    url: https://api.chucknorris.io/jokes/random
    method: GET

- name: Login to an API
  uri:
    url: https://auth.example.com/oauth/access_token
    method: POST
    body_format: json
    body:
      name: your_username
      password: your_password
      client_id: YOUR_CLIENT_ID
      access_token: ACCESS_TOKEN
      connection: CONNECTION
      scope: SCOPE

I use the first API for the rest of this article to show how the returned data can be used. The question is, how do you collect the data being returned, and what does it look like?

[ Advance your automation expertise. Get the Ansible checklist: 5 reasons to migrate to Red Hat Ansible Automation Platform 2 ]

To collect the output from any task running in Ansible, you use the register attribute, and then you can use the debug task to display the raw data. In the case of APIs called using uri, all the output is put under the .json. Subsection of the result. The uri commands and other its output are also at that top level. These can be useful to make sure the API call works by looking at other data fields like status.

These are the two tasks you must add to the original playbook to add the API call to the mix to later do something with.

   - name: Getting the definition of awesome
      uri:
        url: https://api.chucknorris.io/jokes/random
        method: GET
      register: results

    - debug:
        var: results

Run it to see the output generated by debug:

TASK [debug] ********************************************************************************************************
ok: [localhost] => {
    "results": {
        "alt_svc": "h3=\":443\"; ma=86400, h3-29=\":443\"; ma=86400, h3-28=\":443\"; ma=86400, h3-27=\":443\"; ma=86400",
        "cf_cache_status": "DYNAMIC",
        "cf_ray": "694f7d791aeb19e7-EWR",
        "changed": false,
        "connection": "close",
        "content_type": "application/json;charset=UTF-8",
        "cookies": {},
        "cookies_string": "",
        "date": "Sun, 26 Sep 2021 21:12:23 GMT",
        "elapsed": 0,
        "expect_ct": "max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"",
        "failed": false,
        "json": {
            "categories": [],
            "created_at": "2020-01-05 13:42:26.991637",
            "icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png",
            "id": "IjqNNWKvSDeVKaI82PaT1g",
            "updated_at": "2020-01-05 13:42:26.991637",
            "url": "https://api.chucknorris.io/jokes/IjqNNWKvSDeVKaI82PaT1g",
            "value": "One person stated that Chuck Norris has forgotten more about killing than anyone will ever know. That is not true -- Chuck Norris never forgets. Ever."
        },
        "msg": "OK (unknown bytes)",
        "nel": "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}",
        "redirected": false,
        "report_to": "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=HVPJYMVr%2B3wB1HSlgxv6GThBMjkBJgfdu0DPw%2BunjQzQ9YfXZqifggIJ%2FxOIKgOu6JP1SrPsx1jCCp3GQ9hZAp7NO0pmlTZ0y3ufbASGwLmCOV1zyaecUkSwQD%2Fv3RYYgZTkaSQ%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}",
        "server": "cloudflare",
        "status": 200,
        "transfer_encoding": "chunked",
        "url": "https://api.chucknorris.io/jokes/random",
        "via": "1.1 vegur"
    }
}

Now that you can see all the output make a custom message listing the value returned by the API. Here is the completed playbook:

---
- name: Using a REST API
  become: false
  hosts: localhost
  gather_facts: false
  tasks:
    - debug:
        msg: “Let’s call an API”

    - name: Everyone loves a good Chuck Norris joke
      uri:
        url: https://api.chucknorris.io/jokes/random
        method: GET
      register: results

    - debug:
        var: results.json.value

And now the complete output:

PLAY [Using a REST API] *********************************************************************************************

TASK [debug] ********************************************************************************************************
ok: [localhost] => {
    "msg": "“Let’s call an API”"
}

TASK [Everyone loves a good Chuck Norris joke] **********************************************************************
ok: [localhost]

TASK [debug] ********************************************************************************************************
ok: [localhost] => {
    "results.json.value": "Chuck Norris is the only computer system that beats a Mac or a PC. Too bad all it does is round house kicks the user."
}

PLAY RECAP **********************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Next steps

Things can get much more complicated than I've shown here. To get more details, head over to Ansible's documentation.

What to read next
Tags
Avatar
I dabble in all things Development, Security, and Operations. I bet there is a buzzword to fit what I do.

Comments are closed.

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.