Note:

Set Up Events Based Automated Email Notifications for Cost Analysis Reports Using Oracle Cloud Native Framework

Introduction

Every organization running their workloads on cloud, is very keen on understanding their cost expenditure on cloud, and want to report it in a meaningful way. For this, Oracle Cloud Infrastructure (OCI) provides a functionality using which you can create automated reports based on cost, and then get those reports in OCI Object Storage.

For these cost reports, teams would also like to set up automation in a way that the corresponding team will get the notification as soon as the report is getting generated. With this, they would also like to have the link to the reports as part of the notification. You can set up all of this automation using the Oracle Cloud Native Framework.

Objectives

Prerequisites

Task 1: Set up the Required OCI IAM policies and Dynamic Groups

  1. Log in to the OCI Console, go to Dynamic Group and create a new dynamic group.

    Create Dynamic Group

  2. Enter the compartment OCID in Rule 1. Your OCI Functions should exist in the same compartment.

    Setup Dynamic Group

  3. Go to OCI IAM policies and create a policy with the following statement.

    allow dynamic-group dynamic_group_name to manage ons-family in compartment compartment_name
    
    allow dynamic-group dynamic_group_name to manage object-family in compartment compartment_name
    

    Note:

    • Please use more specific IAM policy as suitable to your use case. See the example as below:

      • allow dynamic-group dynamic_group_name to use ons-topics in compartment compartment_name where request.operation = 'PublishMessage'
      • allow dynamic-group dynamic_group_name to manage buckets in compartment compartment_name where all {request.operation = 'CreatePreauthenticatedRequest', target.bucket.name = 'bucket_name'}

Task 2: Set up an OCI Object Storage Bucket and Create a Scheduled Report

  1. Create a bucket using OCI Console. We will use this bucket to push our reports. Make sure the bucket you have created is private as this will make sure your content is secure from public access as well as the Emit Object Events is enabled on this bucket, as this will allow events to emit when there is a new object pushed.

    Create Bucket

  2. Go to Cost Management, Scheduled Reports, and create a scheduled report. Enter Name, Description, Start date and Recurrence.

    Create Scheduled Report

    Select the bucket to which you want to publish your reports. To select the bucket from the drop-down menu, you have to add the policy as shown in the following image so that metering service will have access to the bucket created.

    Create Scheduled Report

  3. After the bucket and scheduled report is created, you can check the history of reports executed, and you will also see the reports are published to the selected OCI Object Storage bucket.

    History Scheduled Report

Task 3: Create an OCI Notifications Topic and Add a Subscription

  1. Create an OCI Notifications topic which is required to send the email notification. Go to Notifications, Topic and create a topic.

    Create Topic

  2. Create a subscription in the same topic and add an email to which the notification will be sent.

    Create Subscription

    Now, the OCI Notifications topic is configured.

Task 4: Create and Deploy the Function Code

Go to functions/applications and create a function. This function will use the OCI IAM dynamic group and policy to access the OCI Object Storage bucket and perform the required operation (create PAR URL for the scheduled report file, and send notifications with it), when the function is triggered by the event emitted.

