diff --git a/oracle_clone.sh b/oracle_clone.sh new file mode 100755 index 0000000..87f902b --- /dev/null +++ b/oracle_clone.sh @@ -0,0 +1,447 @@ +#!/bin/bash +# +# Perform a Clone of an Oracle DB +# v1.3 - 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) +# -b Comma-separated list of PDBs to clone (include only these PDBs. PDB$SEED is always included) +# -c Number of RMAN channels to use +# -d Dry run mode - show API payload without executing +# --refresh Refresh target database before cloning (if it exists) +# +# 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 + +# Set up cleanup trap to ensure temporary files are removed +trap 'cleanup' EXIT INT TERM + +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 " -b Comma-separated list of PDBs to clone (include only these PDBs)" 1>&2 + echo " -c Number of RMAN channels to use" 1>&2 + echo " -d Dry run mode" 1>&2 + echo " --refresh Refresh target database before cloning (if it exists)" 1>&2 + exit 1; } + +declare -a config_pairs +declare -a pdb_list +dryrun=false +refresh_host=false + +num_channels="" + +# Handle long options first and rebuild argument array +args=() +for arg in "$@"; do + case $arg in + --refresh) + refresh_host=true + ;; + *) + args+=("$arg") + ;; + esac +done +set -- "${args[@]}" + +while getopts "h:dt:n:p:a:b:c:" o; do + case "${o}" in + h) + RBK_HOST=${OPTARG} + ;; + t) + datestring=${OPTARG} + ;; + n) + newsid=${OPTARG} + ;; + p) + custompfile=${OPTARG} + ;; + a) + config_pairs+=("${OPTARG}") + ;; + b) + IFS=',' read -ra pdb_list <<< "${OPTARG}" + ;; + c) + num_channels=${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 +echo "Finding source database: $RBK_SID" +if ! find_database; then + echo "ERROR: Failed to find source database $RBK_SID" + exit 1 +fi + +if [ -z "$db_id" ]; then + echo "ERROR: Could not determine database ID for $RBK_SID" + exit 1 +fi +echo "Source database ID: $db_id" + +# API call to get the host ID of the target +echo "Finding target host: $RBK_TGT" +ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/host?name=$RBK_TGT" +if ! rest_api_get; then + echo "ERROR: Failed to query target host information" + exit 1 +fi + +# Check if API response exists and is valid +if [ ! -s /tmp/rbkresponse.$$ ]; then + echo "ERROR: Empty response when querying target host" + exit 1 +fi + +total=$(cat /tmp/rbkresponse.$$ | jq -r .total 2>/dev/null) +if [ $? -ne 0 ] || [ -z "$total" ]; then + echo "ERROR: Invalid response format when querying target host" + exit 1 +fi + +if [ "$total" -ne 1 ]; then + echo "ERROR: Target host name '$RBK_TGT' does not map to a single host (found: $total)" + echo "Available hosts:" + cat /tmp/rbkresponse.$$ | jq -r '.data[].name' 2>/dev/null || echo "Could not parse host list" + exit 1 +fi + +target_id=$(cat /tmp/rbkresponse.$$ | jq -r '.data[0].id' 2>/dev/null) +if [ -z "$target_id" ] || [ "$target_id" = "null" ]; then + echo "ERROR: Could not determine target host ID" + exit 1 +fi +echo "Target host ID: $target_id" + +# Refresh target database if requested and it exists +if [ "$refresh_host" = true ]; then + echo "=== DATABASE REFRESH REQUESTED ===" + + # Determine the target database name (use newsid if specified, otherwise use source SID) + if [ -n "$newsid" ]; then + target_db_name="$newsid" + echo "Target database name: $target_db_name (specified with -n option)" + else + target_db_name="$RBK_SID" + echo "Target database name: $target_db_name (using source SID)" + fi + + echo "Searching for existing database '$target_db_name' on host '$RBK_TGT'..." + + # Search for existing database on target host + ENDPOINT="https://$RUBRIK_IP/api/v1/oracle/db?name=$target_db_name" + if ! rest_api_get; then + echo "WARNING: Failed to query existing databases via API" + echo "Continuing without refresh" + else + if [ ! -s /tmp/rbkresponse.$$ ]; then + echo "WARNING: Empty API response when querying existing databases" + echo "Continuing without refresh" + else + # Show all databases found with this name (for informational purposes) + db_count=$(cat /tmp/rbkresponse.$$ | jq -r --arg TARGET_NAME "$target_db_name" '[.data[] | select(.name==$TARGET_NAME)] | length' 2>/dev/null) + echo "Found $db_count database(s) with name '$target_db_name' in Rubrik inventory" + + if [ "$db_count" -gt 0 ]; then + echo "Database details found:" + cat /tmp/rbkresponse.$$ | jq -r --arg TARGET_NAME "$target_db_name" '.data[] | select(.name==$TARGET_NAME) | " - Host: \(.instances[0].hostName), Relic: \(.isRelic), ID: \(.id)"' 2>/dev/null + fi + + # Look for database with matching name on target host (not in relic state) + # Handle both short hostname and FQDN matching + echo "Filtering for databases on target host '$RBK_TGT' (non-relic)..." + existing_db_id=$(cat /tmp/rbkresponse.$$ | jq -r --arg HOST "$RBK_TGT" --arg TARGET_NAME "$target_db_name" ' + .data[] | + select(.name==$TARGET_NAME and (.instances[0].hostName==$HOST or (.instances[0].hostName | startswith($HOST + "."))) and .isRelic==false) | + .id' 2>/dev/null) + + if [ -z "$existing_db_id" ] || [ "$existing_db_id" = "null" ]; then + echo "RESULT: No matching non-relic database found on target host" + echo " - This is normal when cloning to a new database name or host" + echo " - Refresh skipped, proceeding with clone operation" + else + echo "RESULT: Match found! Database ID: $existing_db_id" + echo "Initiating refresh of database '$target_db_name' on host '$RBK_TGT'..." + + # Perform database refresh + ENDPOINT="https://$RUBRIK_IP/api/v1/oracle/db/$existing_db_id/refresh" + if ! rest_api_post_empty; then + echo "ERROR: Failed to refresh database via API call" + echo "Continuing with clone operation despite refresh failure" + else + echo "SUCCESS: Database refresh initiated for '$target_db_name'" + echo "Monitoring database status - waiting for it to become relic..." + echo "Press Ctrl+C to abort and exit script" + + # Set up signal handler for monitoring loop + monitoring_interrupted=false + trap 'monitoring_interrupted=true; echo ""; echo "Monitoring interrupted by user - exiting script"; cleanup; exit 130' INT + + # Monitor the database until it becomes relic or timeout + timeout_seconds=300 # 2 minutes + check_interval=10 # Check every 10 seconds + elapsed_time=0 + + while [ $elapsed_time -lt $timeout_seconds ] && [ "$monitoring_interrupted" = false ]; do + # Check current database status + remaining=$((timeout_seconds - elapsed_time)) + + ENDPOINT="https://$RUBRIK_IP/api/v1/oracle/db/$existing_db_id" + if rest_api_get 2>/dev/null; then + # Extract isRelic status + is_relic=$(cat /tmp/rbkresponse.$$ | jq -r '.isRelic' 2>/dev/null) + + if [ "$is_relic" = "true" ]; then + echo "SUCCESS: Database '$target_db_name' is now in relic mode" + echo "Safe to proceed with clone operation" + break + else + echo "Status check: Database still active, waiting... (${remaining}s remaining)" + fi + else + echo "WARNING: Failed to check database status, continuing anyway..." + break + fi + + # Use a loop with 1-second sleeps to make Ctrl+C more responsive + sleep_count=0 + while [ $sleep_count -lt $check_interval ] && [ "$monitoring_interrupted" = false ]; do + sleep 1 + sleep_count=$((sleep_count + 1)) + done + + elapsed_time=$((elapsed_time + check_interval)) + done + + # Restore original signal handler + trap 'cleanup' EXIT INT TERM + + # Check if we were interrupted + if [ "$monitoring_interrupted" = true ]; then + exit 130 + fi + + # Check if we timed out + if [ $elapsed_time -ge $timeout_seconds ]; then + echo "ERROR: Timeout after 2 minutes waiting for database to become relic" + echo "The target database '$target_db_name' is still running on host '$RBK_TGT'" + echo "Please shut down Oracle database '$target_db_name' before attempting clone" + echo "Clone operation aborted to prevent conflicts" + echo "=== SCRIPT ABORTED DUE TO TIMEOUT ===" + exit 1 + fi + fi + fi + fi + fi + echo "=== REFRESH PROCESSING COMPLETE ===" + echo "" +fi + +# Convert datestamp from string into milliseconds +if [ -z "$datestring" ]; then + echo "No timestamp specified, determining latest recoverable point" + ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/db/$db_id/recoverable_range" + if ! rest_api_get; then + echo "ERROR: Failed to get recoverable range for database" + exit 1 + fi + + if [ ! -s /tmp/rbkresponse.$$ ]; then + echo "ERROR: Empty response when getting recoverable range" + exit 1 + fi + + datestring=$(cat /tmp/rbkresponse.$$ | jq -r '[.data[].endTime] | max' 2>/dev/null) + if [ $? -ne 0 ] || [ -z "$datestring" ] || [ "$datestring" = "null" ]; then + echo "ERROR: Could not determine latest recoverable time" + exit 1 + fi +fi +echo "Requested timestamp: $datestring" + +# Validate and convert timestamp +if ! ts=$(date -d "$datestring" +%s 2>/dev/null); then + echo "ERROR: Invalid timestamp format: $datestring" + echo "Expected format: YYYY-MM-DD HH:MM:SS" + exit 1 +fi +((millis = ts * 1000)) +echo "Recovery point (milliseconds): $millis" + +configmap="" +# Build configmap from -a options +echo "Processing advanced configuration options:" +for pair in "${config_pairs[@]}"; do + if [[ "$pair" != *","* ]]; then + echo "WARNING: Skipping malformed config pair (missing comma): $pair" + continue + fi + + key=$(echo "$pair" | cut -d',' -f1) + value=$(echo "$pair" | cut -d',' -f2-) + + # Validate key is not empty + if [ -z "$key" ]; then + echo "WARNING: Skipping config pair with empty key: $pair" + continue + fi + + echo " $key = $value" + if [ -n "$configmap" ]; then + configmap="$configmap," + fi + # Escape quotes in the value for JSON + escaped_value=$(echo "$value" | sed 's/"/\\"/g') + configmap="$configmap\"$key\":\"$escaped_value\"" +done + +# Build the payload +echo "Building API payload..." +PAYLOAD="{\"recoveryPoint\":{\"timestampMs\":$millis},\"targetOracleHostOrRacId\":\"$target_id\",\"shouldRestoreFilesOnly\":false" + +if [ -n "$newsid" ]; then + echo " Clone DB name: $newsid" + PAYLOAD="$PAYLOAD,\"cloneDbName\":\"$newsid\"" +fi + +if [ -n "$custompfile" ]; then + echo " Custom pfile path: $custompfile" + PAYLOAD="$PAYLOAD,\"customPfilePath\":\"$custompfile\"" +fi + +if [ -n "$num_channels" ]; then + echo " Number of channels: $num_channels" + # Validate num_channels is numeric + if ! [[ "$num_channels" =~ ^[0-9]+$ ]]; then + echo "ERROR: Number of channels must be numeric: $num_channels" + exit 1 + fi + PAYLOAD="$PAYLOAD,\"numChannels\":$num_channels" +fi + +# Add pdbsToClone array if -b specified +if [ ${#pdb_list[@]} -gt 0 ]; then + pdbs_json="\"PDB\$SEED\"" + for pdb in "${pdb_list[@]}"; do + # Validate PDB name is not empty + if [ -n "$pdb" ]; then + pdbs_json="$pdbs_json,\"$pdb\"" + fi + done + echo " Including PDBs in clone: PDB\$SEED ${pdb_list[*]}" + PAYLOAD="$PAYLOAD,\"pdbsToClone\":[$pdbs_json]" +fi + +# Add advanced configuration if any +if [ -n "$configmap" ]; then + PAYLOAD="$PAYLOAD,\"advancedRecoveryConfigMap\":{$configmap}" +else + PAYLOAD="$PAYLOAD,\"advancedRecoveryConfigMap\":{}" +fi +PAYLOAD="$PAYLOAD}" + +ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/db/$db_id/export" + +# Validate JSON payload before sending +if ! echo "$PAYLOAD" | jq empty 2>/dev/null; then + echo "ERROR: Invalid JSON payload generated" + echo "Payload: $PAYLOAD" + exit 1 +fi + +echo "$PAYLOAD" > /tmp/payload.$$ + +if [ "$dryrun" = true ]; then + echo "Dry run mode - API payload that would be sent:" + echo "$PAYLOAD" | jq . + exit 0 +fi + +echo "Initiating clone operation..." +if ! rest_api_post_file; then + echo "ERROR: Failed to submit clone request" + exit 1 +fi + +# Check if we got a valid response with a status link +if [ ! -s /tmp/rbkresponse.$$ ]; then + echo "ERROR: Empty response from clone request" + exit 1 +fi + +ENDPOINT=$(cat /tmp/rbkresponse.$$ | jq -r '.links[0].href' 2>/dev/null) +if [ -z "$ENDPOINT" ] || [ "$ENDPOINT" = "null" ]; then + echo "ERROR: No status link returned from clone request" + cat /tmp/rbkresponse.$$ | jq . 2>/dev/null || cat /tmp/rbkresponse.$$ + exit 1 +fi + +echo "Monitoring clone progress..." +while true; do + if ! rest_api_get; then + echo "WARNING: Failed to check status, retrying..." + sleep 10 + continue + fi + + status=$(cat /tmp/rbkresponse.$$ | jq -r '.status' 2>/dev/null) + if [ -z "$status" ] || [ "$status" = "null" ]; then + echo "WARNING: Could not determine status, retrying..." + sleep 10 + continue + fi + + case "$status" in + "SUCCEEDED") + echo "CLONE OPERATION SUCCEEDED" + cat /tmp/rbkresponse.$$ | jq . 2>/dev/null || cat /tmp/rbkresponse.$$ + exit 0 + ;; + "FAILED"|"CANCELED"|"UNDOING") + echo "CLONE OPERATION FAILED WITH STATUS: $status" + cat /tmp/rbkresponse.$$ | jq . 2>/dev/null || cat /tmp/rbkresponse.$$ + exit 1 + ;; + *) + echo "Status: $status, checking again in 10 seconds..." + ;; + esac + + sleep 10 +done \ No newline at end of file diff --git a/oracle_funcs.sh b/oracle_funcs.sh new file mode 100755 index 0000000..1b3c39c --- /dev/null +++ b/oracle_funcs.sh @@ -0,0 +1,254 @@ +#!/bin/bash +# +#-------------------------------------------------------------------------------------------------------- +# Oracle shell script support functions +# v0.2 - James Pattinson - August 2021 +# v0.3 - U701053 - 30.03.2022 - if database not found, try to search with the ENDPOINT datagurad +# v0.4 - James Pattinson 25/01/23 - Adding support for Service Accounts +#-------------------------------------------------------------------------------------------------------- + +MYDIR="$(dirname "$(realpath "$0")")" +source $MYDIR/rbk_api.conf + +# -- Mobi config --------- +#RBK_ENV=$OUTI/shell/rubrik/conf/rbk_env +# -- End Mobi config ---- + +# -- Mobi config ----------- +#if [ -f "${RBK_ENV}" ] ; then +# source ${RBK_ENV} +#else +# echo "The ${RBK_ENV} file is not found ...." +# exit_with_error +#fi +# -- End Mobi config ------- + +nowait=0 + +if [[ "$OSTYPE" == "darwin"* ]]; then + DATE=gdate +else + DATE=date +fi + +exit_with_error () { + rm -f /tmp/rbkresponse.$$ + echo Aborting Script! + exit 1 +} + +# for canceled status +exit_with_cancel () { + rm -f /tmp/rbkresponse.$$ + echo Canceling Script! + exit 1 +} + +# for undoing status +exit_with_undoing () { + rm -f /tmp/rbkresponse.$$ + echo undoing Script! + exit 1 +} + +check_http_error () { + # All good responses start with a 2 + if [ ${http_response:0:1} != "2" ]; then + echo FATAL: HTTP error from API call: $http_response. The server responded with: + cat /tmp/rbkresponse.$$ ; echo ; exit_with_error + fi +} + +# Given RBK_SID return $db_id of matching database +find_database () { + + # First get IDs of all the mounted DBs for this SID + ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/db/mount" + rest_api_get + + cat /tmp/rbkresponse.$$ | jq -r --arg SID "$RBK_SID" '.data[] | select(.mountedDatabaseId!=null and .mountedDatabaseName==$SID) | .mountedDatabaseId' > /tmp/mountedDBs.$$ + + # Now get a list of Oracle DBs + ENDPOINT="https://$RUBRIK_IP/api/v1/oracle/db?name=$RBK_SID" + rest_api_get + + # If no dtaabse is found, try with dataguard + if [ `cat /tmp/rbkresponse.$$ | grep "id\":" | wc -l` -eq 0 ]; then + echo " DB not found, try with dataguard..." + ENDPOINT="https://$RUBRIK_IP/api/v1/oracle/db?is_data_guard_group=true&is_relic=false&name=$RBK_SID" + rest_api_get + fi + +#echo "=================================" +#cat /tmp/rbkresponse.$$ | jq -r '.data[]' | tee /tmp/titi +#echo "=================================" +#echo "cat /tmp/rbkresponse.$$ | jq -r '.data[]'" + + + # If no host is specified then just look for the DB with the right SID + if [ -z $RBK_HOST ]; then + + # get list of DB IDs in scope (sid matches and not a relic) + myDBs=$(cat /tmp/rbkresponse.$$ | jq -r --arg SID "$RBK_SID" '.data[] | select(.name==$SID and .isRelic==false) | .id' ; cat /tmp/rbkresponse.$$ | jq -r --arg SID "$RBK_SID" '.data[] | select(.name==$SID and .isRelic==false) | .dataGuardGroupId' | sort | uniq) + + for db in $myDBs; do + id=$(echo $db | cut -d: -f 4) + if grep -q $id /tmp/mountedDBs.$$; then + continue + else + break + fi + done + + # Get the details for the specific DB ID + read name db_id sla_id dg_type dg_id RBK_HOST num_instances grp_name < <(echo $(cat /tmp/rbkresponse.$$ | jq -r --arg ID "OracleDatabase:::$id" '.data[] | select(.id==$ID) | .name, .id, .effectiveSlaDomainId, .dataGuardType, .dataGuardGroupId, .instances[0].hostName, .numInstances, .dataGuardGroupName')) + + # Host was specified + else + read name db_id sla_id dg_type dg_id RBK_HOST num_instances grp_name < <(echo $(cat /tmp/rbkresponse.$$ | jq -r --arg SID "$RBK_SID" --arg HOST "$RBK_HOST" '.data[] | select(.sid==$SID and .infraPath[0].name==$HOST and .isRelic==false) | .name, .id, .effectiveSlaDomainId, .dataGuardType, .dataGuardGroupId, .instances[0].hostName, .numInstances, .dataGuardGroupName')) + fi + + if [ "$dg_type" == "DataGuardMember" ]; then + db_id=$dg_id + # Now find SLA of the DG GROUP not just the DB (which will be unprotected) + ENDPOINT="https://$RUBRIK_IP/api/v1/oracle/db/$db_id" + rest_api_get + sla_id=$(cat /tmp/rbkresponse.$$ | jq -r '.effectiveSlaDomainId') + fi + + if [ -z "$db_id" ]; then + echo FATAL: No DB found with SID $RBK_SID on host $RBK_HOST + if [ "${SCRIPT}" = "check_recoverable_range.sh" ] ; then + export ret=11 + else + exit_with_error + fi + else + echo " db_id is: $db_id" + fi +} + +check_get_token () { + + if [ -z "${AUTH_TOKEN}" ]; then + + if [[ "${ID}" =~ ^client ]]; then + # Looks like an RSC service account + id_string=$(echo $ID | cut -d\| -f 2) + else + # Not an RSC service account + id_string=$(echo $ID | cut -d: -f 4) + fi + + # If there is a cached credential file, use it + if [ -f ~/.rbksession.$id_string ]; then + read expiration token < <(echo $(cat ~/.rbksession.$id_string)) + # If token expires within 30 min, get a new one + if [ $($DATE +%s -d $expiration) -lt $(( $($DATE +%s) + 1800 )) ]; then + get_token + else + AUTH_TOKEN=$token + fi + else + get_token + fi + fi +} + +get_token () { + + MYENDPOINT="https://$RUBRIK_IP/api/v1/service_account/session" + MYPAYLOAD="{\"serviceAccountId\":\"$ID\",\"secret\":\"$SECRET\"}" + + http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X POST $MYENDPOINT -H "accept: application/json" -H "Content-Type: application/json" -d $MYPAYLOAD) + check_http_error + + AUTH_TOKEN=$(cat /tmp/rbkresponse.$$ | jq -r '.token') + SESSION=$(cat /tmp/rbkresponse.$$ | jq -r '.sessionId') + EXPIRATION=$(cat /tmp/rbkresponse.$$ | jq -r '.expirationTime') + echo "$EXPIRATION $AUTH_TOKEN" > ~/.rbksession.$id_string + +} + +# HTTP GET: Given $ENDPOINT write output to file +rest_api_get () { + check_get_token + http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X GET $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN") + check_http_error +} + +# HTTP POST: Given $ENDPOINT and $PAYLOAD write output to file +rest_api_post () { + 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" -d $PAYLOAD) + 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") + check_http_error +} + +rest_api_patch () { + check_get_token + http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X PATCH $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN" -H "Content-Type: application/json" -d $PAYLOAD) + check_http_error +} + +rest_api_delete () { + check_get_token + http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X DELETE $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN") + check_http_error +} + +get_cluster_uuid () { + ENDPOINT="https://$RUBRIK_IP/api/v1/cluster/me" + rest_api_get + cluster_uuid=$(cat /tmp/rbkresponse.$$ | jq -r .id) +} + +# Given an ENDPOINT of an async job, monitor it +check_status () { + if [ $nowait -ne 1 ]; then + # Check the status in a loop + while true; do + rest_api_get + status=$(cat /tmp/rbkresponse.$$ | jq -r '.status') + if [ $status != "SUCCEEDED" ] && [ $status != "FAILED" ] && [ $status != "CANCELED" ] && [ $status != "UNDOING" ]; then + echo Status is $status, checking in 30 seconds + else + if [ $status != "SUCCEEDED" ]; then + + if [ $status = "CANCELED" ] ; then + echo OPERATION FAILED WITH STATUS $status + exit_with_cancel + elif [ $status = "UNDOING" ] ; then + echo OPERATION FAILED WITH STATUS $status + exit_with_undoing + else + echo OPERATION FAILED WITH STATUS $status + exit_with_error + fi + else + echo OPERATION SUCCEEDED + exit 0 + fi + fi + sleep 30 + done + fi +} + +cleanup () { + rm -f /tmp/mountedDBs.$$ + rm -f /tmp/rbkresponse.$$ + rm -f /tmp/payload.$$ +} diff --git a/oracle_list_db.sh b/oracle_list_db.sh new file mode 100755 index 0000000..a881f4f --- /dev/null +++ b/oracle_list_db.sh @@ -0,0 +1,112 @@ +#!/bin/bash +# +# Example API call script for Die Mobiliar +# v0.2 - James Pattinson - August 2021 +# +# Lists the registered DBs for a given Oracle Host or RAC +# and their assigned SLAs +# +# usage: oracle_list_db.sh + +MYDIR="$(dirname "$(realpath "$0")")" +# source $MYDIR/rbk_api.conf +source $MYDIR/oracle_funcs.sh + +# Set up cleanup trap to ensure temporary files are removed +trap 'rm -f /tmp/rbkdata.$$; cleanup' EXIT INT TERM + +if [ $# -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +RBK_HOST=$1 + +echo "Connecting to Rubrik with IP $RUBRIK_IP" + +# API call to list Oracle DBs +ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/db" + +# Check if rest_api_get function exists and call it +if ! rest_api_get; then + echo "ERROR: Failed to retrieve data from Rubrik API" + exit 1 +fi + +# Check if the API response file exists and is not empty +if [ ! -s /tmp/rbkresponse.$$ ]; then + echo "ERROR: Empty or missing API response" + cleanup + exit 1 +fi + +# Extract the data and store in a temporary file for processing +# Simplified jq command - single pass instead of piping through jq twice +if ! jq -r --arg HOST "$RBK_HOST" ' + .data[] | + select(.infraPath[0].name==$HOST and .isRelic==false) | + "\(.sid)|\(.effectiveSlaDomainName)|\(.isArchiveLogModeEnabled)|\(.dataGuardType)|\(.dataGuardGroupName)" +' /tmp/rbkresponse.$$ > /tmp/rbkdata.$$; then + echo "ERROR: Failed to process API response with jq" + cleanup + exit 1 +fi + +# Calculate column widths and display results +if [ -s /tmp/rbkdata.$$ ]; then + # Initialize minimum column widths based on headers + col1_width=3 # SID + col2_width=3 # SLA + col3_width=14 # ArchivelogMode + col4_width=7 # DG Type + col5_width=8 # DG Group + + # Calculate actual maximum widths needed + while IFS='|' read -r sid sla archlog dgtype dggroup || [ -n "$sid" ]; do + # Handle null values and empty strings + [ "$sid" = "null" ] && sid="" + [ "$sla" = "null" ] && sla="" + [ "$archlog" = "null" ] && archlog="" + [ "$dgtype" = "null" ] && dgtype="" + [ "$dggroup" = "null" ] && dggroup="" + + # Update column widths if current data is longer + [ ${#sid} -gt $col1_width ] && col1_width=${#sid} + [ ${#sla} -gt $col2_width ] && col2_width=${#sla} + [ ${#archlog} -gt $col3_width ] && col3_width=${#archlog} + [ ${#dgtype} -gt $col4_width ] && col4_width=${#dgtype} + [ ${#dggroup} -gt $col5_width ] && col5_width=${#dggroup} + done < /tmp/rbkdata.$$ + + # Print headers with proper spacing + printf "%-${col1_width}s %-${col2_width}s %-${col3_width}s %-${col4_width}s %-${col5_width}s\n" \ + "SID" "SLA" "ArchivelogMode" "DG Type" "DG Group" + + # Print separator line + printf "%-${col1_width}s %-${col2_width}s %-${col3_width}s %-${col4_width}s %-${col5_width}s\n" \ + "$(printf '%*s' $col1_width | tr ' ' '-')" \ + "$(printf '%*s' $col2_width | tr ' ' '-')" \ + "$(printf '%*s' $col3_width | tr ' ' '-')" \ + "$(printf '%*s' $col4_width | tr ' ' '-')" \ + "$(printf '%*s' $col5_width | tr ' ' '-')" + + # Print data rows with proper spacing + while IFS='|' read -r sid sla archlog dgtype dggroup || [ -n "$sid" ]; do + # Handle null values and empty strings + [ "$sid" = "null" ] && sid="" + [ "$sla" = "null" ] && sla="" + [ "$archlog" = "null" ] && archlog="" + [ "$dgtype" = "null" ] && dgtype="" + [ "$dggroup" = "null" ] && dggroup="" + + printf "%-${col1_width}s %-${col2_width}s %-${col3_width}s %-${col4_width}s %-${col5_width}s\n" \ + "$sid" "$sla" "$archlog" "$dgtype" "$dggroup" + done < /tmp/rbkdata.$$ +else + echo "No Oracle databases found for host $RBK_HOST" +fi + +# Clean up temporary files +rm -f /tmp/rbkdata.$$ + +cleanup diff --git a/rbk_api.conf b/rbk_api.conf new file mode 100644 index 0000000..ac16859 --- /dev/null +++ b/rbk_api.conf @@ -0,0 +1,5 @@ +# IP Address (or DNS name) of Rubrik CDM +RUBRIK_IP=jp-edge-proxmox.pattinson.org + +ID="client|673af632-150d-47e0-908e-66a6d71fe621" +SECRET=lIHYPGMjmDLf3jflRRHtl1Oqf0YlfY7z0YNdwLG0VetfKuiVkIl_SsD4QAjAhEOb \ No newline at end of file diff --git a/refresh_host.sh b/refresh_host.sh new file mode 100755 index 0000000..30d0320 --- /dev/null +++ b/refresh_host.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# +# Example API call script for Die Mobiliar +# v0.2 - James Pattinson - August 2021 +# +# usage: refresh_host.sh + +MYDIR="$(dirname "$(realpath "$0")")" +# source $MYDIR/rbk_api.conf +source $MYDIR/oracle_funcs.sh + +if [ $# -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +RBK_HOST=$1 + +echo Connecting to Rubrik with IP $RUBRIK_IP + +# API call to get host details +ENDPOINT="https://$RUBRIK_IP/api/v1/host?name=$RBK_HOST" +rest_api_get + +total=$(cat /tmp/rbkresponse.$$ | jq -r .total) +if [ $total -ne 1 ]; then + echo Host name of $RBK_HOST does not map to a single host: + cat /tmp/rbkresponse.$$ | jq -r '.data[].name' + exit_with_error +fi + +host_id=$(cat /tmp/rbkresponse.$$ | jq -r '.data[0].id') + +if [ -z $host_id ]; then + echo FATAL: Unable to map host name to ID + exit_with_error +fi + +# API call to assign SLA +ENDPOINT="https://$RUBRIK_IP/api/v1/host/$host_id/refresh" + +echo Requesting host refresh for $RBK_HOST + +rest_api_post_empty + +echo Refreshed host details for $RBK_HOST + +cleanup