In my previous post, I introduced my approach and shared the 19.26 version of these playbooks. This time, I’m publishing an updated version for Oracle 19.28 Release Update, along with improvements based on real-world usage and feedback.
As always, the goal is simple:
* Make patching predictable, repeatable, and stress-free.
This release includes updates aligned with the latest RU cycle and enhancements in automation logic:
- Oracle 19.28 Release Update
- Latest MRP (Monthly Recommended Patches)
- Updated one-off patches (Doc ID 555.1)
- Improvements in precheck validations
- Better handling of edge cases during patching
- Enhancements for RAC environments
I continue to follow my standard approach:
👉 Stay one RU behind (n-1 strategy) and apply the latest MRP on top.
This provides a good balance between stability and security.
Why I Prefer Ansible?
Patching is one of those tasks that is simple in theory but painful in practice.
Using Ansible gives:
- Consistency → Same steps every time
- Automation → Less manual intervention
- Repeatability → Easier rollback & retry
- Confidence → Especially in multi-node RAC setups
As I mentioned before, Ansible is agentless and designed for orchestration, which makes it particularly powerful for DBAs managing multiple servers.
My previous posts to power up:
Oracle Grid Patching by using Ansible
Oracle Database Patching by Using Ansible
Automate Oracle Database Patching with Ansible Playbooks – 19.24
Ansible Playbooks for Oracle Grid and Database Release Update (19.26 with MRP & One-Off Fixes)
Playbooks for Grid Patching:
For Grid Infrastructure patching, you’ll need to download the following setup files from Oracle Support. Place them under the /u01/app/Setup directory, which should be owned by the ansible user. That’s all for the preparation phase. For a more detailed explanation, you can refer to my blog post: Oracle Grid Patching by using Ansible.
- Grid Home Setup – LINUX.X64_193000_grid_home.zip
- Grid Infrastructure Release Update 19.28.0.0.250719 37957391 (GIRU System Patch involves DBRU) – p37957391_190000_Linux-x86-64.zip
- GI MRP 19.28.0.0.251021 38509394 (GIMRP involve Database Montly Recommended Paches) – p38509394_1928000DBRU_Linux-x86-64.zip
- OPatch 12.2.0.1.48 (Version 48) – p6880880_122010_Linux-x86-64.zip
- Recommended one of patch 34672698 – p34672698_1928000DBRU_Linux-x86-64.zip
To begin and finish the patching process, simply run the main playbook. It will complete all necessary steps with a single command:
[ansible@blt01 ansible]$ ansible-playbook -i hosts projects/dbru/apply_grid_update_main.yml [ansible@blt01 ansible]$ cat projects/dbru/apply_grid_update_main.yml - name: Import playbook to prepare new Oracle Grid Infrastructure Home ansible.builtin.import_playbook: 1_prepare_grid_allnodes.yml- name: Import playbook to apply Oracle Grid Infrastructure Release Update ansible.builtin.import_playbook: 2_apply_giru_onfirstnode.yml- name: Import playbook to finish Oracle Grid Infrastructure Release Update ansible.builtin.import_playbook: 3_finish_giru_allnodes.yml- name: Import playbook to start Database Recovery Process on Standby Databases ansible.builtin.import_playbook: 4_start_recover_allnodes.yml- name: Import playbook to remove old grid home ansible.builtin.import_playbook: 5_remove_giru_allnodes.yml
Playbook contents:
Prepare Step – 1_prepare_grid_allnodes.yml
- name: Prepare new grid home directories hosts: allhosts remote_user: root vars: new_grid_home: /u01/app/19.28/grid patch_dir: /u01/app/Setup tasks: - name: Ensure patch directory exists ansible.builtin.file: path: "{{ patch_dir }}" state: directory owner: oracle group: oinstall - name: Ensure new grid home directory exists ansible.builtin.file: path: "{{ new_grid_home }}" state: directory owner: oracle group: oinstall
Apply Grid Infrastructure Release Update using Out of Place Patching – 2_apply_giru_onfirstnode.yml
- name: Apply Oracle Grid Infrastructure Release Update hosts: allhosts remote_user: oracle run_once: true vars: u01_size_mb: 13356 new_grid_home: /u01/app/19.28/grid old_grid_home: /u01/app/19.26/grid patch_dir: /u01/app/Setup source_patch_dir: /u01/app/Setup tasks: - name: Copy setup files to target server ansible.builtin.copy: src: "{{ source_patch_dir }}/" dest: "{{ patch_dir }}/" owner: oracle group: oinstall - name: Unzip new grid home ansible.builtin.unarchive: src: "{{ patch_dir }}/LINUX.X64_193000_grid_home.zip" dest: "{{ new_grid_home }}" remote_src: yes - name: Unzip OPatch to new grid home ansible.builtin.unarchive: src: "{{ patch_dir }}/p6880880_190000_Linux-x86-64.zip" dest: "{{ new_grid_home }}" remote_src: yes - name: Prepare patches directories ansible.builtin.file: path: "{{ item }}" state: directory owner: oracle group: oinstall loop: - "{{ patch_dir }}/GIRU" - "{{ patch_dir }}/GIRUOneOff" - "{{ patch_dir }}/GIMRP" - name: Unzip GIRU system patch ansible.builtin.unarchive: src: "{{ patch_dir }}/p37957391_190000_Linux-x86-64.zip" dest: "{{ patch_dir }}/GIRU" remote_src: yes - name: Unzip GIMRP patch ansible.builtin.unarchive: src: "{{ patch_dir }}/p38509394_1928000DBRU_Linux-x86-64.zip" dest: "{{ patch_dir }}/GIMRP" remote_src: yes - name: Unzip GIRU oneoff patch ORA-800 ansible.builtin.unarchive: src: "{{ patch_dir }}/p34672698_1928000DBRU_Linux-x86-64.zip" dest: "{{ patch_dir }}/GIRUOneOff" remote_src: yes - name: Register cluster nodes for response file ansible.builtin.shell: cmd: "{{ old_grid_home }}/bin/olsnodes | tr '\n' ',' | sed 's/,$//'" register: cluster_nodes - name: Register OSDBA value for response file ansible.builtin.shell: cmd: "grep 'OSDBA=' {{ old_grid_home }}/install/response/grid_20*.rsp" register: osdba - name: Register OSOPER value for response file ansible.builtin.shell: cmd: "grep 'OSOPER=' {{ old_grid_home }}/install/response/grid_20*.rsp" register: osoper - name: Register OSASM value for response file ansible.builtin.shell: cmd: "grep 'OSASM=' {{ old_grid_home }}/install/response/grid_20*.rsp" register: osasm - name: Create contents of response file ansible.builtin.copy: dest: "{{ patch_dir }}/grid_oop.rsp" content: | oracle.install.responseFileVersion=/oracle/install/rspfmt_crsinstall_response_schema_v19.0.0 INVENTORY_LOCATION=/u01/app/oraInventory oracle.install.option=CRS_SWONLY ORACLE_BASE=/u01/app/oracle {{ osdba.stdout }} {{ osoper.stdout }} {{ osasm.stdout }} oracle.install.crs.config.clusterNodes={{ cluster_nodes.stdout }} ORACLE_HOME={{ new_grid_home }} oracle.install.crs.config.ClusterConfiguration=STANDALONE oracle.install.crs.config.configureAsExtendedCluster=false oracle.install.crs.config.gpnp.configureGNS=false oracle.install.crs.config.autoConfigureClusterNodeVIP=false - name: Clean up grid_home setup zip files ansible.builtin.file: path: "{{ item }}" state: absent loop: - "{{ patch_dir }}/LINUX.X64_193000_grid_home.zip" - "{{ patch_dir }}/p37957391_190000_Linux-x86-64.zip" - "{{ patch_dir }}/p38509394_1928000DBRU_Linux-x86-64.zip" - "{{ patch_dir }}/p34672698_1928000DBRU_Linux-x86-64.zip" - name: Check u01 free disk space ansible.builtin.shell: cmd: "df -P /u01 | awk 'END { print $4 }'" register: u01size failed_when: u01size.stdout | int < u01_size_mb * 1024 - name: Apply GIRU and oneoffs ansible.builtin.shell: cmd: | {{ new_grid_home }}/gridSetup.sh -silent \ -ignorePrereqFailure -waitforcompletion \ -applyRU {{ patch_dir }}/GIRU/37957391 \ -applyOneOffs {{ patch_dir }}/GIMRP/38509394/36913519,{{ patch_dir }}/GIMRP/38509394/38311920,{{ patch_dir }}/GIRUOneOff/34672698 \ -responsefile {{ patch_dir }}/grid_oop.rsp environment: CV_ASSUME_DISTID: OL7 NEWGRIDHOME: "{{ new_grid_home }}" register: apply_giru failed_when: "'Successfully Setup Software' not in apply_giru.stdout" - name: Clean up grid_home setup and patch files directory and contents ansible.builtin.file: path: "{{ item }}" state: absent loop: - "{{ patch_dir }}/GIRU" - "{{ patch_dir }}/GIRUOneOff" - "{{ patch_dir }}/GIMRP" - name: Output patching success message ansible.builtin.debug: msg: "Patching succeeded. InsaneDBA was here." when: apply_giru.rc in [6,0]
Switch to new Grid Home – 3_finish_giru_allnodes.yml
- name: Finish Oracle Grid Infrastructure Release Update hosts: allhosts remote_user: root vars: crs_value: CRS="true"/> new_grid_home: /u01/app/19.28/grid old_grid_home: /u01/app/19.26/grid patch_dir: /u01/app/Setup tasks: - name: Register cluster nodes for response file ansible.builtin.shell: cmd: "{{ old_grid_home }}/bin/olsnodes | tr '\n' ',' | sed 's/,$//'" register: cluster_nodes - name: Tag the new grid home in inventory ansible.builtin.shell: cmd: | $NEWGRIDHOME/gridSetup.sh -silent \ -switchGridHome oracle.install.option=CRS_SWONLY \ ORACLE_HOME={{ new_grid_home }} \ oracle.install.crs.config.clusterNodes={{ cluster_nodes.stdout }} \ oracle.install.crs.rootconfig.executeRootScript=false become: yes become_user: oracle environment: NEWGRIDHOME: "{{ new_grid_home }}" - name: Check CRS flag in inventory ansible.builtin.shell: cmd: | grep "{{ new_grid_home }}" /u01/app/oraInventory/ContentsXML/inventory.xml | grep CRS | awk 'END { print $6 }' register: crsflag failed_when: crsflag.stdout != crs_value - name: Execute root.sh and start grid from new home ansible.builtin.command: cmd: "{{ new_grid_home }}/root.sh" throttle: 1 - name: Remove response file from stage directory ansible.builtin.file: path: "{{ patch_dir }}/grid_oop.rsp" state: absent - name: Copy old grid home dependent files to the new grid home ansible.builtin.copy: src: "{{ old_grid_home }}/sqlplus/admin/glogin.sql" dest: "{{ new_grid_home }}/sqlplus/admin/" owner: oracle group: oinstall remote_src: yes - name: Replace old_grid_home value in .bashrc files with the new_gridhome value ansible.builtin.replace: path: "{{ item }}" regexp: "{{ old_grid_home }}" replace: "{{ new_grid_home }}" loop: - /root/.bashrc - /home/oracle/.bashrc
Start Database Recovery Process on Standby Databases – it will only run on standby databases, otherwise it will not do anything. 4_start_recover_allnodes.yml
- name: Start Database Recovery Process on Standby Databases hosts: allhosts remote_user: oracle vars: db_home: /u01/app/oracle/product/19.26/dbhome_1 recovery_script_dest_dir: /home/oracle recovery_script_dir: /etc/ansible/projects patch_dir: /u01/app/Setup tasks: - name: Register instances ansible.builtin.shell: "ps -ef | grep ora_pmon | grep -v grep | awk '{ print $NF }' | cut -c10-50" register: find_instances - name: Copy start recovery file to remote server ansible.builtin.copy: src: "{{ recovery_script_dir }}/start_recover_onremote.sh" dest: "{{ recovery_script_dest_dir }}" owner: oracle group: oinstall mode: '0750' - name: Debug List found instances ansible.builtin.debug: msg: "Found database instance: {{ item }}" loop: "{{ find_instances.stdout_lines }}" - name: Start recovery ansible.builtin.shell: "{{ recovery_script_dest_dir }}/start_recover_onremote.sh {{ item }} {{ db_home }}" loop: "{{ find_instances.stdout_lines }}" register: recovery_output - name: Debug Recovery output ansible.builtin.debug: var: item.stdout loop: "{{ recovery_output.results }}"
Optional : Remove Old Grid Home – 5_remove_giru_allnodes.yml
- name: Deinstall Oracle Grid Infrastructure hosts: allhosts remote_user: root vars: removed_value: 'REMOVED="T"' old_grid_home: /u01/app/19.26/grid patch_dir: /u01/app/Setup ansible_python_interpreter: /usr/bin/python3 tasks: - name: Create response file for silent deinstallation ansible.builtin.shell: cmd: "{{ old_grid_home }}/deinstall/deinstall -checkonly" become: yes become_user: oracle become_method: su throttle: 1 - name: Register old grid_home response file for deinstallation ansible.builtin.shell: cmd: "ls -lsrt /tmp/deinstall*/response/*.rsp | tail -1 | awk '{print $NF}'" register: oldgridresponse - name: Unlock old grid_home for deinstallation ansible.builtin.shell: cmd: "{{ old_grid_home }}/crs/install/rootcrs.sh -unlock -crshome {{ old_grid_home }} -paramfile {{ oldgridresponse.stdout }}" - name: Recursively change ownership of old_grid_home directory ansible.builtin.file: path: "{{ old_grid_home | dirname }}" state: directory recurse: yes owner: oracle group: oinstall - name: Deinstall old grid_home ansible.builtin.expect: command: "/bin/bash -c '{{ old_grid_home }}/deinstall/deinstall'" responses: 'Do you want to continue': 'y' echo: yes timeout: 600 become: yes become_user: oracle run_once: true register: deinstall_result - name: Check inventory whether old_grid_home is tagged with removed flag ansible.builtin.shell: cmd: "grep '{{ old_grid_home }}' /u01/app/oraInventory/ContentsXML/inventory.xml | awk '{ print $NF }'" register: removedflag - name: Remove old grid_home directory ansible.builtin.file: path: "{{ old_grid_home | dirname }}" state: absent when: deinstall_result.rc == 0 or removedflag.stdout == removed_value
Playbooks for Database Patching:
For Database patching, you’ll need to download the following setup files from Oracle Support. Place them under the /u01/app/Setup directory, which should be owned by the ansible user. That’s all for the preparation phase. For a more detailed explanation, you can refer to my blog post: Oracle Database Patching by Using Ansible.
- Database home Setup – LINUX.X64_193000_db_home.zip
- Grid Infrastructure Release Update 19.28.0.0.250719 37957391 (GIRU System Patch involves DBRU) – p37957391_190000_Linux-x86-64.zip
- GI MRP 19.28.0.0.251021 38509394 (GIMRP involve Database Montly Recommended Paches) – p38509394_1928000DBRU_Linux-x86-64.zip
- OPatch 12.2.0.1.48 (Version 48) – p6880880_122010_Linux-x86-64.zip
- Data Pump Bundle Patch – p38170982_1928000DBRU_Generic.zip
- Spatial Bundle Patch – p38599552_1928000DBRU_Linux-x86-64.zip
- Oracle Text Bundle Patch – p38219529_1928000DBRU_Linux-x86-64.zip
- OJVM Patch – p37847857_190000_Linux-x86-64.zip
- Recommended one of patch – p34672698_1928000DBRU_Linux-x86-64.zip
To begin and finish the patching process, simply run the main playbook. It will complete all necessary steps with a single command:
- name: Import playbook to prepare new Oracle Database Home ansible.builtin.import_playbook: 1_prepare_dbru.yml- name: Import playbook to apply Oracle Oracle Database Release Update ansible.builtin.import_playbook: 2_apply_dbru_onfirstnode.yml- name: Import playbook to run root.sh in new Oracle Database Homes ansible.builtin.import_playbook: 3_apply_dbru_runrootallnodes.yml- name: Import playbook to finish Oracle Database Release Update ansible.builtin.import_playbook: 4_finish_dbru_onallnodes.yml- name: Import playbook to apply Datapatch on Primary Databases ansible.builtin.import_playbook: 5_apply_datapatch_onprimary.yml - name: Import playbook to Remove Old DB Home ansible.builtin.import_playbook: 6_remove_dbru_onallnodes.yml
Playbook contents:
Prepare Step – 1_prepare_dbru_allnodes.yml
- name: Configure New Oracle Database Homes hosts: allhosts remote_user: oracle vars: new_db_home: /u01/app/oracle/product/19.28/dbhome_1 patch_dir: /u01/app/Setup tasks: - name: Prepare patch directory for setup files ansible.builtin.file: path: "{{ patch_dir }}" state: directory owner: oracle group: oinstall - name: Prepare new database home ansible.builtin.file: path: "{{ new_db_home }}" state: directory owner: oracle group: oinstall
Apply Database Release Update using Out of Place Patching – 2_apply_dbru_onfirstnode.yml
- name: Apply Oracle Database Release Update hosts: allhosts vars: u01_size_mb: 13356 new_db_home: /u01/app/oracle/product/19.28/dbhome_1 old_db_home: /u01/app/oracle/product/19.26/dbhome_1 grid_home: /u01/app/19.28/grid patch_dir: /u01/app/Setup source_patch_dir: /u01/app/Setup remote_user: oracle run_once: true tasks: - name: Copy setup files to target server ansible.builtin.copy: src: "{{ source_patch_dir }}/" dest: "{{ patch_dir }}/" owner: oracle group: oinstall - name: Unzip new DB_HOME ansible.builtin.unarchive: src: "{{ patch_dir }}/LINUX.X64_193000_db_home.zip" dest: "{{ new_db_home }}" remote_src: yes - name: Unzip OPatch to new DB_HOME ansible.builtin.unarchive: src: "{{ patch_dir }}/p6880880_190000_Linux-x86-64.zip" dest: "{{ new_db_home }}" remote_src: yes - name: Prepare patches directories ansible.builtin.file: path: "{{ item }}" state: directory owner: oracle group: oinstall loop: - "{{ patch_dir }}/GIRU" - "{{ patch_dir }}/OJVM" - "{{ patch_dir }}/DBRUOneOff" - "{{ patch_dir }}/GIMRP" - name: unzip GIRU system patch ansible.builtin.unarchive: src: "{{ patch_dir }}/p37957391_190000_Linux-x86-64.zip" dest: "{{ patch_dir }}/GIRU" remote_src: yes - name: Unzip OJVM patch ansible.builtin.unarchive: src: "{{ patch_dir }}/p37847857_190000_Linux-x86-64.zip" dest: "{{ patch_dir }}/OJVM" remote_src: yes - name: unzip GIMRP ansible.builtin.unarchive: src: "{{ patch_dir }}/p38509394_1928000DBRU_Linux-x86-64.zip" dest: "{{ patch_dir }}/GIMRP" remote_src: yes - name: unzip DBRU oneoff patch ansible.builtin.unarchive: src: "{{ patch_dir }}/p34672698_1928000DBRU_Linux-x86-64.zip" dest: "{{ patch_dir }}/DBRUOneOff" remote_src: yes - name: unzip Data Pump Bundle patch ansible.builtin.unarchive: src: "{{ patch_dir }}/p38170982_1928000DBRU_Generic.zip" dest: "{{ patch_dir }}/DBRUOneOff" remote_src: yes - name: unzip Spatial Bundle patch ansible.builtin.unarchive: src: "{{ patch_dir }}/p38599552_1928000DBRU_Linux-x86-64.zip" dest: "{{ patch_dir }}/DBRUOneOff" remote_src: yes - name: unzip Oracle text Bundle patch ansible.builtin.unarchive: src: "{{ patch_dir }}/p38219529_1928000DBRU_Linux-x86-64.zip" dest: "{{ patch_dir }}/DBRUOneOff" remote_src: yes - name: Register cluster nodes for response file ansible.builtin.shell: cmd: "{{ grid_home }}/bin/olsnodes | tr '\n' ',' | sed 's/,$//'" register: cluster_nodes - name: Register osdba value for response file ansible.builtin.shell: cmd: "grep 'OSDBA_GROUP=' {{ old_db_home }}/install/response/db_20*.rsp" register: osdba - name: Register osoper value for response file ansible.builtin.shell: cmd: "grep 'OSOPER_GROUP=' {{ old_db_home }}/install/response/db_20*.rsp" register: osoper - name: Register osbackupdba value for response file ansible.builtin.shell: cmd: "grep 'OSBACKUPDBA_GROUP=' {{ old_db_home }}/install/response/db_20*.rsp" register: osbackupdba - name: Register osdgdba value for response file ansible.builtin.shell: cmd: "grep 'OSDGDBA_GROUP=' {{ old_db_home }}/install/response/db_20*.rsp" register: osdgdba - name: Register oskmdba value for response file ansible.builtin.shell: cmd: "grep 'OSKMDBA_GROUP=' {{ old_db_home }}/install/response/db_20*.rsp" register: oskmdba - name: Register osracdba value for response file ansible.builtin.shell: cmd: "grep 'OSRACDBA_GROUP=' {{ old_db_home }}/install/response/db_20*.rsp" register: osracdba - name: Create contents of response file ansible.builtin.copy: dest: "{{ patch_dir }}/db_oop.rsp" content: | oracle.install.responseFileVersion=/oracle/install/rspfmt_dbinstall_response_schema_v19.0.0 oracle.install.option=INSTALL_DB_SWONLY UNIX_GROUP_NAME=oinstall INVENTORY_LOCATION=/u01/app/oraInventory ORACLE_BASE=/u01/app/oracle ORACLE_HOME={{ new_db_home }} oracle.install.db.InstallEdition=EE {{ osdba.stdout }} {{ osoper.stdout }} {{ osbackupdba.stdout }} {{ osdgdba.stdout }} {{ oskmdba.stdout }} {{ osracdba.stdout }} oracle.install.db.CLUSTER_NODES={{ cluster_nodes.stdout }} SECURITY_UPDATES_VIA_MYORACLESUPPORT=false DECLINE_SECURITY_UPDATES=true - name: Clean up DB home zip files ansible.builtin.file: path: "{{ item }}" state: absent loop: - "{{ patch_dir }}/LINUX.X64_193000_db_home.zip" - "{{ patch_dir }}/LINUX.X64_193000_grid_home.zip" - "{{ patch_dir }}/p37957391_190000_Linux-x86-64.zip" - "{{ patch_dir }}/p37847857_190000_Linux-x86-64.zip" - "{{ patch_dir }}/p38509394_1928000DBRU_Linux-x86-64.zip" - "{{ patch_dir }}/p34672698_1928000DBRU_Linux-x86-64.zip" - "{{ patch_dir }}/p38170982_1928000DBRU_Generic.zip" - "{{ patch_dir }}/p38599552_1928000DBRU_Linux-x86-64.zip" - "{{ patch_dir }}/p38219529_1928000DBRU_Linux-x86-64.zip" - "{{ patch_dir }}/p6880880_122010_Linux-x86-64.zip" - name: Check u01 free disk space ansible.builtin.shell: cmd: "df -P /u01 | awk 'END { print $4 }'" register: u01size failed_when: u01size.stdout|int < u01_size_mb * 1024 - name: Apply DBRU and one-offs ansible.builtin.shell: cmd: | {{ new_db_home }}/runInstaller -silent -ignorePrereqFailure -waitforcompletion \ -applyRU {{ patch_dir }}/GIRU/37957391 \ -applyOneOffs \ {{ patch_dir }}/OJVM/37847857,{{ patch_dir }}/GIMRP/38509394/36913519,{{ patch_dir }}/GIMRP/38509394/37019735,{{ patch_dir }}/GIMRP/38509394/37082638,{{ patch_dir }}/GIMRP/38509394/37914344,{{ patch_dir }}/GIMRP/38509394/38039345,{{ patch_dir }}/GIMRP/38509394/38191300,{{ patch_dir }}/GIMRP/38509394/38213005,{{ patch_dir }}/GIMRP/38509394/38284844,{{ patch_dir }}/GIMRP/38509394/38295523,{{ patch_dir }}/GIMRP/38509394/38326922,{{ patch_dir }}/GIMRP/38509394/38327069,{{ patch_dir }}/GIMRP/38509394/38387135,{{ patch_dir }}/GIMRP/38509394/38389186,{{ patch_dir }}/GIMRP/38509394/38410961,{{ patch_dir }}/GIMRP/38509394/38447750,{{ patch_dir }}/DBRUOneOff/34672698,{{ patch_dir }}/DBRUOneOff/38170982,{{ patch_dir }}/DBRUOneOff/38599552,{{ patch_dir }}/DBRUOneOff/38219529 \ -responsefile {{ patch_dir }}/db_oop.rsp environment: CV_ASSUME_DISTID: OL7 NEWDBHOME: "{{ new_db_home }}" register: apply_dbru failed_when: "'Successfully Setup Software' not in apply_dbru.stdout" async: 10800 poll: 60 - name: Output patching success message ansible.builtin.debug: msg: "Patching succeeded. InsaneDBA was here." when: apply_dbru.rc in [6,0]
Run root.sh step – 3_apply_dbru_runrootallnodes.yml
- name: Run root.sh in the new Oracle database home hosts: allhosts vars: new_db_home: /u01/app/oracle/product/19.28/dbhome_1 old_db_home: /u01/app/oracle/product/19.26/dbhome_1 remote_user: root tasks: - name: Execute root.sh ansible.builtin.command: cmd: "{{ new_db_home }}/root.sh" throttle: 1 - name: Replace old_dbhome value in .bashrc files with the new_dbhome value ansible.builtin.replace: path: "{{ item }}" regexp: "{{ old_db_home }}" replace: "{{ new_db_home }}" loop: - /root/.bashrc - /home/oracle/.bashrc
Finish Oracle Database Release Update and start databases from new database home – 4_finish_dbru_onallnodes.yml
- name: Finish Database Release Update hosts: allhosts vars: new_db_home: /u01/app/oracle/product/19.28/dbhome_1 old_db_home: /u01/app/oracle/product/19.26/dbhome_1 patch_dir: /u01/app/Setup recovery_script_dest_dir: /home/oracle recovery_script_dir: /etc/ansible/projects remote_user: oracle tasks: - name: Remove response file from stage directory ansible.builtin.file: path: "{{ patch_dir }}/db_oop.rsp" state: absent - name: Copy old db home dependent network files to the new db home ansible.builtin.copy: src: "{{ old_db_home }}/network/admin/" dest: "{{ new_db_home }}/network/admin/" owner: oracle group: oinstall remote_src: yes - name: Copy old db home dependent dbs parameter files to the new db home ansible.builtin.copy: src: "{{ old_db_home }}/dbs/" dest: "{{ new_db_home }}/dbs/" owner: oracle group: oinstall remote_src: yes - name: Copy old db home dependent customized files to the new db home ansible.builtin.copy: src: "{{ old_db_home }}/sqlplus/admin/glogin.sql" dest: "{{ new_db_home }}/sqlplus/admin/" owner: oracle group: oinstall remote_src: yes - name: Relink unified auditing in new_dbhome ansible.builtin.command: cmd: "make -f ins_rdbms.mk uniaud_on ioracle" chdir: "{{ new_db_home }}/rdbms/lib" environment: ORACLE_HOME: "{{ new_db_home }}" - name: Register databases ansible.builtin.shell: cmd: "ps -ef | grep ora_pmon | grep -v grep | awk '{ print $NF }' | cut -c10-50 | awk '{print substr($1, 1, length($1)-1)}'" register: find_databases - name: Register instances ansible.builtin.shell: cmd: "ps -ef | grep ora_pmon | grep -v grep | awk '{ print $NF }' | cut -c10-50" register: find_instances - name: Modify databases with srvctl to run from new_dbhome ansible.builtin.command: cmd: "srvctl modify database -d {{ item }} -oraclehome {{ new_db_home }}" loop: "{{ find_databases.stdout_lines }}" run_once: true - name: Bounce instances and start from new_dbhome ansible.builtin.shell: cmd: | export dbname=$(echo {{ item }} | awk '{print substr($1, 1, length($1)-1)}') srvctl stop instance -d $dbname -i {{ item }} -f srvctl start instance -d $dbname -i {{ item }} loop: "{{ find_instances.stdout_lines }}" throttle: 1 - name: Remove patch setup folders from stage directory ansible.builtin.file: path: "{{ item }}" state: absent loop: - "{{ patch_dir }}/GIRU" - "{{ patch_dir }}/DBRUOneOff" - "{{ patch_dir }}/OJVM" - "{{ patch_dir }}/GIMRP" - name: Copy start recovery file to remote server ansible.builtin.copy: src: "{{ recovery_script_dir }}/start_recover_onremote.sh" dest: "{{ recovery_script_dest_dir }}" owner: oracle group: oinstall mode: '0750' - name: Start recovery ansible.builtin.command: cmd: "{{ recovery_script_dest_dir }}/start_recover_onremote.sh {{ item }} {{ new_db_home }}" loop: "{{ find_instances.stdout_lines }}" register: recovery_output - name: Debug Recovery output ansible.builtin.debug: var: item.stdout loop: "{{ recovery_output.results }}"
Apply datapatch – 5_apply_datapatch_onprimary.ymlOptional : Remove Old Database Home – remove_dbru_onallnodes.yml
- name: Apply Data Patch on First Nodes of Primary Databases hosts: allhosts vars: new_db_home: /u01/app/oracle/product/19.28/dbhome_1 db_role: PRIMARY remote_user: oracle run_once: true tasks: - name: Register instances ansible.builtin.shell: cmd: ps -ef | grep ora_pmon | grep -v grep | awk '{ print $NF }' | cut -c10-50 register: find_instances - name: Apply datapatch ansible.builtin.shell: cmd: | export instname={{ item }} export dbname=$(echo $instname | awk '{print substr($1, 1, length($1)-1)}') export inst_id=$(echo $instname | awk '{print substr($1,length($1),1)}') export dbrole=$(srvctl config database -d $dbname | grep "Database role" | awk '{ print $NF }') if [ "$inst_id" == 1 ] && [ "$dbrole" == "{{ db_role }}" ]; then export ORACLE_HOME={{ new_db_home }} export ORACLE_SID=$instname $ORACLE_HOME/OPatch/datapatch -verbose fi register: datapatch_candidates loop: "{{ find_instances.stdout_lines }}" throttle: 1 - name: Debug datapatch output ansible.builtin.debug: var: item.stdout loop: "{{ datapatch_candidates.results }}"
Optional : Remove Old Database Home – 6_remove_dbru_onallnodes.yml
- name: Deinstall old Oracle Database Software and remove old oracle database home hosts: allhosts vars: old_db_home: /u01/app/oracle/product/19.26/dbhome_1 patch_dir: /u01/app/Setup ansible_python_interpreter: /usr/bin/python3 removed_value: REMOVED="T"/> remote_user: root tasks: - name: Deinstall old_dbhome become: yes become_user: oracle become_method: su ansible.builtin.expect: command: /bin/bash -c {{ old_db_home }}/deinstall/deinstall responses: 'Do you want to continue': 'y' 'Specify the list of database names': '' echo: yes timeout: 600 run_once: true register: deinstall_result - name: Check inventory whether old_dbhome is tagged with removed flag ansible.builtin.shell: cmd: "grep '{{ old_db_home }}' /u01/app/oraInventory/ContentsXML/inventory.xml | awk '{ print $NF }'" register: removedflag - name: Remove parent directory of old_db_home ansible.builtin.file: path: "{{ old_db_home | dirname }}" state: absent environment: OLDDBHOME: "{{ old_db_home }}" when: deinstall_result.rc == 0 or removedflag.stdout == removed_value
Content of the start_recover_onremote.sh
export ORACLE_SID=$1export ORACLE_HOME=$2$ORACLE_HOME/bin/sqlplus -s /nolog << END_SQLconn / as sysdbaset serveroutput on feedback off;DECLAREinst_id number;mrp number;inst_name varchar2(50);BEGIN IF sys_context ('userenv','database_role') = 'PHYSICAL STANDBY' THEN select instance_number, instance_name into inst_id, inst_name from v\$instance ; select count(*) into mrp from v\$managed_standby where process='MRP0'; if inst_id = 1 and mrp = 0 THEN execute immediate 'ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT'; dbms_output.put_line('Recovery started on: ' || inst_name); ELSE dbms_output.put_line('No action taken. '); END IF; ELSE dbms_output.put_line('No action taken. '); END IF;END;/exit;END_SQL
The playbooks orchestrate the full lifecycle:
- Preparation
- Patching
- Root execution
- Datapatch
- Cleanup
Automation does not remove responsibility. Even with Ansible:
- Always test in non-production environments
- Validate patch conflicts
- Ensure backups and fallback plans
Ansible makes things easier, but not risk-free.
Contribute or Comment
You can find the latest version here:
👉 GitHub Repository (Insane-DBAs-Backpack)
Feel free to:
- Fork
- Improve
- Suggest changes
I always appreciate feedback from fellow DBAs. If you are managing Oracle environments and still doing patching manually…
You are working too hard 🙂
Hope it helps.


Leave your comment