Edit in GitHubLog an issue

Creating Actions

For your code to execute as an action on Adobe I/O Runtime, your code has to comply with two rules:

  • You either call your function main or you export the entry point as main. This is the function that will be executed when it is invoked.
  • Your function accepts valid JSON objects as input and produces valid JSON objects as output, if needed

You have to configure aio CLI on your machine to create and invoke actions. Refer to the aio CLI page for how to install and configure it.

Let’s assume you have this function available on your machine:

Copied to your clipboard
// this is saved in a file named first-function.js
function main(params) {
var nm = params.name || 'stranger';
return {payload: 'Hello ' + nm};
}
exports.main = main;

You can create an action called test using this command:

Copied to your clipboard
aio rt:action:create test first-function.js

You can update an action at any time using the following command:

Copied to your clipboard
aio rt:action:update test first-function.js

If you don’t need an action anymore, you can delete it:

Copied to your clipboard
aio rt:action:delete test

If you want to save an action that is deployed to your machine, then you can use this command:

Copied to your clipboard
aio rt:action:get test --save

Listing all the available actions in your current namespace is as simple as running this command:

Copied to your clipboard
aio rt:action:list

Invoking actions

Now, that you have an action, you can call it using the following command (in this example the action name is test):

Copied to your clipboard
aio rt:action:invoke test --result

Note the flag --result used in the command. This flag outputs the result of the invocation. Without it, instead of seeing the result of the invocation, you’d get the activation ID. To get the result, you’d use this ID to retrieve the result like this:

Copied to your clipboard
aio rt:activation:get <activation ID>

When you invoke an action, as in the example above, the invocation is not blocking (it is async). If you want to execute it in a blocking style and, as a result, get the activation record instead of just getting an activation ID, you have to add the --blocking flag to the command:

Copied to your clipboard
aio rt:action:invoke test --blocking

Working with parameters

Actions can receive parameters when are being executed. First, any parameters you sent to the action will be available through the params variable. If you send two parameters called first-name and last-name, they will be available as params.first-name and params.last-name.

Second, let’s see how you can invoke the action with parameters. Our function sample from above uses a parameter called name. This is how you can set the parameter when invoking the action:

Copied to your clipboard
aio rt:action:invoke test --param name "John Doe" --result

Setting default parameters

Sometimes you want to bind the same parameter values for all invocations or you just want to set default values. In either case, you have two different options: setting params at the package level (so all actions in that package can inherit them) or at action level.

Default params and encryption

Before we dive deeper in how to set and use default params, let’s discuss the security aspect first. Many developers use the default params as a mechanism to provision actions with the secrets/passwords needed to authenticate against some other systems (databases, services, APIs).

In order to support this use case, all default params are automatically encrypted. They are decrypted just before the action code is executed. Thus, the only time you have access to the decrypted value is while executing the action code.

If you run the CLI command for getting an action or package, you’d get a listing for the names of the default params while the values will be listed as a hash instead of the actual value.

Default parameters set on action

Let’s assume that you want the default value of your parameter to be "Runtime". You can set this value when creating the action, or if the action already exists, updating the action. In both cases you add the *--param" flag:

Copied to your clipboard
// creation time
aio rt:action:create test first-function.js --param name "Runtime"
// update
aio rt:action:update test first-function.js --param name "Runtime"

Now, you can run the action without any parameters, and it will use the default one you’ve set. If you set a parameter when invoking, this will overwrite the default one.

Important! When updating an action's params using --param <key> <value> as above, you must specify ALL of the parameters. All previous values will be overwritten.

Default parameters set on package

When you create an action, it is always created in a package. If you don’t specify a package, the default package is used. Similar to how you set default parameters at the action level, you can specify default parameters at the package level.

The difference is that the params set at the package level will be used for all the actions you create in that package.

Copied to your clipboard
// creation time
aio rt:package:create my-package --param name "Runtime"
// update
aio rt:package:update my-package --param name "Runtime"

At this point, you might ask yourself what is the precedence when setting parameters at the package level, action level, and invocation time. The winner is parameters set at invocation time, followed by parameters set at action level, and the last is parameters set at package level.

Default parameters with parameter file

A neat way for setting default params is using a dedicated file for storing their values, and then using this file to set those values as default parameter values.

This is especially useful when you are dealing with multiple parameter values for things like configuring API access keys, endpoints, and so on.

Coming back to our sample function that expects one parameter, name, you’d create a JSON file to store that value:

Copied to your clipboard
// filename is my-params.json
{
"name": "Runtime"
}

Then, you use the --param-file flag when creating actions, creating packages, or invoking actions.

Copied to your clipboard
// update action
aio rt:action:update test first-function.js --param-file my-params.json
// invoke action
aio rt:action:invoke test --param-file my-params.json

Final Parameters

Sometimes, an application needs to ensure that the default parameters are final (or immutable), and a calling client can’t override them. You can achieve this by adding the final annotation - -a final true:

Copied to your clipboard
aio rt:action:update test first-function.js --web true --param name "Runtime" -a final true

This mechanism works for all type of actions including web actions.

Invoking web actions

So far, we’ve been invoking actions only from the CLI. While this might be good for trying out your actions, as you design actions for production systems, you might need to be able to invoke actions via HTTP REST calls. This would enable you to invoke the actions from your web application.

You create a web action by adding the --web flag to the aio action command:

Copied to your clipboard
// creation time
aio rt:action:create test first-function.js --web true
// update
aio rt:action:update test first-function.js --web true

Notice the true value used in the command. If you set that value to false, then you disable a web action.

To call the action as a web action, you need to know the full path to the action. You can find the path by adding the --url flag to the action command:

Copied to your clipboard
aio rt:action:get test --url

This will give you something like:

Copied to your clipboard
ok: got action test
https://adobeioruntime.net/api/v1/web/[your namespace]/default/test

In the URL above, the default in the path stands for the default package: if you don’t create your actions explicitly in a package, then they get under the default package.

You can invoke the action like this:

Copied to your clipboard
curl -L https://adobeioruntime.net/api/v1/web/[your namespace]/default/test -X GET

or

Copied to your clipboard
curl https://[your namespace].adobeioruntime.net/api/v1/web/default/test -X GET

Note the change in the URL here in comparison to what the aio returns. This is due some additional protections Runtime provides to segregate namespaces from each other when invoking web actions. The aio generated link will still work but it will return a 308 redirect to your namespace's subdomain on Runtime. For a further discussion of this please see the Securing Web Actions page.

Successful response

When creating actions to be used as web actions, you might want to send the response that follows the HTTP response structure (status code, headers, body). For example, our sample function could be rewritten:

Copied to your clipboard
function main(params) {
var nm = params.name || 'stranger';
return {
statusCode: 200,
body: {
payload: 'Hello ' + nm
}
}
}
exports.main = main;

You can also set cookies or cache control headers, perform a HTTP redirect, and so forth.

Unsuccessful response

On failed action invocations, the error code and message should be wrapped in an error object, as this would allow the system to interpret the response as an applicationError.

Copied to your clipboard
async function main(params) {
try {
throw new Error("Boom!")
} catch (err) {
return {
error: {
statusCode: 500,
body: {
payload: `Something went wrong: ${err}`
}
}
}
}
}
exports.main = main;

Dealing with errors in general

It is extremely important to handle errors in your action code. It is actually impossible to recover from an unhandled async error in Node.js. Please refer to the Node.js documentation related to uncaughtExceptions.

In the event of an unhandled async errors, the action will be terminated and the container running that action will be destroyed. This means that all in flight activations will be failed and, the next invocation of the action will incur the overhead of creating a new container.

The following examples will show incorrect and correct handling of async errors:

Incorrect handling of async errors

Copied to your clipboard
function doSomethingAsync() {
return new Promise((resolve, reject) => {
try {
setTimeout(() => {
new Error("Something went wrong");
}, 1000);
} catch (err) {
reject(err);
}
});
}

This code will fail to handle the error and the action will be terminated; here the error is generated not while the executor is running, but later. So the try catch block will not be able to catch the error.

Correct handling of async errors

Copied to your clipboard
function doSomethingAsync() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("Something went wrong"));
}, 1000);
});
}

This code will execute correctly and the error will be handled.

In general when an asynchronous operation is performed, there is a chance that something could go wrong, such as a network error, a database connection issue, or an unexpected input. If an error occurs but is not handled, the node process will be terminated. Therefore, it is important to always handle errors inside the callback function passed to setTimeout or any other async function to ensure the reliability and stability of the program.

HTTP context

When executing an action as a web action, the params object is decorated with additional information:

MethodDescription
__ow_method
the HTTP method of the request
__ow_headers
the request headers
__ow_path
the unmatched path of the request (matching stops after consuming the action extension)
__ow_body
the request body entity, as a base64-encoded string when its content is binary or JSON object/array, or as a plain string otherwise
__ow_query
the query parameters from the request as an unparsed string

By default, web actions are accessible to anyone who knows the URL. If you want to secure the access, you can find more info on the Securing Web Actions page.

Deploying ZIP actions

So far, we’ve been creating actions from a single source file. What if you need to create an action from multiple files and you also need some npm modules? The answer is deploying a ZIP action.

You need to do three things to deploy a ZIP action: create a manifest file, create the package.json file, and configure/use wskdeploy instead of aio for the actual deploy.

wskdeploy

If you need to install wskdeploy, check the page Setting up the wskdeploy CLI.

Package.json file

You use the package.json file to specify the dependencies. If you don’t have one already, in the same folder where your function files are, run npm init -y.

Make sure you have the npm dependencies you need for your functions declared in the package file.

wskdeploy manifest file

The last step is creating the manifest file that will be used by wskdeploy to create your action. In the parent folder of the folder where you have your function files and npm modules, create a file manifest.yaml. (For more information on YAML format, see the YAML reference card.)

This is the folder structure:

Copied to your clipboard
--/actions
--/actions/node_modules/
--/actions/index.js
--/actions/package.json
--manifest.yaml

Here is an example manifest.yaml:

Copied to your clipboard
packages:
# this is the package name
test:
actions:
# name of the action
test-zip:
# source for the action; in this case it is a folder
function: actions
runtime: nodejs:10
# publish the action as a web action
web: yes

Note: If your action require dependencies (such as other node modules or JavaScript files), you need to place the action code in a index.js file and in the manifest.yaml file, the function value should point to the folder where the action is stored instead of the action file. You can see this in action in the example above.

Now you are ready to deploy by running the wskdeploy command from the same folder where manifest.yaml is:

Copied to your clipboard
wskdeploy

This should deploy an action called test-zip under a package called test. You can invoke it like this:

Copied to your clipboard
aio rt:action:invoke test/test-zip --result

If you want to remove this action, you run wskdeploy with the undeploy flag:

Copied to your clipboard
wskdeploy undeploy

There are other useful flows wskdeploy supports. Please check the official documentation if you want to find more.

Note: There is another way of deploying a ZIP action using the aio command. You miss the manifest.yaml file flexibility with this mode; you can’t define multiple actions/packages at the same time. The ZIP file has to have in the root the package.json file.
aio rt:action:create my-action --kind nodejs:20 zip-file.zip

  • Privacy
  • Terms of Use
  • Do not sell or share my personal information
  • AdChoices
Copyright © 2024 Adobe. All rights reserved.