Introduction

To administer an Oracle Database Appliance (ODA), you probably use the odacli commandline tool.

The principle of this tool is to run jobs in background. Fire-and-forget… or Fire-and-poll

odacli update-repository -f "/tmp/odacli-dcs-19.30.0.0.0-260210-GI-19.30.0.0.zip"

This command returns a jobId (e.g. f1338963-87a2-4cb9-8a3e-06e104270203) and the job is executed in background. To see if this job is (sucessfully) completed, you have to poll for the above jobId and wait for Status: Success.

odacli describe-job -i f1338963-87a2-4cb9-8a3e-06e104270203
watch -n 2 odacli describe-job -i f1338963-87a2-4cb9-8a3e-06e104270203

Automation with Ansible

For automation, e.g. with Ansible, that is not optimal. We have to implement a polling mechanism for the odacli commands.

For example. we will add new software to the ODA-repository. (version=19.30, software_zip=/tmp/odacli-dcs-19.30.0.0.0-260210-GI-19.30.0.0.zip)

First of all, we run odacli to create the job to add the software to the repository and extract the jobId from the output.

  - name: import software in repository
    ansible.builtin.shell: |
      /opt/oracle/dcs/bin/odacli describe-dbsystem-image -j \
        | jq -e '.[].dbSystemImageComponents[]|select(.componentName=="DB")|.availableVersions[]|select(startswith("{{version2}}")) ' \
        |grep  {{version2}} >&2 && echo 'ALREADY_INSTALLED' && exit 0
      /opt/oracle/dcs/bin/odacli update-repository -f "{{software_zip}}"
    register: repo
    changed_when: "'ALREADY_INSTALLED' not in repo.stdout"

  - name: set job-id
    set_fact:
      jobid: "{{ (repo.stdout | from_json).jobId }}
    when: "'ALREADY_INSTALLED' not in repo.stdout"

Hint: the 1st command is to check if this software is already imported

Now, we can poll the job until it is completed. Ansible is optimized to work with json. So we will enforce odacli to return the output in json format (-j):

/opt/oracle/dcs/bin/odacli describe-job -i f1338963-87a2-4cb9-8a3e-06e104270203 -j
{
  "jobId" : "f1338963-87a2-4cb9-8a3e-06e104270203",
  "status" : "Created",
  "message" : "/tmp/odacli-dcs-19.30.0.0.0-260210-GI-19.30.0.0.zip",
  "reports" : [ ],
  "createTimestamp" : "February 24, 2026 13:58:30 PM CET",
  "resourceList" : [ ],
  "description" : "Repository Update",
  "updatedTime" : "February 24, 2026 13:58:30 PM CET",
  "jobType" : null,
  "cpsMetadata" : null
}

For polling, we can use the ansible loop control, see the Ansible documentatioon

  - name: check until job completed
    ansible.builtin.shell: /opt/oracle/dcs/bin/odacli describe-job -j -i {{jobid}}
    register: check_status
    until: "(check_status.stdout|from_json).status == 'Success'"
    retries: 10
    delay: 8
    changed_when: false
    when: "'ALREADY_INSTALLED' not in repo.stdout"

That means, Ansible will run the command every 8 seconds until status Success is returned (Success) or after 10 attemts (Failed)

TASK [check until job completed] ************************************************************************
FAILED - RETRYING: check until job completed (10 retries left).
FAILED - RETRYING: check until job completed (9 retries left).
FAILED - RETRYING: check until job completed (8 retries left).
FAILED - RETRYING: check until job completed (7 retries left).
FAILED - RETRYING: check until job completed (6 retries left).
FAILED - RETRYING: check until job completed (5 retries left).
FAILED - RETRYING: check until job completed (4 retries left).
ok: [server01]

Re-usability with roles

For re-usablility, I recommend to move the job-polling to a role, so that it can be used for all asynchronous jobs

# roles/odacli_job/tasks/main.yml
  - name: set job-id
    set_fact:
      jobid: "{{ (job_stdout | from_json).jobId |default('') }}"
    when: job_stdout|default('') != ''
  - debug: var=jobid

  - name: check until repo job completed
    ansible.builtin.shell: /opt/oracle/dcs/bin/odacli describe-job -j -i {{jobid}}
    register: check_status
    failed_when: false
    until: "(check_status.stdout|from_json).status == 'Success' or (check_status.stdout|from_json).status == 'Failure'"
    retries: "{{retries}}"
    delay:   "{{delay}}"
    changed_when: false

The role can be used as follows:

  - name: include role to poll job
    include_role:
      role: odacli_job
    when: "'ALREADY_INSTALLED' not in repo.stdout"
    vars:
      job_stdout: repo.stdout 
      retries: 50
      delay: 3

The job to poll can be specified by variable “jobid”, or you can provide the json-output of launching the job via “job_stdout”, then the role extracts the jobid

After the import of the software (you will see it in /opt/oracle/oak/pkgrepos/orapkgs/clones/), you can now deploy an ORACLE_HOME with it. For that, we can use the same role to poll this asynchronous job:

  - name: create database home
    ansible.builtin.shell: |
      /opt/oracle/dcs/bin/odacli create-dbhome -j -v {{version}}
    register: home

  - name: include role to poll job
    include_role:
      role: odacli_job
    vars:
      job_stdout: home.stdout
      retries: 80
      delay: 10

Errorhandling

What is not done in the role is the error-handling. It is up to you to define an adequate error-handling. You will get the result of the asynchronous job in the variable check_status.stdout which is of json format.

(check_status.stdout|from_json).status

If the status is

  • “Success”, it is OK
  • “Failure”, it is not OK
  • Any other value (e.g. “Created”), you got a timeout (>retries*timeout sec.), then the result it is unknown (maybe the job completes at a later time, sucessful or not).