Skip to content

Custom Actions

Custom Action Data Messages

You can use Custom Actions to enable communication between two Applications on either the same cluster or across different clusters.

Note

To understand the purpose of Custom Actions or view the overall structure of how they work, check out the documentation in the overview page here.

As a consumer of Custom Action Messages, this Application is defined as Consumer (Executor) Application.

Note

The Publisher Application which outputs Custom Actions to a Consumer (Executor) Application, is discussed in detail in the Produce Custom Action section here.

To ensure the Custom Action Messages are handled properly, the app.yaml inputs needs to be declared:

Warning

Only ONE Consumer (Executor) Application can be deployed on the Kelvin Platform to receive each Type name.

Multiple Consumer (Executor) Applications with the same TYPE name is not supported.

app.yaml Example
1
2
3
custom_actions:
  inputs:
    - type: custom-action-name

The Custom Action Object in the main.py script supports the following attributes :

Attribute Required Default Value Description
resource required N/A The KRNAsset that this Custom Action is meant for.
type required N/A The name of Custom Action.
title required N/A Title of the Custom Action
description required N/A Description details of the Custom Action
expiration_date required N/A Absolute datetime or a timedelta (from now) when the Custom Action will expire.
payload required N/A The custom information of the Custom Action that will be required by the Consumer Application
trace_id optional N/A A custom id for tracking the Custom Action status

The Custom Action Result Object supports the following attributes :

Attribute Required Default Value Description
success required N/A Whether the Custom Actions were completed successfully. Boolean True or False.
message optional N/A Any message to return to the Publishing Application.
response optional N/A The response body to return to the Publishing Application. A dictionary in any structure you choose. See Custom Action Responses.
metadata optional N/A Deprecated. Use response instead. If only metadata is given, its value is used as the response.
action_id optional N/A The id of the Custom Action Object
resource optional N/A The KRNAsset that this Custom Action is meant for.

Custom Action Responses

A Consumer (Executor) Application does not only receive requests. It can also send a response back to the Publisher Application that created the Custom Action.

This is useful when the Publisher needs to know the outcome. For example, an email Consumer (Executor) Application can respond that the email was sent, or that there was an error with the SMTP server.

The response is made of;

  • success, to say whether the Custom Action worked
  • message, for a short text
  • response, for any other information you want to return, as a dictionary

The response is passed on exactly as you send it, so you choose its structure. Use it for whatever the Publisher needs for its next steps, for example the status returned by the system you called.

Response Example
1
2
3
4
5
6
7
await app.publish(
    action.result(
        success=True,
        message="Email sent",
        response={"status": "sent", "recipient": "operations@example.com"},
    )
)
  • The response is delivered to the Publisher Application asynchronously, like any other message. Your Application does not wait for the Publisher
  • A Publisher can only use the response if it is programmed to receive it. See Receive a Response in the Produce Custom Actions page

Note

Because the structure of the response is your choice, document it for the developers of the Publisher Applications, in the same way as the payload schema.

Custom Action Payload Schemas

This Application owns the Custom Action type it declares under custom_actions.inputs. It is the single Application deployed on the Platform that receives every request for that type and processes it centrally, on behalf of every Publisher Application that calls it.

Because of that, the payload schema of this Application is the contract every Publisher must follow. It defines the exact structure of the request this Application accepts, so that what a Publisher produces is always what this Application can actually process.

Example

This email Consumer (Executor) Application processes every send-email action on the Platform. Its payload schema declares the recipient, subject, and body fields it reads from each incoming action, and marks which of them are required.

Declare the schema for each type your Application handles in the ui_schemas.custom_actions section of your app.yaml, keyed by the action type. Each key must match a type declared under custom_actions.inputs or custom_actions.outputs. The schemas are embedded into the app manifest at upload time.

app.yaml Example
1
2
3
4
5
6
7
custom_actions:
  inputs:
    - type: send-email

ui_schemas:
  custom_actions:
    send-email: "ui_schemas/send_email.json"

Each file is a JSON Schema that describes the payload of one Custom Action type:

ui_schemas/send_email.json
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Email Action",
    "description": "Payload schema for the email custom action.",
    "type": "object",
    "properties": {
        "recipient": {
            "type": "string",
            "title": "Recipient",
            "description": "Email address the action is sent to."
        },
        "subject": {
            "type": "string",
            "title": "Subject",
            "description": "Subject line of the email."
        },
        "body": {
            "type": "string",
            "title": "Body",
            "description": "Body content of the email."
        }
    },
    "required": ["recipient", "subject", "body"],
    "additionalProperties": false
}

The schema also gives the Kelvin UI a typed interface for displaying and validating the payload field when operators interact with the Custom Action.

Kelvin also enforces the schema. When a Custom Action is created, the payload is validated against the schema of its type, and the API returns 400 Bad Request if it does not match. At the edge, the Gateway validates the payload in the same way.

Note

Custom Action payload schemas are optional. If not provided, the payload field is treated as free-form JSON and any payload is accepted.

Note

If the Custom Action type already exists on the Platform, the schema in the Application you upload must match the existing schema. Otherwise the upload is rejected with 409 Conflict.

Note

The Publisher Application that calls this action must send a payload matching this schema, and must declare the same type name under its custom_actions.outputs.

See UI Schemas in the app.yaml reference for full details on the schema format.

Consumer App Example

In this example we will create a Consumer (Executor) Application that will;

  1. Listen and receive any new Custom Action objects with the type email.
  2. Extract the relevant email information from the payload
  3. Connect to an SMTP and send the email (This function is defined but not fully coded)
  4. Return the status of the Custom Action (True or False) and a response to the Publisher Application

Check out the Produce Custom Actions documentation here to see how to send this Custom Action from the Publisher Application.

app.yaml

app.yaml Example
spec_version: 5.0.0
type: app            # Any app type can handle and/or publish custom actions.

name: hello-app
title: Hello App
description: Lorem ipsum dolor sit amet, consectetur adipiscing elit
version: 1.0.0

custom_actions:
  inputs:
    - type: email

  ...

Consumer (Executor) Application

main.py Example
from kelvin.application import KelvinApp
from kelvin.message import CustomAction

app = KelvinApp()

async def send_mail(recipient, subject, body):
    print(f"Sending email to {recipient}")

@app.on_custom_action
async def on_custom_action(action: CustomAction) -> None:
    if action.type == "email":
        try:
            await send_mail(
                action.payload.get("recipient"),
                action.payload.get("subject"),
                action.payload.get("body")
            )

            await app.publish(
                action.result(
                    success=True,
                    message="Email sent",
                    response={"status": "sent"},
                )
            )

        except Exception as e:
            await app.publish(
                action.result(
                    success=False,
                    message=str(e),
                    response={"status": "error"},
                )
            )

app.run()

Note

The action.result(...) helper carries over the id and trace_id of the received Custom Action.

To build the result explicitly instead, use CustomActionResult(resource=..., action_id=..., success=..., message=..., response=...).