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 Template
  • name » 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
Last updated on 31 Mar 2020
Published on 31 Mar 2020