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 only extra thing that I needed was a script that I could directly run in the OCI console and have everything automated in just one click.
The solution
β
What I did is to use Jonβs blog post in the chat context and ask a large language model (LLM) to help me generate a shell script and guide me on how to execute it. What's more, it already had my project repository loaded into the context too. So the LLM response figured out all the details like naming, paths and so on. I literally just had to go and run in using the OCI CLI.
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).

π‘
Whatβs cool about Cursor is that it detects the error in the built-in Terminal and suggests putting the error message in the chat, so it can be answered by the LLM. I already knew what I was missing, so did it by myself. The following steps are enough to fulfil the prerequisites.
Prerequisites
brew update && brew install oci-cli
oci --version
brew install jq
jq --version
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
oci iam region list
oci iam user get --user-id <your-user-ocid>
β
If you have multiple records in your .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
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.
set -e
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"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
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() {
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
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() {
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() {
log "Creating compartment: $COMPARTMENT_NAME"
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"
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_bucket() {
log "Creating Object Storage bucket: $BUCKET_NAME"
NAMESPACE=$(oci os ns get | jq -r '.data')
info "Object Storage namespace: $NAMESPACE"
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() {
log "Creating service account: $SERVICE_ACCOUNT_NAME"
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_keys() {
log "Generating API key pair for service account..."
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"
openssl genrsa -out "$PRIVATE_KEY_FILE" 2048
openssl rsa -pubout -in "$PRIVATE_KEY_FILE" -out "$PUBLIC_KEY_FILE"
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"
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() {
log "Creating security group: $SECURITY_GROUP_NAME"
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_group() {
log "Adding service account to security 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() {
log "Creating security policy: $POLICY_NAME"
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
)
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_summary() {
log "Generating configuration summary..."
REGION=$(oci iam region-subscription list | jq -r '.data[] | select(.["is-home-region"] == true) | .["region-name"]')
NAMESPACE=$(oci os ns get | jq -r '.data')
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() {
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!"
}
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