Ansible has become a widely accepted automation platform not just for Infrastructure Automation but for various other use cases
Some of them are Web and API automation, Monitoring Automation, Cloud Automation and Infrastructure as a code like Terraform
In this article, we are going to see how to use Ansible for Web and API automation.
Ansible has two powerful modules for WebService and API automation. they are
- get_url / win_get_url - to download files from HTTP/HTTPS and FTP sites
- uri / win_uri - to interact with web service ( we are going to discuss in this article)
While the get_url module is simply to interact with web and FTP sites and download files.
The URI module is much more powerful to interact with API and Webservices, You can use the URI module for Complete API monitoring and Automation.
Ansible URI module Quick Examples
Ansible URI module can be used for simple use cases like checking the status of the web pages and validating the status code as well as complex use cases such as API automation with different HTTP methods and payloads.
Before we start to go deep into the URI module, Let me give two quick examples of Ansible URI to show you what is ansible URI module capable of.
Ansible URI used for Simple Status code check
--- - name: Playbook for Web automation hosts: localhost tasks: - name: Check if devopsjunction.com is available and returning status 200 uri: url: https://devopsjunction.com register: result
Ansible URI module used for API or WebService testing
--- - name: Playbook for Web automation hosts: localhost tasks: - name: Ansible URI used for user registration uri: url: https://reqres.in/api/users method: POST body: |- { "name": "Sarav", "job": "leader" } return_content: yes status_code: 201
Both these playbooks can be tested from your end as we have used publically available URLs/ Webservices.
We will decode these playbooks in detail shortly.
Ansible URI parameters
These are the list of parameters supported by the Ansible URI module. I have compiled all the parameters of Ansible URI and their usage, in the following image
Please go through the list, we would be using most of these parameters in our ansible URI module example playbooks shortly.
Let us explore and try a few examples of this Ansible URI module. we will start with the ones from the previous Quick examples section.
Beginner to Ansible? No Problem
Unless you are new to Ansible, you must already be aware of the basic skeleton of the Ansible playbook.
If you are completely new to the Ansible playbook, I would refer you to read our Beginner Articles on Ansible by following the below links
We are presuming that you know the basics of the Ansible playbook, therefore we are going to decode only the tasks.
Let us move on to the examples of the Ansible URI module.
Ansible URI Examples
Let us go through various examples and learn and understand the Ansible URI module better
Ansible URI module to connect and validate status code
This was our first playbook from the Quick example section, it is to connect to URL and to validate if it is alive by validating the returning status code 200
--- - name: Playbook for Web automation hosts: localhost tasks: - name: Check if devopsjunction.com is available and returning status 200 uri: url: https://devopsjunction.com register: result
we have one task in the playbook, with a URI module.
- name: Check if devopsjunction.com is available and returning status 200 uri: url: https://devopsjunction.com register: result
In this task, we are connecting to the remote URL https://devopsjunction.com
and validating whether it's alive and responding with HTTP status code 200
If you look at the playbook, nowhere we are defining the status code to look for.
If
status_code
directive is not specified.Ansible, by default, would consider status code 200 as a success, anything else would fail the playbook
In cases, where the remote URL is supposed to return a NON 200 status code, like 301
or 302
we must explicitly define what is the expected status code with the status_code
parameter
Here is the execution output of this playbook
You can see the playbook execution is successful and passed. This is a simple ansible playbook to check if a website or webservice is up and running and returning a specific status code.
Let's suppose we want the URI module to succeed only when the remote URL is returning the status HTTP 301
As we have already discussed, we need to explicitly specify it with the status_code parameter, as shown in the following playbook
--- - name: Ansible URI module examples hosts: localhost tasks: - name: Check if devopsjunction.com is available and returning status 301 uri: url: https://devopsjunction.com status_code: 301 register: result
Here is the execution output of this new playbook
Let us move on.
The Ansible URI module follow redirection
Our previous playbook from example 1, should have ideally failed but it got succeeded. Let me tell you why.
If you try the https://devopsjunction.com
directly through CURL, you would see it returning 301 status code
~|⇒ curl https://devopsjunction.com -I HTTP/2 301 location: https://middlewareinventory.com/devops-junction cache-control: max-age=3600 expires: Sat, 12 Nov 2022 13:28:56 GMT content-type: text/html; charset=iso-8859-1 date: Sat, 12 Nov 2022 12:28:56 GMT server: Apache
in that case, our playbook should have not succeeded right? but it has. why?
The reason is follow_redirection
. By default, Ansible URI follows if there is a Safe redirection and consider the last hop's status.
In our case, despite the first hop was returning 301
once it has completed the redirections. the end result was 200
that's what made our previous playbook succeed.
To understand this better. You can use the follow redirection option available on the CURL itself. using the -L
flag
Here is the output of CURL with follow redirection for devopsjunction.com. You can see the final status code is eventually 200.
~|⇒ curl -L https://devopsjunction.com -I HTTP/2 301 location: https://middlewareinventory.com/devops-junction cache-control: max-age=3600 expires: Sat, 12 Nov 2022 16:55:36 GMT content-type: text/html; charset=iso-8859-1 date: Sat, 12 Nov 2022 15:55:36 GMT server: Apache HTTP/2 301 date: Sat, 12 Nov 2022 15:55:37 GMT content-type: text/html; charset=UTF-8 location: https://www.middlewareinventory.com/devops-junction x-redirect-by: WordPress vary: X-Forwarded-Proto,Accept-Encoding,User-Agent ...... ...... HTTP/2 301 date: Sat, 12 Nov 2022 15:55:37 GMT content-type: text/html; charset=UTF-8 location: https://www.middlewareinventory.com/devops-junction/ x-redirect-by: WordPress vary: X-Forwarded-Proto,Accept-Encoding,User-Agent cache-control: max-age=3600 ...... ...... HTTP/2 200 date: Sat, 12 Nov 2022 15:55:38 GMT content-type: text/html; charset=UTF-8 vary: Accept-Encoding,Cookie last-modified: Thu, 10 Nov 2022 19:39:14 GMT cache-control: max-age=0, public ...... ......
This is exactly what Ansible did. It followed the redirections.
You can control whether or not to follow the redirects with a parameter
follow_redirects
If you set the follow_redirects to none, Ansible URI would not follow the redirects and our playbook would fail.
Look at the playbook and the execution output of both with and without follow_redirects
Here is the execution output of the playbook without follow_redirects
parameter.
Ansible assumes to follow the redirects by default ( this is the same playbook from previous example)
The output of the playbook with follow_redirects
parameter set to none
we instruct ansible to not follow redirects this time and it fails.
hope you have understood the crucial role of follow_redirects in the Ansible URI module.
Ansible URI - How to work with body of the response or page content
the last two examples we have seen on Ansible URI were around the status code. sometimes we might want to work with the content of the web page.
In this example, we are going to see how Ansible URI handles the content of the webpage or the API response
Let us see it with a practical example, Let's suppose we want to know the public IP address of the node where the Ansible task is being executed. ( controller or remote)
Based on that result, we might want to execute or skip a downward task
Look at the following playbook where we connect to the remote URL https://checkip.amazonaws.com
this returns ( response/content) the public IP of the origin ( from where the call has been made)
--- - name: Ansible URI module examples hosts: localhost tasks: - name: Check the outbound IP address uri: url: https://checkip.amazonaws.com return_content: true register: result - name: Creating a new variable to store this IP set_fact: publicip: "{{ result.content | trim() }}" - debug: var: publicip
Here is the execution output of this playbook
If you look closer, you would be able to see we are using a parameter return_content
and it is set to yes
Why do we need return_content on Ansible?
Without the return_content parameter, Ansible would not collect the response content or page content.
To validate this you can try the same playbook without the return_content parameter.
As shown in the preceding snapshot, we have removed the return_content
and the task is failing.
If you look at the highlighted text on the failure message. you can see there is no attribute named content
By default, ansible does not collect the entire page response or the content it is more like using curl -I
where curl collects only the headers, not the content.
Ansible URI for API Automation - JSON payload
So far we have been using only web services with ansible URI. Let us start to use API endpoints and different content-type and payloads.
Let us start with the famous JSON payload. JSON has become an indispensable element in API and Webservices.
In this example, we are going to use a publicly available API endpoint reqres.in
--- - name: Ansible URI examples hosts: localhost tasks: - name: Ansible URI playbook for API with JSON Payload. uri: url: https://reqres.in/api/login method: POST body_format: json body: |- { "email": "[email protected]", "password": "cityslicka" } return_content: yes register: result changed_when: "'token' in result.content" - debug: var=result.content
As you can see, our preceding playbook is designed to send a payload using body
parameter
body: |- { "email": "[email protected]", "password": "cityslicka" }
Unless defined, this payload would be considered as body raw
not as JSON
so you need to define the right interpretation of our body
body_format: json
Since we care about the response content. we are using return_content: yes
which is something we have learnt in the previous examples.
We are also defining the method of the HTTP as POST
by default Ansible would consider the request as GET
when you are making a wrong method of call, you might get HTTP 400 error.
Finally, the expected response from this API endpoint is a token. If our credentials are accepted the API should present us with a token.
We are ensuring that we get a token in our response
The reason for using changed_when is to ensure that the job is considered successful only when a certain condition is met.
You can alternatively use failed_when as well in this context with inverted condition.
Read my exclusive article on changed_when and failed_when here
execution output of our playbook is here
Ansible playbook to log in to Form-based Authentication - Ansible URI
In the previous example, we have seen how to login to API and obtain a token by sending a payload over HTTP POST using Ansible URI
In this example, we will see how to log in to the form-based Authentication enabled Web Application.
As I could not find a simple Application to demonstrate I ended up creating one
It is a simple Form based Login App designed on Flask. you can find the source code in my GitHub repo
https://github.com/AKSarav/login-form-app-sample
You can download the source code and host it yourself on your locally.
Or use the readily available public Heroku link of this application
https://form-login-webapp.herokuapp.com/
Note*: This URL might change or expire this is hosted on HerokuApp free plan.
Before we automate this with Ansible, let us take a quick look at how this application works when tried from the browser.
As shown in the preceding GIF, we are entering the credentials admin/admin and clicking on the Login button.
After successful login, we can see a message you have successfully logged in
If you put in the wrong credentials, you would be redirected to the login form once again with an error message.
Here is the Ansible playbook to Login to the form based Authentication enabled Web App
--- - name: Ansible URI examples hosts: localhost tasks: - name: Login to WebApplication with Ansible uri: url: https://form-login-webapp.herokuapp.com method: POST body_format: form-urlencoded follow_redirects: all body: - [ username, admin ] - [ password, admin ] - [ enter, Login] return_content: yes register: login changed_when: "'you have successfully logged in' in login.content" - debug: var=login.content
Let us decode the important fields of this playbook
- url - URL of our form-based login application
- method - we are using HTTP POST to send our username and password
- body_format - to send the credentials we should use
form-urlencoded
body format - follow_redirects - we are following through all the redirects until we get to the home screen
- body - In this form-urlencoded body we need to define what is the key and the value for each form item
- username - we are passing
admin
as our username - password - we are passing
admin
as our password too. - enter - where to click. A button name to submit. in our case it is
Login
- username - we are passing
- return_content: we are enabling return_content to see the response content
- changed_when: Just to let Ansible know when to consider this task successful. in our case, when we have the message
you have successfully logged in
on the response content
How to wait for the URL to be reachable or until it returns a Status code or Content
As we have seen various examples already on Ansible URI. we might want to add some conditional execution on it
Like making it wait until it returns a specific status code or message etc.
I have a dedicated article on the same. please refer the following link
Ansible Wait for URL to respond or Retry – WEB and API | Devops Junction
More examples are coming
I have already put a few days/weeks of effort already in compiling this article and creating necessary supporting artefacts like the cheat sheet, the flask application etc.
I still feel there is more to cover in Ansible URI such as
- File upload with Ansible URI
- Authenticating with already stored Cookie
- More examples on API calls etc
While am working on these items with necessary examples and supporting images/videos. I am publishing this article to help those in need
if you have any more requirements or ideas that can or should be covered in this article. let me know in the comments.
Cheers
Sarav AK
Follow me on Linkedin My Profile Follow DevopsJunction onFacebook orTwitter For more practical videos and tutorials. Subscribe to our channel
Signup for Exclusive "Subscriber-only" Content