From 1508078efb91f1368b576a9df3aab16c6b40eda4 Mon Sep 17 00:00:00 2001 From: James Pattinson Date: Fri, 3 Oct 2025 10:09:04 -0400 Subject: [PATCH] Finished clone script --- oracle_clone.sh | 114 ++++++++++++++++++++++++++++++------------------ oracle_funcs.sh | 7 +++ 2 files changed, 79 insertions(+), 42 deletions(-) diff --git a/oracle_clone.sh b/oracle_clone.sh index 86d4704..8e8d220 100755 --- a/oracle_clone.sh +++ b/oracle_clone.sh @@ -1,40 +1,57 @@ #!/bin/bash # -# Perform a Live Mount of an Oracle DB -# v0.2 - James Pattinson - August 2021 +# Perform a Clone of an Oracle DB +# v1.0 - James Pattinson - October 2025 # -# usage: oracle_mount.sh [-h ] [-d [-f] [-t ] +# usage: oracle_clone.sh [options] # -# -d specify $ORACLE_HOME on target - needed for mount of Data Guard DBs -# -f mount files only - do not create database +# 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 # -# Timestamp YYYY-MM-DD HH:MM:SS # 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 [-h ] [-d [-f|-n] <\"timestamp\">" 1>&2 - echo "Format Timestamp YYYY-MM-DD HH:MM:SS" +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; } -filesonly=false +declare -a config_pairs +dryrun=false -while getopts "h:d:t:f" o; do +while getopts "h:dt:n:p:a:" o; do case "${o}" in h) RBK_HOST=${OPTARG} ;; - f) - filesonly=true - ;; - d) - oraclehome=${OPTARG} - ;; t) datestring=${OPTARG} ;; + n) + newsid=${OPTARG} + ;; + p) + custompfile=${OPTARG} + ;; + a) + config_pairs+=("${OPTARG}") + ;; + d) + dryrun=true + ;; *) usage ;; @@ -43,14 +60,12 @@ done shift $((OPTIND-1)) RBK_SID=$1 +RBK_TGT=$2 -if [ -z "${RBK_SID}" ]; then +if [ -z "${RBK_SID}" ] || [ -z "${RBK_TGT}" ]; then usage fi -RBK_SID=$1 -RBK_TGT=$2 - echo Connecting to Rubrik with IP $RUBRIK_IP # API call to list Oracle DBs @@ -85,17 +100,42 @@ if [ $? -ne 0 ]; then fi ((millis = $ts * 1000)) -exit +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 -if [ ! -z ${oraclehome+x} ]; then - configmap="\"ORACLE_HOME\":\"$oraclehome\"" +# Build the payload +PAYLOAD="{\"recoveryPoint\":{\"timestampMs\":$millis},\"targetOracleHostOrRacId\":\"$target_id\",\"shouldRestoreFilesOnly\":false" + +if [ -n "$newsid" ]; then + PAYLOAD="$PAYLOAD,\"cloneDbName\":\"$newsid\"" fi -# API call to perform the mount -PAYLOAD="{\"recoveryPoint\":{\"timestampMs\":$millis},\"targetOracleHostOrRacId\":\"$target_id\",\"targetMountPath\":\"/rbk/\",\"shouldMountFilesOnly\":$filesonly,\"advancedRecoveryConfigMap\":{$configmap}}" -ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/db/$db_id/mount" +if [ -n "$custompfile" ]; then + PAYLOAD="$PAYLOAD,\"customPfilePath\":\"$custompfile\"" +fi -rest_api_post +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 @@ -104,28 +144,18 @@ while true; do status=$(cat /tmp/rbkresponse.$$ | jq -r '.status') if [ $status != "SUCCEEDED" ] && [ $status != "FAILED" ]; then echo Status is $status, checking in 10 seconds - if [ $status = "RUNNING" ] && [ ${LOOP} -ne 1 ] ; then - LOOP=1 - sleep 5 - save=$ENDPOINT - ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/db/mount?source_database_name=$dbname" - rest_api_get - # cat /tmp/rbkresponse.$$ | jq -r '.data[]' - RBK_ID_LV=$(cat /tmp/rbkresponse.$$ | jq -r '.data[] | select(.status=="Mounting") | .id') - ENDPOINT=$save - fi else if [ $status != "SUCCEEDED" ]; then - echo LIVE MOUNT FAILED WITH STATUS $status + echo CLONE FAILED WITH STATUS $status + cat /tmp/rbkresponse.$$ | jq exit_with_error else - echo LIVE MOUNT SUCCEEDED - echo "The live mount id is: ${RBK_ID_LV}" - # cat /tmp/rbkresponse.$$ | jq -r ' ' + echo CLONE SUCCEEDED + cat /tmp/rbkresponse.$$ | jq exit 0 fi fi sleep 10 done -cleanup +cleanup \ No newline at end of file diff --git a/oracle_funcs.sh b/oracle_funcs.sh index 0b3468e..1b3c39c 100755 --- a/oracle_funcs.sh +++ b/oracle_funcs.sh @@ -184,6 +184,13 @@ rest_api_post () { check_http_error } +rest_api_post_file () { + check_get_token + http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X POST $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN" -H "Content-Type: application/json" --data-binary @/tmp/payload.$$) + check_http_error + rm -f /tmp/payload.$$ +} + rest_api_post_empty () { check_get_token http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X POST $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN" -H "Content-Type: application/json")