DEV Community

Cover image for How to use Functions in OCI (Python)
Faris Durrani
Faris Durrani

Posted on • Edited on

How to use Functions in OCI (Python)

Oracle Cloud's answer to serverless architecture--an overview of how to use it in Python including with configuration variables and input request bodies

What is OCI Functions

Oracle Cloud's Functions is equivalent of AWS Lambda--a scalable, on-demand, multi-tenant Functions-as-a-Service platform solution which can be used to perform 5-minute-max short functions on the cloud. It is built on and powered by the Fn Project open source engine and can be deployed by the OCI API, CLI, and UI Console. Deployed Functions can be invoked by using the CLI, signed HTTP requests, or automatically on a schedule using the Resource Scheduler.

You can write code in Java, Python, Node, Go, Ruby, and C# (and for advanced use cases, bring your own Dockerfile, and Graal VM) to use in OCI Functions.

How to create a Function in OCI (Python)

1️⃣ Create an Application

Within the UI console on https://cloud.oracle.com, head to Developer Services > Applications. Hit Create application.

A shape of GENERIC_X86 is appropriate in most use cases.

Create Application

Create Application 2

2️⃣ Install prerequisites

To continue, we need to make sure you've installed certain requirements.

🔹 1. Ensure you have your OCI API config profile set up. See Setting up the OCI Configuration File using API Keys .
🔹 2. Install the fn CLI:

MacOS using Brew:

brew update && brew install fn
Enter fullscreen mode Exit fullscreen mode

Linux or MacOS:

curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh
Enter fullscreen mode Exit fullscreen mode

Windows:
In a Windows environment, install the Fn Project CLI by following the Install Fn Client instructions in the How-to: Run Fn client on Windows and connect to a remote Fn server topic on GitHub.

More info: Installing the Fn Project CLI

3️⃣ Create and Deploy a sample Function in Python

These instructions can be found on the Console UI of the new Application page under Details > Getting Started > Local setup.

Application page

Local setup

Note that since we are creating an AMD image as opposed to an ARM one, you cannot deploy this Function from a Mac or any ARM machines.

Making sure to substitute the appropriate values like your tenancy namespace (idds2omv6oq), compartment OCID, compartment name (fdurrani-lab), and Docker login info, run the following to deploy and invoke a new Function from your local Linux/AMD machine:

# 1. Initialize your function
fn init --runtime python my-func

# 2. Switch into the generated directory
cd my-func

# 3. Create a context for this compartment and select it for 
fn create context fdurrani-lab --provider oracle
fn use context fdurrani-lab

# 4. Update the context with the compartment ID and the Oracle Functions API URL
fn update context oracle.compartment-id ocid1.compartment.oc1..aaaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
fn update context api-url https://functions.us-ashburn-1.oraclecloud.com

# 5. Provide a unique repository name prefix to distinguish your function images from other people's.
fn update context registry iad.ocir.io/idds2omv6oq/test-app-func-repo

# 6. Log into the Registry using the Auth Token as your password
docker login -u 'idds2omv6oq/[email protected]' iad.ocir.io

# 7. (Optional) Switch to the right OCI profile
fn update context oracle.profile DEFAULT

# 8. Deploy your function
fn deploy --app test-app

# 9. Invoke your function
fn invoke test-app my-func
Enter fullscreen mode Exit fullscreen mode

The Python template will have the following files:

func.py

import io
import json
import logging

from fdk import response


def handler(ctx, data: io.BytesIO = None):
    name = "World"
    try:
        body = json.loads(data.getvalue())
        name = body.get("name")
    except (Exception, ValueError) as ex:
        logging.getLogger().info('error parsing json payload: ' + str(ex))

    logging.getLogger().info("Inside Python Hello World function")
    return response.Response(
        ctx, response_data=json.dumps(
            {"message": "Hello {0}".format(name)}),
        headers={"Content-Type": "application/json"}
    )
Enter fullscreen mode Exit fullscreen mode

func.yaml

schema_version: 20180708
name: my-func
version: 0.0.7
runtime: python
build_image: fnproject/python:3.11-dev
run_image: fnproject/python:3.11
entrypoint: /python/bin/fdk /function/func.py handler
memory: 256
Enter fullscreen mode Exit fullscreen mode

requirements.txt

fdk>=0.1.93
Enter fullscreen mode Exit fullscreen mode

4️⃣ Invoke the Function

🔹 1. Using Fn CLI

We've seen above how to invoke the Function using the Fn CLI in Step #9.

After deploying the Function and invoking it with Step #9 above, we get a simple Hello World reply:

Hello World reply

🔹 2. Using the OCI CLI (Signed HTTP)

We also can copy the Invoke endpoint of the Function and run a raw-request call using the OCI CLI to make a signed HTTP POST request to the endpoint:

