442 lines
12 KiB
Bash
Executable File
442 lines
12 KiB
Bash
Executable File
#!/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
|
|
VERBOSE=${VERBOSE:-0}
|
|
log_info() {
|
|
if [ "${VERBOSE}" -eq 1 ]; then
|
|
echo "INFO: $*"
|
|
fi
|
|
}
|
|
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 @- <<EOF
|
|
{"query": "$gqlQuery", "variables": $gqlVars}
|
|
EOF
|
|
|
|
}
|
|
|
|
rsc_gql_query () {
|
|
|
|
check_get_rsc_token
|
|
|
|
ENDPOINT="https://${RSC_HOST}/api/graphql"
|
|
|
|
cat - <<EOF > /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 -ne 1 ]]; then
|
|
log_error "Multiple hosts found for '$1':"
|
|
cat /tmp/rbkresponse.$$ | jq -r '.data.physicalHosts.nodes[] | "\(.name) \(.id)"'
|
|
exit_with_error
|
|
fi
|
|
# Set the first match (or empty if none)
|
|
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
|
|
}
|
|
|
|
|