I was playing lately with Oracle Cloud Infrastructure (OCI) Events Service and Functions and while searching through resources already available, I stumbled upon this great video about OCI Functions and I thought that it would be nice to actually create the demo presented in that video.
The demo use case sounds something like this:
When a new Compute instance is provisioned or terminated, an OCI Event rule will call an OCI Function that will create or delete a new Object Storage Bucket with the name of the instance in the same compartment.
Let’s build this.
Setup Dynamic-Group & Policies
Before going into Events, Functions, and Python, we should set up the environment first.
Create a Dynamic-Group for OCI Functions
I have a dynamic-group that I use for both my Fn development environment (which is on an OCI Compute instance) and for giving rights to my Functions in OCI.
Check this post if you want to see how to set up your development environment for OCI Functions.
Under Identity – Dynamic Groups click on Create Dynamic Group:
There are two rules here:
- The first rule is for my development environment – so the instance from where I deploy my Functions has access
- I specify the Instance ID but also the Compartment ID if I want to use another instance from the same compartment
- The second rule is for the Functions
- I specify the resource type fnfunc and the compartment where I will deploy the Function
Create Policies
Under Identity – Policies click on Create Policy:
Statement 1 – Will give access to Function as a Service to read the docker repositories in my tenancy
Statement 2 – Will give access to Function as a Service to access network resources in my compartment
Statement 3 – Will give access to my Fn Development Instance and my deployed Functions to manage all resources in my compartment. Of course, you can fine grain the policies depending on your use case.
Make sure you also have the right policies for the FaaS – check this page to create the policy you need.
Create a new Application
Under Developer Services – Functions click on Create Application:
I have deployed my app in a public subnet, inside my VCN and I will send the logs to my papertrail account – I like to use papertrail for logging, especially in a development phase of my apps.
Create Function
From your development environment, create a new python function – I am doing this from my OCI Compute instance:
ubuntu@fn-dev:~$ fn init --runtime python create-bucket-for-compute Creating function at: ./create-bucket-for-compute Function boilerplate generated. func.yaml created.
The newly created function should look like this:
ubuntu@fn-dev:~$ cd create-bucket-for-compute/ ubuntu@fn-dev:~/create-bucket-for-compute$ ll total 20 drwxr-xr-x 2 ubuntu ubuntu 4096 Apr 18 16:34 ./ drwxr-xr-x 10 ubuntu ubuntu 4096 Apr 18 16:34 ../ -rw-r--r-- 1 ubuntu ubuntu 574 Apr 18 16:34 func.py -rw-r--r-- 1 ubuntu ubuntu 154 Apr 18 16:34 func.yaml -rw-r--r-- 1 ubuntu ubuntu 3 Apr 18 16:34 requirements.txt
Deploy & Test
We’ll just deploy and test the hello-world function for now and we’ll come back a bit later on this to actually write the code that will create or delete a bucket based on a Compute event.
From inside the function folder, deploy to the application created from the OCI Console – mine was create-bucket-app.
ubuntu@fn-dev:~/create-bucket-for-compute$ fn -v deploy --app create-bucket-app
Let’s try to invoke the function to see that it works:
ubuntu@fn-dev:~/create-bucket-for-compute$ fn invoke create-bucket-app create-bucket-for-compute {"message": "Hello World"}
Great – now that this works, we should set up the event first and then write the complete function based on the CloudEvent.
Create Event Rule
Let’s create an event that will trigger the whole process.
Under Application Integration – Events Services click on Create Rule:
I am choosing to trigger this event when:
- A new instance is created – Instance – Lanch End
- An instance is terminated – Instance – Terminate End
- I want only to trigger the event only for instances inside my compartment – Ionut
The Action is to trigger the function – create-bucket-for-compute – that we just deployed earlier.
The python function
Now, let’s head back to the development environment and let’s write the actual script that will do all the work.
I will be using the OCI Python SDK to create and delete the buckets, so I have to add it to the requirements.txt so it will be installed on the docker container that will hold the function:
ubuntu@fn-dev:~/create-bucket-for-compute$ cat requirements.txt fdk oci==2.12.4
This is the latest stable version at the time of the article.
Authentication
To execute API calls against OCI, you have to authenticate. I will use Instance Principals signer for that. You can read more about that in this article.
# authenticate via Instance Principals signer = oci.auth.signers.get_resource_principals_signer()
Read the Cloud Event
When called by the event service, a Cloud Event type of message will be sent and you can read it like this:
# get the info from the event service body = json.loads(data.getvalue()) logging.getLogger().info("Body is: " + str(body))
Get the event type
We will use the same function for both create and delete, so we’d have to know whether the instance was created or deleted:
# see if instance was created or terminated action_type = body["eventType"] logging.getLogger().info("action_type is: " + str(action_type)) # create or delete bucket based on event type if action_type == "com.oraclecloud.computeapi.launchinstance.end": create_bucket() elif action_type == "com.oraclecloud.computeapi.terminateinstance.end": delete_bucket()
You can find all the event types in here.
Initiate the Object Storage Client
We will initiate the Object Storage Client so we can create and/or delete Object Storage Buckets:
# initiate the object storage client object_storage_client = oci.object_storage.ObjectStorageClient({}, signer=signer)
We pass the signer for authentication purposes. You can find out more about how to use the OCI Python SDK here.
Create Bucket
We will create a bucket in the same compartment as the instance and with the same name as the instance:
# get the namespace (tenancy name) namespace = object_storage_client.get_namespace().data logging.getLogger().info("Namespace is: "+ str(namespace)) # create the bucket create_bucket_response = object_storage_client.create_bucket( namespace, oci.object_storage.models.CreateBucketDetails( name=resource_name, compartment_id=compartment_id, public_access_type='ObjectRead', storage_tier='Standard', object_events_enabled=True ) )
The compartment id and the resource name will come from the Cloud Event.
Delete Bucket
To delete a bucket, you need the namespace and its name:
# get the namespace (tenancy name) namespace = object_storage_client.get_namespace().data # delete the bucket object_storage_client.delete_bucket(namespace, resource_name)
Complete solution
Let’s put everything together in the function:
import io import json import logging from fdk import response import oci def handler(ctx, data: io.BytesIO=None): try: # authenticate via Instance Principals signer = oci.auth.signers.get_resource_principals_signer() # get the info from the event service body = json.loads(data.getvalue()) logging.getLogger().info("Body is: " + str(body)) # get compartment id and instance name compartment_id = body["data"]["compartmentId"] resource_name = body["data"]["resourceName"] # see if instance was created or terminated action_type = body["eventType"] logging.getLogger().info("action_type is: " + str(action_type)) # initiate the object storage client object_storage_client = oci.object_storage.ObjectStorageClient({}, signer=signer) # create or delete bucket based on event type if action_type == "com.oraclecloud.computeapi.launchinstance.end": create_bucket(object_storage_client, compartment_id, resource_name) elif action_type == "com.oraclecloud.computeapi.terminateinstance.end": delete_bucket(object_storage_client, compartment_id, resource_name) except (Exception, ValueError) as ex: logging.getLogger().error("There was an error: " + str(ex)) return response.Response( ctx, response_data=json.dumps( {"message": "The function executed successfully"}), headers={"Content-Type": "application/json"} ) def create_bucket(object_storage_client, compartment_id, resource_name): logging.getLogger().info("Create bucket") try: # get the namespace (tenancy name) namespace = object_storage_client.get_namespace().data logging.getLogger().info("Namespace is: "+ str(namespace)) # create the bucket create_bucket_response = object_storage_client.create_bucket( namespace, oci.object_storage.models.CreateBucketDetails( name=resource_name, compartment_id=compartment_id, public_access_type='ObjectRead', storage_tier='Standard', object_events_enabled=True ) ) except Exception as ex: logging.getLogger().error("There was an error creating the bucket: " + str(ex)) logging.getLogger().info(f"Bucket {resource_name} was created") def delete_bucket(object_storage_client, compartment_id, resource_name): logging.getLogger().info("Delete bucket") try: # get the namespace (tenancy name) namespace = object_storage_client.get_namespace().data # delete the bucket object_storage_client.delete_bucket(namespace, resource_name) except Exception as ex: logging.getLogger().error("There was an error deleting the bucket: " + str(ex)) logging.getLogger().info(f"Bucket {resource_name} was deleted")
and deploy it:
ubuntu@fn-dev:~/create-bucket-for-compute$ fn -v deploy --app create-bucket-app
Test the solution
All that’s left now is to test the solution.
First of all, let’s create a new Compute instance in our compartment and wait for it to be provisioned:
Once it was provisioned – the Event Rule created earlier will be triggered (you can check the logs under Application Integration – Events Service – Your Rule – Logs) and that will call our Function.
The Function will create, in the same compartment, a bucket with the same name as the instance:
Now, terminate the instance and the bucket will be automatically deleted.
Conclusions
The combination of OCI Events and OCI Functions can be really useful when trying to automate your tasks or when trying to build Infrastructure as Code.
You can find the whole function on my Github.
Resources
Setup development environment for OCI Functions
How to use the OCI Python SDK to make API Calls