๐ OCI Multi-Region Compute Host Lifecycle Inventory Utility
6/1/2021
๐ 1. Description & Overview
Managing a large OCI tenancy with multiple compartments and regions requires continuous visibility into instance lifecycle states for governance, audit, and cost optimization.
๐ก๏ธ Strict API Rate-Limiting Guardrail
To prevent API throttling (HTTP 429 TooManyRequests), tenancy quota exhaustion, and execution timeouts:
[!CAUTION] API Guardrail Rule: The script enforces a MINIMUM of 1 region and a MAXIMUM of 3 regions per execution.
- If
0regions or more than 3 regions are provided, the script immediately aborts execution before making any compute or compartment API requests.
๐ 2. Required IAM Policies
To run this inventory tool using either Instance Principals (recommended for automated VMs) or User API Keys:
Option A: Instance Principal Setup (Recommended)
- Dynamic Group:
InventoryCollectorDynamicGroupANY {instance.id = 'ocid1.instance.oc1.iad.your_master_vm_ocid'} - IAM Policy Statements (Scoped to Tenancy Root):
Allow dynamic-group InventoryCollectorDynamicGroup to read instances in tenancy Allow dynamic-group InventoryCollectorDynamicGroup to read compartments in tenancy
Option B: User / Group Policy Setup
If executing via CLI configured with user credentials (~/.oci/config):
Allow group AuditInventoryGroup to read instances in tenancy
Allow group AuditInventoryGroup to read compartments in tenancy
๐ ๏ธ 3. Prerequisites & Setup
- Install OCI CLI:
sudo bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)" - Export Path:
Ensure
ociis in your environmentPATH:export PATH="/usr/local/bin:/usr/bin:/bin:${PATH}" - Python 3: Ensure Python 3 is installed for stream JSON parsing.
Key Guardrail Implementation & Features
Strict Region Limit Guardrail (1 to 3 Regions): The script inspects the input region parameters before making any OCI API calls:
REGION_COUNT=${#REGIONS[@]}
if [ "${REGION_COUNT}" -lt 1 ] || [ "${REGION_COUNT}" -gt 3 ]; then
echo "[-] STRICT GUARDRAIL VIOLATION: Execution Aborted!"
echo " Reason : Script permits MINIMUM 1 region and MAXIMUM 3 regions per execution."
echo " Passed : ${REGION_COUNT} region(s) [${REGIONS[*]:-None}]"
echo " Purpose : Prevents OCI API rate-limit overload and tenancy throttling."
exit 1
fi
Tenancy-Wide Discovery Across All Compartments:
- Recursively queries all active compartments in the tenancy (โcompartment-id-in-subtree true) for each allowed region (up to 3).
Comprehensive Lifecycle Tracking:
- Categorizes compute hosts by state (RUNNING, STOPPED, TERMINATED, PROVISIONING), shape, OCPU/RAM specs, Availability Domain, and Compartment ownership.
Dual Output Artifacts:
- CSV File: oci_compute_inventory_YYYYMMDD_HHMMSS.csv for Excel/BI reporting.
- Text Summary Report: oci_compute_inventory_report_YYYYMMDD_HHMMSS.txt for console logs and metrics auditing.
Verified Guardrail Behavior
Test 1: Passing > 3 Regions (Triggers Execution Abort)
$ ./oci_compute_inventory.sh us-ashburn-1 us-phoenix-1 eu-frankfurt-1 uk-london-1
=======================================================================
OCI COMPUTE HOST LIFECYCLE INVENTORY UTILITY
=======================================================================
[+] Specified Regions Count : 4
[+] Specified Regions List : us-ashburn-1 us-phoenix-1 eu-frankfurt-1 uk-london-1
=======================================================================
[-] STRICT GUARDRAIL VIOLATION: Execution Aborted!
Reason : Script permits MINIMUM 1 region and MAXIMUM 3 regions per execution.
Passed : 4 region(s) [us-ashburn-1 us-phoenix-1 eu-frankfurt-1 uk-london-1]
Purpose : Prevents OCI API rate-limit overload and tenancy throttling.
Test 2: Passing 2 Regions (Valid Execution Range)
./oci_compute_inventory.sh us-ashburn-1 us-phoenix-1
Required IAM Policy Statements
To allow your Master Management VM to query compute instances tenancy-wide via Instance Principals:
Allow dynamic-group InventoryCollectorDynamicGroup to read instances in tenancy
Allow dynamic-group InventoryCollectorDynamicGroup to read compartments in tenancy
๐ 4. Full Bash Script (oci_compute_inventory.sh)
Save the script as oci_compute_inventory.sh:
#!/usr/bin/env bash
# ==============================================================================
# Script: oci_compute_inventory.sh
# Purpose: Multi-Region & Multi-Compartment Compute Host Lifecycle Inventory
# Strict Guardrail: Enforces MINIMUM 1 region and MAXIMUM 3 regions per execution
# to prevent OCI API rate-limiting / throttling overload.
# ==============================================================================
set -euo pipefail
# ------------------------------------------------------------------------------
# ENVIRONMENT & CRON PATH SETUP
# ------------------------------------------------------------------------------
export PATH="/usr/local/bin:/usr/bin:/bin:${PATH:-}"
# Configuration Defaults
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
OUTPUT_CSV="oci_compute_inventory_${TIMESTAMP}.csv"
OUTPUT_REPORT="oci_compute_inventory_report_${TIMESTAMP}.txt"
OCI_AUTH_TYPE="${OCI_AUTH_TYPE:-instance_principal}" # "instance_principal" or "api_key"
TENANCY_OCID="${TENANCY_OCID:-}"
# Parse Input Regions from Arguments
REGIONS=()
for arg in "$@"; do
if [[ "$arg" != -* ]]; then
REGIONS+=("$arg")
fi
done
# ------------------------------------------------------------------------------
# STRICT GUARDRAIL CHECK: MIN 1 REGION, MAX 3 REGIONS
# ------------------------------------------------------------------------------
REGION_COUNT=${#REGIONS[@]}
echo "======================================================================="
echo " OCI COMPUTE HOST LIFECYCLE INVENTORY UTILITY "
echo "======================================================================="
echo "[+] Specified Regions Count : ${REGION_COUNT}"
echo "[+] Specified Regions List : ${REGIONS[*]:-None}"
echo "======================================================================="
if [ "${REGION_COUNT}" -lt 1 ] || [ "${REGION_COUNT}" -gt 3 ]; then
echo ""
echo "[-] STRICT GUARDRAIL VIOLATION: Execution Aborted!"
echo " Reason : Script permits MINIMUM 1 region and MAXIMUM 3 regions per execution."
echo " Passed : ${REGION_COUNT} region(s) [${REGIONS[*]:-None}]"
echo " Purpose : Prevents OCI API rate-limit overload and tenancy throttling."
echo ""
echo "Usage Examples:"
echo " $0 us-ashburn-1"
echo " $0 us-ashburn-1 us-phoenix-1"
echo " $0 us-ashburn-1 us-phoenix-1 eu-frankfurt-1"
echo ""
exit 1
fi
# Pre-flight Check: OCI CLI binary existence
if ! command -v oci &>/dev/null; then
echo "[-] ERROR: OCI CLI binary ('oci') not found in PATH." >&2
exit 1
fi
AUTH_FLAG=""
if [ "${OCI_AUTH_TYPE}" == "instance_principal" ]; then
AUTH_FLAG="--auth instance_principal"
fi
# Retrieve Tenancy OCID if not provided
if [ -z "${TENANCY_OCID}" ]; then
echo "[+] Retrieving Tenancy OCID..."
TENANCY_OCID=$(oci iam compartment list ${AUTH_FLAG} --all --query "data[0].\"compartment-id\"" --output raw 2>/dev/null || echo "")
fi
# ------------------------------------------------------------------------------
# STEP 1: FETCH ALL COMPARTMENTS IN TENANCY
# ------------------------------------------------------------------------------
echo "[+] Step 1: Fetching all active compartments across the tenancy..."
COMPARTMENT_JSON=$(oci iam compartment list \
${AUTH_FLAG} \
--compartment-id-in-subtree true \
--access-level ACCESSIBLE \
--all \
--query "data[?\"lifecycle-state\"=='ACTIVE'].{id: id, name: name}" \
--output json 2>/dev/null || echo "[]")
COMPARTMENT_COUNT=$(echo "${COMPARTMENT_JSON}" | grep -c '"id"' || echo 0)
echo " -> Located ${COMPARTMENT_COUNT} active compartment(s)."
# Create CSV Header
echo "Region,Compartment_Name,Compartment_OCID,Instance_Name,Instance_OCID,Lifecycle_State,Shape,OCPU_Count,RAM_GB,Availability_Domain,Time_Created" > "${OUTPUT_CSV}"
# Create Report Header
{
echo "======================================================================="
echo " OCI COMPUTE LIFECYCLE INVENTORY REPORT "
echo " Date of Execution : $(date)"
echo " Target Regions : ${REGIONS[*]}"
echo " Total Compartments: ${COMPARTMENT_COUNT}"
echo "======================================================================="
echo ""
} > "${OUTPUT_REPORT}"
TOTAL_INSTANCES=0
RUNNING_COUNT=0
STOPPED_COUNT=0
OTHER_COUNT=0
# ------------------------------------------------------------------------------
# STEP 2: ITERATE OVER ALLOWED REGIONS (MAX 3) AND COMPARTMENTS
# ------------------------------------------------------------------------------
echo "[+] Step 2: Querying compute instances across specified regions..."
for region in "${REGIONS[@]}"; do
echo -e "\n---> Scanning Region: ${region}" | tee -a "${OUTPUT_REPORT}"
REG_RUNNING=0
REG_STOPPED=0
REG_OTHER=0
python3 -c '
import sys, json
try:
data = json.loads(sys.argv[1])
for item in data:
print(f"{item[\"id\"]}|{item[\"name\"]}")
except Exception:
pass
' "${COMPARTMENT_JSON}" | while IFS='|' read -r comp_id comp_name; do
JMES_QUERY="data[].{id: id, name: display_name, state: lifecycle_state, shape: shape, ocpus: shape_config.ocpus, ram: shape_config.memory_in_gbs, ad: availability_domain, created: time_created}"
INSTANCES_JSON=$(oci compute instance list \
${AUTH_FLAG} \
--region "${region}" \
--compartment-id "${comp_id}" \
--all \
--query "${JMES_QUERY}" \
--output json 2>/dev/null || echo "[]")
python3 -c '
import sys, json
try:
data = json.loads(sys.argv[1])
for inst in data:
name = inst.get("name") or "N/A"
ocid = inst.get("id") or "N/A"
state = inst.get("state") or "UNKNOWN"
shape = inst.get("shape") or "N/A"
ocpus = inst.get("ocpus") or "N/A"
ram = inst.get("ram") or "N/A"
ad = inst.get("ad") or "N/A"
created = inst.get("created") or "N/A"
print(f"{name}|{ocid}|{state}|{shape}|{ocpus}|{ram}|{ad}|{created}")
except Exception:
pass
' "${INSTANCES_JSON}" | while IFS='|' read -r inst_name inst_id inst_state inst_shape inst_ocpus inst_ram inst_ad inst_created; do
echo "${region},\"${comp_name}\",${comp_id},\"${inst_name}\",${inst_id},${inst_state},${inst_shape},${inst_ocpus},${inst_ram},${inst_ad},${inst_created}" >> "${OUTPUT_CSV}"
echo " [${inst_state}] Instance: ${inst_name} | Shape: ${inst_shape} | AD: ${inst_ad} | Comp: ${comp_name}" >> "${OUTPUT_REPORT}"
TOTAL_INSTANCES=$((TOTAL_INSTANCES + 1))
case "${inst_state}" in
RUNNING)
RUNNING_COUNT=$((RUNNING_COUNT + 1))
REG_RUNNING=$((REG_RUNNING + 1))
;;
STOPPED)
STOPPED_COUNT=$((STOPPED_COUNT + 1))
REG_STOPPED=$((REG_STOPPED + 1))
;;
*)
OTHER_COUNT=$((OTHER_COUNT + 1))
REG_OTHER=$((REG_OTHER + 1))
;;
esac
done
done
echo " Region Summary (${region}): Running=${REG_RUNNING}, Stopped=${REG_STOPPED}, Other=${REG_OTHER}" | tee -a "${OUTPUT_REPORT}"
done
# ------------------------------------------------------------------------------
# STEP 3: SUMMARY & REPORT FINALIZATION
# ------------------------------------------------------------------------------
{
echo ""
echo "======================================================================="
echo " TENANCY SUMMARY METRICS "
echo "======================================================================="
echo " Regions Scanned : ${REGIONS[*]} (${REGION_COUNT} region/s)"
echo " Total Compute Hosts Found : ${TOTAL_INSTANCES}"
echo " - RUNNING State : ${RUNNING_COUNT}"
echo " - STOPPED State : ${STOPPED_COUNT}"
echo " - OTHER (Provision/Term) : ${OTHER_COUNT}"
echo "======================================================================="
echo " Detailed CSV Output File : ${OUTPUT_CSV}"
echo "======================================================================="
} | tee -a "${OUTPUT_REPORT}"
echo ""
echo "[+] SUCCESS: Compute inventory completed successfully."
echo "[+] Report File : ${OUTPUT_REPORT}"
echo "[+] CSV Data : ${OUTPUT_CSV}"
๐ 5. How to Run & Execution Examples
1. Single Region Run (1 Region - Valid)
chmod +x oci_compute_inventory.sh
./oci_compute_inventory.sh us-ashburn-1
2. Dual Region Run (2 Regions - Valid)
./oci_compute_inventory.sh us-ashburn-1 us-phoenix-1
3. Triple Region Run (3 Regions - Valid Maximum)
./oci_compute_inventory.sh us-ashburn-1 us-phoenix-1 eu-frankfurt-1
4. Over Limit Example (> 3 Regions - Aborts Execution)
$ ./oci_compute_inventory.sh us-ashburn-1 us-phoenix-1 eu-frankfurt-1 uk-london-1
=======================================================================
OCI COMPUTE HOST LIFECYCLE INVENTORY UTILITY
=======================================================================
[+] Specified Regions Count : 4
[+] Specified Regions List : us-ashburn-1 us-phoenix-1 eu-frankfurt-1 uk-london-1
=======================================================================
[-] STRICT GUARDRAIL VIOLATION: Execution Aborted!
Reason : Script permits MINIMUM 1 region and MAXIMUM 3 regions per execution.
Passed : 4 region(s) [us-ashburn-1 us-phoenix-1 eu-frankfurt-1 uk-london-1]
Purpose : Prevents OCI API rate-limit overload and tenancy throttling.
๐ 6. Output Files & Formats
The script automatically generates two artifacts:
- Text Summary Report (
oci_compute_inventory_report_YYYYMMDD_HHMMSS.txt): Contains human-readable regional summaries and lifecycle counts. - CSV Spreadsheet (
oci_compute_inventory_YYYYMMDD_HHMMSS.csv): Structured dataset containing:Region, Compartment_Name, Compartment_OCID, Instance_Name, Instance_OCID, Lifecycle_State, Shape, OCPU_Count, RAM_GB, Availability_Domain, Time_Created
โ ๏ธ 7. Precautions & Important OCI Caveats
[!WARNING]
1. API Rate Limiting & Throttling
OCI IAM and Compute endpoints enforce per-tenancy API call rate limits. Executing recursive API queries across more than 3 regions simultaneously can trigger
429 TooManyRequestserrors. Always respect the 1-to-3 region limit per batch run.
[!NOTE]
2. Terminated Instance Lifecycle State
OCI retains metadata for
TERMINATEDcompute instances for up to 30 days after deletion. The script filters and counts active states (RUNNING,STOPPED,PROVISIONING) separately from legacyTERMINATEDrecords.
[!TIP]
3. Automated Scheduling for Large Tenancies
If your tenancy has 6 subscribed regions, break your cron jobs into two staggered executions:
- Batch 1 (02:00 AM):
./oci_compute_inventory.sh us-ashburn-1 us-phoenix-1 ca-toronto-1- Batch 2 (02:30 AM):
./oci_compute_inventory.sh eu-frankfurt-1 uk-london-1 ap-tokyo-1