Database

Automate Oracle Enterprise Manager 24ai CSPU Patching with Ansible

Stop Patching Oracle Enterprise Manager Manually

Since Oracle has introduced CSPU Critical Security Patch Update, the Enterprise Manager 24ai got two new Release Updates and Holistic Patches in the last two months. The cadence of for new releases is higher and higher. The patching sequence is always the same: update OMSPatcher if required, update OPatch if required, run Release Update in three phases, apply the Holistic SPB Patch. Each step requires interactive password prompts, commands that run for up to 90 minutes, and version checks before you move on.

Miss a step, mistype a password at the interactive prompt, or lose the SSH session during the 90-minute deploy phase - and you have a broken OMS.

I got tired of doing this manually. I wrote Ansible playbooks that automate the entire sequence. Pre-flight checks, OMS backup, all four patching steps, all interactive prompts handled automatically. No Python module installation required on the control node. No passwords stored in files. All you need is Ansible.

The repository: https://codeberg.org/martinberger_ch/em24ai-security-ansible

Documents

What the Playbooks Cover

The full CSPU patching sequence for Oracle EM 24ai, in order:

PlaybookWhat it does
precheck.ymlDisk space, patch ZIPs staged, tool versions, OMS status, DB connectivity
00_backup_oms.ymlemctl exportconfig oms before any changes
01_update_omspatcher.ymlReplace OMSPatcher binary, assert minimum version
02_update_opatch.ymlSilent JAR install of OPatch, assert minimum version
03_release_update.ymlThree-phase: analyze / deploy / update via omspatcher
04_holistic_patch.ymlApply SPB via omspatcher apply -spb_patch
site.ymlMaster playbook - runs all of the above in sequence

My Lab Setup

  • Oracle Enterprise Manager 24ai Release Update 11 Standalone, OCI Marketplace
  • Oracle Linux Server Release 9.x
  • Running on Compute Instance in Oracle Cloud Infrastructure OCI
  • Software stage directory: /u01/app/oracle/stage/patch
  • Backup directory: /u01/app/oracle/admin/oms/backup
  • Properties file for OMSPatcher: /u01/app/oracle/admin/oms/etc/oms.properties
  • Ansible running in Fedora 44 Windows Subsystem for Linux

Step 0 - Configure

Clone the repository on your control node (the machine running Ansible):

$ git clone https://codeberg.org/martinberger/em24ai-security-ansible.git
$ cd em24ai-security-ansible

Edit vars/config.yml - set your host, SSH key, Oracle paths, and the patch numbers and ZIP filenames for the current CSPU related files:

# OMS Host
oms_host: "your-oms-host.example.com"        # FQDN or IP of the OMS server
oms_ssh_user: "oracle"                         # OS user for SSH (must own ORACLE_HOME)
ssh_key_dir: "~/.ssh"
ssh_key_file: "id_ed25519"                    # private key filename inside ssh_key_dir

# Oracle Paths
oracle_home: "/u01/app/oracle/em/middleware_241/oms_home"
stage_dir: "/u01/app/oracle/stage/patch"
backup_dir: "/u01/app/oracle/admin/oms/backup"
properties_file: "/u01/app/oracle/admin/oms/etc/oms.properties"
# Use "{{ oracle_home }}/oraInst.loc" when the EM home uses a private inventory;
# use "/etc/oraInst.loc" when the DB and EM home share the system inventory.
omspatcher_inv_ptr_loc: "{{ oracle_home }}/oraInst.loc"
oms_admin_user: "sysman"

# WebLogic Admin Server (used by omspatcher apply -spb_patch)
wls_admin_url: "t3s://your-oms-host.example.com:7101"
wls_admin_user: "weblogic"
# password via env var OMS_WLS_PASSWORD

# Repository Database
repo_db_host: "your-db-host.example.com"
repo_db_port: 1521
repo_db_service: "emrep"                      # DB service name (not SID)
repo_db_user: "sysman"
repo_db_admin_user: "sys"                     # SYS or admin user prompted by omspatcher
# password via env var OMS_DB_ADMIN_PASSWORD

