Home / OCI

OCICheat sheetAdvanced

OCI CLI automation at scale cheat sheet: --from-json, raw-request, waiters, retries and multi-region loops

Patterns for OCI CLI scripts that run unattended across regions and compartments, including auth choice, JSON input, waiters, work requests, ETags, retries, canned queries and raw-request.

Deepak Bhardwaj6 min read

Applies toOCI CLI 3.x on Linux, macOS, Cloud Shell and OCI instances; bash with jq

This cheat sheet is for scripts that run from CI, cron or an operations host, where nobody is watching the output. The basics (--all, --query, --raw-output) are in the daily operations cheat sheet. This one covers what breaks when the same script runs across 20 regions and 300 compartments.

Pick the auth mode for where the script runs

Where it runs Auth How
Laptop, human operator Session token oci session authenticate, then --auth security_token
OCI compute (cron, ops host) Instance principal --auth instance_principal or OCI_CLI_AUTH=instance_principal
OCI Functions, Resource Manager, Cloud Shell-style services Resource principal --auth resource_principal
External CI without federation API key Profile in ~/.oci/config
# Browser login, short-lived token, no long-lived key on disk
oci session authenticate --region eu-frankfurt-1 --profile-name OPS
oci session validate --profile OPS
oci session refresh  --profile OPS     # extend before it expires

oci os ns get --profile OPS --auth security_token

Tip

Set OCI_CLI_AUTH, OCI_CLI_PROFILE and OCI_CLI_REGION at the top of the script so every command uses the same identity and region unless you override it. A script that silently falls back to the operator's DEFAULT profile is a common cause of changes landing in the wrong tenancy.

Loop over every subscribed region

TENANCY=ocid1.tenancy.oc1..aaaa     # pass in from the pipeline, don't discover it

for r in $(oci iam region-subscription list --tenancy-id "$TENANCY" \
             --query 'data[?status==`READY`]."region-name"' | jq -r '.[]'); do
  echo "== $r"
  oci search resource structured-search --region "$r" \
    --query-text "query instance resources where lifecycleState = 'RUNNING'" \
    --query 'data.items[].{name:"display-name",ocid:identifier}' --output table
done

With an API key profile, the tenancy OCID is also in ~/.oci/config (tenancy=). Search is regional, so a tenancy-wide inventory needs the region loop.

Build complex requests from JSON

# Full skeleton for every parameter
oci compute instance launch --generate-full-command-json-input > launch.json

# Skeleton for one complex parameter
oci compute instance launch --generate-param-json-input shape-config

# Run from the file; command-line flags override values in the file
oci compute instance launch --from-json file://launch.json \
  --display-name "app-$(date +%s)" --wait-for-state RUNNING

Keep launch.json in Git without secrets. Generate it per environment with jq:

jq --arg s "$SUBNET" '.createVnicDetails.subnetId=$s' launch.json > launch.prod.json

Waiters and work requests

Flag Use
--wait-for-state STATE Repeatable; returns when the resource reaches any listed state
--max-wait-seconds N Stop waiting after N seconds (the command fails, the operation continues)
--wait-interval-seconds N Poll interval; raise it in large loops to cut API calls
oci compute instance action --instance-id $ID --action SOFTSTOP \
  --wait-for-state STOPPED --max-wait-seconds 900 --wait-interval-seconds 15

Some asynchronous operations return a work request. On these, --wait-for-state waits for the work request (for example SUCCEEDED) rather than the resource:

WR=$(oci work-requests work-request list -c $C --resource-id $ID \
      --query 'data[0].id' --raw-output)
oci work-requests work-request get --work-request-id $WR --query 'data.status' --raw-output
oci work-requests work-request-error list --work-request-id $WR --all
oci work-requests work-request-log-entry list --work-request-id $WR --all

Service-specific work request commands exist too (for example under oci ce, oci db and oci lb). Names vary by service; check oci <service> --help.

