Skip to content

Function Drivers

Overview

Function Driver makes it easier to run functions in popular programming languages like Go, and Python directly within workflows. Users no longer need to package these functions in containers; the framework takes care of all the necessary packaging and dependencies, simplifying the process.

Users can leverage this feature for various tasks, such as executing actions on infrastructure, integrating with services like Jira and ServiceNow, or sending notifications. By allowing functions to run without needing containers, the feature reduces complexity and makes it easier to implement actions quickly.

The framework is flexible and can be expanded to support more programming languages in the future. It also includes helpful tools for error handling and logging, making it easier to troubleshoot issues. Additionally, users can choose to run functions either synchronously or asynchronously, depending on their needs. Overall, this feature streamlines workflows and improves the user experience by making function execution straightforward and efficient.

Did you know ?

Function Driver Lifecycle can be managed through various methods:

Go Function Driver Specification

Below is an example specification that defines a driver resource named simple-func-go, which executes a 'Go' function within an environment management framework. It includes configuration settings for resource limits, polling behavior, and the function's execution logic, allowing it to process requests and handle various scenarios dynamically.

apiVersion: eaas.envmgmt.io/v1
kind: Driver
metadata:
  name: simple-func-go
  project: defaultproject
spec:
  config:
    type: function
    timeoutSeconds: 300
    pollingConfig:
      repeat: "15s"
      until: "1h"
    function:
      cpuLimitMilli: "50"
      memoryLimitMi: "128"
      language: go
      languageVersion: "1.22"
      maxConcurrency: 10
      numReplicas: 1
      source: |
        package function

        import (
          "context"

          sdk "github.com/RafaySystems/function-templates/sdk/go"
        )

        func Handle(ctx context.Context, logger sdk.Logger, req sdk.Request) (sdk.Response, error) {
          logger.Info("received request", "req", req)

          counter := 0.0
          if prev, ok := req["previous"]; ok {
            logger.Info("previous request", "prev", prev)
            counter = prev.(map[string]any)["counter"].(float64)
          }

          resp := make(sdk.Response)
          resp["output"] = "Hello World"
          resp["request"] = req

          if err, ok := req["error"]; ok {
            errString, _ := err.(string)
            switch errString {
            case "execute_again":
              if counter > 1 {
                break
              }
              return nil, sdk.NewErrExecuteAgain(errString, map[string]any{
                "rkey":    "rvalue",
                "counter": counter + 1,
              })
            case "transient":
              return nil, sdk.NewErrTransient(errString)
            default:
              return nil, sdk.NewErrFailed(errString)
            }
          }

          return sdk.Response(resp), nil
        }

Environment Template Integration

The below specification illustrates how the simple-func-go driver is integrated into an environment template named demo-envtemp as hooks. These hooks are triggered upon environment initialization, allowing for dynamic execution of functions and enhancing automation capabilities within resource management.

apiVersion: eaas.envmgmt.io/v1
kind: EnvironmentTemplate
metadata:
  name: demo-envtemp
  project: defaultproject
spec:
  version: v1
  resources:
    - type: dynamic
      kind: resourcetemplate
      name: resource1
      resourceOptions:
        version: v1
  hooks:
    onInit:
      - name: func-go
        type: driver
        onFailure: unspecified
        driver:
          name: simple-func-go
      - name: func-py
        type: driver
        options: {}
        onFailure: unspecified
        driver:
          name: simple-func-py

Note: Function drivers can be attached as hooks in environment templates, resource templates, or as tasks in the Resource Template - Custom Provider.


Return Errors/Exceptions

Functions executed through the Function Driver can return three (3) types of errors/exceptions:

  • Failed: This error type indicates that the function has encountered a critical issue and cannot proceed. When a function returns a Failed error, it transitions to a terminal state, which signifies that no further processing will occur for that invocation
  • Transient: A Transient error signals that the function has encountered a temporary issue that may resolve upon retrying. The function will be retried for the number of times specified in its configuration. This mechanism allows for handling situations where external factors might cause intermittent failures
  • ExecuteAgain: This error type indicates that the function should be executed again based on specific conditions defined in the polling configuration. It allows the function to pause and wait for a certain condition to be met before proceeding to the next step in the workflow

Important

The function can have only two terminal states: Success and Failed

Example 1

Below is an example showcasing how different return errors/exceptions can be handled within a Go function:

package function

import (
    "context"
    sdk "github.com/RafaySystems/function-templates/sdk/go"
)

func Handle(ctx context.Context, logger sdk.Logger, req sdk.Request) (sdk.Response, error) {
    logger.Info("Request received", "request", req)
    // Access inputs and metadata
    i1 := req["input1"]
    meta := req["metadata"]
    prev := req["previous"]

    // Example logic for triggering errors
    if executeAgain {
        // If the function needs to be executed again before proceeding
        return nil, sdk.NewErrExecuteAgain("execute again", map[string]interface{}{"key": "value"})
    }

    if transientError {
        // For retrying the function in case of a transient error (e.g., network issues)
        return nil, sdk.NewErrTransient("transient error")
    }

    if failedError {
        // Mark the function as failed and transition to a terminal state
        return nil, sdk.NewErrFailed("failed")
    }

    // On success, return the desired output
    return sdk.Response{"output1": "value1"}, nil
}

Example 2

Below is an example showcasing how different return errors/exceptions can be handled within a Python function:

from typing import *
from logging import Logger

def handle(logger: Logger,request: Dict[str, Any]) -> Dict[str, Any]:
    logger.info(f"inside function handler, request: {request}", extra={"request": request})
    # inputs can be accessed from (request)
    i1 = req["input1"]
    # metadata of the function can be accessed using "metadata" key
    meta = req["metadata"]
    # data passed using NewErrExecuteAgain can be accessed using "previous" key
    prev = req["previous"]

    # logic
    # ...
    # logic

    if executeAgain:
        # If the function has to be executed again
        # before proceeding to next activity.
        # For eg: to check the status of a third party integration.
        # Any data passed using this error
        # is available for the next execution of the function.
        raise sdk.ExecuteAgainException("Please wait for the ticket to be approved or declined", ticket_id=id, counter=counter+1)

    if transientError:
        # If the function has to be retried.
        # For eg: due to intermittent errors like network errors.
        # Will be only retried for the num of times configured
        raise sdk.TransientException(f"ConnectionError: {str(e)}")

    if failedError:
        # If the function has to marked as failure
        raise sdk.FailedException(f"FailedException: {str(e)}")

    return {
       "output1": "value1"
    }