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-s3obs-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

main.tf

This files contains all resources to be created for this sample.

/main.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
    ]
  }

}


##########################################################
# Create Function
##########################################################
resource "opentelekomcloud_fgs_function_v2" "MyFunction" {
  # depends_on       = [opentelekomcloud_obs_bucket_object.code_object]
  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 = "obs"
  #code_url  = format("https://%s/code/%s", opentelekomcloud_obs_bucket.codebucket.bucket_domain_name, basename(var.zip_file_local))

  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
  }

  # lifecycle {
  #   # replace if code in bucket changed
  #   replace_triggered_by = [
  #     terraform_data.replacement
  #   ]
  # }
}

##########################################################
# 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
  }
}

##########################################################
# 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
  }

}

##########################################################
# 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"))

  })
}


##########################################################
# 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"
        }
      }
    }]
  }))
}


output "INPUT_BUCKET" {
  value = opentelekomcloud_s3_bucket.inbucket.bucket
}
output "OUTPUT_BUCKET" {
  value = opentelekomcloud_s3_bucket.outbucket.bucket
}

provider.tf

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.36.52"
    }
  }
  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%