Warning

A waiter timeout is not a rollback. If --max-wait-seconds expires, the create, resize or delete keeps running. The script must re-check state before retrying, or it can launch duplicates.

Retries, timeouts and throttling

The CLI retries throttled (429) and some transient failures by default.

oci compute instance list -c $C --all --no-retry            # fail fast
oci compute instance list -c $C --all --max-retries 8       # more patience
oci os object list -bn big --all --connection-timeout 10 --read-timeout 120

Check oci --help on your build for the global retry and timeout options it supports.

For parallel work, cap concurrency instead of firing hundreds of calls at once:

oci compute instance list -c $C --all --lifecycle-state STOPPED \
  --query 'data[].id' | jq -r '.[]' \
  | xargs -P 4 -I{} oci compute instance action --instance-id {} --action START

Safe concurrent updates with ETags

Read the ETag, then update only if nothing changed in between:

ETAG=$(oci network nsg get --nsg-id $NSG --query 'etag' --raw-output)
oci network nsg update --nsg-id $NSG --display-name nsg-app-prod --if-match "$ETAG"
# 412 PreconditionFailed = someone else changed it; re-read and decide

This matters for anything that replaces a full list in one call, such as security list rules, route table rules and policy statements.

Canned queries and defaults in oci_cli_rc

# ~/.oci/oci_cli_rc
[DEFAULT]
compartment-id = ocid1.compartment.oc1..aaaa

[OCI_CLI_CANNED_QUERIES]
inst = data[].{name:"display-name",state:"lifecycle-state",shape:shape,ad:"availability-domain"}
ids  = data[].id
oci compute instance list --all --query query://inst --output table

Defaults in oci_cli_rc apply to every command that accepts the parameter. Don't set compartment-id there on a shared automation host; pass it explicitly.

raw-request for APIs the CLI doesn't wrap yet

oci raw-request --http-method GET \
  --target-uri "https://iaas.eu-frankfurt-1.oraclecloud.com/20160918/instances?compartmentId=$C&limit=5"

# <service-host> and <api-version>/<resource> are placeholders from the service's API reference
oci raw-request --http-method POST \
  --target-uri "https://<service-host>/<api-version>/<resource>" \
  --request-body file://body.json \
  --request-headers '{"opc-retry-token":"run-42"}'

raw-request signs the call with your configured auth, supports JSON payloads only and returns data, headers and status. Endpoint hostnames and API versions vary by service; take them from the service's API reference.

JMESPath patterns that hold up at scale

# Hyphenated keys must be double-quoted inside the query
--query 'data[?"lifecycle-state"==`RUNNING`].{n:"display-name",id:id}'

# Tag lookups
--query 'data[?"freeform-tags".env==`prod`].id'
--query 'data[?"defined-tags".Ops.Owner==`team-a`].id'

# Count without jq
--query 'length(data)'

# Sort and take the newest
--query 'sort_by(data, &"time-created")[-1].id' --raw-output

Without --all, --query only sees the first page, so counts and filters are silently wrong on large compartments.

Troubleshooting automation runs

Symptom Likely cause Check
Works for a human, 404 NotAuthorizedOrNotFound in cron Different profile or principal; policy missing for the dynamic group oci iam region list --debug 2>&1 | grep -i auth
Results differ between runs Missing --all, or region not set Echo OCI_CLI_REGION and add --all
429 TooManyRequests storms Unbounded parallelism xargs -P cap; longer --wait-interval-seconds
Duplicate resources Retry after waiter timeout Re-check state; idempotent display names
security_token expired mid-run Long job with session auth Use instance principal for long jobs

Prevent it next time

  • One auth mode per environment, set explicitly at the top of every script.
  • Always --all on list calls that feed logic, and always an explicit --region.
  • Use --if-match for updates that replace lists.
  • Log the opc-request-id from --debug on failures so Oracle Support can trace the call.