oci raw-request --http-method POST --target-uri https://oofozydkb5a.us-ashburn-1.functions.oci.oraclecloud.com/20181201/functions/ocid1.fnfunc.oc1.iad.aaaaaaaaxxxxxxxxxxx/actions/invoke --profile DEFAULT
Enter fullscreen mode Exit fullscreen mode

Copy Invoke endpoint

Invoke results

🔹 3. Periodic scheduling using Resource Scheduler

You can also schedule the Function to run periodically based on a Cron schedule using OCI's Resource Scheduler.

First, you need to give the Resource Scheduler permission to use the Function with the OCI IAM Policies:

allow any-user to use fn-function in compartment compA where all {request.principal.type='resourceschedule', request.principal.id='ocid1.resourceschedule.oc1.iad.aaaaaxxxxxxxxxxx'}
Enter fullscreen mode Exit fullscreen mode

Of course, filtering by ID is optional if you want to allow the Resource Scheduler to invoke all Functions in the tenancy.

Include as well any policies the Function may need to read or access your OCI resources like below:

allow dynamic-group 'lb-fn-dynamic-grp' to inspect load-balancers in tenancy
Enter fullscreen mode Exit fullscreen mode

where lb-fn-dynamic-grp is a Dynamic Group defined as:

resource.id = 'ocid1.fnfunc.oc1.iad.aaaaaxxxxxxxx'
Enter fullscreen mode Exit fullscreen mode

Then, create a Schedule to Start the Function:

Scheduling a Function

5️⃣ Insert inputs

You can pass arguments and values to a Function.

Looking at the initial Python Function template above, we see the handler() function retrieving the POST API request body from the data:

func.yaml

...
def handler(ctx, data: io.BytesIO = None):
    name = "World"
    try:
        body = json.loads(data.getvalue())
        name = body.get("name")
...
Enter fullscreen mode Exit fullscreen mode

🔹 Prefixing in fn invoke

We can prefix the JSON body by prefixing the JSON to the fn invoke command:

echo -n '{"name":"John"}' | fn invoke test-app my-func
Enter fullscreen mode Exit fullscreen mode

Prefix fn invoke

🔹 Add in HTTP request

You can also add the JSON value into the body of the HTTP POST request when invoking the API:

oci raw-request --http-method POST --target-uri https://oofozydkb5a.us-ashburn-1.functions.oci.oraclecloud.com/20181201/functions/ocid1.fnfunc.oc1.iad.aaaaaaaaxxxxxxxxxxx/actions/invoke --request-body '{"name":"John"}' --profile DEFAULT
Enter fullscreen mode Exit fullscreen mode

OCI CLI call HTTP body

6️⃣ Use environment variables

You can insert configuration environment variables onto the Function or the parent Application page on the Console to be fed into the Function. This can be helpful if you want to allow the end user to modify certain variables without modifying the Function's code and redeploying it.

🔹 1. Add configuration variables

You can add the configuration variables either in the parent Application:

Config in parent app

or in the Function:

Config in Function

As you can see, the parent Application's configuration variables are inherited to the Function.

🔹 2. Modify the Python code

Now, we modify the Python code to read in the configuration variables and return them:

func.yaml

import io
import json
import logging

from fdk import response


def handler(ctx, data: io.BytesIO = None):
    name = "World"
    allKeyValuesString = ""
    try:
        cfg = ctx.Config()
        allKeyValuesString += cfg.get("key1", "missing") + " "
        allKeyValuesString += cfg.get("key2", "missing") + " "
        allKeyValuesString += cfg.get("key3", "missing") + " "
        allKeyValuesString += cfg.get("keyParent", "missing") + " "

        body = json.loads(data.getvalue())
        name = body.get("name")
    except (Exception, ValueError) as ex:
        logging.getLogger().info('error parsing json payload: ' + str(ex))

    logging.getLogger().info("Inside Python Hello World function")
    return response.Response(
        ctx, response_data=json.dumps(
            {"message": "Hello {0}. {1}".format(name, allKeyValuesString)}),
        headers={"Content-Type": "application/json"}
    )
Enter fullscreen mode Exit fullscreen mode

🔹 3. Re-deploy and invoke the Function

Deploying and invoking the Function again, and we get a printout of all the available values from the configuration:

fn deploy --app test-app
fn invoke test-app my-func
Enter fullscreen mode Exit fullscreen mode

Printout of key values

References

  1. Overview of Functions

Safe harbor statement

The information provided on this channel/article/story is solely intended for informational purposes and cannot be used as a part of any contractual agreement. The content does not guarantee the delivery of any material, code, or functionality, and should not be the sole basis for making purchasing decisions. The postings on this site are my own and do not necessarily reflect the views or work of Oracle or Mythics, LLC.

This work is licensed under a Creative Commons Attribution 4.0 International License.

Top comments (0)