# Patch Numbers - update each CSPU
patch_omspatcher: "19999993"
patch_release_update: "39466468"
patch_holistic: "39730260"

# ZIP Filenames - must match what you downloaded from MOS
omspatcher_zip: "p19999993_241000_Generic.zip"
opatch_zip: "p28186730_1394224_Generic.zip"   # MOS patch 6880880 - filename varies per CSPU
opatch_installer_dir: "6880880"               # directory name extracted from the OPatch ZIP
release_update_zip: "p39466468_241000_Generic.zip"
release_update_patch_dir: "39466468"          # directory name extracted from release_update_zip
holistic_zip: "p39730260_241000_Generic.zip"
holistic_patch_dir: "39730260"                # directory name extracted from holistic_zip

# Required Minimum Versions (pre-flight checks)
omspatcher_min_version: "13.9.24.15.0"
opatch_min_version: "13.9.4.2.24"

# Disk Space Pre-checks (minimum free GB per filesystem)
precheck_min_free_gb_oracle_home: 10          # filesystem hosting $ORACLE_HOME
precheck_min_free_gb_stage: 15                # filesystem hosting stage_dir
precheck_min_free_gb_backup: 3                # filesystem hosting backup_dir
precheck_min_free_gb_tmp: 3                   # /tmp

Set the host alias in inventory.yml to match your OMS FQDN - Ansible uses it in all task output:

all:
  children:
    oms:
      hosts:
        172.16.2.77:
          ansible_host: "{{ oms_host }}"
          ansible_user: "{{ oms_ssh_user }}"
          ansible_ssh_private_key_file: "{{ ssh_key_dir }}/{{ ssh_key_file }}"

For local overrides (real hostnames, IPs) copy vars/config.yml to vars/config.local.yml and pass both with -e @vars/config.yml -e @vars/config.local.yml. The .gitignore keeps config.local.yml out of git automatically.

SSH Connect

Verify SSH connect from workstation running the playbooks to the target server as OS user oracle. This must run passwordless. If not already confîgured, scroll down to see how to do.

Properties File

Some patching actions require access to Weblogic admin server by key, to achieve, an Oracle Enterprise Manager properties file is created. How to create a properties file? See the README of the Release Update in section 10 Appendix or below in OCI Markeplace image notes.

Step 1 - Export Secrets

All passwords are consumed from environment variables. Nothing goes into files:

$ export OMS_EMCTL_PASSWORD="..."        # SYSMAN password (emctl exportconfig)
$ export OMS_REPO_DB_PASSWORD="..."      # OMS_SYSMAN_PWD (OMSPatcher)
$ export OMS_DB_ADMIN_PASSWORD="..."     # SYS password (deploy / update prompts)
$ export OMS_WLS_PASSWORD="..."          # WebLogic admin password (holistic patch)

Step 2 - Stage the Patch ZIPs

Download all four patch ZIPs from My Oracle Support and copy them to stage_dir on the OMS server before running any playbook.

PatchMOS Patch Number
OMSPatcher19999993
OPatch8186730
Release Update 1139466468
Holistic Patch39730260

Step 3 - Run Pre-flight Checks

Always run this first. It checks everything before a single patch file is touched:

$ ansible-playbook -i inventory.yml -e @vars/config.yml playbooks/precheck.yml

What it verifies:

  • Free disk space on $ORACLE_HOME, stage_dir, backup_dir, /tmp - configurable thresholds, directories existing
  • All four patch ZIPs present in stage_dir
  • Oracle inventory pointer file (oraInst.loc) exists
  • expect binary installed on the OMS server
  • OMSPatcher and OPatch binaries accessible, versions reported
  • OMS running via emctl status oms
  • Repository DB reachable via tnsping

If anything fails the assertion, the playbook stops with a clear error message before any change is made.

Step 4 - Backup OMS

$ ansible-playbook -i inventory.yml -e @vars/config.yml playbooks/00_backup_oms.yml

