Ansible AWX – Using Python to launch a Job template
Using 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.
For more information regarding AWX OAUTH2 tokens see this page.
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.
AWX_JOB_TEMPLATES_API="https://AWX_HOST/api/v2/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": ""
}
}
}
Example
Iterating through the list of Job templates, then checking to see whether the Job template name matches the one JOB_TEMPLATE
we want to launch.
# define a class to encapsulate Job template info
class JobTemplate():
def __init__(self,id,name,launch_url):
self.id=id
self.name=name
self.launch_url=launch_url
AWX_HOST="http://<awx_server>"
JOB_TEMPLATE="Demo Inventory"
# Iterate through all the job templates
def getJobTemplate():
for job in response.json()['results']:
job_template = JobTemplate(job['id'], job['name'], AWX_HOST + job['related']['launch'])
if(job_template.name == JOB_TEMPLATE):
logger.info("Job template {} located.".format(jt.name))
return job_template
The following fields are extracted from the JSON response.
id
» unique Identifier of the Job Templatename
» name of the Job Template[related][launch]
» URL to launch the Job Template
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.
A HTTP response code of 201 indicates a successful post.
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.
def launchJobTemplate():
response = requests.post(job_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