Actions are stateless code snippets that run on the OpenWhisk platform. For example, an action can be used to detect the faces in an image, respond to a database change, aggregate a set of API calls, or post a Tweet. An action can be written as a JavaScript, Swift, Python or PHP function, a Java method, any binary-compatible executable including Go programs and custom executables packaged as Docker containers.
Actions can be explicitly invoked, or run in response to an event. In either case, each run of an action results in an activation record that is identified by a unique activation ID. The input to an action and the result of an action are a dictionary of key-value pairs, where the key is a string and the value a valid JSON value. Actions can also be composed of calls to other actions or a defined sequence of actions.
You will need to use OpenWhisk CLI. Read how to use it when running OpenWhisk from a VM here. Or download binaries for your platform here. You can also download the CLI directly from your local installation at the https://<IP_ADDRESS>/cli/go/download/ path.
Learn how to create, invoke, and debug actions in your preferred development environment:
In addition, learn about:
- Watching action output
- Getting actions
- Listing actions
- Deleting actions
- Accessing action metadata within the action body
The following sections guide you through working with actions in JavaScript. You begin with the creation and invocation of a simple action. Then, you move on to adding parameters to an action and invoking that action with parameters. Then, you create asynchronous actions and, finally, work with action sequences.
Review the following steps and examples to create your first JavaScript action.
- Create a JavaScript file with the following content. For this example, the file name is 'hello.js'.
function main() {
return {payload: 'Hello world'};
}
The JavaScript file might contain additional functions. However, by convention, a function called main
must exist to provide the entry point for the action.
- Create an action from the following JavaScript function. For this example, the action is called 'hello'.
wsk action create hello hello.js
ok: created action hello
The CLI automatically infers the type of the action by using the source file extension. For .js
source files, the action runs by using a Node.js 6 runtime. You can also create an action that runs with Node.js 8 by explicitly specifying the parameter --kind nodejs:8
. For more information, see the Node.js 6 vs 8 reference.
- List the actions that you have created:
wsk action list
actions
hello private
You can see the hello
action you just created.
- After you create your action, you can run it in the cloud in OpenWhisk with the 'invoke' command. You can invoke actions with a blocking invocation (i.e., request/response style) or a non-blocking invocation by specifying a flag in the command. A blocking invocation request will wait for the activation result to be available. The wait period is the lesser of 60 seconds or the action's configured time limit. The result of the activation is returned if it is available within the wait period. Otherwise, the activation continues processing in the system and an activation ID is returned so that one may check for the result later, as with non-blocking requests (see here for tips on monitoring activations).
This example uses the blocking parameter, --blocking
:
wsk action invoke --blocking hello
ok: invoked hello with id 44794bd6aab74415b4e42a308d880e5b
{
"result": {
"payload": "Hello world"
},
"status": "success",
"success": true
}
The command outputs two important pieces of information:
- The activation ID (
44794bd6aab74415b4e42a308d880e5b
) - The invocation result if it is available within the expected wait period
The result in this case is the string Hello world
returned by the JavaScript function. The activation ID can be used to retrieve the logs or result of the invocation at a future time.
- If you don't need the action result right away, you can omit the
--blocking
flag to make a non-blocking invocation. You can get the result later by using the activation ID. See the following example:
wsk action invoke hello
ok: invoked hello with id 6bf1f670ee614a7eb5af3c9fde813043
wsk activation result 6bf1f670ee614a7eb5af3c9fde813043
{
"payload": "Hello world"
}
- To access the most recent activation record, activation results or activation logs, use the
--last
or-l
flag. Run the following command to get your last activation result.
wsk activation result --last
{
"payload": "Hello world"
}
Note that you should not use an activation ID with the flag --last
.
- If you forget to record the activation ID, you can get a list of activations ordered from the most recent to the oldest. Run the following command to get a list of your activations:
wsk activation list
activations
44794bd6aab74415b4e42a308d880e5b hello
6bf1f670ee614a7eb5af3c9fde813043 hello
JavaScript functions that run asynchronously may need to return the activation result after the main
function has returned. You can accomplish this by returning a Promise in your action.
- Save the following content in a file called
asyncAction.js
.
function main(args) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve({ done: true });
}, 2000);
})
}
Notice that the main
function returns a Promise, which indicates that the activation hasn't completed yet, but is expected to in the future.
The setTimeout()
JavaScript function in this case waits for two seconds before calling the callback function. This represents the asynchronous code and goes inside the Promise's callback function.
The Promise's callback takes two arguments, resolve and reject, which are both functions. The call to resolve()
fulfills the Promise and indicates that the activation has completed normally.
A call to reject()
can be used to reject the Promise and signal that the activation has completed abnormally.
- Run the following commands to create the action and invoke it:
wsk action create asyncAction asyncAction.js
wsk action invoke --result asyncAction
{
"done": true
}
Notice that you performed a blocking invocation of an asynchronous action.
- Fetch the activation log to see how long the activation took to complete:
wsk activation list --limit 1 asyncAction
activations
b066ca51e68c4d3382df2d8033265db0 asyncAction
wsk activation get b066ca51e68c4d3382df2d8033265db0
{
"start": 1455881628103,
"end": 1455881648126,
...
}
Comparing the start
and end
time stamps in the activation record, you can see that this activation took slightly over two seconds to complete.
The examples so far have been self-contained JavaScript functions. You can also create an action that calls an external API.
This example invokes a Yahoo Weather service to get the current conditions at a specific location.
- Save the following content in a file called
weather.js
.
var request = require('request');
function main(params) {
var location = params.location || 'Vermont';
var url = 'https://query.yahooapis.com/v1/public/yql?q=select item.condition from weather.forecast where woeid in (select woeid from geo.places(1) where text="' + location + '")&format=json';
return new Promise(function(resolve, reject) {
request.get(url, function(error, response, body) {
if (error) {
reject(error);
}
else {
var condition = JSON.parse(body).query.results.channel.item.condition;
var text = condition.text;
var temperature = condition.temp;
var output = 'It is ' + temperature + ' degrees in ' + location + ' and ' + text;
resolve({msg: output});
}
});
});
}
Note that the action in the example uses the JavaScript request
library to make an HTTP request to the Yahoo Weather API, and extracts fields from the JSON result. The References detail the Node.js packages that you can use in your actions.
This example also shows the need for asynchronous actions. The action returns a Promise to indicate that the result of this action is not available yet when the function returns. Instead, the result is available in the request
callback after the HTTP call completes, and is passed as an argument to the resolve()
function.
- Create an action from the
weather.js
file:
wsk action create weather weather.js
- Use the following command to run the action, and observe the output:
wsk action invoke --result weather --param location "Brooklyn, NY"
Using the --result
flag means that the value returned from the action is shown as output on the command-line:
{
"msg": "It is 28 degrees in Brooklyn, NY and Cloudy"
}
This example also passed a parameter to the action by using the --param
flag and a value that can be changed each time the action is invoked. Find out more about parameters in the Working with parameters section.
As an alternative to writing all your action code in a single JavaScript source file, you can write an action as a npm
package. Consider as an example a directory with the following files:
First, package.json
:
{
"name": "my-action",
"main": "index.js",
"dependencies" : {
"left-pad" : "1.1.3"
}
}
Then, index.js
:
function myAction(args) {
const leftPad = require("left-pad")
const lines = args.lines || [];
return { padded: lines.map(l => leftPad(l, 30, ".")) }
}
exports.main = myAction;
Note that the action is exposed through exports.main
; the action handler itself can have any name, as long as it conforms to the usual signature of accepting an object and returning an object (or a Promise
of an object). Per Node.js convention, you must either name this file index.js
or specify the file name you prefer as the main
property in package.json.
To create an OpenWhisk action from this package:
- Install first all dependencies locally
$ npm install
- Create a
.zip
archive containing all files (including all dependencies):
$ zip -r action.zip *
Please note: Using the Windows Explorer action for creating the zip file will result in an incorrect structure. OpenWhisk zip actions must have
package.json
at the root of the zip, while Windows Explorer will put it inside a nested folder. The safest option is to use the command linezip
command as shown above.
- Create the action:
wsk action create packageAction --kind nodejs:6 action.zip
When creating an action from a .zip
archive with the CLI tool, you must explicitly provide a value for the --kind
flag by using nodejs:6
or nodejs:8
.
- You can invoke the action like any other:
wsk action invoke --result packageAction --param lines "[\"and now\", \"for something completely\", \"different\" ]"
{
"padded": [
".......................and now",
"......for something completely",
".....................different"
]
}
Finally, note that while most npm
packages install JavaScript sources on npm install
, some also install and compile binary artifacts. The archive file upload currently does not support binary dependencies but rather only JavaScript dependencies. Action invocations may fail if the archive includes binary dependencies.
It is convenient to only include the minimal code into a single .js
file that includes dependencies. This approach allows for faster deployments, and in some circumstances where packaging the action as a zip might be too large because it includes unnecessary files.
You can use a JavaScript module bundler such as webpack. When webpack processes your code, it recursively builds a dependency graph that includes every module that your action needs.
Here is a quick example using webpack:
Taking the previous example package.json
add webpack
as a development dependency and add some npm script commands.
{
"name": "my-action",
"main": "dist/bundle.js",
"scripts": {
"build": "webpack --config webpack.config.js",
"deploy": "wsk action update my-action dist/bundle.js --kind nodejs:8"
},
"dependencies": {
"left-pad": "1.1.3"
},
"devDependencies": {
"webpack": "^3.8.1"
}
}
Create the webpack configuration file webpack.config.js
.
var path = require('path');
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
target: 'node'
};
Set the variable global.main
to the main function of the action.
From the previous example:
function myAction(args) {
const leftPad = require("left-pad")
const lines = args.lines || [];
return { padded: lines.map(l => leftPad(l, 30, ".")) }
}
global.main = myAction;
If your function name is main
, use this syntax instead:
global.main = main;
To build and deploy an OpenWhisk Action using npm
and webpack
:
- First, install dependencies locally:
npm install
- Build the webpack bundle:
npm run build
The file dist/bundle.js
is created, and is used to deploy as the Action source code.
- Create the Action using the
npm
script or the CLI. Usingnpm
script:
npm run deploy
Using the CLI:
wsk action update my-action dist/bundle.js
Finally, the bundle file that is built by webpack
doesn't support binary dependencies but rather JavaScript dependencies. So Action invocations will fail if the bundle depends on binary dependencies, because this is not included with the file bundle.js
.
You can create an action that chains together a sequence of actions.
Several utility actions are provided in a package called /whisk.system/utils
that you can use to create your first sequence. You can learn more about packages in the Packages section.
- Display the actions in the
/whisk.system/utils
package.
wsk package get --summary /whisk.system/utils
package /whisk.system/utils: Building blocks that format and assemble data
(parameters: none defined)
action /whisk.system/utils/namespace: Returns namespace for the authorization key used to invoke this action
(parameters: none defined)
action /whisk.system/utils/date: Current date and time
(parameters: none defined)
action /whisk.system/utils/sort: Sorts an array
(parameters: lines)
action /whisk.system/utils/split: Split a string into an array
(parameters: payload, separator)
action /whisk.system/utils/hosturl: Returns the URL to activation an action or trigger
(parameters: ext, path, trigger, web)
...
You will be using the split
and sort
actions in this example.
- Create an action sequence so that the result of one action is passed as an argument to the next action.
wsk action create sequenceAction --sequence /whisk.system/utils/split,/whisk.system/utils/sort
This action sequence converts some lines of text to an array, and sorts the lines.
- Invoke the action:
wsk action invoke --result sequenceAction --param payload "Over-ripe sushi,\nThe Master\nIs full of regret."
{
"length": 3,
"lines": [
"Is full of regret.",
"Over-ripe sushi,",
"The Master"
]
}
In the result, you see that the lines are sorted.
Note: Parameters passed between actions in the sequence are explicit, except for default parameters. Therefore parameters that are passed to the action sequence are only available to the first action in the sequence. The result of the first action in the sequence becomes the input JSON object to the second action in the sequence (and so on). This object does not include any of the parameters originally passed to the sequence unless the first action explicitly includes them in its result. Input parameters to an action are merged with the action's default parameters, with the former taking precedence and overriding any matching default parameters. For more information about invoking action sequences with multiple named parameters, see Setting default parameters.
The process of creating Python actions is similar to that of JavaScript actions. The following sections guide you through creating and invoking a single Python action, and packaging your actions in zip files.
An action is simply a top-level Python function. For example, create a file called hello.py
with the following source code:
def main(args):
name = args.get("name", "stranger")
greeting = "Hello " + name + "!"
print(greeting)
return {"greeting": greeting}
Python actions always consume a dictionary and produce a dictionary. The entry method for the action is main
by default but may be specified explicitly when creating the action with the wsk
CLI using --main
, as with any other action type.
You can create an OpenWhisk action called helloPython
from this function as follows:
wsk action create helloPython hello.py
The CLI automatically infers the type of the action from the source file extension. For .py
source files, the action runs using a Python 2.7 runtime. You can also create an action that runs with Python 3.6 by explicitly specifying the parameter --kind python:3
. See the Python reference for more information about Python 2.7 vs. 3.6.
Action invocation is the same for Python actions as it is for JavaScript actions:
wsk action invoke --result helloPython --param name World
{
"greeting": "Hello World!"
}
Find out more about parameters in the Working with parameters section.
You can package a Python action and dependent modules in a zip file.
The filename of the source file containing the entry point (e.g., main
) must be __main__.py
.
For example, to create an action with a helper module called helper.py
, first create an archive containing your source files:
zip -r helloPython.zip __main__.py helper.py
and then create the action:
wsk action create helloPython --kind python:3 helloPython.zip
Another way of packaging Python dependencies is using a virtual environment (virtualenv
). This allows you to link additional packages
that may be installed via pip
for example.
To ensure compatibility with the OpenWhisk container, package installations inside a virtualenv must be done in the target environment.
So the docker image openwhisk/python2action
or openwhisk/python3action
should be used to create a virtualenv directory for your action.
As with basic zip file support, the name of the source file containing the main entry point must be __main__.py
. In addition, the virtualenv directory must be named virtualenv
.
Below is an example scenario for installing dependencies, packaging them in a virtualenv, and creating a compatible OpenWhisk action.
- Given a
requirements.txt
file that contains thepip
modules and versions to install, run the following to install the dependencies and create a virtualenv using a compatible Docker image:
docker run --rm -v "$PWD:/tmp" openwhisk/python3action bash \
-c "cd tmp && virtualenv virtualenv && source virtualenv/bin/activate && pip install -r requirements.txt"
- Archive the virtualenv directory and any additional Python files:
zip -r helloPython.zip virtualenv __main__.py
- Create the action:
wsk action create helloPython --kind python:3 helloPython.zip
While the steps above are shown for Python 3.6, you can do the same for Python 2.7 as well.
The process of creating PHP actions is similar to that of JavaScript actions. The following sections guide you through creating and invoking a single PHP action, and demonstrate how to zip your PHP actions.
An action is simply a top-level PHP function. For example, create a file called hello.php
with the following source code:
<?php
function main(array $args) : array
{
$name = $args["name"] ?? "stranger";
$greeting = "Hello $name!";
echo $greeting;
return ["greeting" => $greeting];
}
PHP actions always consume an associative array and return an associative array. The entry method for the action is main
by default but may be specified explicitly when creating the action with the wsk
CLI using --main
, as with any other action type.
You can create an OpenWhisk action called helloPHP
from this function as follows:
wsk action create helloPHP hello.php
The CLI automatically infers the type of the action from the source file extension. For .php
source files, the action runs using a PHP 7.1 runtime. See the PHP reference for more information.
Action invocation is the same for PHP actions as it is for JavaScript actions:
wsk action invoke --result helloPHP --param name World
{
"greeting": "Hello World!"
}
Find out more about parameters in the Working with parameters section.
You can package a PHP action along with other files and dependent packages in a zip file.
The filename of the source file containing the entry point (e.g., main
) must be index.php
.
For example, to create an action that includes a second file called helper.php
, first create an archive containing your source files:
zip -r helloPHP.zip index.php helper.php
and then create the action:
wsk action create helloPHP --kind php:7.1 helloPHP.zip
If your PHP action requires Composer dependencies, you can install them as usual using composer require
which will create a vendor
directory. Add this directory to your action's zip file and create the action:
zip -r helloPHP.zip index.php vendor
wsk action create helloPHP --kind php:7.1 helloPHP.zip
The PHP runtime will automatically include Composer's autoloader for you, so you can immediately use the dependencies in your action code.
Note that if you don't include your own vendor
folder, then the runtime will include one for you. The packages included are listed in the reference.
The process of creating Swift actions is similar to that of JavaScript actions. The following sections guide you through creating and invoking a single swift action, and packaging an action in a zip file.
You can also use the online Online Swift Playground to test your Swift code without having to install Xcode on your machine.
Attention: Swift actions run in a Linux environment. Swift on Linux is still in development, and OpenWhisk usually uses the latest available release, which is not necessarily stable. In addition, the version of Swift that is used with OpenWhisk might be inconsistent with versions of Swift from stable releases of Xcode on MacOS.
An action is simply a top-level Swift function. For example, create a file called
hello.swift
with the following content:
func main(args: [String:Any]) -> [String:Any] {
if let name = args["name"] as? String {
return [ "greeting" : "Hello \(name)!" ]
} else {
return [ "greeting" : "Hello stranger!" ]
}
}
In this example the Swift action consumes a dictionary and produces a dictionary.
You can create an OpenWhisk action called helloSwift
from this function as
follows:
wsk action create helloSwift hello.swift --kind swift:3.1.1
New in Swift 4 in addition of the above main function signature there are two more signatures out of the box taking advantage of the Codable type. You can learn more about data types encodable and decodable for compatibility with external representations such as JSON here.
The following takes as input parameter a Codable Input with field name
, and returns a Codable output with a field greetings
struct Input: Codable {
let name: String?
}
struct Output: Codable {
let greeting: String
}
func main(param: Input, completion: (Output?, Error?) -> Void) -> Void {
let result = Output(greeting: "Hello \(param.name ?? "stranger")!")
print("Log greeting:\(result.greeting)")
completion(result, nil)
}
In this example the Swift action consumes a Codable and produces a Codable type. If you don't need to handle any input you can use the function signature that doesn't take any input, only Codable output.
struct Output: Codable {
let greeting: String
}
func main(completion: (Output?, Error?) -> Void) -> Void {
let result = Output(greeting: "Hello OpenWhisk!")
completion(result, nil)
}
You can create a OpenWhisk action called helloSwift
from this function as
follows:
wsk action create helloSwift hello.swift --kind swift:4.1
See the Swift reference for more information about the Swift runtime.
Action invocation is the same for Swift actions as it is for JavaScript actions:
wsk action invoke --result helloSwift --param name World
{
"greeting": "Hello World!"
}
Find out more about parameters in the Working with parameters section.
When you create an OpenWhisk Swift action with a Swift source file, it has to be compiled into a binary before the action is run. Once done, subsequent calls to the action are much faster until the container holding your action is purged. This delay is known as the cold-start delay.
To avoid the cold-start delay, you can compile your Swift file into a binary and then upload to OpenWhisk in a zip file. As you need the OpenWhisk scaffolding, the easiest way to create the binary is to build it within the same environment as it will be run in.
You can use a script to automate the packaging of the action. Create script compile.sh
h file the following.
#!/bin/bash
set -ex
if [ -z "$1" ] ; then
echo 'Error: Missing action name'
exit 1
fi
if [ -z "$2" ] ; then
echo 'Error: Missing kind, for example swift:4.1'
exit 2
fi
OUTPUT_DIR="build"
if [ ${2} == "swift:3.1.1" ]; then
BASE_PATH="/swift3Action"
DEST_SOURCE="$BASE_PATH/spm-build"
RUNTIME="openwhisk/action-swift-v3.1.1"
elif [ ${2} == "swift:4.1" ]; then
RUNTIME="openwhisk/action-swift-v4.1"
BASE_PATH="/swift4Action"
DEST_SOURCE="/$BASE_PATH/spm-build/Sources/Action"
else
echo "Error: Kind $2 not recognize"
exit 3
fi
DEST_PACKAGE_SWIFT="$BASE_PATH/spm-build/Package.swift"
BUILD_FLAGS=""
if [ -n "$3" ] ; then
BUILD_FLAGS=${3}
fi
echo "Using runtime $RUNTIME to compile swift"
docker run --rm --name=compile-ow-swift -it -v "$(pwd):/owexec" $RUNTIME bash -ex -c "
if [ -f \"/owexec/$OUTPUT_DIR/$1.zip\" ] ; then
rm \"/owexec/$OUTPUT_DIR/$1.zip\"
fi
echo 'Setting up build...'
cp /owexec/actions/$1/Sources/*.swift $DEST_SOURCE/
# action file can be either {action name}.swift or main.swift
if [ -f \"$DEST_SOURCE/$1.swift\" ] ; then
echo 'renaming $DEST_SOURCE/$1.swift $DEST_SOURCE/main.swift'
mv \"$DEST_SOURCE/$1.swift\" $DEST_SOURCE/main.swift
fi
# Add in the OW specific bits
cat $BASE_PATH/epilogue.swift >> $DEST_SOURCE/main.swift
echo '_run_main(mainFunction:main)' >> $DEST_SOURCE/main.swift
# Only for Swift4
if [ ${2} != "swift:3.1.1" ]; then
echo 'Adding wait to deal with escaping'
echo '_ = _whisk_semaphore.wait(timeout: .distantFuture)' >> $DEST_SOURCE/main.swift
fi
echo \"Compiling $1...\"
cd /$BASE_PATH/spm-build
cp /owexec/actions/$1/Package.swift $DEST_PACKAGE_SWIFT
# we have our own Package.swift, do a full compile
swift build ${BUILD_FLAGS} -c release
echo 'Creating archive $1.zip...'
#.build/release/Action
mkdir -p /owexec/$OUTPUT_DIR
zip \"/owexec/$OUTPUT_DIR/$1.zip\" .build/release/Action
"
The script assumes you have a directory actions
with each top level directory representing an action.
actions/
├── hello
│ ├── Package.swift
│ └── Sources
│ └── main.swift
-
Create the
Package.swift
file to add dependencies. The syntax is different from Swift 3 to Swift 4 tools. For Swift 3 here is an example:import PackageDescription let package = Package( name: "Action", dependencies: [ .Package(url: "https://github.com/apple/example-package-deckofplayingcards.git", majorVersion: 3), .Package(url: "https://github.com/IBM-Swift/CCurl.git", "0.2.3"), .Package(url: "https://github.com/IBM-Swift/Kitura-net.git", "1.7.10"), .Package(url: "https://github.com/IBM-Swift/SwiftyJSON.git", "15.0.1"), .Package(url: "https://github.com/watson-developer-cloud/swift-sdk.git", "0.16.0") ] )
For Swift 4 here is an example:
// swift-tools-version:4.0 import PackageDescription let package = Package( name: "Action", products: [ .executable( name: "Action", targets: ["Action"] ) ], dependencies: [ .package(url: "https://github.com/apple/example-package-deckofplayingcards.git", .upToNextMajor(from: "3.0.0")) ], targets: [ .target( name: "Action", dependencies: ["DeckOfPlayingCards"], path: "." ) ] )
As you can see this example adds
example-package-deckofplayingcards
as a dependency. Notice thatCCurl
,Kitura-net
andSwiftyJSON
are provided in the standard Swift action and so you should include them in your ownPackage.swift
only for Swift 3 actions. -
Build the action by running the following command for a Swift 3 action:
bash compile.sh hello swift:3.1.1
To compile for Swift 4 use
swift:4.1
instead ofswift:3.1.1
bash compile.sh hello swift:4.1
This has created hello.zip in the
build
. -
Upload it to OpenWhisk with the action name helloSwifty: For Swift 3 use the kind
swift:3.1.1
wsk action update helloSwiftly build/hello.zip --kind swift:3.1.1
For Swift 4 use the kind
swift:3.1.1
wsk action update helloSwiftly build/hello.zip --kind swift:4.1
-
To check how much faster it is, run
wsk action invoke helloSwiftly --blocking
The time it took for the action to run is in the "duration" property and compare to the time it takes to run with a compilation step in the hello action.
With the new Codable completion handler, you can pass an Error to indicate a failure in your Action.
Error handling in Swift resembles exception handling in other languages, with the use of the try, catch
and throw
keywords.
The following example shows a an example on handling an error
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
func main(param: Input, completion: (Output?, Error?) -> Void) -> Void {
// Return real error
do{
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
} catch {
completion(nil, error)
}
}
The process of creating Java actions is similar to that of JavaScript and Swift actions. The following sections guide you through creating and invoking a single Java action, and adding parameters to that action.
In order to compile, test and archive Java files, you must have a JDK 8 installed locally.
A Java action is a Java program with a method called main
that has the exact signature as follows:
public static com.google.gson.JsonObject main(com.google.gson.JsonObject);
For example, create a Java file called Hello.java
with the following content:
import com.google.gson.JsonObject;
public class Hello {
public static JsonObject main(JsonObject args) {
String name = "stranger";
if (args.has("name"))
name = args.getAsJsonPrimitive("name").getAsString();
JsonObject response = new JsonObject();
response.addProperty("greeting", "Hello " + name + "!");
return response;
}
}
Then, compile Hello.java
into a JAR file hello.jar
as follows:
javac Hello.java
jar cvf hello.jar Hello.class
Note: google-gson must exist in your Java CLASSPATH when compiling the Java file.
You can create a OpenWhisk action called helloJava
from this JAR file as
follows:
wsk action create helloJava hello.jar --main Hello
When you use the command line and a .jar
source file, you do not need to
specify that you are creating a Java action;
the tool determines that from the file extension.
You need to specify the name of the main class using --main
. An eligible main
class is one that implements a static main
method as described above. If the
class is not in the default package, use the Java fully-qualified class name,
e.g., --main com.example.MyMain
.
If needed you can also customize the method name of your Java action. This
can be done by specifying the Java fully-qualified method name of your action,
e.q., --main com.example.MyMain#methodName
Action invocation is the same for Java actions as it is for Swift and JavaScript actions:
wsk action invoke --result helloJava --param name World
{
"greeting": "Hello World!"
}
Find out more about parameters in the Working with parameters section.
With OpenWhisk Docker actions, you can write your actions in any language.
Your code is compiled into an executable binary and embedded into a Docker image. The binary program interacts with the system by taking input from stdin
and replying through stdout
.
As a prerequisite, you must have a Docker Hub account. To set up a free Docker ID and account, go to Docker Hub.
For the instructions that follow, assume that the Docker user ID is janesmith
and the password is janes_password
. Assuming that the CLI is already set up, three steps are required to set up a custom binary for use by OpenWhisk. After that, the uploaded Docker image can be used as an action.
- Download the Docker skeleton. You can download it by using the CLI as follows:
wsk sdk install docker
The Docker skeleton is now installed at the current directory.
$ ls dockerSkeleton/
Dockerfile README.md buildAndPush.sh example.c
The skeleton is a Docker container template where you can inject your code in the form of custom binaries.
- Set up your custom binary in the blackbox skeleton. The skeleton already includes a C program that you can use.
cat dockerSkeleton/example.c
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("This is an example log message from an arbitrary C program!\n");
printf("{ \"msg\": \"Hello from arbitrary C program!\", \"args\": %s }",
(argc == 1) ? "undefined" : argv[1]);
}
You can modify this file as needed, or, add additional code and dependencies to the Docker image.
In case of the latter, you may need to tweak the Dockerfile
as necessary to build your executable.
The binary must be located inside the container at /action/exec
.
The executable receives a single argument from the command line. It is a string serialization of the JSON
object representing the arguments to the action. The program may log to stdout
or stderr
.
By convention, the last line of output must be a stringified JSON object which represents the result of the action.
- Build the Docker image and upload it using a supplied script. You must first run
docker login
to authenticate, and then run the script with a chosen image name.
docker login -u janesmith -p janes_password
cd dockerSkeleton
./buildAndPush.sh janesmith/blackboxdemo
Notice that part of the example.c file is compiled as part of the Docker image build process, so you do not need C compiled on your machine. In fact, unless you are compiling the binary on a compatible host machine, it may not run inside the container since formats will not match.
Your Docker container may now be used as an OpenWhisk action.
wsk action create example --docker janesmith/blackboxdemo
Notice the use of --docker
when creating an action. Currently all Docker images are assumed to be hosted on Docker Hub.
The action may be invoked as any other OpenWhisk action.
wsk action invoke --result example --param payload Rey
{
"args": {
"payload": "Rey"
},
"msg": "Hello from arbitrary C program!"
}
To update the Docker action, run buildAndPush.sh to upload the latest image to Docker Hub. This will allow the system to pull your new Docker image the next time it runs the code for your action.
If there are no warm containers any new invocations will use the new Docker image.
However, if there is a warm container using a previous version of your Docker image, any new invocations will continue to use that image unless you run wsk action update
. This will indicate to the system that for new invocations it should execute a docker pull to get your new Docker image.
./buildAndPush.sh janesmith/blackboxdemo
wsk action update example --docker janesmith/blackboxdemo
You can find more information about creating Docker actions in the References section.
Note: Previous version of the CLI supported --docker
without a parameter and the image name was a positional argument.
In order to allow Docker actions to accept initialization data via a (zip) file, similar to other actions kinds, we have
normalized the user experience for Docker actions so that a positional argument if present must be a file (e.g., a zip file)
instead. The image name must be specified following the --docker
option. Furthermore, due to user feedback, we have added
--native
as shorthand for --docker openwhisk/dockerskeleton
so that executables that run inside the standard Docker action
SDK are more convenient to create and deploy.
For example, the tutorial above created a binary executable inside the container locate at /action/exec
. If you copy this file
to your local file system and zip it into exec.zip
then you can use the following commands to create a docker action which receives
the executable as initialization data.
wsk action create example exec.zip --native
which is equivalent to the following command.
wsk action create example exec.zip --docker openwhisk/dockerskeleton
The --native
option allows for packaging of any executable as an action. This works for Go as an example.
As with Docker actions, the Go executable receives a single argument from the command line.
It is a string serialization of the JSON object representing the arguments to the action.
The program may log to stdout
or stderr
.
By convention, the last line of output must be a stringified JSON object which represents the result of the action.
Here is an example Go action.
package main
import "encoding/json"
import "fmt"
import "os"
func main() {
//program receives one argument: the JSON object as a string
arg := os.Args[1]
// unmarshal the string to a JSON object
var obj map[string]interface{}
json.Unmarshal([]byte(arg), &obj)
// can optionally log to stdout (or stderr)
fmt.Println("hello Go action")
name, ok := obj["name"].(string)
if !ok { name = "Stranger" }
// last line of stdout is the result JSON object as a string
msg := map[string]string{"msg": ("Hello, " + name + "!")}
res, _ := json.Marshal(msg)
fmt.Println(string(res))
}
Save the code above to a file sample.go
and cross compile it for OpenWhisk. The executable must be called exec
.
GOOS=linux GOARCH=amd64 go build -o exec
zip exec.zip exec
wsk action create helloGo --native exec.zip
The action may be run as any other action.
wsk action invoke helloGo -r -p name gopher
{
"msg": "Hello, gopher!"
}
Find out more about parameters in the Working with parameters section.
Logs are retrieved in a similar way as well.
wsk activation logs --last --strip
my first Go action.
Using --native
, you can see that any executable may be run as an OpenWhisk action. This includes bash
scripts,
or cross compiled binaries. For the latter, the constraint is that the binary must be compatible with the
openwhisk/dockerskeleton
image.
OpenWhisk actions might be invoked by other users, in response to various events, or as part of an action sequence. In such cases it can be useful to monitor the invocations.
You can use the OpenWhisk CLI to watch the output of actions as they are invoked.
- Issue the following command from a shell:
wsk activation poll
This command starts a polling loop that continuously checks for logs from activations.
- Switch to another window and invoke an action:
wsk action invoke /whisk.system/samples/helloWorld --param payload Bob
ok: invoked /whisk.system/samples/helloWorld with id 7331f9b9e2044d85afd219b12c0f1491
- Observe the activation log in the polling window:
Activation: helloWorld (7331f9b9e2044d85afd219b12c0f1491)
2016-02-11T16:46:56.842065025Z stdout: hello bob!
Similarly, whenever you run the poll utility, you see in real time the logs for any actions running on your behalf in OpenWhisk.
Metadata that describes existing actions can be retrieved via the wsk action get
command.
wsk action get hello
ok: got action hello
{
"namespace": "user@email.com",
"name": "hello",
"version": "0.0.1",
"exec": {
"kind": "nodejs:6",
"binary": false
},
"annotations": [
{
"key": "exec",
"value": "nodejs:6"
}
],
"limits": {
"timeout": 60000,
"memory": 256,
"logs": 10
},
"publish": false
}
An action can be invoked through the REST interface via an HTTPS request. To get an action URL, execute the following command:
wsk action get actionName --url
A URL with the following format will be returned for standard actions:
ok: got action actionName
https://${APIHOST}/api/v1/namespaces/${NAMESPACE}/actions/actionName
For web actions, a URL will be returned in the the following format:
ok: got action actionName
https://${APIHOST}/api/v1/web/${NAMESPACE}/${PACKAGE}/actionName
Note: For standard actions, authentication must be provided when invoking an action via an HTTPS request. For more information regarding action invocations using the REST interface, see Using REST APIs with OpenWhisk.
Code associated with an existing action may be retrieved and saved locally. Saving can be performed on all actions except sequences and docker actions.
- Save action code to a filename that corresponds with an existing action name in the current working directory. A file extension that corresponds to the action kind is used, or an extension of
.zip
will be used for action code that is a zip file.
wsk action get actionName --save
ok: saved action code to /absolutePath/currentDirectory/actionName.js
- Instead of allowing the CLI to determine the destination of the code to be saved, a custom file path, filename and extension can be provided by using the
--save-as
flag.
wsk action get actionName --save-as codeFile.js
ok: saved action code to /absolutePath/currentDirectory/codeFile.js
You can list all the actions that you have created using wsk action list
:
wsk action list
actions
/guest/packageB/A private nodejs:6
/guest/C private nodejs:6
/guest/A private nodejs:6
/guest/packageA/B private nodejs:6
/guest/packageA/A private nodejs:6
/guest/B private nodejs:6
Here, we see actions listed in order from most to least recently updated. For easier browsing, you can use the flag --name-sort
or -n
to sort the list alphabetically:
wsk action list --name-sort
actions
/guest/A private nodejs:6
/guest/B private nodejs:6
/guest/C private nodejs:6
/guest/packageA/A private nodejs:6
/guest/packageA/B private nodejs:6
/guest/packageB/A private nodejs:6
Notice that the list is now sorted alphabetically by namespace, then package name, and finally action name, with the default package (no specified package) listed at the top.
Note: The printed list is sorted alphabetically after it is received from the server. Other list flags such as --limit
and --skip
will be applied to the block of actions before they are received for sorting. To list actions in order by creation time, use the flag --time
.
As you write more actions, this list gets longer and it can be helpful to group related actions into packages. To filter your list of actions to just those within a specific package, you can use:
wsk action list [PACKAGE NAME]
You can clean up by deleting actions that you do not want to use.
- Run the following command to delete an action:
wsk action delete hello
ok: deleted hello
- Verify that the action no longer appears in the list of actions.
wsk action list
actions
The action environment contains several properties that are specific to the running action. These allow the action to programmatically work with OpenWhisk assets via the REST API, or set an internal alarm when the action is about to use up its allotted time budget. The properties are accessible via the system environment for all supported runtimes: Node.js, Python, Swift, Java and Docker actions when using the OpenWhisk Docker skeleton.
__OW_API_HOST
the API host for the OpenWhisk deployment running this action__OW_API_KEY
the API key for the subject invoking the action, this key may be a restricted API key__OW_NAMESPACE
the namespace for the activation (this may not be the same as the namespace for the action)__OW_ACTION_NAME
the fully qualified name of the running action__OW_ACTIVATION_ID
the activation id for this running action instance__OW_DEADLINE
the approximate time when this action will have consumed its entire duration quota (measured in epoch milliseconds)