S3 Event Thumbnail Sample

This is a sample that processes an image uploaded to OBS, resizes it to fit within a maximum dimension, and uploads the resized image back to another OBS using FunctionGraph with OBS trigger event.

Overview

Following diagram shows components used in this example:

Components

Source

Source for this sample can be found in: /samples-doc/event-obss3-thumbnail.

Deployment

This sample can be deployed using Terraform, see Prepare the Terraform environment for setup details.

Terraform deployment scripts can be found in: /samples-doc/event-obss3-thumbnail/terraform

Makefile

Adapt file Makefile variables according to your needs.

Makefile
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))/..
current_dir := $(notdir $(patsubst %/,%,$(dir $(mkfile_path))))

BACKEND_CONFIG_BUCKET := "doc-samples-tf-backend"
BACKEND_CONFIG_KEY := "terraform_state/csharp/doc-sample-event-obss3-thumbnail.tf"
BACKEND_CONFIG_REGION := "eu-de"
BACKEND_CONFIG_ENDPOINTS := "endpoints={s3=\"https://obs.eu-de.otc.t-systems.com\"}"

clean:
	rm -f $(mkfile_path)/src/*.zip
	rm -rf $(mkfile_path)/src/bin 
	rm -rf $(mkfile_path)/src/obj

build:
	dotnet build

all: build

release: 
	dotnet build -c Release

initTerraform:
	terraform -chdir=terraform \
	  init \
	  -backend-config=$(BACKEND_CONFIG_ENDPOINTS) \
	  -backend-config="bucket=$(BACKEND_CONFIG_BUCKET)" \
	  -backend-config="key=$(BACKEND_CONFIG_KEY)" \
	  -backend-config="region=$(BACKEND_CONFIG_REGION)"

plan: release
	if [ ! -f "terraform/.terraform.lock.hcl" ]; then \
		$(MAKE) initTerraform; \
	fi
	terraform -chdir=terraform \
	  plan \
		-var-file="net6.tfvars"

deploy: release
	if [ ! -f "terraform/.terraform.lock.hcl" ]; then \
		$(MAKE) initTerraform; \
	fi
	terraform -chdir=terraform \
	  apply -auto-approve \
	  -var-file="net6.tfvars"

destroy:
	terraform -chdir=terraform \
	  destroy -auto-approve \
		-var-file="net6.tfvars"

.PHONY: build zip clean all release deploy destroy initTerraform plan

Terraform files

  • Agency and permissions

    agency.tf
    ###########################################################
    # Custom role to allow FunctionGraph to access LTS and OBS
    ###########################################################
    resource "opentelekomcloud_identity_role_v3" "role" {
      display_name  = format("%s-%s-role", var.prefix, var.function_name)
      description   = "Role for FunctionGraph to access OBS"
      display_layer = "project"
    
      statement {
        effect = "Allow"
        action = [
          "functiongraph:*:*",
          "lts:*:*",
        ]
      }
    
      statement {
        effect = "Allow"
        action = [
          "obs:*:*",
        ]
        resource = [
          "OBS:*:*:object:*",
          format("OBS:*:*:bucket:%s", opentelekomcloud_s3_bucket.inbucket.bucket),
          format("OBS:*:*:bucket:%s", opentelekomcloud_s3_bucket.outbucket.bucket),
        ]
      }
    
    }
    
    ##########################################################
    # Agency for FunctionGraph
    # Attention: Crating agency will take some time.
    # Calls to function after creating agency will fail until
    # agency is set up.
    ##########################################################
    resource "opentelekomcloud_identity_agency_v3" "agency" {
      depends_on            = [opentelekomcloud_identity_role_v3.role]
      delegated_domain_name = "op_svc_cff"
    
      name        = format("%s-%s-agency", var.prefix, var.function_name)
      description = "Agency for FunctionGraph to access OBS"
    
      project_role {
        all_projects = true
        project      = "eu-de"
        roles = [
          opentelekomcloud_identity_role_v3.role.display_name
        ]
      }
    
    }
    
  • Input and output buckets

    buckets.tf
    ##########################################################
    # Input bucket for source images
    ##########################################################
    resource "opentelekomcloud_s3_bucket" "inbucket" {
      bucket        = lower(format("%s-%s-%s", var.prefix, var.function_name, "images"))
      acl           = "private"
    
      # Warning: force_destroy will delete bucket on 
      # terraform destroy even if it contains objects
      force_destroy = true
    
      tags = {
        "app_group" = var.tag_app_group
      }
    
    }
    
    ##########################################################
    # Output bucket for thumbnail images
    # For output a different bucket is used to avoid potential
    # risk of recursive invocation of FunctionGraph
    ##########################################################
    resource "opentelekomcloud_s3_bucket" "outbucket" {
      bucket        = lower(format("%s-%s-%s", var.prefix, var.function_name, "images-output"))
      acl           = "private"
    
      # Warning: force_destroy will delete bucket on 
      # terraform destroy even if it contains objects
      force_destroy = true
    
      tags = {
        "app_group" = var.tag_app_group
      }
    
    }
    
    output "INPUT_BUCKET" {
      value = opentelekomcloud_s3_bucket.inbucket.bucket
    }
    output "OUTPUT_BUCKET" {
      value = opentelekomcloud_s3_bucket.outbucket.bucket
    }
    
  • FunctionGraph function

    function.tf
    ##########################################################
    # Create Function
    ##########################################################
    resource "opentelekomcloud_fgs_function_v2" "MyFunction" {
      name             = format("%s_%s", var.prefix, var.function_name)
      app              = "default"
      agency           = opentelekomcloud_identity_agency_v3.agency.name
      handler          = var.function_handler_name
      initializer_handler = var.function_initializer_name
      initializer_timeout =  30
    
      description      = "Sample on how to create Thumbnails from images uploaded to OBS"
      memory_size      = 512
      timeout          = 30
      max_instance_num = 1
      runtime = var.function_runtime
    
      code_type     = "zip"
      func_code     = filebase64(var.zip_file_local)
    
      code_filename = basename(var.zip_file_local)
    
      log_group_id   = opentelekomcloud_lts_group_v2.MyLogGroup.id
      log_group_name = opentelekomcloud_lts_group_v2.MyLogGroup.group_name
    
      log_topic_id   = opentelekomcloud_lts_stream_v2.MyLogStream.id
      log_topic_name = opentelekomcloud_lts_stream_v2.MyLogStream.stream_name
    
      # set some environment variables
      user_data = jsonencode({
        "OUTPUT_BUCKET" : opentelekomcloud_s3_bucket.outbucket.bucket,
        "OBS_ENDPOINT" : "https://obs.otc.t-systems.com",
        # "RUNTIME_LOG_LEVEL" : "ERROR",
        # "RUNTIME_LOG_PATH" : "/tmp"
      })
    
      tags = {
        "app_group" = var.tag_app_group
      }
    
    }
    
  • LogGroup and LogStream

    loggroup.tf
    ##########################################################
    # Create Log Group
    ##########################################################
    resource "opentelekomcloud_lts_group_v2" "MyLogGroup" {
      group_name  = format("%s_%s_%s", var.prefix, var.function_name, "log_group")
      ttl_in_days = 1
    
      tags = {
        "app_group" = var.tag_app_group
      }
    }
    
    ##########################################################
    # Create Log Stream
    ##########################################################
    resource "opentelekomcloud_lts_stream_v2" "MyLogStream" {
      group_id    = opentelekomcloud_lts_group_v2.MyLogGroup.id
      stream_name = format("%s_%s_%s", var.prefix, var.function_name, "log_stream")
    
      tags = {
        "app_group" = var.tag_app_group
      }
    }
    
  • OBS trigger

    trigger_obs.tf
    ##########################################################
    # Create OBS Trigger listening for "ObjectCreate" in
    # input bucket
    ##########################################################
    resource "opentelekomcloud_fgs_trigger_v2" "obstrigger" {
      function_urn = opentelekomcloud_fgs_function_v2.MyFunction.urn
      type         = "OBS"
      event_data = jsonencode({
        "bucket" : opentelekomcloud_s3_bucket.inbucket.bucket
        "events" : [
          "s3:ObjectCreated:*"
        ]
        "name" : lower(format("%s-%s-%s", var.prefix, var.function_name, "event"))
    
      })
    }
    
  • Test event

    testevent.tf
    ##########################################################
    # Create Test Event
    ##########################################################
    resource "opentelekomcloud_fgs_event_v2" "test_event" {
      function_urn = opentelekomcloud_fgs_function_v2.MyFunction.urn
      name         = "UploadTest"
      content = base64encode(jsonencode({
        "Records" = [{
          "eventVersion" = "2.0"
          "eventSource"  = "obs"
          "eventTime"    = "2025-10-24T08:30:00+08:00"
          "eventName"    = "ObjectCreated:PutObject"
          "awsRegion"    = "eu-de"
          "userIdentity" = {
            "principalId" = "EXAMPLE"
          }
          "requestParameters" = {
            "sourceIPAddress" = "EXAMPLE"
          }
          "s3" = {
            "configurationId" = "testConfigRule"
            "bucket" = {
              "name" = opentelekomcloud_s3_bucket.inbucket.id
              "ownerIdentity" = {
                "principalId" = "EXAMPLE"
              }
              "arn" = opentelekomcloud_s3_bucket.inbucket.arn
            }
            "object" = {
              "key" = "image.jpg"
              "size" = 1024
              "eTag" = "0123456789abcdef0123456789abcdef"
              "sequencer" = "0A1B2C3D4E5F678901"
            }
          }
        }]
      }))
    }
    
  • Terraform Provider

    This file contains the terraform provider configuration.

    Note

    Check especially the backend “s3” configuration for bucket and key.

    provider.tf
    # ----------------------------------------------------------------------------
    # Secret variables to be injected as envvar (capital letters for Windows systems)
    # - no defaults
    # - Declared as sensitive --> Not printed in console or log if used in resources
    # ----------------------------------------------------------------------------
    
    
    # set by environment variable TF_VAR_OTC_SDK_AK
    variable "OTC_SDK_AK" {
      description = "Personal access key"
      type        = string
      sensitive   = true
    }
    
    # set by environment variable TF_VAR_OTC_SDK_SK
    variable "OTC_SDK_SK" {
      description = "Personal secret key"
      type        = string
      sensitive   = true
    }
    
    # set by environment variable TF_VAR_OTC_SDK_DOMAIN_NAME
    variable "OTC_SDK_DOMAIN_NAME" {
      description = "Domain Name, eg. OTC-EU-DE-000000000010000XXXXX"
      type        = string
    }
    
    # set by environment variable TF_VAR_OTC_SDK_PROJECTID
    variable "OTC_SDK_PROJECTID" {
      description = "Project Id"
      type        = string
    }
    
    # set by environment variable TF_VAR_OTC_SDK_PROJECTNAME
    variable "OTC_SDK_PROJECTNAME" {
      description = "Project Name, eg. eu-de_MYPROJECT"
      type        = string
    }
    
    # set by environment variable TF_VAR_OTC_IAM_ENDPOINT
    variable "OTC_IAM_ENDPOINT" {
      description = "IAM Endpoint"
      type        = string
      default     = "https://iam.eu-de.otc.t-systems.com/v3"
    }
    
    
    terraform {
      required_providers {
        # specifies required provider, source and version
        # see https://registry.terraform.io/providers/opentelekomcloud/opentelekomcloud/latest
    
        opentelekomcloud = {
          source  = "opentelekomcloud/opentelekomcloud"
          version = ">= 1.37.1"
        }
      }
      backend "s3" {    
        # See: https://registry.terraform.io/providers/opentelekomcloud/opentelekomcloud/latest/docs/guides/backends
    
        # (Required) Specifies the endpoint for OpenTelekomCloud OBS.
        # The value is https://obs.{{region}}.otc.t-systems.com.
        # This can also be sourced from the AWS_S3_ENDPOINT environment variable
        endpoints = {
          s3 = "https://obs.eu-de.otc.t-systems.com"
        }
        
        # (Required) Specifies the bucket name where to store the state.
        # Make sure to create it before.
        bucket = "<your-bucket-name>"
    
        # (Required) Specifies the path to the state file inside the bucket.
        key    = "<path/to/your/terraform.tfstate>"
    
        # (Required) Specifies the region where the bucket is located.
        # This can also be sourced from the AWS_DEFAULT_REGION and 
        # AWS_REGION environment variables.
        region = "<your-region>"
    
        # (Required) Skip credentials validation via the STS API.
        # It's mandatory for OpenTelekomCloud.
        skip_credentials_validation = true
    
        # (Required) Skip validation of provided region name. 
        # It's mandatory for OpenTelekomCloud.
        skip_region_validation = true
    
        skip_requesting_account_id = true
    
        # (Required) Skip usage of EC2 Metadata API.
        # It's mandatory for OpenTelekomCloud.
        skip_metadata_api_check = true
    
        # (Optional) Do not include checksum when uploading S3 Objects.
        # Useful for some S3-Compatible APIs.
        skip_s3_checksum = true
    
        # Although the terraform block does not accept variables or locals and
        # all backend configuration values must be hardcoded, you can provide 
        # the credentials via the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY 
        # environment variables to access OBS, respectively:
        #
        # export AWS_ACCESS_KEY_ID="your accesskey"
        # export AWS_SECRET_ACCESS_KEY="your secretkey"
        #
        # secret_key                  set env var: AWS_ACCESS_KEY_ID
        # access_key                  set env var: AWS_SECRET_ACCESS_KEY
    
      }
    
    }
    
    # ----------------------------------------------------------------------------
    # Providers settings --> OTC
    # We use the AKSK auth scheme
    # See https://registry.terraform.io/providers/opentelekomcloud/opentelekomcloud/latest/docs
    # ----------------------------------------------------------------------------
    #
    
    provider "opentelekomcloud" {
      auth_url = var.OTC_IAM_ENDPOINT
    
      access_key = var.OTC_SDK_AK
      secret_key = var.OTC_SDK_SK 
    
      domain_name = var.OTC_SDK_DOMAIN_NAME
      tenant_name = var.OTC_SDK_PROJECTNAME
    
    }
    

To deploy use following command in directory where Makefile is located:

make deploy

Note

This terraform deployment creates an agency with permissions for FunctionGraph to access OBS.

The creation of an agency with permissions is a time consuming task and may take up to several minutes to complete.

Until the agency is fully, testing the function may fail with permission errors like:

`Error fetching object otc.jpg from bucket csharp-doc-sample-event-obss3-thumbnail-csharp-images: One or more errors occurred. (Access Denied)`

References

In this sample the AWS SDK for .Net is used to access OBS service:

  <ItemGroup>
    <PackageReference Include="AWSSDK.S3" Version="3.7.4.3" />
    <PackageReference Include="AWSSDK.Core" Version="3.7.4.3" />
  </ItemGroup>

Documentation on the AWS SDK for .Net can be found here: Amazon S3 examples using SDK for .NET

Upload image file to source bucket

Linux/Ubuntu

Note

For s3cmd tool installation see: S3cmd on github.

To upload image to source bucket, following script an be used for Ubuntu users:

/samples-doc/event-obss3-thumbnail/testUpload.sh
# https://github.com/opentelekomcloud/obs-s3/blob/master/s3cmd/README.md

# For bucket name see output of terraform output
OBS_INPUT_BUCKET="csharp-doc-sample-event-s3obs-thumbnail-csharp-images"

s3cmd --access_key=${OTC_SDK_AK} --secret_key=${OTC_SDK_SK} --no-ssl \
  put ./test/resources/otc.jpg \
  s3://$OBS_INPUT_BUCKET/otc.jpg

Microsoft Windows

Note

For Microsoft Windows, see OBS Browser+

Alternatives (unsupported)

Huawei obsutil

Note

Huawei obsutil is available for Windows, Linux and Mac from Huawei, see: obsutil Introduction

/samples-doc/event-obss3-thumbnail/testUpload.cmd
REM ########################################################################
REM Sample to upload picture to obs bucket using Huawei obsutil.
REM Huawei obsutil is available, see:
REM https://support.huaweicloud.com/intl/en-us/utiltg-obs/obs_11_0001.html
REM ########################################################################

REM for proxy use, set following environment variables
REM set HTTP_PROXY=proxy:port
REM set HTTPS_PROXY=proxy:port

REM For bucket name see output of terraform output
set OBS_INPUT_BUCKET="csharp-doc-sample-event-s3obs-thumbnail-csharp-images"

obsutil.exe cp .\test\resources\otc.jpg ^
  obs://%OBS_INPUT_BUCKET%/otc.jpg ^
  -e=https://obs.eu-de.otc.t-systems.com ^
  -i=%ACCESS_KEY% ^
  -k=%SECRET_ACCESS_KEY%