Runs emctl exportconfig oms and writes the backup to backup_dir. No downtime required.

Step 5 - Update OMSPatcher

Verify current version

The playbook checks the current version and reports it before making any change. Check the CSPU Patch Availability Document for the required minimum - only update if below it.

$ ansible-playbook -i inventory.yml -e @vars/config.yml playbooks/01_update_omspatcher.yml

What the playbook does

  1. Records the current OMSPatcher version
  2. Renames $ORACLE_HOME/OMSPatcher to OMSPatcher.bak_YYYYMMDD
  3. Extracts the new ZIP into $ORACLE_HOME
  4. Asserts the new version meets omspatcher_min_version from vars/config.yml

The old OMSPatcher.bak_YYYYMMDD directory can be removed after the full patching sequence completes successfully.

Step 6 - Update OPatch

$ ansible-playbook -i inventory.yml -e @vars/config.yml playbooks/02_update_opatch.yml

OPatch ships as a JAR installer. The playbook extracts the ZIP, runs the silent install using Oracle’s bundled JDK (no separate Java installation needed), and asserts the minimum version. In my case running the previous RU, OPatch was already at the required version - the assertion simply confirmed it.

Step 7 - Release Update (three phases)

The Release Update is the most complex step. Run each phase individually by tags and review the output before proceeding. Or don’t use tags and all steps are done in order.

Phase 1 - Analyze

$ ansible-playbook -i inventory.yml -e @vars/config.yml playbooks/03_release_update.yml --tags analyze

The -analyze flag verifies the patch against the installed version and checks for compatibility issues. No changes are made. Wait for OMSPatcher succeeded in the output before moving on.

Phase 2 - Deploy

$ ansible-playbook -i inventory.yml -e @vars/config.yml playbooks/03_release_update.yml --tags deploy

This is the pre-downtime phase. OMSPatcher creates the clone home (cloneExtOMSHome and cloneOMSHome), generates the SQL edition, and registers artifacts. Expect 60–90 minutes. The task runs async - no SSH timeout risk.

Phase 3 - Update

$ ansible-playbook -i inventory.yml -e @vars/config.yml playbooks/03_release_update.yml --tags update

This is the downtime phase. OMSPatcher stops the OMS, applies the patch, switches the SQL edition, and brings OMS back up. No manual emctl stop/start needed. Wait for OMSPatcher succeeded.

How interactive prompts are handled

All three phases prompt for a SYS username, SYS password, and “Do you want to proceed?” confirmation. The playbook uses ansible.builtin.expect to respond automatically:

responses:
  'Enter SYS or Non-SYS \(Admin User\) username:': "{{ repo_db_admin_user }}"
  'Enter .* password\s*:': "{{ lookup('env', 'OMS_DB_ADMIN_PASSWORD') }}"
  'Do you want to proceed\?.*': 'y'

The password comes from the environment variable. no_log: true keeps it out of Ansible output. If pexpect is not installed on the OMS server, the playbook installs it via pip --user before proceeding - no root access required.

Verification

After the update phase, check the EM version via SYSMAN → About Enterprise Manager:

SYSMAN EM Version Pop-Up

Or via OMSPatcher:

$ $ORACLE_HOME/OMSPatcher/omspatcher lspatches | grep "Platform Update"

Step 8 - Holistic Patch

$ ansible-playbook -i inventory.yml -e @vars/config.yml playbooks/04_holistic_patch.yml

Applies the Security Bundle Patch via omspatcher apply -spb_patch. This step updates underlying components (JDK, Perl, and others) and creates a short OMS downtime - handled internally by OMSPatcher.

The holistic patch prompts for a WebLogic admin URL, username, and password. All answered automatically:

responses:
  'Please enter OMS weblogic admin server URL.*:>': "{{ wls_admin_url }}"
  'Please enter OMS weblogic admin server username.*:>': "{{ wls_admin_user }}"
  'Please enter OMS weblogic admin server password:>': "{{ lookup('env', 'OMS_WLS_PASSWORD') }}"

