Ansible AWX – Using Python to launch a Job template
In this post I shall document how I used Python and the AWX RESTful API to launch a Job template.
Python code examples
Authenticate using the OAuth2 token
The AWX_OAUTH2_TOKEN
is set inside the HTTP request header.
headers = {"User-agent": "python-awx-client", "Content-Type": "application/json","Authorization": "Bearer {}".format(AWX_OAUTH2_TOKEN)}
Then the request can be made with the headers set above.
Query Job templates
AWX_JOB_TEMPLATES_API
is the /api/v2/job_templates
endpoint.
Requesting list of Job templates.
response = requests.get(AWX_JOB_TEMPLATES_API,headers)
The response returned will be in JSON format.
"results": [
{
"id": 7,
"type": "job_template",
"url": "/api/v2/job_templates/7/",
"related": {
"created_by": "/api/v2/users/1/",
"modified_by": "/api/v2/users/1/",
"labels": "/api/v2/job_templates/7/labels/",
"inventory": "/api/v2/inventories/1/",
"project": "/api/v2/projects/6/",
"extra_credentials": "/api/v2/job_templates/7/extra_credentials/",
"credentials": "/api/v2/job_templates/7/credentials/",
"last_job": "/api/v2/jobs/27/",
"jobs": "/api/v2/job_templates/7/jobs/",
"schedules": "/api/v2/job_templates/7/schedules/",
"activity_stream": "/api/v2/job_templates/7/activity_stream/",
"launch": "/api/v2/job_templates/7/launch/",
"webhook_key": "/api/v2/job_templates/7/webhook_key/",
"webhook_receiver": "",
"access_list": "/api/v2/job_templates/7/access_list/",
"survey_spec": "/api/v2/job_templates/7/survey_spec/",
"object_roles": "/api/v2/job_templates/7/object_roles/",
"instance_groups": "/api/v2/job_templates/7/instance_groups/",
"slice_workflow_jobs": "/api/v2/job_templates/7/slice_workflow_jobs/",
"copy": "/api/v2/job_templates/7/copy/"
},
"summary_fields": {
"inventory": {
"id": 1,
"name": "Demo Inventory",
"description": "",
"has_active_failures": false,
"total_hosts": 1,
"hosts_with_active_failures": 0,
"total_groups": 0,
"has_inventory_sources": false,
"total_inventory_sources": 0,
"inventory_sources_with_failures": 0,
"organization_id": 1,
"kind": ""
}
}
}
AWX_HOST="http://<awx_server>"
# Iterate through all the job templates
for job in response.json()['results']:
jt = JobTemplate(job['id'], job['name'], AWX_HOST + job['related']['launch'])
if(jt.name == job_template):
logger.info("Job template {} located.".format(jt.name))
break
The following fields are extracted from the JSON response.
- [id]
- [name]
- [related][launch]
Launching the job template
Once all the information to launch the job template have been acquired.
A HTTP POST
is made to the /api/v2/job_templates/<id>/launch
endpoint.
Note
If the Job template requires external variables to run, then it is sent via the JSON payload.
In this example there are no extra variables to send. Which is why the data variable is empty.
response = requests.post(j_template.launch_url, headers=headers, data={})
Processing the Job template result
Once the Job template has been launched successfully. The next step is to evaluate the result of the run of the Job template.
The status of a Job template launch can be one of:
- new
- pending
- waiting
- running
- successful
- failed
- error
- canceled
- never updated
- ok
For more information see: https://docs.ansible.com/ansible-tower/2.3.0/html/towerapi/unified_job_template.html
In the code below, adding a while loop helped poll for the latest status of a Job template run.
Adding a timeout ensures a break out of the loop (so no infinite loop).
# Checking the response status code, ensures the launch was ok
if(response.status_code == 201):
job_status_url = AWX_HOST + response.json()['url']
logger.info("Job launched successfully.")
logger.info("Job URL = {}".format(job_status_url))
logger.info("Job id = {}".format(response.json()['id']))
logger.info("Status = {}".format(
response.json()['status']))
logger.info(
"Waiting for job to complete (timeout = 15mins).")
timeout = time.time() + 60*15
while(True):
time.sleep(2)
job_response = requests.get(
job_status_url, headers=headers)
if(job_response.json()['status'] == "new"):
logger.info("Job status = new.")
if(job_response.json()['status'] == "pending"):
logger.info("Job status = pending.")
if(job_response.json()['status'] == "waiting"):
logger.info("Job status = waiting.")
if(job_response.json()['status'] == "running"):
logger.info("Job status = running.")
if(job_response.json()['status'] == "successful"):
logger.info("Job status = successful.")
break
if(job_response.json()['status'] == "failed"):
logger.error("Job status = failed.")
break
if(job_response.json()['status'] == "error"):
logger.error("Job status = error.")
break
if(job_response.json()['status'] == "canceled"):
logger.info("Job status = canceled.")
break
if(job_response.json()['status'] == "never updated"):
logger.info("Job status = never updated.")
# timeout of 15m break loop
if time.time() > timeout:
logger.warning("Timeout after 15mins.")
break
logger.info("Fetching Job stdout")
job_stdout_response = requests.get(
AWX_HOST + response.json()['related']['stdout'] + "?format=json", headers=headers, verify=False)
print(job_stdout_response.json()['content'])
The last few lines of the code above fetches the JSON formatted stdout of the Job template run.
Console output when running the Python code
2020-03-05 19:56:18:INFO > Launch Ansible AWX Job.
2020-03-05 19:56:18:INFO > Requesting list of Job templates.
2020-03-05 19:56:19:DEBUG > Response status code = 200 (OK).
2020-03-05 19:56:19:INFO > Total no. Job templates = 1
2020-03-05 19:56:19:INFO > Searching for Job template = Demo Inventory
2020-03-05 19:56:19:INFO > Gathered all Job template information, proceeding to launch job.
2020-03-05 19:56:21:INFO > Job launched successfully.
2020-03-05 19:56:21:INFO > Job URL = http://<awx_host>/api/v2/jobs/29/
2020-03-05 19:56:21:INFO > Job id = 29
2020-03-05 19:56:21:INFO > Status = pending
2020-03-05 19:56:21:INFO > Waiting for job to complete (timeout = 15mins).
2020-03-05 19:56:24:INFO > Job status = pending.
2020-03-05 19:56:27:INFO > Job status = pending.
2020-03-05 19:56:29:INFO > Job status = pending.
2020-03-05 19:56:32:INFO > Job status = pending.
2020-03-05 19:56:34:INFO > Job status = pending.
2020-03-05 19:56:37:INFO > Job status = pending.
2020-03-05 19:56:39:INFO > Job status = pending.
2020-03-05 19:56:42:INFO > Job status = pending.
2020-03-05 19:57:05:INFO > Job status = pending.
2020-03-05 19:57:08:INFO > Job status = pending.
2020-03-05 19:57:11:INFO > Job status = waiting.
2020-03-05 19:57:13:INFO > Job status = running.
2020-03-05 19:57:15:INFO > Job status = running.
2020-03-05 19:57:18:INFO > Job status = running.
2020-03-05 19:57:20:INFO > Job status = running.
2020-03-05 19:57:23:INFO > Job status = running.
2020-03-05 19:57:25:INFO > Job status = running.
2020-03-05 19:57:28:INFO > Job status = running.
2020-03-05 19:57:31:INFO > Job status = running.
2020-03-05 19:57:33:INFO > Job status = successful.
2020-03-05 19:57:33:INFO > Fetching Job stdout
PLAY [Hello World Sample]
TASK [Gathering Facts] *ok: [localhost]
TASK [Hello Message] ***ok: [localhost] => {"msg": "Hello World!"}
PLAY RECAP *localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0