There are a wide range of approaches which can be used to develop OCI Functions, with quickstart guides which walk you through setting up a development environment using OCI Cloud Shell, an OCI virtual machine, or running locally. The images in the remainder of this guide will show OCI Cloud Shell, but any of these approaches are valid. We have used Python to code the required logic, but you can use another language and change the logic as needed.

  1. To write the code, open the requirement.txt and func.py file in the OCI Cloud Shell.

    Deploy Function

  2. The following is the function code, it uses the OCI resource principals so that you do not have to keep your OCI credentials. Remember to replace the variables for your creations, like bucket name, topic OCID and so on.

    • requirements.txt.

      fdk>=0.1.71
      oci>=2.112.0
      
    • func.yaml.

      schema_version: 20180708
      name: reportsauto
      version: 0.0.6
      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
      
    • func.py.

      import io
      import json
      import logging
      import oci
      from datetime import datetime
      
      from fdk import response
      
      
      def handler(ctx, data: io.BytesIO = None):
          try:
              body = json.loads(data.getvalue())
              logging.getLogger().info(body.get("eventType"))
              logging.getLogger().info(body.get("data").get("resourceName"))
          except (Exception, ValueError) as ex:
              logging.getLogger().info('error parsing json payload: ' + str(ex))
      
          logging.getLogger().info("Inside Python Hello World function")
          initiateFn(body.get("data").get("resourceName"))
          return response.Response(
              ctx, response_data=json.dumps(
                  {"message": "Function Executed!"}),
              headers={"Content-Type": "application/json"}
          )
      
      def initiateFn(uploaded_file):
          logging.getLogger().info("Reached initiate function...")
      
          signer = oci.auth.signers.get_resource_principals_signer()
          object_storage_client = oci.object_storage.ObjectStorageClient(config={}, signer=signer)
          namespace_name="orasenatdplt01"
          bucket_name="demobucket"
      
          now = datetime.now()
          par_name = "PAR_Request_" + str(now).split('.')[0].replace(" ", "_").replace(":", "_").replace("-", "_")
          create_preauthenticated_request_response = object_storage_client.create_preauthenticated_request(
              namespace_name=namespace_name,
              bucket_name=bucket_name,
              create_preauthenticated_request_details=oci.object_storage.models.CreatePreauthenticatedRequestDetails(
                  name=par_name,
                  access_type="ObjectRead",
                  time_expires=datetime.strptime(
                      "2037-06-05T04:25:22.344Z",
                      "%Y-%m-%dT%H:%M:%S.%fZ"),
                  object_name=uploaded_file))
      
          callNotificationAPI(create_preauthenticated_request_response.data)
      
      # Function to call Notification API as soon as an object push is detected
      def callNotificationAPI(parData):
          logging.getLogger().info("Trigger notification as object push is detected...")
          topicOcid = "ocid1.onstopic.oc1.iad.amaaaaxxxxxxxxxamn4"
          signer = oci.auth.signers.get_resource_principals_signer()
          ons_client = oci.ons.NotificationDataPlaneClient(config={}, signer=signer)
          publish_message_response = ons_client.publish_message(
              topic_id=topicOcid,
              message_details=oci.ons.models.MessageDetails(
                  body="OCI Notification Service - Cost Report File is created: " + str(parData.full_path),
                  title="OCI Notification Service - Cost Report File is created"),
              message_type="RAW_TEXT")
          logging.getLogger().info("Triggered notification as object push is detected...")
      
  3. Update the function in registry and deploy the latest code using the following command on the OCI Cloud Shell.

    fn -v deploy — app reportsauto
    
  4. Once deployed, your function is ready to be invoked. You can try to test using the function invoke command and ensure the function is working as required.

    DEBUG=1 fn invoke reportsauto your_app_name
    

    Note:

    • OCI supports resource/instance principals to avoid providing any user related information while executing the function. So this task is needed to make sure the function has the correct permission set to call the APIs, failing which the function will not work properly.

    • (Optional ) You can also check the Debug mode for troubleshooting. For example: run the following command from OCI Cloud Shell to invoke and test the function if it is working as expected.

      DEBUG=1 fn invoke e2e-function-demo e2e-fn-streaming
      

Task 5: Set up OCI Events Rules

  1. Go to the OCI Console, click Application Integration, Events Service, Rules, and Create event rule. Enter a rule condition and add a function created in Task 4 as the trigger action.

  2. Enter Display Name and Description.

    Create Event Rule

  3. In the Rule condition section, select first condition as Event Type, Object Storage, Object - Create and another condition as Attribute, bucketName, demoBucket.

    Define Event Rule

  4. Connect event with the function through the Actions section. Select Function Compartment, Function Application and the function deployed in Task 4.

    Event Rule Target

  5. Click Save changes to save the event rule. This will invoke the function when the condition matches for the emitted event from the OCI Object Storage bucket.

Acknowledgments

More Learning Resources

Explore other labs on docs.oracle.com/learn or access more free learning content on the Oracle Learning YouTube channel. Additionally, visit education.oracle.com/learning-explorer to become an Oracle Learning Explorer.

For product documentation, visit Oracle Help Center.