From aa07962766d8ffb9d8443c0fbee2ff3d61255abc Mon Sep 17 00:00:00 2001 From: SupraJames Date: Wed, 15 Oct 2025 17:54:20 +0100 Subject: [PATCH] Clone script improvements --- rsc_clone.sh | 87 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 29 deletions(-) diff --git a/rsc_clone.sh b/rsc_clone.sh index 7563cc2..000e7ab 100755 --- a/rsc_clone.sh +++ b/rsc_clone.sh @@ -3,9 +3,9 @@ # Example RSC API call script # v0.1 - James Pattinson - August 2025 # -# Perfoms a database clone operation +# Performs a database clone operation # -# usage: rsc_clone.sh -n -o -h [-s sourcehost] [-t "YYYY-MM-DD HH:MM:SS"] [-d] [-c numChannels] +# usage: rsc_clone.sh -n -o -h [-s sourcehost] [-t "YYYY-MM-DD HH:MM:SS"] [-d] [-c numChannels] [-p customPfilePath] # # Options: # -n : db_name / SID of the new cloned database @@ -15,6 +15,7 @@ # -t "YYYY-MM-DD HH:MM:SS" : Optional timestamp for the recovery point, defaults to latest PIT # -d : Dry-run mode - show mutation variables without executing the clone # -c : Optional - number of RMAN channels to configure for the clone +# -p : Optional - custom pfile path for the clone # : Source database name or RSC dbid (if known, can be used directly) # # Example options file content: @@ -24,13 +25,13 @@ # DB_CREATE_FILE_DEST=/u01/app/oracle/oradata/NEWNAME/ # AUDIT_FILE_DEST='/u01/app/oracle/admin/NEWNAME/adump' -usage() { log_error "Usage: $0 -n -o -h [-s sourcehost] [-t \"YYYY-MM-DD HH:MM:SS\"] [-d] [-c numChannels] "; exit 1; } +usage() { log_error "Usage: $0 -n -o -h [-s sourcehost] [-t \"YYYY-MM-DD HH:MM:SS\"] [-d] [-c numChannels] [-p customPfilePath] "; exit 1; } MYDIR="$(dirname "$(realpath "$0")")" source $MYDIR/rsc_ops.sh -while getopts "n:o:t:h:s:d:v:c:" o; do +while getopts "n:o:t:h:s:dc:p:" o; do case "${o}" in n) newName=${OPTARG} @@ -58,6 +59,14 @@ while getopts "n:o:t:h:s:d:v:c:" o; do exit_with_error fi ;; + p) + customPfilePath=${OPTARG} + # Basic validation - ensure it's an absolute path + if [[ ! "$customPfilePath" =~ ^/ ]]; then + log_error "-p requires an absolute path (starting with /)" + exit_with_error + fi + ;; *) usage ;; @@ -147,7 +156,8 @@ get_latest_pit() { rsc_gql_query # Get latest endTime - latest_endtime=$(cat /tmp/rbkresponse.$$ | jq -r '.data.oracleRecoverableRanges.data[] | .endTime' | sort -r | head -n 1) + response_content=$(cat /tmp/rbkresponse.$$) + latest_endtime=$(echo "$response_content" | jq -r '.data.oracleRecoverableRanges.data[] | .endTime' | sort -r | head -n 1) log_info "Latest PIT (ISO8601): $latest_endtime" # Convert to unixtime in milliseconds @@ -194,11 +204,12 @@ get_oracle_host_id() { rsc_gql_query # Get all matching host IDs (portable, no mapfile) - host_ids=$(cat /tmp/rbkresponse.$$ | jq -r '.data.oracleTopLevelDescendants.nodes[] | .id') + response_content=$(cat /tmp/rbkresponse.$$) + host_ids=$(echo "$response_content" | jq -r '.data.oracleTopLevelDescendants.nodes[] | .id') host_count=$(echo "$host_ids" | grep -c .) if [[ $host_count -ne 1 ]]; then - log_error "Multiple hosts found for '$1':" - cat /tmp/rbkresponse.$$ | jq -r '.data.oracleTopLevelDescendants.nodes[] | "\(.name) \(.id)"' + log_error "Multiple hosts found for '$1':" + echo "$response_content" | jq -r '.data.oracleTopLevelDescendants.nodes[] | "\(.name) \(.id)"' exit_with_error fi # Set the first match (or empty if none) @@ -224,7 +235,8 @@ if [[ "$1" == *-* ]]; then gqlVars="$(echo $variables)" rsc_gql_query - cdmId=$(cat /tmp/rbkresponse.$$ | jq -r '.data.oracleDatabase.cluster.id') + response_content=$(cat /tmp/rbkresponse.$$) + cdmId=$(echo "$response_content" | jq -r '.data.oracleDatabase.cluster.id') if [[ -z "$cdmId" ]]; then log_error "Could not find CDM ID for dbid '$dbid'" exit 1 @@ -270,27 +282,29 @@ oracleDatabases(filter: $filter) { gqlVars="$(echo $variables)" rsc_gql_query + response_content=$(cat /tmp/rbkresponse.$$) + if [[ -n "$node_name" ]]; then # Filter by source host if specified - dbid=$(cat /tmp/rbkresponse.$$ | jq -r --arg NODE "$node_name" '.data.oracleDatabases.nodes[] | select(.logicalPath[]?.name | test("^" + $NODE + "(\\.|$)")) | .id') - cdmId=$(cat /tmp/rbkresponse.$$ | jq -r --arg NODE "$node_name" '.data.oracleDatabases.nodes[] | select(.logicalPath[]?.name | test("^" + $NODE + "(\\.|$)")) | .cluster.id') + dbid=$(echo "$response_content" | jq -r --arg NODE "$node_name" '.data.oracleDatabases.nodes[] | select(.logicalPath[]?.name | test("^" + $NODE + "(\\.|$)")) | .id') + cdmId=$(echo "$response_content" | jq -r --arg NODE "$node_name" '.data.oracleDatabases.nodes[] | select(.logicalPath[]?.name | test("^" + $NODE + "(\\.|$)")) | .cluster.id') dbid_count=$(echo "$dbid" | grep -c .) if [[ "$dbid_count" -ne 1 || -z "$dbid" ]]; then log_error "Expected exactly one database running on node '$node_name', found $dbid_count:" - cat /tmp/rbkresponse.$$ | jq -r '.data.oracleDatabases.nodes[] | "\(.dbUniqueName) \(.logicalPath[0].name) \(.id)"' + echo "$response_content" | jq -r '.data.oracleDatabases.nodes[] | "\(.dbUniqueName) \(.logicalPath[0].name) \(.id)"' cleanup exit 4 fi else # No source host specified, get all matching databases - dbid=$(cat /tmp/rbkresponse.$$ | jq -r '.data.oracleDatabases.nodes[].id') - cdmId=$(cat /tmp/rbkresponse.$$ | jq -r '.data.oracleDatabases.nodes[].cluster.id') + dbid=$(echo "$response_content" | jq -r '.data.oracleDatabases.nodes[].id') + cdmId=$(echo "$response_content" | jq -r '.data.oracleDatabases.nodes[].cluster.id') dbid_count=$(echo "$dbid" | grep -c .) if [[ "$dbid_count" -ne 1 || -z "$dbid" ]]; then log_error "Expected exactly one database with name '$1', found $dbid_count:" - cat /tmp/rbkresponse.$$ | jq -r '.data.oracleDatabases.nodes[] | "\(.dbUniqueName) \(.logicalPath[0].name) \(.id)"' + echo "$response_content" | jq -r '.data.oracleDatabases.nodes[] | "\(.dbUniqueName) \(.logicalPath[0].name) \(.id)"' cleanup exit 4 fi @@ -301,14 +315,20 @@ fi # Only run UTC conversion if -t was used if [[ -n "${datestring:-}" ]]; then - utctime=$($DATE -d"$datestring" +"%Y-%m-%d %H:%M:%S") - if [ $? -ne 0 ]; then - echo ERROR: Unable to convert supplied timestamp to UTC time - exit_with_error + # Try date first, then gdate for macOS compatibility + if ! utctime=$(date -d"$datestring" +"%Y-%m-%d %H:%M:%S" 2>/dev/null); then + if ! utctime=$(gdate -d"$datestring" +"%Y-%m-%d %H:%M:%S" 2>/dev/null); then + log_error "Unable to convert supplied timestamp to UTC time: $datestring" + exit_with_error + fi + fi + + # Get unixtime using the same date command that worked + if ! unixtime=$(date -d"$datestring" +%s 2>/dev/null); then + unixtime=$(gdate -d"$datestring" +%s 2>/dev/null) fi - unixtime=$($DATE -d"$datestring" +%s) unixtime_ms=$((unixtime * 1000)) - echo INFO: Requested time is $datestring which is $utctime in UTC, unixtime is $unixtime + log_info "Requested time is $datestring which is $utctime in UTC, unixtime is $unixtime" else log_info "No time specified, using latest PIT" get_latest_pit @@ -334,6 +354,13 @@ else numChannelsPart="" fi +# Optionally include customPfilePath in the config +if [[ -n "${customPfilePath:-}" ]]; then + customPfilePathPart="\"customPfilePath\": \"$customPfilePath\"," +else + customPfilePathPart="" +fi + variables=" { \"input\": { @@ -341,6 +368,7 @@ variables=" \"id\": \"$dbid\", \"config\": { $numChannelsPart + $customPfilePathPart \"targetOracleHostOrRacId\": \"$targetHostId\", \"shouldRestoreFilesOnly\": false, \"recoveryPoint\": { @@ -386,11 +414,11 @@ if [ "$dryrun" = true ]; then fi rsc_gql_query -cat /tmp/rbkresponse.$$ | jq - +response_content=$(cat /tmp/rbkresponse.$$) +echo "$response_content" | jq # Save the id from the response -job_id=$(cat /tmp/rbkresponse.$$ | jq -r '.data.exportOracleDatabase.id') +job_id=$(echo "$response_content" | jq -r '.data.exportOracleDatabase.id') log_info "Job id is $job_id" gqlCheckStatus='query OracleDatabaseAsyncRequestDetails($input: GetOracleAsyncRequestStatusInput!) { @@ -419,26 +447,27 @@ gqlVars="$(echo $variables)" while true; do rsc_gql_query - status=$(cat /tmp/rbkresponse.$$ | jq -r '.data.oracleDatabaseAsyncRequestDetails.status') - progress=$(cat /tmp/rbkresponse.$$ | jq -r '.data.oracleDatabaseAsyncRequestDetails.progress') + response_content=$(cat /tmp/rbkresponse.$$) + status=$(echo "$response_content" | jq -r '.data.oracleDatabaseAsyncRequestDetails.status') + progress=$(echo "$response_content" | jq -r '.data.oracleDatabaseAsyncRequestDetails.progress') log_info "Job status: $status $progress percent" if [[ "$status" == "FAILED" ]]; then log_error "Database clone FAILED" - cat /tmp/rbkresponse.$$ | jq + echo "$response_content" | jq cleanup exit 2 elif [[ "$status" == "CANCELLED" ]]; then log_warn "Database clone CANCELLED" + cleanup exit 3 elif [[ "$status" == "SUCCEEDED" ]]; then log_info "Database clone SUCCEEDED" - cat /tmp/rbkresponse.$$ | jq + echo "$response_content" | jq cleanup exit 0 fi sleep 15 - done