#!/bin/bash # # Perform a Clone of an Oracle DB # v1.0 - James Pattinson - October 2025 # # usage: oracle_clone.sh [options] # # Options: # -h Source DB hostname # -t Recovery point timestamp "YYYY-MM-DD HH:MM:SS" (uses latest if not specified) # -n New database SID for the clone # -p Custom pfile for the clone # -a Advanced Cloning Options (can be used multiple times) # -d Dry run mode - show API payload without executing # # Time is passed to 'date' on THIS machine, will use local timezone MYDIR="$(dirname "$(realpath "$0")")" # source $MYDIR/rbk_api.conf source $MYDIR/oracle_funcs.sh usage() { echo "Usage: $0 [options] " 1>&2 echo "Options:" 1>&2 echo " -h Source DB hostname" 1>&2 echo " -t Recovery point \"YYYY-MM-DD HH:MM:SS\"" 1>&2 echo " -n New database SID for clone" 1>&2 echo " -p Custom pfile for the clone" 1>&2 echo " -a Advanced Cloning Options (can be used multiple times)" 1>&2 echo " -d Dry run mode" 1>&2 exit 1; } declare -a config_pairs dryrun=false while getopts "h:dt:n:p:a:" o; do case "${o}" in h) RBK_HOST=${OPTARG} ;; t) datestring=${OPTARG} ;; n) newsid=${OPTARG} ;; p) custompfile=${OPTARG} ;; a) config_pairs+=("${OPTARG}") ;; d) dryrun=true ;; *) usage ;; esac done shift $((OPTIND-1)) RBK_SID=$1 RBK_TGT=$2 if [ -z "${RBK_SID}" ] || [ -z "${RBK_TGT}" ]; then usage fi echo Connecting to Rubrik with IP $RUBRIK_IP # API call to list Oracle DBs find_database # API call to get the host ID of the target ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/host?name=$RBK_TGT" rest_api_get total=$(cat /tmp/rbkresponse.$$ | jq -r .total) if [ $total -ne 1 ]; then echo Target host name of $RBK_TGT does not map to a single host: cat /tmp/rbkresponse.$$ | jq -r '.data[].name' exit_with_error fi target_id=$(cat /tmp/rbkresponse.$$ | jq -r '.data[0].id') # convert datestamp from string into milliseconds if [ -z "$datestring" ]; then echo "No timestamp specified, determining latest recoverable" ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/db/$db_id/recoverable_range" rest_api_get datestring=$(cat /tmp/rbkresponse.$$ | jq -r '[.data[].endTime] | max') fi echo "requested timestamp is $datestring" ts=$(date -d"$datestring" +%s) if [ $? -ne 0 ]; then echo Problem with timestamp exit_with_error fi ((millis = $ts * 1000)) configmap="" # Build configmap from -a options for pair in "${config_pairs[@]}"; do key=$(echo "$pair" | cut -d',' -f1) value=$(echo "$pair" | cut -d',' -f2-) echo "ACO: $key = $value" if [ -n "$configmap" ]; then configmap="$configmap," fi configmap="$configmap\"$key\":\"$value\"" done # Build the payload PAYLOAD="{\"recoveryPoint\":{\"timestampMs\":$millis},\"targetOracleHostOrRacId\":\"$target_id\",\"shouldRestoreFilesOnly\":false" if [ -n "$newsid" ]; then PAYLOAD="$PAYLOAD,\"cloneDbName\":\"$newsid\"" fi if [ -n "$custompfile" ]; then PAYLOAD="$PAYLOAD,\"customPfilePath\":\"$custompfile\"" fi PAYLOAD="$PAYLOAD,\"advancedRecoveryConfigMap\":{$configmap}}" ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/db/$db_id/export" echo "$PAYLOAD" > /tmp/payload.$$ if [ "$dryrun" = true ]; then echo "Dry run mode - API payload that would be sent:" echo $PAYLOAD | jq exit 0 fi rest_api_post_file ENDPOINT=$(cat /tmp/rbkresponse.$$ | jq -r '.links[0].href') LOOP=0 while true; do rest_api_get status=$(cat /tmp/rbkresponse.$$ | jq -r '.status') if [ $status != "SUCCEEDED" ] && [ $status != "FAILED" ]; then echo Status is $status, checking in 10 seconds else if [ $status != "SUCCEEDED" ]; then echo CLONE FAILED WITH STATUS $status cat /tmp/rbkresponse.$$ | jq exit_with_error else echo CLONE SUCCEEDED cat /tmp/rbkresponse.$$ | jq exit 0 fi fi sleep 10 done cleanup