OCI CLI and Containers- the simplest things are the best

David Allan
7 min readApr 12, 2023

--

Aren’t the best things, the simplest things? When you join 1 + 1 and get much much more! We’re going to see how you can run any set of commands in a serverless manner in OCI.

Let’s see where we go here….. Oracle Cloud Infrastructure (OCI) is a cloud platform that provides various services, including Object Storage. Object Storage is a highly scalable, durable, and secure cloud storage solution. It allows you to store and retrieve any type of data, including unstructured data, such as images, videos, and documents. OCI Object Storage also provides various tools and APIs to interact with the stored data, including the OCI Command Line Interface (CLI). OCI CLI is a powerful command-line tool that allows you to manage various OCI resources, including Object Storage. With OCI CLI, you can easily create, update, and delete objects in OCI Object Storage. Additionally, OCI CLI provides various options to read and write data to OCI Object Storage using the standard input/output streams.

In this blog, we will explore how the OCI CLI can be used to read and write data to OCI Object Storage using standard input/output streams. From local files to REST endpoints and much more!

Phase 1 — explore the CLI — Hello World

This is not a required step but its useful to use ad be familiar with the OCI CLI before jumping to containers, if you are familiar with containers, jump to phase 3!

Prerequisite:

  • An OCI account with access to OCI Object Storage
  • OCI CLI installed and configured
  • Curl command-line tool installed

Step 1: Set up OCI CLI and Object Storage

To use OCI CLI, you need to first set it up and configure it with your OCI account credentials. Additionally, you need to create an Object Storage bucket in OCI where you can store the data. Follow these steps to set up OCI CLI and Object Storage:

  1. Install OCI CLI and configure it with your OCI account credentials. You can find the installation instructions here: https://docs.cloud.oracle.com/en-us/iaas/Content/API/SDKDocs/cliinstall.htm
  2. Create an Object Storage bucket in OCI. You can find the instructions here: https://docs.cloud.oracle.com/en-us/iaas/Content/Object/Tasks/managingbuckets.htm

Step 2: Write data to OCI Object Storage using standard input

OCI CLI provides commands called “os put” and “os get” that allows you to copy files to and from OCI Object Storage. Additionally, you can use “-” as the source or destination to read or write data to/from standard input/output streams. Here’s an example of how to write data to OCI Object Storage using standard input:

  1. Open a terminal window and navigate to the directory where the file you want to upload to OCI Object Storage is located.
  2. Use the following command to upload the file to OCI Object Storage:

echo "Hello World" | oci os object put --bucket-name my-bucket --name file.txt --file -

In this command, we echo the text “Hello World” and pipe it to OCI CLI’s “oci os object put” command. The “bucket-name” option specifies the name of the bucket where you want to upload the file. The “ name” option specifies the name of the file in OCI Object Storage. Finally, the “from-file -” option tells OCI CLI to read the contents of the file from standard input.

Step 3: Read data from OCI Object Storage using standard output

OCI CLI also allows you to read data from OCI Object Storage using standard output. You can use the “os object get” command to download the file from OCI Object Storage and output it to standard output. Here’s an example of how to read data from OCI Object Storage using standard output:

  1. Open a terminal window and use the following command to download the file from OCI Object Storage and output it to standard output:

oci os object get --bucket-name my-bucket --name file.txt | cat

In this command, “oci os object get” downloads the file from OCI Object Storage and pipes it to the standard output stream and then executes “cat” to display to the standard output.

This pattern of piping commands is very powerful, you see above we are using “cat” to concatenate and print files, other OS commands are really useful such as “curl” we can execute REST endpoints using CURL and pipe the results to/from the OCI CLI!

Phase 2— CLI and REST Endpoints

Let’s look at more examples for read and write involving REST endpoints.

  1. Read object in Object Storage and POST the results to a REST endpoint;
oci os object get --bucket-name a_jon_example --name afile_users --file -  | curl -H "Content-Type: application/json" -X POST --data-binary @- https://reqres.in/api/users

2. Call a REST endpoint with GET to retrieve data and write object in Object Storage;

curl -H "Content-Type: application/json" -X GET https://reqres.in/api/users | oci os object put --bucket-name a_jon_example --name afile_users2 --file - 

Very simple, let’s look at containers now!

Phase 3— Containers

All of the above can be extended to run in a container based on the existing public OCI CLI container image with no customization. You can also practice more advanced and secure operations such as reading secrets from OCI Vault and then use them in curl payloads (for example Basic authentication information).

The OCI CLI is provided as a container — this is similar to all of the other major cloud platforms; AWS, Azure and Google all have their CLI provided via containers.

Read the documentation here on the OCI CLI;

We will use the OCI Container service to run arbitrary commands using the OCI CLI container image. With this approach we can run the commands like above very easily. We can also run arbitrary python scripts and use the OCI CLI too!

In order to reuse the OCI CLI you can either build a new container image using this as the base, or use your own base and install the CLI manually or you can run the publicly available container and change the initial command when the container starts. Let’s look at the latter way — by default the OCI CLI is run when the container starts, so if you do not provide any command line arguments you will see the following output

