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": ""
    ......
        "name": "Demo Job Template",
        "description": "" 
    }

    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 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 is confined to the following.

  • 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