Obtain previous Job ID in Ansible Tower Workflow

Ansible Tower allows you to create Workflows, which enable you to create complex workflows by putting together multiple Ansible Playbooks. Ansible Tower Workflows can have some simple logics, such as run different Ansible Playbooks based on the outcome (success or failure) of a previous Ansible Playbook run. Sometimes, though, you need to have more information about a previous Ansible Playbook run than just the outcome.

I recently found myself in a situation where I had an Ansible Tower Workflow with two Ansible Playbooks into it, where the first one was performing specific tasks. The second one needed to get and process the output of the first Ansible Playbook. Since Ansible Tower provides an API to fetch an Ansible Playbook run output, this part is trivial if you know the Job ID that Ansible Tower assigned to that specific run. Looking around, I’ve not found much information on how to retrieve the Job ID of a different Job, so I looked at the various APIs and found this solution, which I’m going to share with you today. I’ve not found much information about getting another Job ID because it is usually a bad practice to do such a thing and that very often you can achieve the same goal in a much cleaner way. This better option, though, was not present in my case. Due to many constraints I had in this project, this was the best way I’ve found, even if I’ve tried - at least mentally - many other ways before accepting that this was the only one in my case.

To obtain the previous Job ID in an Ansible Tower Workflow, I created an Ansible Module so that the Ansible Playbook that operates it is nice and tidy. I understand that this Ansible Module is very “basic”, to the point that it does not have any inline documentation. Still, I think it clearly explains how it works, and it was reliable enough for my use-case (demonstrate that this is possible within Ansible Tower). I would not suggest copying and paste it into a file and use it in production but to rely on its logic to write a version of it that is as reliable as your use-case requires.

First of all, this is the code:

#!/usr/bin/python

ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'none'}

from ansible.module_utils.basic import AnsibleModule
import json
import requests

def main():
    
    module = AnsibleModule(
        supports_check_mode = True,
        argument_spec = dict(
            tower_base_url = dict(type='str', required=True),
            tower_username = dict(type='str', required=True),
            tower_password = dict(type='str', required=True, no_log=True),
            tower_job_id = dict(type='int', required=True),
        ),
    )

    output = dict(
        changed=False,
        job_id=0,
    )

    tower_base_url = module.params['tower_base_url']
    tower_username = module.params['tower_username']
    tower_password = module.params['tower_password']
    tower_job_id = module.params['tower_job_id']

    response = requests.get(tower_base_url + '/api/v2/workflow_job_nodes/?job_id=' + str(tower_job_id), auth=requests.auth.HTTPBasicAuth(tower_username, tower_password), verify=False)
    worflow_job_node = response.json()['results'][0]['id']
    response = requests.get(tower_base_url + '/api/v2/workflow_job_nodes/?success_nodes=' + str(worflow_job_node), auth=requests.auth.HTTPBasicAuth(tower_username, tower_password), verify=False)
    output['job_id'] = response.json()['results'][0]['summary_fields']['job']['id']
    
    module.exit_json(**output)

if __name__ == '__main__':
    main()

As you can see, if we exclude the Python boilerplate and the Ansible Module boilerplate, this module fetches the parameters (lines 16-19 and 28-31), execs two API calls (lines 33, 35), parses their output (lines 34,36), and prepares the result (lines 23-26, 38).

To go a little more specific on the logic, you need to know that every Ansible Playbook that runs within Ansible Tower can fetch its own Ansible Tower Playbook Job ID. With this information (called tower_job_id in this script), we can obtain all the Ansible Tower Job information from the API, including an ID that uniquely identifies that Job within the Ansible Tower Workflow job. Having that ID, another API can provide us with some information about the parent nodes of that Job within that specific Ansible Tower Workflow run. One of the available information is the ID of the parent Ansible Tower Job.

If you want to use this module in you Ansible Playbooks, you need to put the file into the library folder (ie: /library/tower_previous_job_id.py, and then you can call it with the following YAML code from your Ansible Playbook tasks list:

    - name: Get the previous job in the current workflow
      tower_previous_workflow_job:
        tower_base_url: "{{ tower_base_url }}"
        tower_username: "{{ tower_username }}"
        tower_password: "{{ tower_password }}"
        tower_job_id: "{{ lookup('env', 'JOB_ID') }}"
      register: previous_job

In my case, tower_base_url, tower_username, and tower_password are variables that are set from Ansible Tower secrets, and I would suggest you do it similarly, so you don’t have to put your credentials within the Ansible Playbook.

I hope this can help you better understand how you can quickly write an Anisbe Module to extend the capability of your Ansible Tower to cover some edge cases that might be needed in your specific situation.