Entities
The parts that OpenServerless applications are made of
Entities
OpenServerless applications are composed by some “entities” that you can
manipulate either using a command line interface or programmatically
with code.
The command line interface is the ops
command line tools, that can be
used directly on the command line or automated through scripts. You can
also a REST API crafted explicitly for OpenServerless.
The entities available in OpenServerless are:
Packages: They serve as a means of
grouping actions together, facilitating the sharing of parameters,
annotations, etc. Additionally, they offer a base URL that can be
utilized by web applications.
Actions: These are the fundamental
components of a OpenServerless application, capable of being written
in any programming language. Actions accept input and produce
output, both formatted in JSON.
Activations: Each action invocations
produces an activation id that can be listed. Action output and
results logged and are associated to activations and can be
retrieved providing an activativation id.
Sequences: Actions can be
interconnected, where the output of one action serves as the input
for another, effectively forming a sequence.
Triggers: Serving as entry points with
distinct names, triggers are instrumental in activating multiple
actions.
Rules: Rules establish an association
between a trigger and an action. Consequently, when a trigger is
fired, all associated actions are invoked accordingly.
The ops
command
Let’s now provide an overview of OpenServerless’ command line interface,
focusing on the ops
command.
The command can be dowloaded in precompile binary format for many
platform following the Download
button on https://www.nuvolaris.io/
The ops
command is composed of many commands, each one with many
subcommands. The general format is:
ops <entity> <command> <parameters> <flags>
Note that <parameters>
and <flags>
are different for each
<command>
, and for each <entity>
there are many subcommands.
The CLI shows documention in the form of help output if you do not
provide enough parameters to it. Start with ops
to get the list of the
main commands. If you type the ops <entity>
get the help for that
entity, and so on.
For example, let’s see ops
output (showing the command) and the more
frequently used command, action
, also showing the more common
subcommands, shared with many others:
$ ops
Welcome to Ops, the all-mighty OpenServerless Build Tool
The top level commands all have subcommands.
Just type ops <command> to see its subcommands.
Commands:
action work with actions
activation work with activations
invoke shorthand for action invoke (-r is the default)
logs shorthand for activation logs
package work with packages
result shorthand for activation result
rule work with rules
trigger work with triggers
url get the url of a web action$ wsk action
There are many more sub commands used for aministrative purposes. In
this documentation we only focus on the subcommands used to manage the
main entities of OpenServerless.
Keep in mind that commands represent entities, and their subcommands
follow the CRUD model (Create, Retrieve via get/list, Update, Delete).
This serves as a helpful mnemonic to understand the ops
command’s
functionality. While there are exceptions, these will be addressed
throughout the chapter’s discussion. Note however that some subcommand
may have some specific flags.
Naming Entities
Let’s see how entities are named.
Each user also has a namespace, and everything a user creates,
belongs to it.
The namespace is usually created by a system administrator.
Under a namespace you can create triggers, rules, actions and packages.
Those entities will have a name like this:
/mirella/demo-triggger
/mirella/demo-rule
/mirella/demo-package
/mirella/demo-action
When you create a package, you can put under it actions and feeds. Those
entities are named
💡 NOTE
In the commands you do not require to specify a namespace. If your user
is mirella
, your namespace is /mirella
, and You type demo-package
to mean /mirella/demo-package
, and demo-package/demo-action
to mean
/mirella/demo-package/demo-action
.
1 - Packages
How to group actions and their related files
Packages
OpenServerless groups actions and feeds in packages under a
namespace. It is conceptually similar to a folder containing a group of
related files.
A package allows you to:
Group related actions together.
Share parameters and annotations (each action sees the parameters
assigned to the package).
Provide web actions with a common prefix in the URL to invoke them.
For example, we can create a package demo-package
and assign a
parameter:
$ ops package create demo-package -p email no-reply@nuvolaris.io
ok: created package demo-package
This command creates a new package with the specified name.
Package Creation, Update, and Deletion
Let’s proceed with the commands to list, get information, update, and
finally delete a package:
First, let’s list our packages:
$ ops package list
packages
/openserverless/demo-package/ private
If you want to update a package by adding a parameter:
$ ops package update demo-package -p email info@nuvolaris.io
ok: updated package demo-package
Let’s retrieve some package information:
$ ops package get demo-package -s
package /openserverless/demo-package/sample:
(parameters: *email)
Note the final -s
, which means “summarize.”
Finally, let’s delete a package:
$ ops package delete demo-package
ok: deleted package demo-package
Adding Actions to the Package
Actions can be added to a package using this command:
ops action create <package-name>/<action-name>
This associates an existing action with the specified package.
Using Packages
Once a package is created, actions within it can be invoked using their
full path, with this schema: <package-name>/<action-name>
. This allows
organizing actions hierarchically and avoiding naming conflicts.
Conclusion
Packages in OpenServerless provide a flexible and organized way to
manage actions and their dependencies. Using the Ops CLI, you can
efficiently create, add actions, and manage package dependencies,
simplifying the development and management of serverless applications.
2 - Actions
Functions, the core of OpenServerless
Actions
An action can generally be considered as a function, a snippet of code,
or generally a method.
The ops action
command is designed for managing actions, featuring
frequently utilized CRUD operations such as list, create, update, and
delete. We will illustrate these operations through examples using a
basic hello action. Let’s assume we have the following file in the
current directory:
The hello.js
script with the following content:
function main(args) {
return { body: "Hello" }
}
Simple Action Deployment
If we want to deploy this simple action in the package demo
, let’s
execute:
$ ops package update demo
ok: updated package demo
$ ops action update demo/hello hello.js
ok: update action demo/hello
Note that we ensured the package exists before creating the action.
We can actually omit the package name. In this case, the package name is
default
, which always exists in a namespace. However, we advise always
placing actions in some named package.
💡 NOTE
We used update
, but we could have used create
if the action does not
exist because update
also creates the action if it does not exist and
updates it if it is already there. Update here is similar to the patch
concept in REST API. However, create
generates an error if an action
does not exist, while update
does not, so it is practical to always
use update
instead of create
(unless we really want an error for an
existing action for some reason).
How to Invoke Actions
Let’s try to run the action:
$ ops invoke demo/hello
{
"body": "Hello"
}
Actually, the invoke
command does not exist, or better, it’s just a
handy shortcut for ops action invoke -r
.
If you try to run ops action invoke demo/hello
, you get:
$ ops action invoke demo/hello
ok: invoked /_/demo/hello with id fec047bc81ff40bc8047bc81ff10bc85
You may wonder where the result is. In reality, in OpenServerless, all
actions are by default asynchronous, so what you usually get is the
activation id to retrieve the result once the action is completed.
To block the execution until the action is completed and get the result,
you can either use the flag -r
or --result
, or use ops invoke
.
Note, however, that we are using ops
to invoke an action, which means
all the requests are authenticated. You cannot invoke actions directly
without logging into the system first.
However, you can mark an action to be public by creating it with
--web true
(see below).
Public Actions
If you want an action to be public, you can do:
$ ops action update demo/hello hello.js --web true
ok: updated action demo/hello
$ ops url demo/hello
https://nuvolaris.dev/api/v1/web/mirella/demo/hello
and you can invoke it with:
$ curl -sL https://nuvolaris.dev/api/v1/web/dashboard/demo/hello
Hello
Note that the output is only showing the value of the body field. This
is because the web actions must follow a pattern to produce an output
suitable for web output, so the output should be under the key body
,
and so on. Check the section on Web Actions for more information.
💡 NOTE
Actually, ops url
is a shortcut for ops action get --url
. You can
use ops action get
to retrieve a more detailed description of an
action in JSON format.
After action create
, action update
, and action get
(and the
shortcuts invoke
and url
), we should mention action list
and
action delete
.
The action list
command obviously lists actions and allows us to
delete them:
$ ops action list
/mirella/demo/hello private nodejs:18
$ ops action delete demo/hello
ok: deleted action demo/hello
Conclusion
Actions are a core part of our entities. A ops action is a
self-contained and executable unit of code deployed on the ops
serverless computing platform.
3 - Activations
Detailed records of action executions
Activations
When an event occurs that triggers a function, ops creates an activation
record, which contains information about the function execution, such as
input parameters, output results, and any metadata associated with the
activation. It’s something similar to the classic concept of log
.
How activations work
When invoking an action with ops action invoke
, you’ll receive only an
invocation id as an answer.
This invocation id allows you to read results and outputs produced by
the execution of an action.
Let’s demonstrate how it works by modifying the hello.js
file to add a
command to log some output.
function main(args) {
console.log("Hello")
return { "body": "Hello" }
}
Now, let’s deploy and invoke it (with a parameter hello=world
) to get
the activation id:
$ ops action update demo/hello hello.js
ok: updated action demo/hello
$ ops action invoke demo/hello
ok: invoked /_/demo/hello with id 0367e39ba7c74268a7e39ba7c7126846
Associated with every invocation, there is an activation id (in the
example, it is 0367e39ba7c74268a7e39ba7c7126846
).
We use this id to retrieve the results of the invocation with
ops activation result
or its shortcut, just ops result
, and we can
retrieve the logs using ops activation logs
or just ops logs
.
$ ops result 0367e39ba7c74268a7e39ba7c7126846
{
"body": "Hello"
}
$ ops logs 0367e39ba7c74268a7e39ba7c7126846
2024-02-17T20:01:31.901124753Z stdout: Hello
List of activations
You can list the activations with ops activation list
and limit the
number with --limit
if you are interested in a subset.
$ ops activation list --limit 5
Datetime Activation ID Kind Start Duration Status Entity
2024-02-17 20:01:31 0367e39ba7c74268a7e39ba7c7126846 nodejs:18 warm 8ms success dashboard/hello:0.0.1
2024-02-17 20:00:00 f4f82ee713444028b82ee71344b0287d nodejs:18 warm 5ms success dashboard/hello:0.0.1
2024-02-17 19:59:54 98d19fe130da4e93919fe130da7e93cb nodejs:18 cold 33ms success dashboard/hello:0.0.1
2024-02-17 17:40:53 f25e1f8bc24f4f269e1f8bc24f1f2681 python:3 warm 3ms success dashboard/index:0.0.2
2024-02-17 17:35:12 bed3213547cc4aed93213547cc8aed8e python:3 warm 2ms success dashboard/index:0.0.2
Note also the --since
option, which is useful to show activations from
a given timestamp (you can obtain a timestamp with date +%s
).
Since it can be quite annoying to keep track of the activation id, there
are two useful alternatives.
With ops result --last
and ops logs --last
, you can retrieve just
the last result or log.
Polling activations
With ops activation poll
, the CLI starts a loop and displays all the
activations as they happen.
$ ops activation poll
Enter Ctrl-c to exit.
Polling for activation logs
Conclusion
Activations provide a way to monitor and track the execution of
functions, enabling understanding of how code behaves in response to
different events and allowing for debugging and optimizing serverless
applications.
4 - Sequences
Combine actions in sequences
Sequences
You can combine actions into sequences and invoke them as a single
action. Therefore, a sequence represents a logical junction between two
or more actions, where each action is invoked in a specific order.
Combine actions sequentially
Suppose we want to describe an algorithm for preparing a pizza. We could
prepare everything in a single action, creating it all in one go, from
preparing the dough to adding all the ingredients and cooking it.
What if you would like to edit only a specific part of your algorithm,
like adding fresh tomato instead of classic, or reducing the amount of
water in your pizza dough? Every time, you have to edit your main action
to modify only a part.
Again, what if before returning a pizza you’d like to invoke a new
action like “add basil,” or if you decide to refrigerate the pizza dough
after preparing it but before cooking it?
This is where sequences come into play.
Create a file called preparePizzaDough.js
function main(args) {
let persons = args.howManyPerson;
let flour = persons * 180; // grams
let water = persons * 120; // ml
let yeast = (flour + water) * 0.02;
let pizzaDough =
"Mix " +
flour +
" grams of flour with " +
water +
" ml of water and add " +
yeast +
" grams of brewer's yeast";
return {
pizzaDough: pizzaDough,
whichPizza: args.whichPizza,
};
}
Now, in a file cookPizza.js
function main(args) {
let pizzaDough = args.pizzaDough;
let whichPizza = args.whichPizza;
let baseIngredients = "tomato and mozzarella";
if (whichPizza === "Margherita") {
return {
result:
"Cook " +
pizzaDough +
" topped with " +
baseIngredients +
" for 3 minutes at 380°C",
};
} else if (whichPizza === "Sausage") {
baseIngredients += "plus sausage";
return {
result:
"Cook " +
pizzaDough +
" topped with " +
baseIngredients +
". Cook for 3 minutes at 380°C",
};
}
}
We have now split our code to prepare pizza into two different actions.
When we need to edit only one action without editing everything, we can
do it! Otherwise, we can now add new actions that can be invoked or not
before cooking pizza (or after).
Let’s try it.
Testing the sequence
First, create our two actions
ops action create preparePizzaDough preparePizzaDough.js
ops action create cookPizza cookPizza.js
Now, we can create the sequence:
ops action create pizzaSequence --sequence preparePizzaDough,cookPizza
Finally, let’s invoke it
ops action invoke --result pizzaSequence -p howManyPerson 4 -p whichPizza "Margherita"
{
"result": "Cook Mix 720 grams of flour with 480 ml of water and add 24 grams of brewer's yeast topped with tomato and mozzarella for 3 minutes at 380°C"
}
Conclusion
Now, thanks to sequences, our code is split correctly, and we are able
to scale it more easily!
5 - Triggers
Event source that triggers an action execution
Triggers
Now let’s see what a trigger is and how to use it.
We can define a trigger as an object representing an event source
that triggers the execution of actions. When activated by an event,
associated actions are executed.
In other words, a trigger is a mechanism that listens for specific
events or conditions and initiates actions in response to those events.
It acts as the starting point for a workflow.
Example: Sending Slack Notifications
Let’s consider a scenario where we want to send Slack notifications when
users visit specific pages and submit a contact form.
Step 1: Define the Trigger
We create a trigger named “PageVisitTrigger” that listens for events
related to user visits on our website. To create it, you can use the
following command:
ops trigger create PageVisitTrigger
Once the trigger is created, you can update it to add parameters, such
as the page parameter:
ops trigger update PageVisitTrigger --param page homepage
💡 NOTE
Of course, there are not only create
and update
, but also delete
,
and they work as expected, updating and deleting triggers. In the next
paragraph, we will also see the fire
command, which requires you to
first create rules to do something useful.
Step 2: Associate the Trigger with an Action
Next, we create an action named “SendSlackNotification” that sends a
notification to Slack when invoked. Then, we associate this action with
our “PageVisitTrigger” trigger, specifying that it should be triggered
when users visit certain pages.
To associate the trigger with an action, you can use the following
command:
ops rule create TriggerRule PageVisitTrigger SendSlackNotification
We’ll have a better understanding of this aspect in
Rules
In this example, whenever a user visits either the homepage or the
contact page, the “SendSlackNotification” action will be triggered,
resulting in a Slack notification being sent.
Conclusion
Triggers provide a flexible and scalable way to automate workflows based
on various events. By defining triggers and associating them with
actions, you can create powerful applications that respond dynamically
to user interactions, system events, or any other specified conditions.
6 - Rules
Connection rules between triggers and actions
Rules
Once we have a trigger and some actions, we can create rules for the
trigger. A rule connects the trigger with an action, so if you fire the
trigger, it will invoke the action. Let’s see this in practice in the
next listing.
Create data
First of all, create a file called alert.js.
function main() {
console.log("Suspicious activity!");
return {
result: "Suspicious activity!"
};
}
Then, create a OpenServerless action for this file:
ops action create alert alert.js
Now, create a trigger that we’ll call notifyAlert:
ops trigger create notifyAlert
Now, all is ready, and now we can create our rule! The syntax follows
this pattern: “ops rule create {ruleName} {triggerName} {actionName}”.
ops rule create alertRule notifyAlert alert
Test your rule
Our environment can now be alerted if something suspicious occurs!
Before starting, let’s open another terminal window and enable polling
(with the command ops activation poll
) to see what happens.
$ ops activation poll
Enter Ctrl-c to exit.
Polling for activation logs
It’s time to fire the trigger!
$ ops trigger fire notifyAlert
ok: triggered /notifyAlert with id 86b8d33f64b845f8b8d33f64b8f5f887
Now, go to see the result! Check the terminal where you are polling
activations now!
Enter Ctrl-c to exit.
Polling for activation logs
Activation: 'alert' (dfb43932d304483db43932d304383dcf)
[
"2024-02-20T03:15.15472494535Z stdout: Suspicious activity!"
]
Conclusion
💡 NOTE
As with all the other commands, you can execute list
, update
, and
delete
by name.
A trigger can enable multiple rules, so firing one trigger actually
activates multiple actions. Rules can also be enabled and disabled
without removing them. As in the last example, let’s try to disable the
first rule and fire the trigger again to see what happens.
$ ops rule disable alertRule
ok: disabled rule alertRule
$ ops trigger fire notifyAlert
ok: triggered /_/notifyAlert with id 0f4fa69d910f4c738fa69d910f9c73af
In the activation polling window, we can see that no action is executed
now. Of course, we can enable the rule again with:
ops rule enable alertRule