Introduction to Functions
Flows wouldn't be very powerful if they were limited to just the predefined states. That's why Direktiv can run "functions" which are basically serverless containers or even a separate flow, referred to as a subflow
. Direktiv uses the action
state to provide this functionality.
Example
direktiv_api: workflow/v1
functions:
- id: http-request
image: gcr.io/direktiv/functions/http-request:1.0
type: knative-workflow
states:
- id: getter
type: action
action:
function: http-request
input:
url: "https://jsonplaceholder.typicode.com/todos/1"
If the flow requires function to run they need to be defined in the functions
section. There are different types of functions and the type is specified in the type
attribute. The value can be one of the following:
Function Types
knative-workflow
This function is for the flow only and will not be re-used across different flows. This type requires an image name to use. The image name should point to a valid container image in a remote registry like Docker Hub, GCR, Azure etc. Two additional attributes can be provided:
size: Sometimes functions need a different size in terms of CPU and memory. Possible values are small
, medium
and large
. The definition of those values can be configured in Direktiv's configuration files via Helm chart.
cmd: The function can use a different command in the container if it is supported by the function container.
knative-namespace
If a function is used frequently by different flows it can be shared across flows with this type. They can be created under Services
in the user interface or via API.
- id: http-request
service: request
type: knative-namespace
For these types a scale
attribute can be defined on creation of the service which sets the minimum instances to run in the cluster and therefore reducing or eliminating the warm-up time and the function can run immediately.
subflow
In Direktiv a function can be a subflows as well. The behaviour is the same as calling serverless container functions. If used the flow provides the input for the subflow and accepts the response of the subflow as result.
- id: http-request
workflow: my-subflow
type: subflow
Input Value
The input value for the function is set in input
in. This YAML object under input
will be send as JSON to the function container and can a multi-level nested object as well. Different containers require different inputs depending on their functionality. This concept is important for custom functions.
- id: getter
type: action
action:
function: http-request
input:
url: "https://jsonplaceholder.typicode.com/todos/1"
Return Value
Every time a function is called the response is stored in return
in the state data and can be processed via e.g. transform
or switch
. The next function call overwrites the return
value so if data is required from a function accross multiple states it needs to be stored with a transition.
In the example above the state data after executing the flow would have an additional JSON object with information about the headers and the content of the HTTP request in the return
attribute.
{
"return": [
{
"code": 200,
"headers": {
"Access-Control-Allow-Credentials": [
"true"
],
"Age": [
"20706"
]
},
"result": {
"completed": false,
"id": 1,
"title": "delectus aut autem",
"userId": 1
},
"status": "200 OK",
"success": true
}
]
}
Store Value
As mentioned earlier, every return of a function is getting overwritten with the next function call. Therefore it is important to store the data in the state if it is needed later in the flow. This can be done with a simple transform at the end of the action state. In the example here we store only the response status.
direktiv_api: workflow/v1
functions:
- id: http-request
image: gcr.io/direktiv/functions/http-request:1.0
type: knative-workflow
states:
- id: getter
type: action
action:
function: http-request
input:
url: "https://jsonplaceholder.typicode.com/todos/1"
transform:
status: jq(.return[0].status)
The http request function returns an array so the JQ command would be .return[0]
to get the 0th item from it and the .status
fetches the status of that item. More function can be found at apps.direktiv.io.
Foreach
Another way to call functions is the foreach
function. This is useful if an array of objects need to be processed the same way, e.g. executing multiple http requests.
direktiv_api: workflow/v1
functions:
- id: http-request
image: gcr.io/direktiv/functions/http-request:1.0
type: knative-workflow
states:
- id: getter
type: foreach
array: |-
jq(
[
{ "url": "https://jsonplaceholder.typicode.com/todos/1"},
{ "url": "https://www.direktiv.io"}]
)
action:
function: http-request
input:
url: jq(.url)
This foreach
call the same function but uses an array of objects. There are a few simple requirements for foreach
states.
- The array has to be a list of objects not e.g. an array of strings.
- The
input
attribute has only access to the object it is iterating over at that time. It does not have access to state data at all.
Parallel
The parallel execution can be used if the flow needs to execute functions in parallel with the same state data. An example would be quality gates during a release process where functional tests and load test can potentially be run in parallel.
direktiv_api: workflow/v1
functions:
- id: http-request
image: gcr.io/direktiv/functions/http-request:1.0
type: knative-workflow
- id: python
image: gcr.io/direktiv/functions/python:1.0
type: knative-workflow
states:
- id: execute-both
type: parallel
mode: and
actions:
- function: http-request
input:
url: https://www.direktiv.io
- function: python
input:
commands:
- command: python3 -c 'import os;print(os.environ["hello"])'
envs:
- name: hello
value: world
Additionally a mode
attribute can be set to either or
or and
to define if all actions need to return successfully or only one.