#!/bin/bash # #-------------------------------------------------------------------------------------------------------- # RSC 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")")" # Logging helpers log_info() { echo "INFO: $*" } log_warn() { echo "WARN: $*" >&2 } log_error() { echo "ERROR: $*" >&2 } # Ensure required commands are available require_cmds() { local miss=0 for cmd in jq curl; do if ! command -v "$cmd" >/dev/null 2>&1; then log_error "Required command '$cmd' not found in PATH" miss=1 fi done if [ "$miss" -ne 0 ]; then exit 1 fi } require_cmds # Load and validate RSC configuration from rsc.json if [ -f "$MYDIR/rsc.json" ]; then RSC_ID=$(jq -r '.client_id // empty' "$MYDIR/rsc.json") RSC_SECRET=$(jq -r '.client_secret // empty' "$MYDIR/rsc.json") ACCESS_TOKEN_URI=$(jq -r '.access_token_uri // empty' "$MYDIR/rsc.json") if [ -z "${RSC_ID}" ] || [ -z "${RSC_SECRET}" ] || [ -z "${ACCESS_TOKEN_URI}" ]; then log_error "rsc.json is missing required fields: client_id, client_secret or access_token_uri" log_error "Please populate $MYDIR/rsc.json (see rsc.json.example)" exit 1 fi # Derive host from access token URI and normalize RSC_HOST=$(echo "$ACCESS_TOKEN_URI" | sed 's|https://||' | sed 's|/api/client_token||') # Restrict config file permissions if [ -f "$MYDIR/rsc.json" ]; then chmod 600 "$MYDIR/rsc.json" 2>/dev/null || true fi log_info "Loaded RSC configuration for host: $RSC_HOST" else log_error "rsc.json configuration file not found at $MYDIR/rsc.json" exit 1 fi # Set DATE command based on available binaries (prefer gdate if installed) if command -v gdate >/dev/null 2>&1; then DATE=gdate else DATE=date fi # Utility functions exit_with_error () { rm -f /tmp/rbkresponse.$$ echo Aborting Script! exit 1 } cleanup () { rm -f /tmp/mountedDBs.$$ rm -f /tmp/rbkresponse.$$ rm -f /tmp/payload.$$ } check_http_error () { # All good responses start with a 2 if [ ${http_response:0:1} != "2" ]; then log_error "HTTP error from API call: $http_response" # Show a short excerpt of the response body for debugging head -c 4096 /tmp/rbkresponse.$$ 2>/dev/null || true echo exit_with_error fi } check_get_rsc_token () { if [ -z "${RSC_AUTH_TOKEN}" ]; then if [[ "${RSC_ID}" =~ ^client ]]; then # Looks like an RSC service account id_string=$(echo $RSC_ID | cut -d\| -f 2) else # Not an RSC service account exit_with_error fi # If there is a cached credential file, use it if [ -f ~/.rbkRscsession.$id_string ]; then read expiration token < <(echo $(cat ~/.rbkRscsession.$id_string)) # If token expires within 30 min, get a new one if [ $expiration -lt $(( $($DATE +%s) + 1800 )) ]; then get_rsc_token else RSC_AUTH_TOKEN=$token fi else get_rsc_token fi fi } get_rsc_token () { MYENDPOINT="https://${RSC_HOST}/api/client_token" MYPAYLOAD="{\"client_id\":\"$RSC_ID\",\"client_secret\":\"$RSC_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 RSC_AUTH_TOKEN=$(cat /tmp/rbkresponse.$$ | jq -r '.access_token') SECONDS=$(cat /tmp/rbkresponse.$$ | jq -r '.expires_in') EXPIRATION=$($DATE +%s -d "+${SECONDS} seconds") # Save session securely umask 077 printf '%s %s\n' "$EXPIRATION" "$RSC_AUTH_TOKEN" > ~/.rbkRscsession.$id_string chmod 600 ~/.rbkRscsession.$id_string 2>/dev/null || true log_info "Cached RSC session to ~/.rbkRscsession.$id_string" } rsc_api_get () { check_get_rsc_token http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X GET $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $RSC_AUTH_TOKEN") check_http_error } rsc_api_post () { check_get_rsc_token http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X POST $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $RSC_AUTH_TOKEN" -H "Content-Type: application/json" -d $PAYLOAD) check_http_error } rsc_gql_query_old () { check_get_rsc_token ENDPOINT="https://${RSC_HOST}/api/graphql" #curl -s -o /tmp/rbkresponse.$$ -w "%{http_code}" -X POST $ENDPOINT \ curl -s -o /tmp/rbkresponse.$$ -X POST $ENDPOINT \ -H "Authorization: Bearer ${RSC_AUTH_TOKEN}" \ -H 'Content-Type: application/json' \ -d @- < /tmp/payload.$$ {"query": "$gqlQuery", "variables": $gqlVars} EOF http_response=$(curl -s -o /tmp/rbkresponse.$$ -w "%{http_code}" -X POST $ENDPOINT \ -H "Authorization: Bearer ${RSC_AUTH_TOKEN}" \ -H 'Content-Type: application/json' \ -d @/tmp/payload.$$) #cat /tmp/payload.$$ | jq -r error=$(cat /tmp/rbkresponse.$$ | jq -r '.errors // empty') if [ "$error" ]; then log_error "The last GraphQL API call returned an error" echo log_error "PAYLOAD:" cat /tmp/payload.$$ | jq -r log_error "RESPONSE:" cat /tmp/rbkresponse.$$ | jq -r '.errors' exit_with_error fi check_http_error } rsc_get_host_id() { gql_list_targets='query PhysicalHosts($hostRoot: HostRoot!, $filter: [Filter!]) { physicalHosts(hostRoot: $hostRoot, filter: $filter) { nodes { id name } } }' variables="{\"hostRoot\":\"LINUX_HOST_ROOT\",\"first\":50,\"filter\":[{\"field\":\"NAME_EXACT_MATCH\",\"texts\":[\"$RBK_HOST\"]},{\"field\":\"IS_RELIC\",\"texts\":[\"false\"]},{\"field\":\"IS_REPLICATED\",\"texts\":[\"false\"]},{\"field\":\"IS_KUPR_HOST\",\"texts\":[\"false\"]}],\"sortBy\":\"NAME\",\"sortOrder\":\"ASC\",\"childFilter\":[{\"field\":\"IS_GHOST\",\"texts\":[\"false\"]},{\"field\":\"IS_RELIC\",\"texts\":[\"false\"]}]}" gqlQuery="$(echo $gql_list_targets)" gqlVars="$(echo $variables)" rsc_gql_query # Get all matching host IDs (portable, no mapfile) host_ids=$(cat /tmp/rbkresponse.$$ | jq -r '.data.physicalHosts.nodes[] | .id') host_count=$(echo "$host_ids" | grep -c .) if [[ $host_count -eq 0 ]]; then log_error "Host '$RBK_HOST' not found by exact match (this script requires an exact host name)." log_error "Please re-run with the exact host name as stored in RSC." exit_with_error fi if [[ $host_count -gt 1 ]]; then log_error "Multiple hosts found for '$RBK_HOST':" cat /tmp/rbkresponse.$$ | jq -r '.data.physicalHosts.nodes[] | "\(.name) \(.id)"' log_error "Please re-run with the exact host name to disambiguate." exit_with_error fi # Exactly one match: set the first match targetHostId=$(echo "$host_ids" | head -n 1) #cat /tmp/rbkresponse.$$ | jq -r } rsc_find_database () { # if cluster_uuid is not set, get it # gql query to find DB name, return the best one (based on CDM UUID etc) # if [ -z "${cluster_uuid}" ]; then #echo cluster UUID not, set, getting it get_cluster_uuid #echo Cluster UUID is $cluster_uuid else log_info "Cluster UUID was already $cluster_uuid" fi variables="{ \"filter\":[{\"field\":\"REGEX\",\"texts\":[\"$RBK_SID\"]},{\"field\":\"IS_GHOST\",\"texts\":[\"false\"]},{\"field\":\"IS_ACTIVE\",\"texts\":[\"true\"]},{\"field\":\"CLUSTER_ID\",\"texts\":[\"$cluster_uuid\"]}], \"sortBy\":\"NAME\", \"sortOrder\":\"ASC\", \"first\":500 }" gql_GlobalSearchOracle='query GlobalSearchObjectQuery($first: Int!, $filter: [Filter!]!, $sortBy: HierarchySortByField, $sortOrder: SortOrder, $after: String) { globalSearchResults( first: $first filter: $filter sortBy: $sortBy sortOrder: $sortOrder after: $after ) { edges { cursor node { id name objectType logicalPath { fid name objectType __typename } physicalPath { fid name objectType __typename } ... on HierarchyObject { ...EffectiveSlaColumnFragment __typename } ... on OracleDatabase { cluster { ...ClusterFragment __typename } primaryClusterLocation { id __typename } isRelic __typename } ... on OracleRac { cluster { ...ClusterFragment __typename } primaryClusterLocation { id __typename } __typename } ... on OracleDataGuardGroup { cluster { ...ClusterFragment __typename } primaryClusterLocation { id __typename } isRelic __typename } __typename } __typename } pageInfo { endCursor startCursor hasNextPage hasPreviousPage __typename } __typename } } fragment ClusterFragment on Cluster { id name __typename } fragment EffectiveSlaColumnFragment on HierarchyObject { id effectiveSlaDomain { ...EffectiveSlaDomainFragment ... on GlobalSlaReply { description __typename } __typename } ... on CdmHierarchyObject { pendingSla { ...SLADomainFragment __typename } __typename } __typename } fragment EffectiveSlaDomainFragment on SlaDomain { id name ... on GlobalSlaReply { isRetentionLockedSla retentionLockMode __typename } ... on ClusterSlaDomain { fid cluster { id name __typename } isRetentionLockedSla retentionLockMode __typename } __typename } fragment SLADomainFragment on SlaDomain { id name ... on ClusterSlaDomain { fid cluster { id name __typename } __typename } __typename }' gqlQuery="$(echo $gql_GlobalSearchOracle)" gqlVars="$(echo $variables)" rsc_gql_query #cat /tmp/rbkresponse.$$ | jq -r num=$(cat /tmp/rbkresponse.$$ | jq -r '[.data.globalSearchResults.edges[] | select (.node.objectType=="ORACLE_DATA_GUARD_GROUP" and .node.isRelic==false)]| length') if [ $num -eq 1 ]; then #echo Good, There is just one DG with name $RBK_SID read name rsc_db_id < <(echo $(cat /tmp/rbkresponse.$$ | jq -r '.data.globalSearchResults.edges[] | select (.node.objectType=="ORACLE_DATA_GUARD_GROUP" and .node.isRelic==false)| .node.name, .node.id')) database_type="Data Guard" elif [ $num -gt 1 ]; then log_error "There were $num entries returned for Data Guard databases with name $RBK_SID" exit_with_error fi if [ -z "$rsc_db_id" ]; then #echo INFO: No Data Guard DB found with SID $RBK_SID. Looking for standalone DBs if [ -z "$RBK_HOST" ]; then num=$(cat /tmp/rbkresponse.$$ | jq -r '[.data.globalSearchResults.edges[] | select (.node.objectType=="OracleDatabase" and .node.isRelic==false)] | length') if [ $num -eq 1 ]; then read name rsc_db_id < <(echo $(cat /tmp/rbkresponse.$$ | jq -r '.data.globalSearchResults.edges[] | select (.node.objectType=="OracleDatabase" and .node.isRelic==false)| .node.name, .node.id')) database_type="Standalone" #echo Good, There is just one Standalone DB with name $name and RSC ID $rsc_db_id else log_error "There were $num entries returned from JQ for DB with name $RBK_SID on host $RBK_HOST" exit_with_error fi else num=$(cat /tmp/rbkresponse.$$ | jq -r --arg HOST "$RBK_HOST" '[.data.globalSearchResults.edges[] | select (.node.logicalPath[0].name==$HOST and .node.objectType=="OracleDatabase" and .node.isRelic==false)] | length') if [ $num -eq 1 ]; then read name rsc_db_id < <(echo $(cat /tmp/rbkresponse.$$ | jq -r --arg HOST "$RBK_HOST" '.data.globalSearchResults.edges[] | select (.node.logicalPath[0].name==$HOST and .node.objectType=="OracleDatabase" and .node.isRelic==false)| .node.name, .node.id')) database_type="Standalone" #echo Good, There is just one Standalone DB with name $name on ${RBK_HOST} and RSC ID $rsc_db_id else log_error "There were $num entries returned from for DB with name $RBK_SID on host $RBK_HOST" exit_with_error fi fi fi }