Deploy FunctionGraph Event Function from ZIP

This sample demonstrates how to deploy a simple event function to FunctionGraph with code from ZIP using terraform.

This approach is used, if your unpacked FunctionGraph deployment package is less than 40MB.

Source code of this sample is available on GitHub.

Prerequisites

  • running on Linux / Windows Subsystem for Linux (WSL)

  • make installed

  • curl installed

  • Terraform/OpenTofu installed and Terraform/OpenTofu configured, see Terraform Setup.

What will be deployed

  • event function

  • lts log group and log stream

  • test event configuration

Creating deployment package

The deployment package for this sample is created with make create_package.

# Makefile for Terraform deployment


# Terraform backend configuration
BACKEND_CONFIG_BUCKET := "doc-samples-tf-backend"
BACKEND_CONFIG_KEY := "terraform_state/php/deploy-from-zip.tf"
BACKEND_CONFIG_REGION := "eu-de"
BACKEND_CONFIG_ENDPOINTS := "endpoints={s3=\"https://obs.eu-de.otc.t-systems.com\"}"

CURRENT_MAKEFILE := $(firstword $(MAKEFILE_LIST))


create_package:
  # install production dependencies without development dependencies
  composer install --no-dev --prefer-dist
  # create a code.zip archive of the production dependencies
  composer archive --format=zip --file=code
  # reinstall all dependencies including development dependencies
  composer install

tf_init:
  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)"

tf_plan: 
  if [ ! -f "terraform/.terraform.lock.hcl" ]; then \
    $(MAKE) -f $(CURRENT_MAKEFILE) tf_init; \
  fi
  terraform -chdir=terraform \
    plan \
    -var-file="variables.tfvars" 

tf_apply: create_package
  if [ ! -f "terraform/.terraform.lock.hcl" ]; then \
    $(MAKE) -f $(CURRENT_MAKEFILE) tf_init; \
  fi
  terraform -chdir=terraform \
    apply -auto-approve \
    -var-file="variables.tfvars"

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


test_deployed:
  # getting Token for authentication from Username/Password...
  $(eval OTC_X_AUTH_TOKEN := $(shell ../../utils/tokenFromUsername.sh))
  # getting the Function URN from terraform output...
  $(eval MY_FUNCTION_URN := $(shell terraform -chdir=terraform output -raw MY_FUNCTION_URN))
  # calling the deployed function via FunctionGraph API...
  @curl -X POST \
   -H "Content-Type: application/json" \
   -H "x-auth-token: $(OTC_X_AUTH_TOKEN)" \
   -d '{"key":"Hello World of FunctionGraph"}' \
   https://functiongraph.$(OTC_SDK_REGION).otc.t-systems.com/v2/$(OTC_SDK_PROJECTID)/fgs/functions/$(MY_FUNCTION_URN):latest/invocations
  @echo "" 
  # finished


.PHONY: create_package tf_init tf_plan tf_apply tf_destroy test_deployed test_deployed_apig_post

This will create a code.zip file with the function code in project root folder.

Terraform files

The terraform files for this sample are located in the samples-doc/deploy-from-zip/terraform folder.

provider.tf

The file provider.tf defines the provider configuration for this sample:

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

variable "OTC_FGS_ENDPOINT" {
  description = "FunctionGraph Endpoint"
  type        = string
  default     = "https://functiongraph.eu-de.otc.t-systems.com"
}

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.70"
    }

  }
  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

}

You might need to adapt the provider configuration to your needs, especially the provider version and backend configuration for terraform state.

For variables used in provider.tf, see Terraform Setup.

variables.tf

The file variables.tf defines the variables for this sample:

# prefix will be prepended to all resource names
variable "prefix" {
  type    = string
  default = "set in variables.tfvars"
}

# description of the function
variable "description" {
  type    = string
  default = "set in variables.tfvars"
}

# FunctionGraph: Function name
variable "function_name" {
  type    = string
  default = "set in variables.tfvars"
}

