#!/bin/bash # # Author: Georg Voell - georg.voell@standby.cloud # Version: @(#)oci-instance 3.2.0 12.08.2024 (c)2024 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 # # Find executable bash library and source it lib=`which lib.bash 2>/dev/null | sed 's|^no 'lib.bash' in .*||'` if [ "$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}.mysf 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 -lt $min ]; then number=$min fi fi fi if [ "$max" != "" ]; then result=`echo "$max" | grep '^[0123456789]*$'` if [ "$result" != "" ]; then if [ $number -gt $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 -o $ip2 -gt 255 -o $ip3 -gt 255 -o $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 -eq 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 -eq 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 -a "$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 -eq 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 -eq 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 -eq 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 -eq 0 ]; then prim=`browse-json isPrimary --import $scratchfile --select 1 --quiet | sed 's|^true$|Yes|'| sed 's|^false$|No|'` privip=`browse-json privateIp --import $scratchfile --select 1 --quiet | sed 's|^null$|None|'` pubip=`browse-json publicIp --import $scratchfile --select 1 --quiet | sed 's|^null$|None|'` else prim="" privip="None" pubip="None" 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 -eq 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 -eq 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 -eq 0 ]; then convert-json "id,lifecycleState,displayName,isPrimary,publicIp,privateIp,ipName,hostnameLabel" --import $scratchfile --quiet --output tsv > ${scratchfile}.vnic stat=$? if [ $stat -eq 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" != "" -a "$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 -eq 0 ]; then convert-json "bootVolumeId,lifecycleState,displayName,isPvEncryptionInTransitEnabled" --import $scratchfile --quiet \ --output tsv --noheader > ${scratchfile}.boot stat=$? if [ $stat -eq 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" != "" -a "$ls" = "ATTACHED" ]; then $oci bv boot-volume get --region "$region" --boot-volume-id "$id" > $scratchfile stat=$? if [ $stat -eq 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 -eq 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 -eq 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" != "" -a "$ls" = "ATTACHED" ]; then $oci bv volume get --region "$region" --volume-id "$id" > $scratchfile stat=$? if [ $stat -eq 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 -eq 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" -o "$state" = "AVAILABLE" -o "$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 -eq 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 -eq 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 -eq 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=`echo "$ls" | toupper` # 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" -a "$force" = false ]; then errormsg $stat "($progstr) Instance '$inst' stopped. Backup canceled." else # Get details of instance GetInstance "$region" "$inst" stat=$? if [ $stat -eq 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 -eq 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" != "" -a "$ls" = "ATTACHED" ]; then if [ $day -eq 0 -o $day -gt 899 ]; then btype="FULL" else btype="INCREMENTAL" fi deleteid="" let 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 -eq 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 -eq 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" != "" -a "$ls" = "ATTACHED" ]; then if [ $day -eq 0 -o $day -gt 899 ]; then btype="FULL" else btype="INCREMENTAL" fi deleteid="" let 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 -eq 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 -eq 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=`echo "$3" | tolower` # 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 -eq 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 -ge $rperiod ]; then overwrite="No" else overwrite="Yes" fi if [ "$btype" != "" ]; then if [ $result -eq 0 -o $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` let disks++ else disks="1" fi if [ $result -eq 0 -o $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 cat ${scratchfile}.disk | sort -t$'\t' -rk3 > $scratchfile else mv -f ${scratchfile}.disk $scratchfile fi if [ "$monochrome" = false ]; then select-table $scratchfile ${scratchfile}.disk --page 30 --green "AVAILABLE" stat=$? else select-table $scratchfile ${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=`echo "$3" | tolower` 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" -a $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" != "None" ]; then devstr="--device $device" fi if [ "$ro" != "No" ]; then rostr="--is-read-only true" fi if [ "$ro" != "No" ]; 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=`echo "$3" | tolower` 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" -a $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 -eq 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" != "None" ]; then # devstr="--device $device" # fi # # if [ "$ro" != "No" ]; then # rostr="--is-read-only true" # fi # # if [ "$ro" != "No" ]; 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 -eq 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 -eq 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 -eq 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 $scratchfile ${scratchfile}.disk stat=$? if [ $stat -eq 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" != "" -a "$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 -eq 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 -eq 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 ${scratchfile}.adisk ${scratchfile}.disk stat=$? if [ $stat -eq 0 ]; then result=`head -n 1 ${scratchfile}.disk` if [ "$result" != "" ]; then if [ "$thisid" != "" -a -r "${scratchfile}.mysf" ]; then # Get infos from this instance (operator) rinst="$thisid" avd=`browse-json "availabilityDomain" --select 1 --import ${scratchfile}.mysf` 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 -eq 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 -eq 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" -a "$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 -eq 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 -eq 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 -eq 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 -eq 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 -eq 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 -ne $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 -eq 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 -eq 0 ]; then RestoreBackupvolumes "$region" "$inst" "create" stat=$? if [ $stat -eq 0 ]; then RestoreBackupvolumes "$region" "$inst" "attach" stat=$? if [ $stat -ne 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 -eq 0 ]; then confirm "\nEverything ok?" --yes "y/[yes]" --no "n/no" stat=$? if [ $stat -eq 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 -eq 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 -eq 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 -gt 0 ]; then stat=6 errormsg $stat "($progstr) Instance '$inst': Unexpected error." else result=`grep " AVAILABLE Yes " ${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 -eq 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 -gt 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 -eq 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 -gt 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 -gt 0 ]; then stat=6 errormsg $stat "($progstr) Instance '$inst': Unexpected error." else result=`grep " AVAILABLE Yes " ${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" = "None" ]; then stat=5 errormsg $stat "($progstr) Instance '$inst' already closed." fi else if [ "$public_ip" != "None" ]; then stat=5 errormsg $stat "($progstr) Instance '$inst' already opened." fi fi if [ $stat -eq 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 " Yes$" $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" -o "$lifecycle_state" != "ASSIGNED" -o "$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" -o "$lifecycle_state" != "AVAILABLE" -o "$check" != "None" ]; then stat=6 errormsg $stat "($progstr) Cannot attach ip '$ip_address': IP may is not a reserved ip." fi fi if [ $stat -eq 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 -o "$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 } function GetThisInstanceInfos() { local result="" local stat=0 transfer --quiet --auth http://169.254.169.254/opc/v2/instance | norm-json --quiet > ${scratchfile}.mysf stat=$? if [ $stat -ne 0 ]; then # try with v1 transfer --quiet http://169.254.169.254/opc/v1/instance | norm-json --quiet > ${scratchfile}.mysf stat=$? fi if [ $stat -eq 0 -a -r "${scratchfile}.mysf" ]; then result=`browse-json "id" --select 1 --import ${scratchfile}.mysf` result=`echo "$result" | grep '^ocid1\..*\.oc1\.'` fi echo "$result" } # 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=`echo "$1" | tolower` 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 paramck=`echo "$pname" | grep '^-'` # Keys don't begin with '-' if [ "$paramck" != "" ]; then errstr="Unknown option '$pname'." else if [ "$param" = "" ]; then param=`echo "$pname" | tolower` else if [ "$optparam" = "" ]; then optparam=`echo "$pname" | tolower` else errstr="Unknown additional parameter: '$pname'." fi fi fi esac done # Plausibility check if [ "$param" = "" ]; then param="list" 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 -eq 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 -eq 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=`GetThisInstanceInfos` 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