docker run ghcr.io/oracle/oci-cli:latest
Usage: oci [OPTIONS] COMMAND [ARGS]...

Oracle Cloud Infrastructure command line interface, with support for
Audit, Block Volume, Compute, Database, IAM, Load Balancing, Networking,
DNS, File Storage, Email Delivery and Object Storage Services.

Most commands must specify a service, followed by a resource type and then
an action. For example, to list users (where $T contains the OCID of the
current tenant):

oci iam user list --compartment-id $T

Output is in JSON format.
....

We can override the command on entry which is going to be really useful, we can add multiple commands and also. Here’s how we do that by mounting out local config file;

docker run --entrypoint '/bin/sh' --mount type=bind,source=$HOME/.oci,target=/oracle/.oci ghcr.io/oracle/oci-cli:latest -c "echo \"Hello World\" | oci os object put --bucket-name abucket --name afile.txt --file -"

Given this pattern, we can run this in OCI by using OCI Container Instances.

Running from OCI Container Instances with the below below, we will use resource_principal as the authentication mechanism. When the container instance is created it will start one container and override the default OCI CLI invocation with a /bin/sh and then execute the commands in the arguments property ….

Select the container URL;

Then define the command and entrypoint arguments;

This is the payload;

{
"containers": [
{
"imageUrl": "ghcr.io/oracle/oci-cli",
"displayName": "container-20230411-1205-1",
"arguments": [
"echo \"Hello World\" | oci os object put --auth resource_principal --bucket-name abucket --name file.txt --file -"
],
"command": [
"/bin/sh", "-c"
],
"definedTags": {},
"freeformTags": {}
}
],
"compartmentId": "ocid1.compartment.oc1.....",
"availabilityDomain": "nDUb:US-ASHBURN-AD-1",
"shape": "CI.Standard.E4.Flex",
"shapeConfig": {
"ocpus": 1,
"memoryInGBs": 1
},
"vnics": [
{
"subnetId": "ocid1.subnet.oc1.iad....",
"displayName": "DISDatabaseVCN",
"isPublicIpAssigned": true,
"skipSourceDestCheck": true
}
],
"displayName": "container-instance-20230411-1205",
"gracefulShutdownTimeoutInSeconds": 0,
"containerRestartPolicy": "NEVER"
}

If you view the container logs, you will see the etag printed out in the response for the OCI Object Storage object put command.

Phase 4 Handling Secrets

You can get much more complex in the script, for example if you want to get more sensitive pieces of information that you want to secure you can access OCI Vault to get secrets;

SECRET_CONTENT=`oci --auth resource_principal secrets secret-bundle get --secret-id $SECRET_ID`

You can get the content from the response and replace variables within with this content, these are just regular shell tips and tricks;

SECRET_ID_CONTENT=`echo $SECRET_CONTENT|jq -r '.data."secret-bundle-content".content'|base64 --decode`
CMD_TEXT=`echo "Some test with #SECRET_ID_CONTENT here" | sed "s/#SECRET_ID_CONTENT/$SECRET_ID_CONTENT/g"`
eval $CMD_TEXT

Phase 5 Executing Python Scripts

So the above used shell commands, how about python, can the same trick be used? You bet! You can use the same command override as shell above, and change the arguments to the command below to retrieve python script from OCI Object Storage and execute using python3, this has all of the OCI SDK at your fingertips too since the container is based on the OCI CLI!

oci - auth resource_principal os object get - bucket-name abucket - name mypython.py - file - | python3

Very simple and very powerful. Here is the full payload;

{
"containers": [
{
"imageUrl": "ghcr.io/oracle/oci-cli",
"displayName": "container-20230411-1228-1",
"arguments": [
"oci --auth resource_principal os object get --bucket-name abucket --name mypython.py --file - | python3"
],
"command": [
"/bin/sh",
"-c"
],
"definedTags": {},
"freeformTags": {}
}
],
"compartmentId": "ocid1.compartment.oc1.....",
"availabilityDomain": "nDUb:US-ASHBURN-AD-1",
"shape": "CI.Standard.E4.Flex",
"shapeConfig": {
"ocpus": 1,
"memoryInGBs": 1
},
"vnics": [
{
"subnetId": "ocid1.subnet.oc1.iad.....",
"displayName": "DISDatabaseVCN",
"isPublicIpAssigned": true,
"skipSourceDestCheck": true
}
],
"displayName": "container-instance-20230411-1228",
"containerRestartPolicy": "NEVER"
}

Summary

Aren’t the best things the simplest things! We’ve seen how you can run any set of commands in a serverless manner in OCI, reading and writing data to OCI Object Storage using standard input/output streams as an example including local files to REST endpoints and much more! Hope you found this useful, would love to hear what you think.

--

--

David Allan

Architect at @Oracle The views expressed on this blog are my own and do not necessarily reflect the views of Oracle.