variable "handler_name" {
  type    = string
  default = "set in variables.tfvars"
}

variable "initializer_name" {
  type    = string
  default = "set in variables.tfvars"
}

# name of zip file to deploy, generated by 'mvn package' command
variable "zip_file_name" {
  type    = string
  default = "set in variables.tfvars"
}

# Resource tag:
variable "tag_app_group" {
  type    = string
  default = "set in variables.tfvars"
}

These variables will set in the variables.tfvars file.

function.tf

The file function.tf defines the function resource for this sample:

##########################################################
# Create ph event function
##########################################################
resource "opentelekomcloud_fgs_function_v2" "MyFunction" {
  
  name = format("%s_%s", var.prefix, var.function_name)
  app  = "default"

  handler = var.handler_name

  initializer_handler = var.initializer_name
  initializer_timeout = 30

  runtime = "PHP8.3"

  ###### relevant part for deploy function code from zip file ######
  code_type = "zip"
  func_code     = filebase64(format("${path.module}/../%s", var.zip_file_name))
  code_filename = basename(var.zip_file_name)
  ###### ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ######

  description      = var.description
  memory_size      = 512
  timeout          = 30
  max_instance_num = 1

  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({
    "RUNTIME_LOG_LEVEL" : "DEBUG",
  })

  tags = {
    "app_group" = var.tag_app_group
  }

}

output "MY_FUNCTION_URN" {
  value = opentelekomcloud_fgs_function_v2.MyFunction.urn
}

output "MY_FUNCTION_VERSION" {
  value = opentelekomcloud_fgs_function_v2.MyFunction.version
}

The relevant part for deploying function code from zip file is:

###### relevant part for deploying function code from zip file ######
code_type     = "zip"
func_code     = filebase64(format("${path.module}/%s", var.zip_file_name))
code_filename = basename(var.zip_file_name)
###### ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ######

loggroup.tf

The file loggroup.tf defines the log group and log stream for this sample:

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

testevent.tf

The file testevent.tf creates a test event configuration to be used in the FunctionGraph console for testing the deployed function.

##########################################################
# Create Test Event
##########################################################
resource "opentelekomcloud_fgs_event_v2" "test_event_post" {
  function_urn = opentelekomcloud_fgs_function_v2.MyFunction.urn
  name         = "TestEventPost"
  content = filebase64("../resources/test_event.json")
}

The test event will have the following content:

{
    "key": "Hello World of FunctionGraph PHP Runtime!"
}

Deploying using Terraform and make

Following makefile can be used to deploy the function to FunctionGraph using Terraform.

# Makefile for Terraform deployment


# Terraform backend configuration
BACKEND_CONFIG_BUCKET := "doc-samples-tf-backend"
BACKEND_CONFIG_KEY := "terraform_state/php/deploy-from-zip.tf"
BACKEND_CONFIG_REGION := "eu-de"
BACKEND_CONFIG_ENDPOINTS := "endpoints={s3=\"https://obs.eu-de.otc.t-systems.com\"}"

CURRENT_MAKEFILE := $(firstword $(MAKEFILE_LIST))


create_package:
  # install production dependencies without development dependencies
  composer install --no-dev --prefer-dist
  # create a code.zip archive of the production dependencies
  composer archive --format=zip --file=code
  # reinstall all dependencies including development dependencies
  composer install

tf_init:
  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)"

tf_plan: 
  if [ ! -f "terraform/.terraform.lock.hcl" ]; then \
    $(MAKE) -f $(CURRENT_MAKEFILE) tf_init; \
  fi
  terraform -chdir=terraform \
    plan \
    -var-file="variables.tfvars" 

tf_apply: create_package
  if [ ! -f "terraform/.terraform.lock.hcl" ]; then \
    $(MAKE) -f $(CURRENT_MAKEFILE) tf_init; \
  fi
  terraform -chdir=terraform \
    apply -auto-approve \
    -var-file="variables.tfvars"

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