After completion the playbook runs emctl status oms to confirm the service came back up.

Verification

$ $ORACLE_HOME/OMSPatcher/omspatcher lspatches | grep 39730260

Updating for a New CSPU Quarter

Only vars/config.yml changes each quarter. Update the patch numbers and ZIP filenames from the CSPU Patch Availability Document on My Oracle Support:

patch_release_update: "39466468"
release_update_zip: "p39466468_241000_Generic.zip"
release_update_patch_dir: "39466468"

patch_holistic: "39730260"
holistic_zip: "p39730260_241000_Generic.zip"
holistic_patch_dir: "39730260"

Stage the new ZIPs, run precheck.yml, proceed. Everything else stays the same.

Anything Else?

Yes:

  • Patch your EM Agents via Agent Update Plan or Gold Image after each CSPU
  • Verify for latest JDK your Agents can run - EM 24.1 AI: How to Use the Latest Certified JDK 8 Update with 24.1 Agents - KB518379
  • Track the Oracle CSPU schedule - Oracle now ships a Holistic Patch monthly alongside the quarterly Release Update
  • Build it into your change calendar - the effort per quarter is low, the risk of skipping is not

Some notes about the Oracle Enteprise Manager EM24ai OCI Marketplace Image

For testing purposes, you can use the OCI Marketplace Image, this runs well, is easy to deploy and with current date, you get Release Update 10. A good base to verify Release Update 11 and the playbooks. I used the SIMPLE PROVISIONING in Stack with one node only to verify the Ansible playbooks.

Adaptions

On provisioned Oracle Enterprise Manager 24ai Management Server (OMS) in Oracle Cloud Infrastructure:

  • Connect as OS user opc first, then switch to oracle via sudo. Add your public key from the Ansible host to the oracle user’s authorized keys to enable passwordless SSH access from the Ansible control plane to the OMS.
$ cd $HOME
$ ssh-keygen
$ cd .ssh
$ vi authorized_keys
>> add your public key here

$ chmod 600 authorized_keys
  • As OS user oracle, create the WLS key and config file. Weblogic username and password are required.
$ mkdir -p /u01/app/oracle/admin/oms/wlskeys
$ cd /u01/app/oracle/admin/oms/wlskeys 

$ORACLE_HOME/OMSPatcher/wlskeys/createkeys.sh -oh $ORACLE_HOME -location /u01/app/oracle/admin/oms/wlskeys
  • As OS user oracle, ensure the OMS properties file is in place. If it does not exist, create a new one. Example:
$ mkdir -p  /u01/app/oracle/admin/oms/etc && cd /u01/app/oracle/admin/oms/etc
$ vi oms.properties

Add the OMS URL here. If unclear, run emctl status oms -details to retrieve it.

AdminServerURL=t3s://oms1:7101
AdminConfigFile=/u01/app/oracle/admin/oms/wlskeys/config 
AdminKeyFile=/u01/app/oracle/admin/oms/wlskeys/key 
$ chmod 600 oms.properties
  • As OS user opc, install expect package
$ sudo dnf install -y expect
  • As OS user oracle, create admin file structure for OMS
$ mkdir -p /u01/app/oracle/admin/oms/backup
$ mkdir -p /u01/app/oracle/admin/oms/etc
  • Pro-tip DNS If the Ansible control plane cannot resolve the OMS hostname, use the IP address directly in inventory.yml and vars/config.yml.

Summary

Oracle Enterprise Manager is THE monitoring platform for your databases. If it is compromised, everything it manages is at risk. Doing the patching manually is error-prone, unrepeatable, and frankly unnecessary.

These Ansible playbooks automate the full sequence: one config file to update whenever Release Updates and Holistic patches are published. Four environment variables for secrets, six playbooks that handle the rest - including all interactive prompts and version assertions. Pre-flight checks catch problems before they matter.

The code is on Codeberg, free to use and adapt: https://codeberg.org/martinberger_ch/em24ai-security-ansible

PATCH YOUR OEM. Often. No excuses!