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