test_deployed:
  # getting Token for authentication from Username/Password...
  $(eval OTC_X_AUTH_TOKEN := $(shell ../../utils/tokenFromUsername.sh))
  # getting the Function URN from terraform output...
  $(eval MY_FUNCTION_URN := $(shell terraform -chdir=terraform output -raw MY_FUNCTION_URN))
  # calling the deployed function via FunctionGraph API...
  @curl -X POST \
   -H "Content-Type: application/json" \
   -H "x-auth-token: $(OTC_X_AUTH_TOKEN)" \
   -d '{"key":"Hello World of FunctionGraph"}' \
   https://functiongraph.$(OTC_SDK_REGION).otc.t-systems.com/v2/$(OTC_SDK_PROJECTID)/fgs/functions/$(MY_FUNCTION_URN):latest/invocations
  @echo "" 
  # finished


.PHONY: create_package tf_init tf_plan tf_apply tf_destroy test_deployed test_deployed_apig_post

Makefile targets:

  • create_package: creates the deployment package as zip file using `composer archive`.

  • tf_init: initializes terraform, this will create the terraform state file in the defined backend.

  • tf_plan: runs terraform plan to see which changes will be applied.

  • tf_apply: runs terraform apply to deploy the function to FunctionGraph.

  • tf_destroy: runs terraform destroy to remove the deployed infrastructure.

Adaptions

Adaptions in Makefile

Before running the targets in the makefile, make sure to

Adapt the BACKEND_CONFIG_* variables in the Makefile

Backend config variables

Variable

Description

Example value

BACKEND_CONFIG_BUCKET

The name of the bucket where terraform state is stored

doc-samples-tf-backend

BACKEND_CONFIG_KEY

The name of the object(key) where terraform state is stored

terraform_state/php/deploy-from-zip.tf

BACKEND_CONFIG_REGION

The region where the bucket for terraform state is stored is located

eu-de

BACKEND_CONFIG_ENDPOINTS

The OBS endpoints for the bucket where terraform state is stored

endpoints={s3="https://obs.eu-de.otc.t-systems.com"}

Adaptions in variables.tfvars

Adapt the variables in the variables.tfvars

# Terraform variables

# prefix of all resources
prefix        = "php"

# description of the function
description = "deploy-from-zip sample"

# name of the function (will be prefixed)
function_name = "deploy-from-zip"

# handler function name defined in your code, e.g. "index.handler"
handler_name = "src/index.handler"

# initializer function name defined in your code, e.g. "index.initializer"
initializer_name = "src/index.initializer"

# name of zip file to deploy
zip_file_name = "code.zip"

# resources will be tagged with this app_group tag
tag_app_group = "deploy-from-zip"
Backend config variables

Variable

Description

Example value

prefix

The prefix for the generated resources, all generated resources will have this prefix in their name.

php

description

The description for the deployed function.

Sample deploy-from-zip

function_name

The name of the deployed function (will be prefixed).

deploy-from-zip

handler_name

The handler name for the deployed

src/index.handler

initializer_name

The initializer name for the deployed function.

src/index.initializer

zip_file_name

The name (with relative path) of the zip file with the function code which is created by `composer archive`.

code.zip

tag_app_group

The tag “app_group” with this value will be added to all created resources, where tagging is applicable.

deploy-from-zip

Deployment to Cloud

After the necessary adaptions are done, you can run the following command to deploy the function to FunctionGraph:

make tf_apply

Testing the deployed function

Synchronous invocation

After the deployment is done, you can test the deployed function with the following command:

make test_deployed

This will call the deployed function with a test event in synchronous way and print the response.

# getting the Function URN from terraform output...
# calling the deployed function via FunctionGraph API...
{"statusCode":200,"headers":{"Content-Type":"application/json"},"isBase64Encoded":false,"body":"{\"key\":\"Hello World of FunctionGraph\"}"}
# finished

Destroy all deployed resources from Cloud

To destroy all deployed resources from the cloud, you can run the following command:

make tf_destroy