Scripting OCI Objects creation using CLI


The problem
Already having some experience with AI assisted coding using Cursor, I tried to put it on another test today. In one of my personal projects, I needed secure access to Oracle Cloud's Object Storage buckets for uploading and downloading files. I've done it already in the past and I knew the steps. You need a new compartment, a new bucket, optionally a new user and security credentials. After that, you need to create a new credential in your APEX application and use the credential static ID in your REST API calls to the OCI object storage. Jon Dixon has all the steps very nicely explained in his blog post from 2022.The guidelines are very clear and easy to execute.
The solution
When I first tried running the shell script, I got the following error (expected, as I didnβt have the OCI CLI installed on my machine).
Prerequisites
# 1.Install OCI CLI
# Install using Homebrew (recommended for Mac)
brew update && brew install oci-cli
# Verify installation
oci --version
# 2.Install jq (JSON processor)
# Install using Homebrew
brew install jq
# Verify installation
jq --version
# 3. Configure OCI CLI with your credentials
# Run the interactive setup
oci setup config
oci setup config
will configure the access to your OCI resources from the CLI.
When you run it, it will prompt you for:
User OCID
- Your OCI user ID (find in OCI Console β Profile β User Settings)Tenancy OCID
- Your tenancy ID (find in OCI Console β Profile β Tenancy)Region
- Your home region (e.g., us-ashburn-1)Key file location
- It will generate API keys for you
The setup will:
Generate an API key pair
Create a config file at ~/.oci/config
Show you the public key to upload to OCI
Upload the public key to OCI Console
Copy the public key shown by the setup command
Go to OCI Console β Profile β User Settings β Tokens and Keys β API Keys
Click "Add API Key" β "Paste Public Key"
Paste the key and click "Add"
Test your OCI CLI setup
# Test basic connectivity
oci iam region list
# Test authentication
oci iam user get --user-id <your-user-ocid>
.oci/config
file, make sure you are using the right profile. In my case I had two etries - [DEFAULT] and [APEX_APPS_OCI] that I just created. To use the newly created you need to add β--profile APEX_APPS_OCIβ at the end of the line, when using cli# Test basic connectivity using a specific profile, located in .oci/profile
oci iam region list --profile APEX_APPS_OCI
Running the script
If both tests are successful, you are ready to run the setup_oci_object_storage.sh
script.
Open your Terminal, navigate to the folder with the script and execute it using this command:
./setup_oci_object_storage.sh
If everything is fine, you will find all the object created in your OCI console.
The Code
Below is the code of the setup_oci_object_storage.sh
script. All you need is replace the configuration values in the beginning as you like.
#!/bin/bash
# OCI Object Storage Setup Script for Oracle APEX and PL/SQL integration
#
# Prerequisites:
# - OCI CLI installed and configured
# - User has administrative privileges in OCI tenancy
# - jq installed for JSON parsing
set -e # Exit on any error
# Configuration Variables - CUSTOMIZE THESE VALUES
COMPARTMENT_NAME="APEX_APPS"
COMPARTMENT_DESCRIPTION="APEX applications development related compartment"
BUCKET_NAME="photo-bucket"
SERVICE_ACCOUNT_NAME="apex-apps-service"
SERVICE_ACCOUNT_DESCRIPTION="Service account for APEX applications"
SECURITY_GROUP_NAME="APEX_APPS_Security_Group"
SECURITY_GROUP_DESCRIPTION="Security group for APEX applications"
POLICY_NAME="APEX_APPS_Policy"
POLICY_DESCRIPTION="Policy for APEX apps Object Storage access"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging function
log() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}"
}
error() {
echo -e "${RED}[ERROR] $1${NC}" >&2
}
warning() {
echo -e "${YELLOW}[WARNING] $1${NC}"
}
info() {
echo -e "${BLUE}[INFO] $1${NC}"
}
# Check prerequisites
check_prerequisites() {
log "Checking prerequisites..."
if ! command -v oci &> /dev/null; then
error "OCI CLI is not installed. Please install it first."
exit 1
fi
if ! command -v jq &> /dev/null; then
error "jq is not installed. Please install it first."
exit 1
fi
# Test OCI CLI configuration
if ! oci iam region list &> /dev/null; then
error "OCI CLI is not properly configured. Please run 'oci setup config' first."
exit 1
fi
log "Prerequisites check passed β"
}
# Get tenancy OCID
get_tenancy_ocid() {
log "Getting tenancy OCID..."
TENANCY_OCID=$(oci iam tenancy get \
--profile DEFAULT \
--tenancy-id $(grep -A 5 "\[DEFAULT\]" ~/.oci/config | grep tenancy | awk -F "=" '{print $2}' | xargs) \
--query 'data.id' --raw-output)
if [ "$TENANCY_OCID" == "null" ] || [ -z "$TENANCY_OCID" ]; then
error "Failed to get tenancy OCID"
exit 1
fi
info "Tenancy OCID: $TENANCY_OCID"
}
# Create compartment
create_compartment() {
log "Creating compartment: $COMPARTMENT_NAME"
# Check if compartment already exists
EXISTING_COMPARTMENT=$(oci iam compartment list --compartment-id "$TENANCY_OCID" --query "data[?name=='$COMPARTMENT_NAME'].id | [0]" --raw-output)
if [ "$EXISTING_COMPARTMENT" != "null" ] && [ -n "$EXISTING_COMPARTMENT" ]; then
warning "Compartment '$COMPARTMENT_NAME' already exists"
COMPARTMENT_OCID="$EXISTING_COMPARTMENT"
else
COMPARTMENT_RESPONSE=$(oci iam compartment create \
--compartment-id "$TENANCY_OCID" \
--name "$COMPARTMENT_NAME" \
--description "$COMPARTMENT_DESCRIPTION")
COMPARTMENT_OCID=$(echo "$COMPARTMENT_RESPONSE" | jq -r '.data.id')
if [ "$COMPARTMENT_OCID" == "null" ] || [ -z "$COMPARTMENT_OCID" ]; then
error "Failed to create compartment"
exit 1
fi
log "Compartment created successfully β"
fi
info "Compartment OCID: $COMPARTMENT_OCID"
# Wait for compartment to be active
log "Waiting for compartment to be active..."
while true; do
STATE=$(oci iam compartment get --compartment-id "$COMPARTMENT_OCID" | jq -r '.data."lifecycle-state"')
if [ "$STATE" == "ACTIVE" ]; then
break
fi
sleep 5
done
log "Compartment is active β"
}
# Create Object Storage bucket
create_bucket() {
log "Creating Object Storage bucket: $BUCKET_NAME"
# Get namespace
NAMESPACE=$(oci os ns get | jq -r '.data')
info "Object Storage namespace: $NAMESPACE"
# Check if bucket already exists
if oci os bucket get --bucket-name "$BUCKET_NAME" --namespace "$NAMESPACE" &> /dev/null; then
warning "Bucket '$BUCKET_NAME' already exists"
else
oci os bucket create \
--compartment-id "$COMPARTMENT_OCID" \
--name "$BUCKET_NAME" \
--namespace "$NAMESPACE"
log "Bucket created successfully β"
fi
}
# Create service account (IAM user)
create_service_account() {
log "Creating service account: $SERVICE_ACCOUNT_NAME"
# Check if user already exists
EXISTING_USER=$(oci iam user list --compartment-id "$TENANCY_OCID" --query "data[?name=='$SERVICE_ACCOUNT_NAME'].id | [0]" --raw-output)
if [ "$EXISTING_USER" != "null" ] && [ -n "$EXISTING_USER" ]; then
warning "Service account '$SERVICE_ACCOUNT_NAME' already exists"
SERVICE_USER_OCID="$EXISTING_USER"
else
USER_RESPONSE=$(oci iam user create \
--compartment-id "$TENANCY_OCID" \
--name "$SERVICE_ACCOUNT_NAME" \
--description "$SERVICE_ACCOUNT_DESCRIPTION")
SERVICE_USER_OCID=$(echo "$USER_RESPONSE" | jq -r '.data.id')
if [ "$SERVICE_USER_OCID" == "null" ] || [ -z "$SERVICE_USER_OCID" ]; then
error "Failed to create service account"
exit 1
fi
log "Service account created successfully β"
fi
info "Service account OCID: $SERVICE_USER_OCID"
}
# Generate API key pair
generate_api_keys() {
log "Generating API key pair for service account..."
# Create directory for keys if it doesn't exist
mkdir -p ./oci-keys
PRIVATE_KEY_FILE="./oci-keys/${SERVICE_ACCOUNT_NAME}_private_key.pem"
PUBLIC_KEY_FILE="./oci-keys/${SERVICE_ACCOUNT_NAME}_public_key.pem"
# Generate private key
openssl genrsa -out "$PRIVATE_KEY_FILE" 2048
# Generate public key
openssl rsa -pubout -in "$PRIVATE_KEY_FILE" -out "$PUBLIC_KEY_FILE"
# Set proper permissions
chmod 600 "$PRIVATE_KEY_FILE"
chmod 644 "$PUBLIC_KEY_FILE"
log "API key pair generated β"
info "Private key: $PRIVATE_KEY_FILE"
info "Public key: $PUBLIC_KEY_FILE"
# Upload public key to OCI
log "Uploading public key to OCI..."
API_KEY_RESPONSE=$(oci iam user api-key upload \
--user-id "$SERVICE_USER_OCID" \
--key-file "$PUBLIC_KEY_FILE")
API_KEY_FINGERPRINT=$(echo "$API_KEY_RESPONSE" | jq -r '.data.fingerprint')
if [ "$API_KEY_FINGERPRINT" == "null" ] || [ -z "$API_KEY_FINGERPRINT" ]; then
error "Failed to upload API key"
exit 1
fi
log "API key uploaded successfully β"
info "API key fingerprint: $API_KEY_FINGERPRINT"
}
# Create security group
create_security_group() {
log "Creating security group: $SECURITY_GROUP_NAME"
# Check if group already exists
EXISTING_GROUP=$(oci iam group list --compartment-id "$TENANCY_OCID" --query "data[?name=='$SECURITY_GROUP_NAME'].id | [0]" --raw-output)
if [ "$EXISTING_GROUP" != "null" ] && [ -n "$EXISTING_GROUP" ]; then
warning "Security group '$SECURITY_GROUP_NAME' already exists"
GROUP_OCID="$EXISTING_GROUP"
else
GROUP_RESPONSE=$(oci iam group create \
--compartment-id "$TENANCY_OCID" \
--name "$SECURITY_GROUP_NAME" \
--description "$SECURITY_GROUP_DESCRIPTION")
GROUP_OCID=$(echo "$GROUP_RESPONSE" | jq -r '.data.id')
if [ "$GROUP_OCID" == "null" ] || [ -z "$GROUP_OCID" ]; then
error "Failed to create security group"
exit 1
fi
log "Security group created successfully β"
fi
info "Security group OCID: $GROUP_OCID"
}
# Add user to security group
add_user_to_group() {
log "Adding service account to security group..."
# Check if user is already in group
MEMBERSHIP_CHECK=$(oci iam group list-users --group-id "$GROUP_OCID" --query "data[?id=='$SERVICE_USER_OCID'].id | [0]" --raw-output)
if [ "$MEMBERSHIP_CHECK" != "null" ] && [ -n "$MEMBERSHIP_CHECK" ]; then
warning "Service account is already a member of the security group"
else
oci iam group add-user \
--group-id "$GROUP_OCID" \
--user-id "$SERVICE_USER_OCID"
log "Service account added to security group β"
fi
}
# Create security policy
create_security_policy() {
log "Creating security policy: $POLICY_NAME"
# Define policy statements
POLICY_STATEMENTS=$(cat <<EOF
[
"Allow group $SECURITY_GROUP_NAME to read buckets in compartment $COMPARTMENT_NAME",
"Allow group $SECURITY_GROUP_NAME to manage objects in compartment $COMPARTMENT_NAME where all {target.bucket.name='$BUCKET_NAME', any {request.permission='OBJECT_CREATE', request.permission='OBJECT_INSPECT', request.permission='OBJECT_READ', request.permission='OBJECT_OVERWRITE', request.permission='OBJECT_DELETE'}}"
]
EOF
)
# Check if policy already exists
EXISTING_POLICY=$(oci iam policy list --compartment-id "$COMPARTMENT_OCID" --query "data[?name=='$POLICY_NAME'].id | [0]" --raw-output)
if [ "$EXISTING_POLICY" != "null" ] && [ -n "$EXISTING_POLICY" ]; then
warning "Policy '$POLICY_NAME' already exists"
POLICY_OCID="$EXISTING_POLICY"
else
POLICY_RESPONSE=$(oci iam policy create \
--compartment-id "$COMPARTMENT_OCID" \
--name "$POLICY_NAME" \
--description "$POLICY_DESCRIPTION" \
--statements "$POLICY_STATEMENTS")
POLICY_OCID=$(echo "$POLICY_RESPONSE" | jq -r '.data.id')
if [ "$POLICY_OCID" == "null" ] || [ -z "$POLICY_OCID" ]; then
error "Failed to create security policy"
exit 1
fi
log "Security policy created successfully β"
fi
info "Policy OCID: $POLICY_OCID"
}
# Generate configuration summary
generate_summary() {
log "Generating configuration summary..."
# Get region and namespace
REGION=$(oci iam region-subscription list | jq -r '.data[] | select(.["is-home-region"] == true) | .["region-name"]')
NAMESPACE=$(oci os ns get | jq -r '.data')
# Read private key content (remove header/footer and newlines)
PRIVATE_KEY_CONTENT=$(grep -v "BEGIN\|END" "$PRIVATE_KEY_FILE" | tr -d '\n')
SUMMARY_FILE="./oci-setup-summary.txt"
cat > "$SUMMARY_FILE" << EOF
=============================================================================
OCI Object Storage Setup Summary
=============================================================================
Generated on: $(date)
CONFIGURATION VALUES:
=============================================================================
Tenancy OCID: $TENANCY_OCID
Compartment Name: $COMPARTMENT_NAME
Compartment OCID: $COMPARTMENT_OCID
Bucket Name: $BUCKET_NAME
Service Account Name: $SERVICE_ACCOUNT_NAME
Service Account OCID: $SERVICE_USER_OCID
Security Group Name: $SECURITY_GROUP_NAME
Security Group OCID: $GROUP_OCID
Policy Name: $POLICY_NAME
Policy OCID: $POLICY_OCID
Region: $REGION
Namespace: $NAMESPACE
API Key Fingerprint: $API_KEY_FINGERPRINT
APEX WEB CREDENTIAL CONFIGURATION:
=============================================================================
Name: APEX_OCI_PHOTO_BUCKET_CREDENTIAL
Static Identifier: APEX_OCI_PHOTO_BUCKET_CREDENTIAL
Authentication Type: Oracle Cloud Infrastructure (OCI)
OCI User ID: $SERVICE_USER_OCID
OCI Private Key: $PRIVATE_KEY_CONTENT
OCI Tenancy ID: $TENANCY_OCID
OCI Public Key Fingerprint: $API_KEY_FINGERPRINT
OBJECT STORAGE URL PATTERN:
=============================================================================
Base URL: https://objectstorage.$REGION.oraclecloud.com/n/$NAMESPACE/b/$BUCKET_NAME/o/
Example URLs:
- Input image: https://objectstorage.$REGION.oraclecloud.com/n/$NAMESPACE/b/$BUCKET_NAME/o/photo-bucket/username/input/filename.png
- Output image: https://objectstorage.$REGION.oraclecloud.com/n/$NAMESPACE/b/$BUCKET_NAME/o/photo-bucket/username/output/filename.png
DBMS_CLOUD CREDENTIAL SQL:
=============================================================================
BEGIN
DBMS_CLOUD.create_credential(
credential_name => 'APEX_APPS_OCI_CREDENTIAL',
user_ocid => '$SERVICE_USER_OCID',
tenancy_ocid => '$TENANCY_OCID',
private_key => '$PRIVATE_KEY_CONTENT',
fingerprint => '$API_KEY_FINGERPRINT'
);
END;
/
UPDATE PACKAGE CONFIGURATION:
=============================================================================
Update the following in your PL/SQL package:
1. Replace 'YOUR_NAMESPACE' with: $NAMESPACE
2. Replace 'us-ashburn-1' with: $REGION (if different)
3. Use credential name: APEX_APPS_OCI_CREDENTIAL
FILES GENERATED:
=============================================================================
Private Key: $PRIVATE_KEY_FILE
Public Key: $PUBLIC_KEY_FILE
Summary: $SUMMARY_FILE
NEXT STEPS:
=============================================================================
1. Create the APEX Web Credential using the values above
2. Create the DBMS_CLOUD credential using the SQL above
3. Update your PL/SQL package with the correct namespace and region
4. Test the connection by uploading a sample file
5. Secure the private key file and remove it from this location
=============================================================================
EOF
log "Configuration summary saved to: $SUMMARY_FILE"
}
# Main execution
main() {
log "Starting OCI Object Storage setup for your APEX App..."
log "=========================================================="
check_prerequisites
get_tenancy_ocid
create_compartment
create_bucket
create_service_account
generate_api_keys
create_security_group
add_user_to_group
create_security_policy
generate_summary
log "=========================================================="
log "OCI Object Storage setup completed successfully! β"
log "Please review the configuration summary: oci-setup-summary.txt"
warning "Remember to secure your private key file!"
}
# Run main function
main "$@"
Takeaways
πΈ OCI allows you to automate and script all console operations, using the CLI
πΈ The script I have used allows you to create a Bucket
, Service Account (User
), Security Group
, Policy
and API Key Pair
πΈ The script is made in a way that allows rerunning it multiple times, as it checks if needed objects are already created
πΈ It outputs a summary file, called oci-setup-summary.txt
with everything that has been created, together with URL to be used when working with the Object Storage, examples and instructions on accessing the buckets using PL/SQL
Subscribe to my newsletter
Read articles from Plamen Mushkov directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Plamen Mushkov
Plamen Mushkov
Creating apps built with β€οΈ using Oracle APEX. PL/SQL dev. Exploring the AI and ML world. Keen on front-end, visual arts and new technologies. Love travel, sports, photography.