#!/usr/bin/env bash # # Author: Georg Voell - georg.voell@standby.cloud # Version: @(#)oci-instance 3.3.0 19.01.2026 (c)2026 Standby.cloud # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ # # This script can be used free of charge. Use it as is or customize as needed. It is not guaranteed to be # error free and you will not be reimbursed for any damage it may cause. # #@ Do some jobs with instances (db-systems) #@ #@Usage: oci-instance [options] [action] [action-parameter] #@ Options: -h, --help : Displays helptext. #@ -v, --version : Displays the version of the script. #@ -f, --force : Force action. #@ -m, --monochrome : Don't display colors. #@ -i, --id : OCID. #@ Action: #@ list : List all instances. #@ status : List only lifecycle-state of instance specified with instance-id. #@ show : Show one instance specified with instance-id. #@ stop : Stop instance specified with instance-id. #@ start : Start instance specified with instance-id. #@ open : Attach reserved public ip and allow access for ssh. #@ close : Detach reserved public ip and deny access for ssh. #@ add : Attach a Virtual IP (VIP) to instance. Extra parameter: IPV4 #@ delete : Detach a Virtual IP (VIP) from instance. Extra parameter: IPV4 #@ fullbackup : Make a full backup of all attached volumes (additional to regular backups). Extra parameter: Number between 0 and 99 #@ backup : Make a backup of all attached volumes. #@ restore : Restore previously backuped volumes. #@ maintenance: Manage backups. #@ #@Helper Scripts: #@ status #@ If this script exist, it displays additional status of services running on instance. #@ virtualip #@ If this script exist, it adds or deletes ip with ifconfig on instance. #@ restore #@ If this script exist, it mounts unmounted attached disks. #@ #@Examples: #@ oci-instance stop --id ocid1.instance.oc1.xxxxxxxxx #@ Stop instance specifies with instance-id. # # Exit codes: # 01: Unknown or wrong parameter. # 02: Unknown action or action not allowed. # 03: No **instance-id** specified with start or stop. # 04: Instance was already running or stopped. *** # 05: Instance was already opened / closed or VIP was already added / deleted. *** # 06: Could not attach / detach public ip (or unexpected error) # 10: Full restore error *** # 95: Need additional parameter *** # 96: Specified region not subscribed. *** # 97: Tools not configured. *** # 98: Unknown function call. *** # 99: User interrupt. # # See also: # **install-scripts**(1) # # ToDo: # # - We currently only detach public ip or VIP from primary vnic. # - Working with --profile $username # - Using 2>/dev/null after oci to prevent warnings # # Update history: # # V 1.0.0 11.06.2020 Using library # V 1.0.1 01.09.2020 Changed parameter v to n for vnic-id # V 1.0.2 25.03.2021 Using raw output from oci cli # V 1.0.3 30.03.2021 Using oci api # V 1.0.4 31.03.2021 Check also status of service(s) on target VM # V 1.0.5 02.04.2021 Check more types (not only instance) # V 1.0.6 22.04.2021 Backup for instance # V 1.0.7 27.04.2021 Renamed oci-instance to oci-object # V 3.0.0 15.05.2021 Separated Search Objects and actions on instance # V 3.0.1 13.12.2021 New action show # V 3.1.0 05.06.2023 New copyright # V 3.1.1 02.08.2024 New parameter --force # V 3.2.0 12.08.2024 New minor version # V 3.2.1 13.11.2024 Use JSON values # V 3.3.0 19.01.2026 Revised with support of Claude Code # # Find executable bash library and source it lib=$(command -v lib.bash 2>/dev/null) if [[ -n "$lib" ]]; then source "$lib" else progdir=$(dirname "$0") if [[ -r "${progdir}/lib.bash" ]]; then source "${progdir}/lib.bash" else echo "Unexpected error: Unable to locate bash library 'lib.bash'." exit 1 fi fi # Set some defaults downloadurl="https://standby.cloud/download" configdir="${HOME}/.config" toolsdir="admintools" listsdir="lists" resfile="resources.txt" apifile="api.txt" regfile="regions.txt" # Source environment variables if [[ -r "${HOME}/.$toolsdir" ]]; then source "${HOME}/.$toolsdir" else if [[ -r "/usr/local/share/info/.$toolsdir" ]]; then source "/usr/local/share/info/.$toolsdir" fi fi # Do extra cleanup function ExtraCleanup() { filecheck -rm "${scratchfile}.inst" filecheck -rm "${scratchfile}.vnic" filecheck -rm "${scratchfile}.iboot" # Instance boot volumes filecheck -rm "${scratchfile}.idisk" # Instance block volumes filecheck -rm "${scratchfile}.adisk" # All volumes filecheck -rm "${scratchfile}.boot" filecheck -rm "${scratchfile}.disk" filecheck -rm "${scratchfile}.bkup" filecheck -rm "${scratchfile}.bpol" filecheck -rm "${scratchfile}.list" filecheck -rm "${scratchfile}.lis2" } # Download all needed files from repo function Initialize() { # Create config dir if it does not exist if [[ ! -d "${configdir}/${toolsdir}/$listsdir" ]]; then mkdir -p "${configdir}/${toolsdir}/$listsdir" fi # Download API info files for file in $resfile; do result=$(filecheck -fc "${configdir}/${toolsdir}/${listsdir}/$file") if [[ "$result" = "" ]]; then # echo "Downloading file '${configdir}/${toolsdir}/${listsdir}/$file'." transfer --quiet "${downloadurl}/${listsdir}/$file" | StripComment | grep . > "$scratchfile" result=$(head -n 1 "$scratchfile" | cut -d$'\t' -f1) if [[ "$result" = "name" ]]; then mv -f "$scratchfile" "${configdir}/${toolsdir}/${listsdir}/$file" fi fi done } # Check if string is a number and within min and max function CheckNumber() { local number=${1} local min=${2} local max=${3} local result="" result=$(echo "$number" | grep '^[0123456789]*$') if [[ "$result" = "" ]]; then echo "" else if [[ "$min" != "" ]]; then result=$(echo "$min" | grep '^[0123456789]*$') if [[ "$result" != "" ]]; then if (( number < min )); then number=$min fi fi fi if [[ "$max" != "" ]]; then result=$(echo "$max" | grep '^[0123456789]*$') if [[ "$result" != "" ]]; then if (( number > max )); then number=$max fi fi fi echo "$number" fi } # Check if ip is a valid IPv4 function CheckIP() { local ip=${1} local result="" result=$(echo "$ip" | grep '^[0123456789]*\.[0123456789]*\.[0123456789]*\.[0123456789]*$') if [[ "$result" = "" ]]; then echo "" else ip1=$(echo "$result" | cut -d'.' -f1) ip2=$(echo "$result" | cut -d'.' -f2) ip3=$(echo "$result" | cut -d'.' -f3) ip4=$(echo "$result" | cut -d'.' -f4) if [[ $ip1 -gt 255 || $ip2 -gt 255 || $ip3 -gt 255 || $ip4 -gt 255 ]]; then echo "" else echo "$ip" fi fi } # Call OCI API and check result # API Reference: https://docs.cloud.oracle.com/en-us/iaas/api/ function UseAPI() { local proc=${1} local region=${2} local comp=${3} local outfile=${4} local optional=${5} local stat=0 local code="" case "$proc" in SearchResources) # Create body file if [[ "$optional" = "" ]]; then optional="all" fi if [[ "$comp" != "" ]]; then code=$(echo "$comp" | grep '^ocid1\.compartment\.oc1\.') if [[ "$code" != "" ]]; then printf '{\n "type": "Structured",\n "query": "query %s resources where compartmentId = '"'%s'"'"\n}\n' "$optional" "$comp" > ${outfile}.body else printf '{\n "type": "Structured",\n "query": "query %s resources where identifier = '"'%s'"'"\n}\n' "$optional" "$comp" > ${outfile}.body fi else printf '{\n "type": "Structured",\n "query": "query %s resources"\n}\n' "$optional" > ${outfile}.body fi target="https://query.${region}.oraclecloud.com/20180409/resources" method="POST" filecheck -rm $outfile filecheck -rm ${outfile}.test $oci raw-request --target-uri "$target" --http-method "$method" --request-body "file://${outfile}.body" > ${outfile}.test 2>&1 stat=$? if (( stat == 0 )); then cat ${outfile}.test | jq -M .data.items | convert-json "identifier,lifecycleState,displayName,availabilityDomain,compartmentId,timeCreated" \ --quiet --noheader --output tsv > $outfile np=$(cat ${outfile}.test | jq -M .headers | browse-json --select 1 --quiet "opcNextPage") while [[ "$np" != "" ]]; do $oci raw-request --target-uri "${target}?page=$np" --http-method "$method" --request-body "file://${outfile}.body" > ${outfile}.test 2>&1 stat=$? if (( stat == 0 )); then cat ${outfile}.test | jq -M .data.items | convert-json "identifier,lifecycleState,displayName,availabilityDomain,compartmentId,timeCreated" \ --quiet --noheader --output tsv >> $outfile np=$(cat ${outfile}.test | jq -M .headers | browse-json --select 1 --quiet "opcNextPage") fi done fi filecheck -rm ${outfile}.body ;; *) stat=98 errormsg $stat "($progstr) Unknown function call." esac # Cleanup filecheck -rm ${outfile}.test # Return status return $stat } function PrintResult() { local filename=${1} local rType=${2} local option=${3} local name="" local isRegional="" local states="" local service="" local api="" local call="" local up="" local down="" local deleted="" local error="" local other="" if [[ -r "$filename" ]]; then if [[ "$rType" != "" ]]; then if [[ -r "${configdir}/${toolsdir}/${listsdir}/$resfile" ]]; then result=$(grep -i "^$rType " "${configdir}/${toolsdir}/${listsdir}/$resfile") if [[ "$result" != "" ]]; then name=$(echo "$result" | cut -d$'\t' -f1) # isRegional=$(echo "$result" | cut -d$'\t' -f2) states=$(echo "$result" | cut -d$'\t' -f3) service=$(echo "$result" | cut -d$'\t' -f4) # api=$(echo "$result" | cut -d$'\t' -f5) # call=$(echo "$result" | cut -d$'\t' -f6) printf "\n%s\n" "Resources for type '$name' (Service: ${service}) found:" else printf "\n%s\n" "Resources for type '$rType' found:" fi fi else printf "\n%s\n" "Resources found:" fi printf "\n" if [[ "$monochrome" = false && "$states" != "" ]]; then up=$(echo "$states" | cut -d';' -f1) down=$(echo "$states" | cut -d';' -f2) deleted=$(echo "$states" | cut -d';' -f3) error=$(echo "$states" | cut -d';' -f4) other=$(echo "$states" | cut -d';' -f5) if [[ "$up" != "" ]]; then up="--green $up" fi if [[ "$down" != "" ]]; then down="--yellow $down" fi if [[ "$deleted" != "" ]]; then deleted="--darkgray $deleted" fi if [[ "$error" != "" ]]; then error="--red $error" fi if [[ "$other" != "" ]]; then other="--bold $other" fi print-table --import "$filename" $option $up $down $deleted $error $other else print-table --import "$filename" $option fi fi } # Display one db system specified with instance-id function GetDBSystem() { local region=${1} local inst=${2} $oci db system get --region $region --db-system-id $inst 2>/dev/null | norm-json --quiet > "$scratchfile" stat=$? if (( stat == 0 )); then # dbsysid=$(browse-json id --select 1 --import "$scratchfile") comp=$(browse-json compartmentId --select 1 --import "$scratchfile" --quiet ) avd=$(browse-json availabilityDomain --select 1 --import "$scratchfile" --quiet ) cat "$scratchfile" | convert-json "id,lifecycleState,compartmentId,displayName,shape,dataStorageSizeInGBs,databaseEdition,version,licenseModel,availabilityDomain" \ --quiet --output tsv > "${scratchfile}.inst" PrintResult "${scratchfile}.inst" "DBSystem" "--output line" # Get all the DBs $oci db database list --region $region --compartment-id $comp --db-system-id $inst 2>/dev/null | norm-json --quiet > "$scratchfile" stat=$? if (( stat == 0 )); then convert-json "id,lifecycleState,dbName,dbUniqueName,dbWorkload" --import "$scratchfile" --quiet --output tsv > "${scratchfile}.db" PrintResult "${scratchfile}.db" "Database" fi $oci db node list --region $region --compartment-id $comp --db-system-id $inst 2>/dev/null | norm-json --quiet > "$scratchfile" stat=$? if (( stat == 0 )); then convert-json "id,lifecycleState,hostname,faultDomain,vnicId" --import "$scratchfile" --quiet --output tsv --noheader > "${scratchfile}.db" printf "id\tlifecycleState\thostname\tprivateIp\tpublicIp\tregion\tavailabilityDomain\tfaultDomain\n" > "${scratchfile}.node" while IFS=$'\t' read -r id ls name fd vnic; do $oci network vnic get --region $region --vnic-id $vnic 2>/dev/null | norm-json --quiet > "$scratchfile" stat=$? if (( stat == 0 )); then prim=$(browse-json isPrimary --import "$scratchfile" --select 1 --quiet) privip=$(browse-json privateIp --import "$scratchfile" --select 1 --quiet) pubip=$(browse-json publicIp --import "$scratchfile" --select 1 --quiet) else prim="" privip="null" pubip="null" fi printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$id" "$ls" "$name" "$privip" "$pubip" "$region" "$avd" "$fd" >> "${scratchfile}.node" done < "${scratchfile}.db" printf "\nAssigned Nodes:\n\n" if [[ "$monochrome" = false ]]; then # lifecycleState: AVAILABLE;STOPPED;TERMINATED;FAILED;PROVISIONING,UPDATING,STOPPING,STARTING,TERMINATING print-table --import "${scratchfile}.node" --green "AVAILABLE" --yellow "STOPPED" --red "FAILED" --darkgray "TERMINATED" \ --bold "PROVISIONING,UPDATING,STOPPING,STARTING,TERMINATING" else print-table --import "${scratchfile}.node" fi fi # Cleanup filecheck -rm "${scratchfile}.db" filecheck -rm "${scratchfile}.node" else printf "\DB System '$inst' not found.\n\n" fi } # Display one instance specified with instance-id function GetInstance() { local region=${1} local inst=${2} local stat=0 local result="" local id="" local comp="" local avdomain="" local ls="" local dsize="" local attachedid="" local encrypted="" local atype="" local device="" local ro="" local shareable="" local prim="" local privip="" local pubip="" local name="" local host="" local dummy="" local backup="ENABLED" local stopit="ENABLED" local retention="30" $oci compute instance get --region $region --instance-id $inst 2>/dev/null | norm-json --quiet > "$scratchfile" stat=$? if (( stat == 0 )); then comp=$(browse-json compartmentId --import "$scratchfile" --select 1 --quiet) avdomain=$(browse-json availabilityDomain --import "$scratchfile" --select 1 --quiet) backup=$(browse-json definedTags/Instance-Backup/Backup --import "$scratchfile" --select 1 --quiet | ToUpper) stopit=$(browse-json definedTags/Instance-Backup/StopInstance --import "$scratchfile" --select 1 --quiet | ToUpper) retention=$(browse-json definedTags/Instance-Backup/RetentionPeriod --import "$scratchfile" --select 1 --quiet) if [[ "$backup" = "" ]]; then backup="ENABLED" fi if [[ "$stopit" = "" ]]; then stopit="ENABLED" fi if [[ "$retention" = "" ]]; then retention="30" else # Check if string is a number and within min and max retention=$(CheckNumber "$retention" "1" "730") fi convert-json "id,lifecycleState,compartmentId,displayName,shape,region,availabilityDomain,faultDomain" --import "$scratchfile" \ --quiet --output tsv > "${scratchfile}.inst" stat=$? if (( stat == 0 )); then PrintResult "${scratchfile}.inst" "Instance" "--output line" else filecheck -rm "${scratchfile}.inst" fi # Get attached VNICs $oci compute instance list-vnics --all --region $region --instance-id $inst --query 'data[?"lifecycle-state" == $(AVAILABLE)]' 2>/dev/null | norm-json --quiet > "$scratchfile" stat=$? if (( stat == 0 )); then convert-json "id,lifecycleState,displayName,isPrimary,publicIp,privateIp,ipName,hostnameLabel" --import "$scratchfile" --quiet --output tsv > "${scratchfile}.vnic" stat=$? if (( stat == 0 )); then mv -f "${scratchfile}.vnic" "$scratchfile" head -n 1 "$scratchfile" > "${scratchfile}.vnic" while IFS=$'\t' read -r id ls name dummy pubip dummy dummy dummy; do $oci network private-ip list --all --region $region --vnic-id $id | convert-json "displayName,isPrimary,ipAddress,hostnameLabel" \ --output tsv --noheader --quiet > "${scratchfile}.iboot" while IFS=$'\t' read -r result prim privip host; do printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$id" "$ls" "$name" "$prim" "$pubip" "$privip" "$result" "$host" >> "${scratchfile}.vnic" done < "${scratchfile}.iboot" done < <(cat "$scratchfile" | tailfromline2) printf "\nAssigned IPs:\n\n" if [[ "$monochrome" = false ]]; then print-table --import "${scratchfile}.vnic" --green "AVAILABLE" --red "TERMINATED" --yellow "TERMINATING" --bold "VIP" else print-table --import "${scratchfile}.vnic" fi fi fi # Get attached boot volume if [[ "$comp" != "" && "$avdomain" != "" ]]; then $oci compute boot-volume-attachment list --all --region "$region" --instance-id "$inst" --compartment-id "$comp" \ --availability-domain $avdomain --query 'data[?"lifecycle-state" == $(ATTACHED)]' 2>/dev/null | norm-json --quiet > "$scratchfile" stat=$? if (( stat == 0 )); then convert-json "bootVolumeId,lifecycleState,displayName,isPvEncryptionInTransitEnabled" --import "$scratchfile" --quiet \ --output tsv --noheader > "${scratchfile}.boot" stat=$? if (( stat == 0 )); then printf "%s\t%s\t%s\t%s\t%s\n" "bootVolumeId" "lifecycleState" "displayName" "encryptionInTransit" "sizeInGBs" > "${scratchfile}.iboot" while IFS=$'\t' read -r id ls name encrypted; do dsize="Unknown" if [[ "$id" != "" && "$ls" = "ATTACHED" ]]; then $oci bv boot-volume get --region "$region" --boot-volume-id "$id" > "$scratchfile" stat=$? if (( stat == 0 )); then name=$(browse-json "displayName" --select 1 --import "$scratchfile") dsize=$(browse-json "sizeInGBs" --select 1 --import "$scratchfile") fi fi printf "%s\t%s\t%s\t%s\t%s\n" "$id" "$ls" "$name" "$encrypted" "$dsize" >> "${scratchfile}.iboot" done < <(cat "${scratchfile}.boot" | sort -t$'\t' -k3) printf "\nAssigned boot volume:\n\n" if [[ "$monochrome" = false ]]; then print-table --import "${scratchfile}.iboot" --green "ATTACHED" --yellow "DETACHED" else print-table --import "${scratchfile}.iboot" fi fi filecheck -rm "${scratchfile}.boot" fi fi # Get attached volumes $oci compute volume-attachment list --all --region $region --instance-id $inst --query 'data[?"lifecycle-state" == $(ATTACHED)]' 2>/dev/null | norm-json --quiet > "$scratchfile" stat=$? if (( stat == 0 )); then convert-json "volumeId,lifecycleState,displayName,availabilityDomain,isPvEncryptionInTransitEnabled,id,attachmentType,device,isReadOnly,isShareable" \ --import "$scratchfile" --quiet --output tsv --noheader >> "${scratchfile}.disk" stat=$? if (( stat == 0 )); then printf "%s\t%s\t%s\t%s\t%s\t%s\n" "volumeId" "lifecycleState" "displayName" "availabilityDomain" "encryptionInTransit" "sizeInGBs" > "${scratchfile}.idisk" while IFS=$'\t' read -r id ls name avd encrypted attachedid atype device ro shareable; do dsize="Unknown" if [[ "$id" != "" && "$ls" = "ATTACHED" ]]; then $oci bv volume get --region "$region" --volume-id "$id" > "$scratchfile" stat=$? if (( stat == 0 )); then name=$(browse-json "displayName" --select 1 --import "$scratchfile") dsize=$(browse-json "sizeInGBs" --select 1 --import "$scratchfile") fi fi printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$id" "$ls" "$name" "$avd" "$encrypted" "$dsize" "$attachedid" "$atype" "$device" "$ro" "$shareable" >> "${scratchfile}.idisk" done < <(cat "${scratchfile}.disk" | sort -t$'\t' -k2,3) printf "\nAssigned volumes:\n\n" if [[ "$monochrome" = false ]]; then print-table --import "${scratchfile}.idisk" --green "ATTACHED" --yellow "DETACHED" else print-table --import "${scratchfile}.idisk" fi fi filecheck -rm "${scratchfile}.disk" else # Instance hasn't any attached volumes stat=0 fi # Show Backup Policy printf "id\tbackup\tstopInstance\tretentionPeriod\n" > "${scratchfile}.bpol" printf "%s\t%s\t%s\t%s\n" "$inst" "$backup" "$stopit" "$retention" >> "${scratchfile}.bpol" printf "\nInstance Backup Policy:\n\n" if [[ "$monochrome" = false ]]; then print-table --import "${scratchfile}.bpol" --green "ENABLED" --yellow "DISABLED" else print-table --import "${scratchfile}.bpol" fi else stat=100 printf "\nInstance '$inst' not found.\n\n" fi return $stat } # Return lifecycle-state of instance specified with instance-id # lifecycle-state could be e.g.: UNKNOWN, PROVISIONING, RUNNING, STARTING, STOPPING, STOPPED, CREATING_IMAGE, TERMINATING, TERMINATED function GetState() { local region=${1} local inst=${2} local stat=0 local state="" local name="" local message="" # Get current state UseAPI "SearchResources" "$region" "$inst" "$scratchfile" "$instance_type" stat=$? if (( stat == 0 )); then # "identifier,lifecycleState,displayName,availabilityDomain,compartmentId,timeCreated" state=$(head -n 1 "$scratchfile" | cut -d$'\t' -f2 | ToUpper) name=$(head -n 1 "$scratchfile" | cut -d$'\t' -f3 | cut -d',' -f1) # If displayName contains a comma, ignore the string after comma if [[ "$state" = "" ]]; then message="UNKNOWN" else message="${name},$state" fi # return displayName,lifecycleState echo "$message" else echo "UNKNOWN" fi } # Display displayName,lifecycleState of VM and if available status of service(s) running on VM function DisplayState() { local region=${1} local inst=${2} local stat=0 local status="" local state="" local name="" local message="" state=$(GetState "$region" "$inst") if [[ "$state" != "UNKNOWN" ]]; then name=$(echo "$state" | cut -d',' -f1) state=$(echo "$state" | cut -d',' -f2) message="Name:${name},State:$state" if [[ "$state" = "RUNNING" || "$state" = "AVAILABLE" || "$state" = "ACTIVE" ]]; then # Check the status of Services running on VM. Need to have a script called "status" on the target VM. status=$(filecheck -x status) if [[ "$status" != "" ]]; then state=$($status "$name" 2>/dev/null) if [[ "$state" != "" ]]; then message="${message},$state" fi fi fi # return displayName,lifecycleState and status of service(s) on target VM echo "$message" | ToUpper else stat=1 errormsg $stat "($progstr) Unable to get status of instance." fi return $stat } # Stop instance specified with instance-id function StopInstance() { local region=${1} local inst=${2} local stat=0 local state="" # Get current state state=$(GetState $region $inst) state=$(echo "$state" | cut -d',' -f2) if [[ "$state" = "STOPPED" ]]; then errormsg $stat "($progstr) Instance '$inst' already stopped." else printf "\nStopping Instance.\n" $oci compute instance action --action stop --wait-for-state "STOPPED" --region $region --instance-id $inst \ | convert-json "id,lifecycleState,displayName" --quiet --output tsv > "$scratchfile" stat=$? printf "\nResult:\n\n" if [[ "$monochrome" = false ]]; then print-table --import "$scratchfile" --output line --green "RUNNING" --red "TERMINATED" --yellow "STOPPED" else print-table --import "$scratchfile" --output line fi fi return $stat } # Start instance specified with instance-id function StartInstance() { local region=${1} local inst=${2} local stat=0 local state="" # Get current state state=$(GetState $region $inst) state=$(echo "$state" | cut -d',' -f2) if [[ "$state" = "RUNNING" ]]; then errormsg $stat "($progstr) Instance '$inst' already running." else printf "\nStarting Instance.\n" $oci compute instance action --action start --wait-for-state "RUNNING" --region $region --instance-id $inst \ | convert-json "id,lifecycleState,displayName" --quiet --output tsv > "$scratchfile" stat=$? printf "\nResult:\n\n" if [[ "$monochrome" = false ]]; then print-table --import "$scratchfile" --output line --green "RUNNING" --red "TERMINATED" --yellow "STOPPED" else print-table --import "$scratchfile" --output line fi fi return $stat } # Check if instanceBackup Tag Namespace exists function CheckTagNamespace() { local compid=${1} local stat=0 local tagid="" $oci iam tag-namespace list --compartment-id $compid | convert-json "id,lifecycleState,name,isRetired,description" --quiet --output tsv > "$scratchfile" stat=$? if (( stat == 0 )); then result=$(grep " Instance-Backup " "$scratchfile") if [[ "$result" = "" ]]; then # Namespace does not exists - create it $oci iam tag-namespace create --compartment-id $compid --name "Instance-Backup" --description "Backup Policy for Instance attached volumes" \ --wait-for-state "ACTIVE" > "$scratchfile" stat=$? if (( stat == 0 )); then tagid=$(browse-json "id" --import "$scratchfile" --select 1 --quiet) if [[ "$tagid" != "" ]]; then $oci iam tag create --tag-namespace-id "$tagid" --name "RetentionPeriod" --description "Keep the backups for specified number of days (min=1, max=730)" >/dev/null 2>&1 transfer --quiet "${downloadurl}/samples/validator.json" | norm-json | grep . > "$scratchfile" $oci iam tag create --tag-namespace-id "$tagid" --name "Backup" --description "If disabled - ignore making backups" \ --validator file://"$scratchfile" >/dev/null 2>&1 $oci iam tag create --tag-namespace-id "$tagid" --name "StopInstance" --description "If enabled, the instance is beeing stopped before backup process" \ --validator file://"$scratchfile" >/dev/null 2>&1 fi fi fi fi } # Generate a list with all available backups function FindBackups() { local region=${1} local btype=${2} local compid=${3} local id="" local ls="" local name="" local avd="" local comp="" local cdate="" local result="" # Find all Bootvolume Backups for compartment UseAPI "SearchResources" "$region" "$compid" "$scratchfile" "$btype" stat=$? if (( stat == 0 )); then printf "id\tlifecycleState\tname\tcompartmentId\tcreated\n" > "${scratchfile}.bkup" while IFS=$'\t' read -r id ls name avd comp cdate; do if [[ "$id" != "" ]]; then # Get rid og the time after the date cdate=$(echo "$cdate" | cut -d'T' -f1) # Print lifecycleState in upper case letters ls="$(ToUpper "$ls")" # result=$(echo "$name" | grep "^Backup,.*,...,") # if [ "$ls" != "TERMINATED" -a "$result" != "" ]; then if [[ "$ls" != "TERMINATED" ]]; then printf "%s\t%s\t%s\t%s\t%s\n" "$id" "$ls" "$name" "$comp" "$cdate" >> "${scratchfile}.bkup" fi fi done < <(cat "$scratchfile" | sort -rt$'\t' -k6) # Sort cdate in reverse order (newest date first) result=$(filecheck -sl "${scratchfile}.bkup") if [[ "$result" = "" ]]; then filecheck -rm "${scratchfile}.bkup" fi fi } # Build a string for Backup name function BackupName() { local instname=${1} local day=${2} local dtype=${3} local num=${4} printf "Backup,%s,%03d,%s%02d" "$instname" "$day" "$dtype" "$num" } # Backup all attached disk of one instance specified with instance-id function BackupInstance() { local region=${1} local inst=${2} local day=${3} local stat=0 local number=0 local deleteid="" local btype="" local result="" local state="" local iname="" local icomp="" local vname="" local id="" local ls="" local name="" local avd="" local dsize="" local attachedid="" local encrypted="" local atype="" local device="" local ro="" local shareable="" local backup="ENABLED" local stopit="ENABLED" local retention="30" # Get current state state=$(GetState "$region" "$inst") state=$(echo "$state" | cut -d',' -f2) if [[ "$state" = "STOPPED" && "$force" = false ]]; then errormsg $stat "($progstr) Instance '$inst' stopped. Backup canceled." else # Get details of instance GetInstance "$region" "$inst" stat=$? if (( stat == 0 )); then # Get Backup Policy if [[ -r "${scratchfile}.bpol" ]]; then result=$(grep "^$inst " "${scratchfile}.bpol") if [[ "$result" != "" ]]; then # "id\tbackup\tstopInstance\tretentionPeriod\n" backup=$(echo "$result" | cut -d$'\t' -f2 | ToUpper) stopit=$(echo "$result" | cut -d$'\t' -f3 | ToUpper) retention=$(echo "$result" | cut -d$'\t' -f4) fi fi if [[ "$backup" = "ENABLED" ]]; then if [[ "$stopit" = "ENABLED" ]]; then if [[ "$state" = "RUNNING" ]]; then StopInstance "$region" "$inst" stat=$? fi else # ssh to vm and execute script 'backup-mode start' echo "Not yet impelmented" fi # Do Backup if we could stop the instance if (( stat == 0 )); then # "lifecycleState,id,compartmentId,displayName,shape,region,availabilityDomain,faultDomain" icomp=$(grep "^$inst " "${scratchfile}.inst" | cut -d$'\t' -f3) iname=$(grep "^$inst " "${scratchfile}.inst" | cut -d$'\t' -f4 | tr ',' ';') # Check if string is a number and within min and max day=$(CheckNumber "$day" "0" "99") if [[ "$day" != "" ]]; then day=$(($day + 900)) else day=$(date +'%s') # Seconds since 01.01.1970 day=$(($day / 86400)) # Days since 01.01.1970 (60 * 60 * 24 = 86400) day=$(($day % $retention)) # Using modulo to get a sequence of numbers e.g. (using mod 30): 26, 27, 28, 29, 0, 1, 2 ... fi # https://docs.oracle.com/en-us/iaas/tools/oci-cli/2.22.2/oci_cli_docs/cmdref/bv/volume-backup-policy.html # https://docs.oracle.com/en-us/iaas/tools/oci-cli/2.17.0/oci_cli_docs/cmdref/bv/backup/list.html # https://docs.oracle.com/en-us/iaas/tools/oci-cli/2.17.0/oci_cli_docs/cmdref/bv/volume/create.html # "bootVolumeId,lifecycleState,displayName,availabilityDomain" number=0 printf "\nStarting bootvolume backup for instance '$iname'.\n" printf "Searching for existing bootvolume backups.\n" FindBackups "$region" "BootVolumeBackup" "$icomp" while IFS=$'\t' read -r id ls name avd encrypted dsize; do if [[ "$id" != "" && "$ls" = "ATTACHED" ]]; then if [[ $day -eq 0 || $day -gt 899 ]]; then btype="FULL" else btype="INCREMENTAL" fi deleteid="" (( number++ )) vname=$(BackupName "$iname" "$day" "Boot" "$number") if [[ -r "${scratchfile}.bkup" ]]; then result=$(grep " $vname " "${scratchfile}.bkup") if [[ "$result" != "" ]]; then deleteid=$(echo "$result" | cut -d$'\t' -f1) fi fi if [[ "$deleteid" != "" ]]; then printf "\nBackup for bootvolume '$vname' ($btype) - replacing old backup.\n" else printf "\nBackup for bootvolume '$vname' ($btype).\n" fi # $oci bv boot-volume get --region "$region" --boot-volume-id "$id" | norm-json > "$scratchfile" # stat=$? # # if [ $stat -eq 0 ]; then # name=$(browse-json displayName --import "$scratchfile" --select 1 --quiet) # vsize=$(browse-json sizeInGBs --import "$scratchfile" --select 1 --quiet) # # vgroup=$(browse-json volumeGroupId --import "$scratchfile" --select 1 --quiet) # fi # https://docs.oracle.com/en-us/iaas/tools/oci-cli/2.17.0/oci_cli_docs/cmdref/bv/boot-volume-backup.html $oci bv boot-volume-backup create --region "$region" --boot-volume-id "$id" --display-name "$vname" --type "$btype" --wait-for-state "AVAILABLE" \ --freeform-tags '{"instanceId": "'"$inst"'", "diskId": "'"$id"'", "diskName": "'"$name"'", "diskSize": "'"$dsize"' GB"}' \ | convert-json "id,lifecycleState,displayName" --quiet --output tsv > "$scratchfile" stat=$? if (( stat == 0 )); then printf "\nResult:\n\n" if [[ "$monochrome" = false ]]; then print-table --import "$scratchfile" --output line --green "AVAILABLE" --red "TERMINATED" --yellow "STOPPED" else print-table --import "$scratchfile" --output line fi if [[ "$deleteid" != "" ]]; then printf "\n" $oci bv boot-volume-backup delete --region "$region" --boot-volume-backup-id "$deleteid" --force --wait-for-state "TERMINATED" stat=$? if (( stat == 0 )); then printf "Old backup '$vname' with id '$deleteid' deleted.\n" else errormsg $stat "($progstr) Deletion of old backup '$deleteid' failed." fi fi else errormsg $stat "($progstr) Backup failed." fi fi done < <(cat "${scratchfile}.iboot" | tailfromline2) # --freeform-tags {"Department": "Finance"} / --defined-tags {"Operations": {"CostCenter": "42"}} # --type FULL / INCREMENTAL # "volumeId,lifecycleState,displayName,availabilityDomain,attachmentType" if [[ -r "${scratchfile}.idisk" ]]; then number=0 printf "\nStarting blockvolume backup for instance '$iname'.\n" printf "Searching for existing blockvolume backups.\n" FindBackups "$region" "VolumeBackup" "$icomp" while IFS=$'\t' read -r id ls name avd encrypted dsize attachedid atype device ro shareable; do if [[ "$id" != "" && "$ls" = "ATTACHED" ]]; then if [[ $day -eq 0 || $day -gt 899 ]]; then btype="FULL" else btype="INCREMENTAL" fi deleteid="" (( number++ )) vname=$(BackupName "$iname" "$day" "Data" "$number") if [[ -r "${scratchfile}.bkup" ]]; then result=$(grep " $vname " "${scratchfile}.bkup") if [[ "$result" != "" ]]; then deleteid=$(echo "$result" | cut -d$'\t' -f1) fi fi if [[ "$deleteid" != "" ]]; then printf "\nBackup for datavolume '$vname' ($btype) - replacing old backup.\n" else printf "\nBackup for datavolume '$vname' ($btype).\n" fi # $oci bv volume get --region "$region" --volume-id "$id" | norm-json > "$scratchfile" # stat=$? # # if [ $stat -eq 0 ]; then # name=$(browse-json displayName --import "$scratchfile" --select 1 --quiet) # vsize=$(browse-json sizeInGBs --import "$scratchfile" --select 1 --quiet) # # vgroup=$(browse-json volumeGroupId --import "$scratchfile" --select 1 --quiet) # fi # https://docs.oracle.com/en-us/iaas/tools/oci-cli/2.17.0/oci_cli_docs/cmdref/bv/backup/create.html $oci bv backup create --region "$region" --volume-id "$id" --display-name "$vname" --type "$btype" --wait-for-state "AVAILABLE" \ --freeform-tags '{"instanceId": "'"$inst"'", "diskId": "'"$id"'", "diskName": "'"$name"'", "diskSize": "'"$dsize"' GB"}' \ | convert-json "id,lifecycleState,displayName" --quiet --output tsv > "$scratchfile" stat=$? if (( stat == 0 )); then printf "\nResult:\n\n" if [[ "$monochrome" = false ]]; then print-table --import "$scratchfile" --output line --green "AVAILABLE" --red "TERMINATED" --yellow "STOPPED" else print-table --import "$scratchfile" --output line fi if [[ "$deleteid" != "" ]]; then printf "\n" $oci bv backup delete --region "$region" --volume-backup-id "$deleteid" --force --wait-for-state "TERMINATED" stat=$? if (( stat == 0 )); then printf "Old backup '$vname' with id '$deleteid' deleted.\n" else errormsg $stat "($progstr) Deletion of old backup '$deleteid' failed." fi fi else errormsg $stat "($progstr) Backup failed." fi fi done < <(cat "${scratchfile}.idisk" | tailfromline2) fi else errormsg $stat "($progstr) Backup for instance '$inst' failed." fi if [[ "$stopit" = "ENABLED" ]]; then if [[ "$state" = "RUNNING" ]]; then StartInstance "$region" "$inst" stat=$? fi else # ssh to vm and execute script 'backup-mode stop' echo "Not yet impelmented" fi else printf "\nBackup disabled for instance. Skipping backup task.\n" fi fi fi return $stat } # List backups for instance-id function ListBackups() { local region=${1} local inst=${2} local btype="$(ToLower "$3")" # Backup type: managed = all backups created with this script / all = all backups local stat=0 local rperiod="" local icomp="" local iname="" local result="" local overwrite="" local disks="" # Get details of instance GetInstance "$region" "$inst" stat=$? if (( stat == 0 )); then # "id\tbackup\tstopInstance\tretentionPeriod\n" "${scratchfile}.bpol" rperiod=$(grep "^$inst " "${scratchfile}.bpol" | cut -d$'\t' -f4) if [[ "$rperiod" = "" ]]; then rperiod=30 # Default fi # "lifecycleState,id,compartmentId,displayName,shape,region,availabilityDomain,faultDomain" icomp=$(grep "^$inst " "${scratchfile}.inst" | cut -d$'\t' -f3) iname=$(grep "^$inst " "${scratchfile}.inst" | cut -d$'\t' -f4 | tr ',' ';') # Find all Bootvolume Backups for instance compartment FindBackups "$region" "BootVolumeBackup" "$icomp" if [[ -r "${scratchfile}.bkup" ]]; then if [[ "$btype" = "all" ]]; then mv "${scratchfile}.bkup" "${scratchfile}.boot" else # Filter only Backups that are created with this script printf "id\tlifecycleState\tname\tcompartmentId\tcreated\n" > "${scratchfile}.boot" if [[ "$btype" = "managed" ]]; then # Find all boot volumes # grep ' Backup,.*,Boot01 ' "${scratchfile}.bkup" >> "${scratchfile}.boot" grep " Backup,${iname},...,Boot.. " "${scratchfile}.bkup" >> "${scratchfile}.boot" else # Find first boot volume - there should be only one grep " Backup,${iname},...,Boot01 " "${scratchfile}.bkup" >> "${scratchfile}.boot" fi fi # Find all Volume Backups for instance compartment FindBackups "$region" "VolumeBackup" "$icomp" if [[ -r "${scratchfile}.bkup" ]]; then if [[ "$btype" != "" ]]; then if [[ "$btype" = "all" ]]; then cat "${scratchfile}.bkup" >> "${scratchfile}.boot" else # Filter only Backups that are created with this script grep " Backup,${iname},...,Data.. " "${scratchfile}.bkup" >> "${scratchfile}.boot" fi fi fi if [[ "$btype" != "" ]]; then printf "created\tlifecycleState\tnumber\toverwrite\ttype\tid\n" > "${scratchfile}.disk" else printf "created\tlifecycleState\tnumber\toverwrite\ttype\tdisks\n" > "${scratchfile}.disk" fi # Search for this specified instance backups while IFS=$'\t' read -r id ls name comp cdate; do # $oci bv boot-volume-backup get --region "$region" --boot-volume-backup-id "$id" | norm-json > "${scratchfile}.inst" # cat "${scratchfile}.inst" # btype=$(browse-json type --import "${scratchfile}.inst" --select 1 --quiet) # instanceId=$(browse-json freeformTags/instanceId --import "${scratchfile}.inst" --select 1 --quiet) # ls=$(browse-json lifecycleState --import "${scratchfile}.inst" --select 1 --quiet) # diskId=$(browse-json freeformTags/diskId --import "${scratchfile}.inst" --select 1 --quiet) # diskName=$(browse-json freeformTags/diskName --import "${scratchfile}.inst" --select 1 --quiet) # diskSize=$(browse-json freeformTags/diskSize --import "${scratchfile}.inst" --select 1 --quiet) # if [ "$instanceId" = "$inst" ]; then # printf "%s\t%s\t%s\n" "$cdate" "$ls" "$btype" >> "${scratchfile}.disk" # fi result=$(echo "$name" | cut -d',' -f3) if (( result >= rperiod )); then overwrite="false" else overwrite="true" fi if [[ "$btype" != "" ]]; then if [[ $result -eq 0 || $result -gt 899 ]]; then printf "%s\t%s\t%s\t%s\t%s\t%s\n" "$cdate" "$ls" "$result" "$overwrite" "FULL" "$id" >> "${scratchfile}.disk" else printf "%s\t%s\t%s\t%s\t%s\t%s\n" "$cdate" "$ls" "$result" "$overwrite" "INCREMENTAL" "$id" >> "${scratchfile}.disk" fi else if [[ -r "${scratchfile}.bkup" ]]; then # disks=$(grep " Backup,${iname},${result},Data.. " "${scratchfile}.bkup" | wc -l | cut -d' ' -f2) ### Check disks=$(grep " Backup,${iname},${result},Data.. " "${scratchfile}.bkup" | wc -l) (( disks++ )) else disks="1" fi if [[ $result -eq 0 || $result -gt 899 ]]; then printf "%s\t%s\t%s\t%s\t%s\t%s\n" "$cdate" "$ls" "$result" "$overwrite" "FULL" "$disks" >> "${scratchfile}.disk" else printf "%s\t%s\t%s\t%s\t%s\t%s\n" "$cdate" "$ls" "$result" "$overwrite" "INCREMENTAL" "$disks" >> "${scratchfile}.disk" fi fi done < <(cat "${scratchfile}.boot" | tailfromline2) result=$(filecheck -sl "${scratchfile}.disk") if [[ "$result" != "" ]]; then printf "\n%s\n" "Backups for Instance found:" if [[ "$btype" != "" ]]; then sort -t$'\t' -rk3 < "${scratchfile}.disk" > "$scratchfile" else mv -f "${scratchfile}.disk" "$scratchfile" fi if [[ "$monochrome" = false ]]; then select-table --import "$scratchfile" --export "${scratchfile}.disk" --page 30 --green "AVAILABLE" stat=$? else select-table --import "$scratchfile" --export "${scratchfile}.disk" --page 30 stat=$? fi else printf "\n%s\n" "No Backups for Instance found." fi else printf "\n%s\n" "No Backups for Instance found." fi fi return $stat } # Detach all blockvolumes from instance - function GetInstance has to be called in advance # allowed actions: detach, revert (re-attach), delete function DetachBlockvolumes() { local region=${1} local inst=${2} local action="$(ToLower "$3")" local volid="" local ls="" local name="" local avd="" local encrypted="" local dsize="" local attachedid="" local atype="" local device="" local ro="" local shareable="" local devstr="" local rostr="" local sharestr="" local stat=0 if [[ -r "${scratchfile}.idisk" ]]; then # echo ">>>>>" # cat "${scratchfile}.idisk" # echo "<<<<<" if [[ "$action" = "detach" ]]; then printf "\nDetaching original blockvolumes.\n" elif [[ "$action" = "revert" ]]; then printf "\nReverting action and attaching original blockvolumes.\n" elif [[ "$action" = "delete" ]]; then printf "\nDeleting original blockvolumes.\n" else return 98 fi stat=0 while IFS=$'\t' read -r volid ls name avd encrypted dsize attachedid atype device ro shareable; do if [[ "$ls" = "ATTACHED" && $stat -eq 0 ]]; then if [[ "$action" = "detach" ]]; then $oci compute volume-attachment detach --region "$region" --volume-attachment-id "$attachedid" \ --wait-for-state "DETACHED" --force stat=$? elif [[ "$action" = "revert" ]]; then if [[ "$device" != "null" ]]; then devstr="--device $device" fi if [[ "$ro" != "false" ]]; then rostr="--is-read-only true" fi if [[ "$ro" != "false" ]]; then sharestr="--is-shareable true" fi $oci compute volume-attachment attach --region "$region" --instance-id "$inst" --volume-id "$volid" --type "$atype" \ --display-name "$name" $devstr $rostr $sharestr --wait-for-state "ATTACHED" > "$scratchfile" stat=$? else # Delete $oci bv volume delete --region "$region" --volume-id "$volid" --wait-for-state "TERMINATED" --force stat=$? fi fi done < <(cat "${scratchfile}.idisk" | tailfromline2) fi return $stat } # Restore all blockvolumes from instance - function GetInstance and RestoreInstance has to be called in advance # allowed actions: detach, revert (re-attach), delete function RestoreBackupvolumes() { local region=${1} local inst=${2} local action="$(ToLower "$3")" local name="" local atype="" local backupid="" local volid="" local volname="" local avd="" local result="" local stat=99 if [[ -r "${scratchfile}.inst" ]]; then # "lifecycleState,id,compartmentId,displayName,shape,region,availabilityDomain,faultDomain" avd=$(grep "^$inst " "${scratchfile}.inst" | cut -d$'\t' -f7) if [[ -r "${scratchfile}.adisk" ]]; then # echo ">>>>>" # cat "${scratchfile}.adisk" # echo "<<<<<" if [[ "$action" = "create" ]]; then printf "\nCreating backup blockvolumes.\n" printf "" > "${scratchfile}.list" elif [[ "$action" = "attach" ]]; then printf "\nAttaching backup blockvolumes.\n" printf "" > "${scratchfile}.lis2" elif [[ "$action" = "revert" ]]; then printf "\nReverting action and detaching backup blockvolumes.\n" elif [[ "$action" = "delete" ]]; then printf "\nDeleting backup blockvolumes.\n" else return 98 fi stat=0 while IFS=$'\t' read -r name atype backupid volid; do if [[ "$atype" = "volume" && $stat -eq 0 ]]; then volname=$(echo "$name" | cut -d',' -f4) atype="paravirtualized" result="" if [[ "$action" = "create" ]]; then $oci bv volume create --region "$region" --volume-backup-id "$backupid" --availability-domain "$avd" --display-name "$volname" \ --wait-for-state "AVAILABLE" > "$scratchfile" stat=$? if (( stat == 0 )); then result=$(browse-json "id" --import "$scratchfile" --select 1 --quiet) printf "%s\t%s\n" "$name" "$result" >> "${scratchfile}.list" fi elif [[ "$action" = "attach" ]]; then # if [ "$device" != "null" ]; then # devstr="--device $device" # fi # # if [ "$ro" != "false" ]; then # rostr="--is-read-only true" # fi # # if [ "$ro" != "false" ]; then # sharestr="--is-shareable true" # fi if [[ -r "${scratchfile}.list" ]]; then result=$(grep "^$name " "${scratchfile}.list" | cut -d$'\t' -f2) fi if [[ "$result" != "" ]]; then $oci compute volume-attachment attach --region "$region" --instance-id "$inst" --volume-id "$result" --type "$atype" \ --display-name "$name" --wait-for-state "ATTACHED" > "$scratchfile" stat=$? else stat=97 fi if (( stat == 0 )); then result=$(browse-json "id" --import "$scratchfile" --select 1 --quiet) printf "%s\t%s\n" "$name" "$result" >> "${scratchfile}.lis2" fi elif [[ "$action" = "revert" ]]; then if [[ -r "${scratchfile}.lis2" ]]; then result=$(grep "^$name " "${scratchfile}.lis2" | cut -d$'\t' -f2) fi if [[ "$result" != "" ]]; then $oci compute volume-attachment detach --region "$region" --volume-attachment-id "$result" \ --wait-for-state "DETACHED" --force stat=$? else stat=97 fi else # Delete if [[ -r "${scratchfile}.list" ]]; then result=$(grep "^$name " "${scratchfile}.list" | cut -d$'\t' -f2) fi if [[ "$result" != "" ]]; then $oci bv volume delete --region "$region" --volume-id "$result" --wait-for-state "TERMINATED" --force stat=$? else stat=97 fi fi fi done < <(cat "${scratchfile}.adisk" | tailfromline2) fi fi return $stat } # Restart Instance function RestartInstance() { local region=${1} local inst=${2} StopInstance "$region" "$inst" stat=$? if (( stat == 0 )); then sleep 5 StartInstance "$region" "$inst" stat=$? fi } # Restore all attached disk of one instance specified with instance-id function RestoreInstance() { local region=${1} local inst=${2} local ils="" local icomp="" local iname="" local result="" local bootvol="" local bdate="" local bnum="" local id="" local ls="" local btype="" local id="" local name="" local comp="" local cdate="" local avd="" local rinst="" local volname="" local volid="" local newvolid="" local dsize="" local attachedid="" local encrypted="" local atype="" local device="" local ro="" local shareable="" local stat=0 local num1=0 local num2=0 ListBackups "$region" "$inst" stat=$? if (( stat == 0 )); then # "id,lifecycleState,compartmentId,displayName,shape,region,availabilityDomain,faultDomain" ils=$(grep "^$inst " "${scratchfile}.inst" | cut -d$'\t' -f2) icomp=$(grep "^$inst " "${scratchfile}.inst" | cut -d$'\t' -f3) iname=$(grep "^$inst " "${scratchfile}.inst" | cut -d$'\t' -f4 | tr ',' ';') if [[ "$ils" != "RUNNING" ]]; then printf "\n" errormsg 0 "Instance has lifecycleState '$ils'." fi result=$(head -n 1 "${scratchfile}.disk") if [[ "$result" != "" ]]; then bdate=$(echo "$result" | cut -d$'\t' -f1) ls=$(echo "$result" | cut -d$'\t' -f2) bnum=$(echo "$result" | cut -d$'\t' -f3) if [[ "$ls" = "AVAILABLE" ]]; then printf "\nRestore date: %s\n" "$bdate" printf "Restore number: %s\n" "$bnum" printf "type\tdescription\n" > "$scratchfile" printf "PART\tPartially restore disks\n" >> "$scratchfile" printf "FULL\tReplace current data disks\n" >> "$scratchfile" printf "CLONE\tRebuild instance from backup\n" >> "$scratchfile" select-table --import "$scratchfile" --export "${scratchfile}.disk" stat=$? if (( stat == 0 )); then result=$(head -n 1 "${scratchfile}.disk") if [[ "$result" != "" ]]; then btype=$(echo "$result" | cut -d$'\t' -f1) printf "\nRestore type: %s\n" "$btype" printf "Searching disks...\n" grep " ${bdate}$" "${scratchfile}.boot" > "${scratchfile}.bpol" # Find all Volume Backups for instance compartment # FindBackups "$region" "VolumeBackup" "$icomp" # Should be already done if [[ -r "${scratchfile}.bkup" ]]; then # Filter only Backups that are created with this script grep " Backup,${iname},...,Data.. " "${scratchfile}.bkup" > "${scratchfile}.disk" grep " ${bdate}$" "${scratchfile}.disk" >> "${scratchfile}.bpol" fi # printf "name\ttype\tid\tvolId\n" > "${scratchfile}.adisk" printf "name\ttype\n" > "${scratchfile}.adisk" while IFS=$'\t' read -r id ls name comp cdate; do if [[ "$id" != "" && "$ls" = "AVAILABLE" ]]; then bootvol=$(echo "$id" | grep "^ocid1\.bootvolumebackup\.") if [[ "$bootvol" != "" ]]; then atype="bootvolume" $oci bv boot-volume-backup get --region "$region" --boot-volume-backup-id "$id" > "$scratchfile" if (( stat == 0 )); then volid=$(browse-json "bootVolumeId" --select 1 --import "$scratchfile") else volid="" fi else atype="volume" $oci bv backup get --region "$region" --volume-backup-id "$id" > "$scratchfile" if (( stat == 0 )); then volid=$(browse-json "volumeId" --select 1 --import "$scratchfile") else volid="" fi fi printf "%s\t%s\t%s\t%s\n" "$name" "$atype" "$id" "$volid" >> "${scratchfile}.adisk" fi done < <(cat "${scratchfile}.bpol" | sort -t$'\t' -k3) case "$btype" in PART) select-table --import "${scratchfile}.adisk" --export "${scratchfile}.disk" stat=$? if (( stat == 0 )); then result=$(head -n 1 "${scratchfile}.disk") if [[ "$result" != "" ]]; then if [[ "$thisid" != "" ]]; then # Get infos from this instance (operator) rinst="$thisid" avd=$(InstanceInfo "/instance/availabilityDomain") else # Get infos from instance where backup was made rinst="$inst" avd=$(grep "^$inst " "${scratchfile}.inst" | cut -d$'\t' -f7) fi ls="" volid="" name=$(echo "$result" | cut -d$'\t' -f1) bootvol=$(echo "$result" | cut -d$'\t' -f2) id=$(echo "$result" | cut -d$'\t' -f3) printf "\nName: '%s'.\n" "$name" volname=$(echo "$name" | sed 's|^Backup,|Restore,|') if [[ "$bootvol" = "bootvolume" ]]; then echo "Restoring Bootvolume Copy" # $oci bv boot-volume-backup get --region "$region" --boot-volume-backup-id $id | norm-json --quiet $oci bv boot-volume create --region "$region" --boot-volume-backup-id "$id" --availability-domain "$avd" --display-name "$volname" \ --wait-for-state "AVAILABLE" > "$scratchfile" stat=$? if (( stat == 0 )); then echo "Attaching Bootvolume Copy" ls=$(browse-json "lifecycleState" --import "$scratchfile" --select 1 --quiet) volid=$(browse-json "id" --import "$scratchfile" --select 1 --quiet) fi else echo "Restoring Blockvolume Copy" # $oci bv backup get --region "$region" --volume-backup-id $id | norm-json --quiet $oci bv volume create --region "$region" --volume-backup-id "$id" --availability-domain "$avd" --display-name "$volname" \ --wait-for-state "AVAILABLE" > "$scratchfile" stat=$? if (( stat == 0 )); then echo "Attaching Blockvolume Copy" ls=$(browse-json "lifecycleState" --import "$scratchfile" --select 1 --quiet) volid=$(browse-json "id" --import "$scratchfile" --select 1 --quiet) fi fi if [[ "$ls" = "AVAILABLE" && "$volid" != "" ]]; then $oci compute volume-attachment attach --region "$region" --instance-id "$rinst" --volume-id "$volid" \ --type "paravirtualized" --display-name "$volname" --wait-for-state "ATTACHED" > "$scratchfile" stat=$? if (( stat == 0 )); then attachedid=$(browse-json "id" --select 1 --quiet --import "$scratchfile") echo "Ready to use." result=$(filecheck -x restore) if [[ "$result" != "" ]]; then # Invoke helper script 'restore' $result "$iname" stat=$? else confirm "\nDo you want to delete copy?" --yes "y/[yes]" --no "n/no" stat=$? fi if (( stat == 0 )); then printf "\nDeleting Copy\n" $oci compute volume-attachment detach --region "$region" --volume-attachment-id "$attachedid" \ --wait-for-state "DETACHED" --force stat=$? if (( stat == 0 )); then if [[ "$bootvol" = "bootvolume" ]]; then $oci bv boot-volume delete --region "$region" --boot-volume-id "$volid" --wait-for-state "TERMINATED" --force stat=$? else $oci bv volume delete --region "$region" --volume-id "$volid" --wait-for-state "TERMINATED" --force stat=$? fi if (( stat == 0 )); then echo "Copy deleted." fi fi fi else # Something went wrong - Delete created volume echo "Something went wrong - Deleting Copy" if [[ "$bootvol" = "bootvolume" ]]; then $oci bv boot-volume delete --region "$region" --boot-volume-id "$volid" --wait-for-state "TERMINATED" --force stat=$? else $oci bv volume delete --region "$region" --volume-id "$volid" --wait-for-state "TERMINATED" --force stat=$? fi if (( stat == 0 )); then echo "Copy deleted." fi fi fi fi fi ;; FULL) printf "\nDisks found:\n\n" print-table --import "${scratchfile}.adisk" num1=$(cat "${scratchfile}.adisk" | tailfromline2 | wc -l) stat=$(cat "${scratchfile}.iboot" | grep " ATTACHED " | wc -l) if [[ -r "${scratchfile}.idisk" ]]; then num2=$(cat "${scratchfile}.idisk" | grep " ATTACHED " | wc -l) num2=$(($num2 + $stat)) else # No block volumes attached num2=$stat fi printf "\n" if (( num1 != num2 )); then # stat=10 stat=0 errormsg $stat "Number of disks in backup do not match number of current attached disks." # return $stat # else # while IFS=$'\t' read -r name atype id volid; do # if [ "$atype" = "bootvolume" ]; then # result=$(grep "^$volid " "${scratchfile}.iboot") # else # result=$(grep "^$volid " "${scratchfile}.idisk") # fi # # if [ "$result" = "" ]; then # stat=10 # errormsg $stat "Backup '$name' does not match with current disks." # return $stat # fi # done < <(cat "${scratchfile}.adisk" | tailfromline2) fi confirm "Do you really want to replace all data disks?" --yes "y/yes" --no "n/[no]" stat=$? if (( stat == 0 )); then if [[ "$ils" != "RUNNING" ]]; then StartInstance "$region" "$inst" stat=$? fi # It is not possible to replace the bootvolume at this time # printf "\nReplacing bootvolume.\n" # while IFS=$'\t' read -r volid ls name avd encrypted dsize; do # if [ "$ls" = "ATTACHED" ]; then # # echo "volid: $volid - name: $name - avd: $avd - encrypted: $encrypted - dsize: $dsize" # $oci compute boot-volume-attachment detach --region "$region" --boot-volume-attachment-id "$inst" \ # --wait-for-state "DETACHED" --force # stat=$? # # if [ $stat -eq 0 ]; then # result=$(grep " $volid$" "${scratchfile}.adisk") # if [ "$result" != "" ]; then # volname=$(echo "$result" | cut -d$'\t' -f1) # bootvol=$(echo "$result" | cut -d$'\t' -f2) # id=$(echo "$result" | cut -d$'\t' -f3) # # $oci bv boot-volume create --region "$region" --boot-volume-backup-id "$id" --availability-domain "$avd" --display-name "$name" \ # --wait-for-state "AVAILABLE" > "$scratchfile" # stat=$? # # if [ $stat -eq 0 ]; then # ls=$(browse-json "lifecycleState" --import "$scratchfile" --select 1 --quiet) # newvolid=$(browse-json "id" --import "$scratchfile" --select 1 --quiet) # # if [ "$ls" = "AVAILABLE" -a "$volid" != "" ]; then # $oci compute boot-volume-attachment attach --region "$region" --instance-id "$inst" --boot-volume-id "$newvolid" \ # --display-name "$volname" --wait-for-state "ATTACHED" > "$scratchfile" # stat=$? # # if [ $stat -eq 0 ]; then # $oci bv boot-volume delete --region "$region" --boot-volume-id "$volid" --wait-for-state "TERMINATED" # stat=$? # fi # fi # fi # fi # fi # fi # done < <(cat "${scratchfile}.iboot" | tailfromline2) DetachBlockvolumes "$region" "$inst" "detach" stat=$? if (( stat == 0 )); then RestoreBackupvolumes "$region" "$inst" "create" stat=$? if (( stat == 0 )); then RestoreBackupvolumes "$region" "$inst" "attach" stat=$? if (( stat != 0 )); then RestoreBackupvolumes "$region" "$inst" "revert" stat=$? RestoreBackupvolumes "$region" "$inst" "delete" stat=$? DetachBlockvolumes "$region" "$inst" "revert" stat=$? fi else RestoreBackupvolumes "$region" "$inst" "delete" stat=$? DetachBlockvolumes "$region" "$inst" "revert" stat=$? fi else DetachBlockvolumes "$region" "$inst" "revert" stat=$? fi RestartInstance "$region" "$inst" if (( stat == 0 )); then confirm "\nEverything ok?" --yes "y/[yes]" --no "n/no" stat=$? if (( stat == 0 )); then DetachBlockvolumes "$region" "$inst" "delete" stat=$? else RestoreBackupvolumes "$region" "$inst" "revert" stat=$? RestoreBackupvolumes "$region" "$inst" "delete" stat=$? DetachBlockvolumes "$region" "$inst" "revert" stat=$? RestartInstance "$region" "$inst" fi fi if (( stat == 0 )); then printf "\nOperation successful.\n" else printf "\nOperation failed.\n" fi fi ;; CLONE) printf "\nNot yet implemented - exiting.\n\n" ;; esac fi fi fi fi fi return 0 } # Restore all attached disk of one instance specified with instance-id function Maintenance() { local region=${1} local inst=${2} ListBackups "$region" "$inst" "managed" stat=$? if (( stat == 0 )); then printf "\nResult:\n\n" cat "${scratchfile}.disk" fi } # Add or delete VIP # Docs: https://docs.oracle.com/en-us/iaas/Content/Network/Tasks/managingIPaddresses.htm function ToggleVIP() { local proc=${1} local region=${2} local inst=${3} local vip=${4} local stat=0 local loc_vnic_id="" local display_name="" local private_ip="" local vnic_hostname_label="" local oldvip="" local result="" # Check if ip is valid vip=$(CheckIP "$vip") if [[ "$vip" = "" ]]; then stat=6 errormsg $stat "($progstr) Instance '$inst': Unexpected error." "No valid IPV4" else if [[ "$proc" = "add" ]]; then printf "\nAdding VIP to instance.\n" else printf "\nDeleting VIP from instance.\n" fi # Get attached vnics from instance $oci compute instance list-vnics --all --region $region --instance-id $inst \ | convert-json "id,lifecycleState,isPrimary,displayName,privateIp,hostnameLabel" --quiet --output tsv --noheader > "${scratchfile}.vnic" stat=$? if (( stat > 0 )); then stat=6 errormsg $stat "($progstr) Instance '$inst': Unexpected error." else result=$(grep " AVAILABLE true " "${scratchfile}.vnic") # Fetching primary vnic if [[ "$result" = "" ]]; then stat=6 errormsg $stat "($progstr) Instance '$inst': Unexpected error." "No primary VNIC" else loc_vnic_id=$(echo "$result" | cut -d$'\t' -f1) display_name=$(echo "$result" | cut -d$'\t' -f4) private_ip=$(echo "$result" | cut -d$'\t' -f5) vnic_hostname_label=$(echo "$result" | cut -d$'\t' -f6) $oci network private-ip list --all --region $region --vnic-id "$loc_vnic_id" | convert-json "isPrimary,displayName,ipAddress" --quiet \ --output tsv --noheader > "$scratchfile" result=$(grep "No VIP " "$scratchfile") # Fetching VIP if [[ "$result" != "" ]]; then oldvip=$(echo "$result" | cut -d$'\t' -f3) fi if [[ "$proc" = "add" ]]; then if [[ "$oldvip" != "" ]]; then stat=5 errormsg $stat "($progstr) Instance '$inst' already has a VIP '$oldvip'." else $oci network vnic assign-private-ip --region $region --vnic-id "$loc_vnic_id" --ip-address "$vip" --display-name "VIP" \ --hostname-label "vip-$vnic_hostname_label" > "$scratchfile" # Filter out JSON output stat=$? if (( stat == 0 )); then echo "Assigned IP address $vip to VNIC $loc_vnic_id" result=$(filecheck -x virtualip) if [[ "$result" != "" ]]; then $result "add" "$display_name" "$vip" stat=$? if (( stat > 0 )); then errormsg $stat "($progstr) Instance '$inst': Unexpected error." "Could not add VIP to Interface" fi else errormsg 0 "($progstr) Instance '$inst': Script 'virtualip' not found." fi fi fi else if [[ "$oldvip" = "" ]]; then stat=5 errormsg $stat "($progstr) Instance '$inst' does not have a VIP." else if [[ "$oldvip" != "$vip" ]]; then stat=5 errormsg $stat "($progstr) Instance '$inst' VIP '$oldvip' is different from specified '$vip'." else $oci network vnic unassign-private-ip --region $region --vnic-id "$loc_vnic_id" --ip-address "$vip" stat=$? if (( stat == 0 )); then # echo "Unassigned IP address $vip from VNIC $loc_vnic_id" result=$(filecheck -x virtualip) if [[ "$result" != "" ]]; then $result "delete" "$display_name" "$vip" stat=$? if (( stat > 0 )); then errormsg $stat "($progstr) Instance '$inst': Unexpected error." "Could not delete VIP from Interface" fi else errormsg 0 "($progstr) Instance '$inst': Script 'virtualip' not found." fi fi fi fi fi fi fi fi return $stat } # Open or close public access function TogglePublicIP() { local proc=${1} local region=${2} local inst=${3} local stat=0 local loc_vnic_id="" local compartment_id="" local public_ip="" local vnic_display_name="" local result="" if [[ "$proc" = "close" ]]; then printf "\nClosing instance.\n" else printf "\nOpening instance.\n" fi # Get attached vnics from instance $oci compute instance list-vnics --all --region $region --instance-id $inst \ | convert-json "id,lifecycleState,isPrimary,compartmentId,publicIp,displayName" --quiet --output tsv --noheader > "${scratchfile}.vnic" stat=$? if (( stat > 0 )); then stat=6 errormsg $stat "($progstr) Instance '$inst': Unexpected error." else result=$(grep " AVAILABLE true " "${scratchfile}.vnic") # Fetching primary vnic if [[ "$result" = "" ]]; then stat=6 errormsg $stat "($progstr) Instance '$inst': Unexpected error." "No primary VNIC" else loc_vnic_id=$(echo "$result" | cut -d$'\t' -f1) compartment_id=$(echo "$result" | cut -d$'\t' -f4) public_ip=$(echo "$result" | cut -d$'\t' -f5) vnic_display_name=$(echo "$result" | cut -d$'\t' -f6) if [[ "$proc" = "close" ]]; then if [[ "$public_ip" = "null" ]]; then stat=5 errormsg $stat "($progstr) Instance '$inst' already closed." fi else if [[ "$public_ip" != "null" ]]; then stat=5 errormsg $stat "($progstr) Instance '$inst' already opened." fi fi if (( stat == 0 )); then # Find the primary private ip $oci network private-ip list --all --region "$region" --vnic-id "$loc_vnic_id" \ | convert-json "id,isPrimary" --quiet --output tsv > "$scratchfile" private_ip_id=$(grep " true$" "$scratchfile" | cut -d$'\t' -f1) # Make a list of all reserved public ip $oci network public-ip list --all --region $region -c $compartment_id --scope REGION \ | convert-json "id,lifecycleState,lifetime,ipAddress,displayName,privateIpId" --quiet --output tsv > "${scratchfile}.list" result=$(filecheck -sl "${scratchfile}.list") if [[ "$result" = "" ]]; then stat=6 errormsg $stat "($progstr) Instance '$inst': Unexpected error." "Could not get list of reserved IPs" else if [[ "$proc" = "close" ]]; then grep " $public_ip " "${scratchfile}.list" > "$scratchfile" # Fetching public-ip from list else grep " $private_ip_id " "${scratchfile}.list" > "$scratchfile" # Fetching private-ip-id (stored in display-name) from list fi result=$(filecheck -s "$scratchfile") if [[ "$result" = "" ]]; then stat=6 errormsg $stat "($progstr) Instance '$inst': Unexpected error." "Could not get required infos from public ip list" else public_ip_id=$(head -n 1 "$scratchfile" | cut -d$'\t' -f1) lifecycle_state=$(head -n 1 "$scratchfile" | cut -d$'\t' -f2) lifetime=$(head -n 1 "$scratchfile" | cut -d$'\t' -f3) ip_address=$(head -n 1 "$scratchfile" | cut -d$'\t' -f4) display_name=$(head -n 1 "$scratchfile" | cut -d$'\t' -f5) check=$(head -n 1 "$scratchfile" | cut -d$'\t' -f6) if [[ "$proc" = "close" ]]; then if [[ "$lifetime" != "RESERVED" || "$lifecycle_state" != "ASSIGNED" || "$private_ip_id" != "$check" ]]; then stat=6 errormsg $stat "($progstr) Cannot detach ip '$ip_address': IP may is not a reserved ip." fi else if [[ "$lifetime" != "RESERVED" || "$lifecycle_state" != "AVAILABLE" || "$check" != "null" ]]; then stat=6 errormsg $stat "($progstr) Cannot attach ip '$ip_address': IP may is not a reserved ip." fi fi if (( stat == 0 )); then if [[ "$proc" = "close" ]]; then # Detach the public ip and rename display-name to private_ip_id $oci network public-ip update --region $region --public-ip-id $public_ip_id --display-name "$private_ip_id" --private-ip-id "" > "$scratchfile" stat=$? else # Attach the public ip and rename display-name to vnic_display_name $oci network public-ip update --region $region --public-ip-id $public_ip_id --display-name "$vnic_display_name" --private-ip-id "$private_ip_id" > "$scratchfile" stat=$? fi result=$(filecheck -sl "$scratchfile") if [[ $stat -ne 0 || "$result" = "" ]]; then stat=6 errormsg $stat "($progstr) Instance '$inst': Unexpected error." else result=$(cat "$scratchfile" | browse-json privateIpId --select 1 --quiet) if [[ "$proc" = "close" ]]; then if [[ "$result" = "$private_ip_id" ]]; then echo "Removing of public ip '$ip_address' from instance '$inst' failed." stat=6 else echo "Removed public ip '$ip_address' from instance '$inst'." fi else if [[ "$result" != "$private_ip_id" ]]; then echo "Attaching of public ip '$ip_address' to instance '$inst' failed." stat=6 else echo "Attached public ip '$ip_address' to instance '$inst'." fi fi fi fi fi fi fi fi fi return $stat } # Preset exitcode=0 force=false monochrome=false namespace="" region_str="" instance_id="" instance_type="" thisid="" # Instance ID of this instance (on which script is running on) param="" optparam="" result="" # Download Resource file Initialize # Check parameters: Loop until all parameters are used up while [[ $# -gt 0 ]]; do pname=${1} case "$pname" in -i | --id) shift if [[ "$1" != "" ]]; then instance_id="$(ToLower "$1")" result=$(ConvertOCID "$instance_id" | tail -n 1) if [[ "$result" = "" ]]; then errstr="Invalid instance ocid '$instance_id' specified after parameter '$pname'." else instance_type=$(echo "$result" | cut -d$'\t' -f2) region_str=$(echo "$result" | cut -d$'\t' -f4) if [[ "$region" = "" ]]; then region=$HOME_REGION fi fi shift else errstr="Please specify an instance ocid after parameter '$pname'." fi ;; -v | --version) shift showversion=true ;; -h | --help) shift showhelp=true ;; -f | --force) shift force=true ;; -m | --monochrome) shift monochrome=true ;; *) shift if [[ "$pname" == -* ]]; then # Options begin with '-' errstr="Unknown option '$pname'." else if [[ "$param" = "" ]]; then param="$(ToLower "$pname")" else if [[ "$optparam" = "" ]]; then optparam="$(ToLower "$pname")" else errstr="Unknown additional parameter: '$pname'." fi fi fi esac done # Plausibility check if [[ "$param" = "" ]]; then if [[ "$instance_id" != "" ]]; then param="show" else param="list" fi fi # Display help or error message DisplayHelp ### Main # Check if OCI is configured oci=$(filecheck -x oci) if [[ "$oci" = "" ]]; then exitcode=97 errormsg $exitcode "($progstr) OCI CLI 'oci' not in PATH." else echo "n" | $oci os ns get > "$scratchfile" 2>&1 stat=$? if (( stat == 0 )); then # namespace=$(browse-json "data" --import "$scratchfile" --select 1 --quiet) namespace=$(cat "$scratchfile" | norm-json --select 1 --quiet) if [[ "$namespace" = "" ]]; then exitcode=97 errormsg $exitcode "($progstr) OCI CLI 'oci' not configured correct." fi else exitcode=97 errormsg $exitcode "($progstr) OCI CLI 'oci' not configured correct. Status: '$stat'." fi fi if (( exitcode == 0 )); then if [[ "$param" = "list" ]]; then if [[ "$monochrome" = false ]]; then oci-object --type instance,dbsystem else oci-object --monochrome --type instance,dbsystem fi else if [[ "$instance_id" = "" ]]; then exitcode=3 errormsg $exitcode "($progstr) No instance-id specified." else # Get instance id of this instance thisid=$(InstanceInfo "/instance/id") case "$param" in show) case "$instance_type" in instance) GetInstance "$region_str" "$instance_id" ;; dbsystem) GetDBSystem "$region_str" "$instance_id" ;; *) exitcode=2 errormsg $exitcode "($progstr) List for instance type '$instance_type' not yet implemented." esac ;; status) DisplayState "$region_str" "$instance_id" exitcode=$? ;; start) if [[ "$instance_type" = "instance" ]]; then StartInstance "$region_str" "$instance_id" exitcode=$? else exitcode=2 errormsg $exitcode "($progstr) Action '$param' not allowed for instance type '$instance_type'." fi ;; stop) if [[ "$instance_id" = "$thisid" ]]; then exitcode=2 errormsg $exitcode "($progstr) Action '$param' not allowed for instance on which this script is running on." else if [[ "$instance_type" = "instance" ]]; then StopInstance "$region_str" "$instance_id" exitcode=$? else exitcode=2 errormsg $exitcode "($progstr) Action '$param' not allowed for instance type '$instance_type'." fi fi ;; fullbackup) if [[ "$instance_id" = "$thisid" ]]; then exitcode=2 errormsg $exitcode "($progstr) Action '$param' not allowed for instance on which this script is running on." else if [[ "$instance_type" = "instance" ]]; then if [[ "$optparam" = "" ]]; then num=0 else num=$(CheckNumber "$optparam") fi BackupInstance "$region_str" "$instance_id" "$num" exitcode=$? else exitcode=2 errormsg $exitcode "($progstr) Action '$param' not allowed for instance type '$instance_type'." fi fi ;; backup) if [[ "$instance_id" = "$thisid" ]]; then exitcode=2 errormsg $exitcode "($progstr) Action '$param' not allowed for instance on which this script is running on." else if [[ "$instance_type" = "instance" ]]; then BackupInstance "$region_str" "$instance_id" exitcode=$? else exitcode=2 errormsg $exitcode "($progstr) Action '$param' not allowed for instance type '$instance_type'." fi fi ;; restore) if [[ "$instance_id" = "$thisid" ]]; then exitcode=2 errormsg $exitcode "($progstr) Action '$param' not allowed for instance on which this script is running on." else if [[ "$instance_type" = "instance" ]]; then RestoreInstance "$region_str" "$instance_id" exitcode=$? else exitcode=2 errormsg $exitcode "($progstr) Action '$param' not allowed for instance type '$instance_type'." fi fi ;; maintenance) if [[ "$instance_type" = "instance" ]]; then Maintenance "$region_str" "$instance_id" exitcode=$? else exitcode=2 errormsg $exitcode "($progstr) Action '$param' not allowed for instance type '$instance_type'." fi ;; open | close) if [[ "$instance_type" = "instance" ]]; then TogglePublicIP "$param" "$region_str" "$instance_id" exitcode=$? else exitcode=2 errormsg $exitcode "($progstr) Action '$param' not allowed for instance type '$instance_type'." fi ;; add | delete) if [[ "$instance_type" = "instance" ]]; then if [[ "$optparam" = "" ]]; then exitcode=95 errormsg $exitcode "($progstr) Action '$param' needs additional parameter (virtual ip)." else ToggleVIP "$param" "$region_str" "$instance_id" "$optparam" exitcode=$? fi else exitcode=2 errormsg $exitcode "($progstr) Action '$param' not allowed for instance type '$instance_type'." fi ;; *) exitcode=2 errormsg $exitcode "($progstr) Unknown action '$param'." esac fi fi fi # Cleanup and exit Cleanup exit $exitcode