Created
August 11, 2026 14:31
-
-
Save majodev/75906493d8d0144d141c7eb926ba82ec to your computer and use it in GitHub Desktop.
oci_compute_maintenances.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euox pipefail | |
| # This script writes out OCI instance maintenance events to a CSV file. | |
| # It checks a single target OCI tenancy and checks all regions/compartments. | |
| # | |
| # Depends on: | |
| # * https://github.com/oracle/oci-cli | |
| # * https://github.com/jqlang/jq | |
| # | |
| # The OCI target tenancy either comes from: | |
| # * the DEFAULT oci profile ($HOME/.oci/config) or | |
| # * the explicitly set env var TENANCY_OCID. | |
| profile=${OCI_CLI_PROFILE:-DEFAULT} | |
| config_file=${OCI_CLI_CONFIG_FILE:-"$HOME/.oci/config"} | |
| output_file=oci_compute_maintenances.csv | |
| for required_command in oci jq; do | |
| if ! command -v "$required_command" >/dev/null 2>&1; then | |
| printf 'error: required command not found: %s\n' "$required_command" >&2 | |
| exit 1 | |
| fi | |
| done | |
| trim() { | |
| local value=$1 | |
| value=${value#"${value%%[![:space:]]*}"} | |
| value=${value%"${value##*[![:space:]]}"} | |
| printf '%s' "$value" | |
| } | |
| read_profile_tenancy() { | |
| local section= line= key= value= | |
| [[ -r $config_file ]] || return 1 | |
| while IFS= read -r line || [[ -n $line ]]; do | |
| line=${line%$'\r'} | |
| line=$(trim "$line") | |
| [[ -z $line || $line == \#* || $line == \;* ]] && continue | |
| if [[ $line == \[*\] ]]; then | |
| section=${line#\[} | |
| section=${section%\]} | |
| section=$(trim "$section") | |
| continue | |
| fi | |
| if [[ $section == "$profile" && $line == *=* ]]; then | |
| key=$(trim "${line%%=*}") | |
| if [[ $key == tenancy ]]; then | |
| value=$(trim "${line#*=}") | |
| [[ -n $value ]] || return 1 | |
| printf '%s\n' "$value" | |
| return 0 | |
| fi | |
| fi | |
| done < "$config_file" | |
| return 1 | |
| } | |
| if [[ -n ${TENANCY_OCID:-} ]]; then | |
| tenancy_id=$TENANCY_OCID | |
| elif ! tenancy_id=$(read_profile_tenancy); then | |
| printf 'error: cannot resolve tenancy for OCI profile %s from %s\n' \ | |
| "$profile" "$config_file" >&2 | |
| exit 1 | |
| fi | |
| regions=$( | |
| oci iam region-subscription list --all | | |
| jq -r '.data[]."region-name"' | |
| ) | |
| [[ -n $regions ]] || { | |
| printf 'error: OCI returned no subscribed regions\n' >&2 | |
| exit 1 | |
| } | |
| compartments=$( | |
| printf '%s\n' "$tenancy_id" | |
| oci iam compartment list \ | |
| --compartment-id "$tenancy_id" \ | |
| --compartment-id-in-subtree true \ | |
| --access-level ACCESSIBLE \ | |
| --lifecycle-state ACTIVE \ | |
| --all | | |
| jq -r '.data[].id' | |
| ) | |
| { | |
| printf '%s\n' 'ACCOUNT NAME,TENANT ID,VM ID,VM DISPLAY NAME,SHAPE,REGION,AD,FAULT DOMAIN,NETWORK TYPE,Maintenance Created Time,Maintenance Start Window,Maintenance Started Time,Maintenance Finished Time,Maintenance Due Date' | |
| while IFS= read -r region; do | |
| [[ -n $region ]] || continue | |
| while IFS= read -r compartment_id; do | |
| [[ -n $compartment_id ]] || continue | |
| # you may want to filter just for --lifecycle-state SCHEDULED | |
| events=$( | |
| oci compute instance-maintenance-event list \ | |
| --compartment-id "$compartment_id" \ | |
| --region "$region" \ | |
| --all | |
| ) | |
| while IFS= read -r event; do | |
| [[ -n $event ]] || continue | |
| instance_id=$(jq -r '."instance-id"' <<< "$event") | |
| if ! instance_json=$( | |
| oci compute instance get \ | |
| --instance-id "$instance_id" \ | |
| --region "$region" 2>/dev/null | |
| ); then | |
| continue | |
| fi | |
| instance=$( | |
| jq -c '.data | { | |
| "display-name", | |
| shape, | |
| "availability-domain", | |
| "fault-domain", | |
| "network-type": ."launch-options"."network-type" | |
| }' <<< "$instance_json" | |
| ) | |
| jq -nr \ | |
| --arg profile "$profile" \ | |
| --arg tenancy "$tenancy_id" \ | |
| --arg region "$region" \ | |
| --argjson event "$event" \ | |
| --argjson instance "$instance" ' | |
| def normalize: | |
| (. // "") | ascii_upcase | gsub("[-:]"; "_"); | |
| def normalize_ad: | |
| (. // "") | split(":")[-1] | normalize; | |
| def display_time: | |
| if . == null or . == "" then "" | |
| else | |
| capture("^(?<y>[0-9]{4})-(?<m>[0-9]{2})-(?<d>[0-9]{2})T(?<h>[0-9]{2}):(?<min>[0-9]{2})") as $t | |
| | ($t.h | tonumber) as $hour | |
| | (if $hour == 0 then 12 elif $hour > 12 then $hour - 12 else $hour end) as $hour12 | |
| | "\($t.m)/\($t.d)/\($t.y) \($hour12 | tostring | if length == 1 then "0" + . else . end):\($t.min) \(if $hour < 12 then "AM" else "PM" end)" | |
| end; | |
| [ | |
| $profile, | |
| $tenancy, | |
| $event."instance-id", | |
| $instance."display-name", | |
| $instance.shape, | |
| ($region | normalize), | |
| ($instance."availability-domain" | normalize_ad), | |
| ($instance."fault-domain" | normalize), | |
| ($instance."network-type" | normalize), | |
| ($event."time-created" | display_time), | |
| ($event."time-window-start" | display_time), | |
| ($event."time-started" | display_time), | |
| ($event."time-finished" | display_time), | |
| ($event."time-hard-due-date" | display_time) | |
| ] | @csv | |
| ' | |
| done < <(jq -c '.data[]' <<< "$events") | |
| done <<< "$compartments" | |
| done <<< "$regions" | |
| } > "$output_file" | |
| printf 'wrote %s\n' "$output_file" >&2 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment