Dynamically search within the Ansible Tower API
Authors: Brennan Stride and George Nalen
Ansible Tower API Project Background:
During a project with a client, we were working on manipulating the Ansible Tower inventory using Ansible templates (plays). If you’ve ever used Ansible to automate the manipulation of Tower via the URI module, you probably have run into scenarios where you’ve needed the ID of an entry. For example, you know the name of a host but not the internal ID of that host. Usually, to find these ID numbers, you must manually browse through the Tower API (via a web browser). Then you manually enter that ID into the needed fields — which isn’t dynamic enough for some scenarios.
We have found a way to retrieve these ID’s without having to manually search for the ID and then enter the ID statically in a task; thus, saving time and allowing further automation in your Tower manipulation tasks.
Was this originally our intention? Well, sort of. We wanted to fetch the IDs for later retrieval, so Brennan (our brilliant intern) figured out a way to do this.
Steps we took:
Before we go into the nitty-gritty details, there are a few main steps that we will outline. This includes:
- Role setup
- Project setup
- Applying the patches by Group
- Applying the patches by Patch ID
- Removing the Patch ID
- Success!
Getting started
For the sake of this blog, we will use a use case around configuration. To make this task easier, it is best to list the names of all created credentials, projects, and templates as “ansible variables”. For the following examples, we will be using the variables shown below. Keep in mind the variables in the xsearch lists are case-sensitive and must be unique for this approach, as they are used to filter the results in the search tasks. The name_x variables are used to refer to positions in the list and help facilitate indexing.
credentialsearch:
- "SSH"
- "Git"
# add credential search items
name_ssh: 0
name_git: 1
# extra name_x variables for additional credentials
projectsearch:
- "Project_1"
# add project search items
name_project1: 0
# extra name_x variables for additional projects
jobtemplatesearch:
- "Job_1"
# add template search items
name_job1: 0
# extra name_x variables for additional templates
With this approach, we use a URI task to retrieve the raw data of any created object, using the filter feature of Tower’s API to retrieve only those we require. This approach assumes the previous initialization of Tower objects, which can also be done manually through the browser or automated via the URI module. Using the previously mentioned variables, we can retrieve the desired objects using the following tasks:
## Task: Get Credentials
- name: get credentials
uri:
url: "https://localhost/api/v2/credentials/?name={{ item }}"
method: GET
user: admin
password: "{{ towerpass }}"
validate_certs: False
force_basic_auth: yes
status_code:
- 200
register: credentialresults
with_items:
- "{{ credentialsearch }}"
## Task: Get Project
- name: get projects
uri:
url: "https://localhost/api/v2/projects/?name={{ item }}"
method: GET
user: admin
password: "{{ towerpass }}"
validate_certs: False
force_basic_auth: yes
status_code:
- 200
register: projectresults
with_items:
- "{{ projectsearch }}"
## Task: Get Templates
- name: get job templates
uri:
url: "https://localhost/api/v2/job_templates/?name={{ item }}"
method: GET
user: admin
password: "{{ towerpass }}"
validate_certs: False
force_basic_auth: yes
status_code:
- 200
register: jobtemplateresults
with_items:
- "{{ jobtemplatesearch }}"
These tasks should be placed immediately after the tasks to initialize all objects of that type. For example, if one were creating a credential that needed to be referenced later, the following could be used.
# TASKS
- name: create git credential
uri:
url: https://localhost/api/v2/credentials/
method: POST
user: admin
password: "{{ towerpass }}"
body: "{{ lookup('file','tower_git_credential.json') }}"
body_format: json
validate_certs: False
force_basic_auth: yes
status_code:
- 200
- 201
register: response
changed_when: response.status == 201
# INSERT GET CREDENTIALS TASK HERE
# TASKS
Once the retrieval tasks are complete, we can then refer to them via the registers specified in each. A simplified version of the register format is displayed below:
{
"results": {
"item": "ITEM_1"
"json": {
"results": {
# corresponding json formatted output for one object
}
}
"item": "ITEM_2"
"json": {
"results": {
# corresponding json formatted output for one object
}
}
...
}
}
Thus, when referencing an attribute, we use the following:
{{ registername.results[name_x].json.results[0].attribute }}
Where name_x corresponds to the name_ variable we specified earlier (such as name_ssh). Using with_items on the register results in the form “item.results[0].json…” rather than the specifier for name_x.
In practice, we can use this to reference variables within URI POST tasks to create job templates for a project or credential a template, as shown below.
- name: create git job template
uri:
url: https://localhost/api/v2/job_templates/
method: POST
user: admin
password: "{{ towerpass }}"
body_format: json
body: >
{
"name": "Job_1",
"description": "",
"job_type": "run",
"inventory": 2,
"project": {{ projectresults.results[name_project1].json.results[0].id }},
"playbook": "site.yaml",
"vault_credential": null,
"forks": 0,
"limit": "",
"verbosity": 0,
"extra_vars": "webapp_version: 91d7a895302744cfd3c5ad40cc261dec4b796de3",
"job_tags": "",
"force_handlers": false,
"skip_tags": "",
"start_at_task": "",
"timeout": 0,
"use_fact_cache": false,
"host_config_key": "",
"ask_diff_mode_on_launch": false,
"ask_variables_on_launch": false,
"ask_limit_on_launch": false,
"ask_tags_on_launch": false,
"ask_skip_tags_on_launch": false,
"ask_job_type_on_launch": false,
"ask_verbosity_on_launch": false,
"ask_inventory_on_launch": false,
"ask_credential_on_launch": false,
"survey_enabled": false,
"become_enabled": true,
"diff_mode": true,
"allow_simultaneous": false,
"cloud_credential": null,
"network_credential": null
}
validate_certs: False
force_basic_auth: yes
status_code:
- 200
- 201
- 202
register: response
until: response.status == 201 or response.status == 202
retries: 10
delay: 30
changed_when: response.status == 201 or response.status == 202
when: responseproj.status == 201
- name: credential job templates
uri:
url: "https://localhost/api/v2/job_templates/{{ item.results[0].json.results[0].id }}/credentials/"
method: POST
user: admin
password: "{{ towerpass }}"
body_format: json
body: >
{
"id": {{ credential.results[name_ssh].json.results[0].id | int }}
}
validate_certs: False
force_basic_auth: yes
status_code:
- 200
- 201
- 202
- 204
register: response
changed_when: response.status == 201 or response.status == 202 or response.status == 204
with_items:
- "{{ jobresults }}"
Your Automation Resource
Adopting automation can make a significant difference in your organization. That is why MindPoint Group created Ansible Counselor, which helps you accelerate Ansible implementation, preventing new automation-associated risks to your business.
Automation Counselor provides on-demand access to Ansible experts — including former Ansible employees — that guide your teams on their automation journey. Whether you need a Playbook to automate a task, best-practices answers, or strategic planning, our Ansible Counselor subscription is for you.
Contact us to learn more.