James Thomas

Notes on JavaScript

Large Applications on OpenWhisk

OpenWhisk supports creating actions from archive files containing source files and project dependencies.

The maximum code size for the action is 48MB.

Applications with lots of third-party modules, native libraries or external tools may be soon find themselves running into this limit. Node.js libraries are notorious for having large amounts of dependencies.

What if you need to deploy an application larger than this limit to OpenWhisk?

Previous solutions used Docker support in OpenWhisk to build a custom Docker image per action. Source files and dependencies are built into a public image hosted on Docker Hub.

This approach overcomes the limit on deployment size but means application source files will be accessible on Docker Hub. This is not an issue for building samples or open-source projects but not realistic for most applications.

So, using an application larger than this limit requires me to make my source files public? 🤔

There’s now a better solution! 👏👏👏

OpenWhisk supports creating actions from an archive file AND a custom Docker image.

If we build a custom Docker runtime which includes shared libraries, those dependencies don’t need including in the archive file. Private source files will still be bundled in the archive and injected at runtime.

Reducing archive file sizes also improves deployment times.

Let’s look at an example…

Using Machine Learning Libraries on OpenWhisk

Python is a popular language for machine learning and data science. Libraries like pandas, scikit-learn and numpy provide all the tools. Serverless computing is becoming a good choice for machine learning microservices.

OpenWhisk supports Python 2 and 3 runtimes.

Popular libraries like flask, requests and beautifulsoup are available as global packages. Additional packages can be imported using virutalenv during invocations.

Python Machine Learning Libraries

Python packages can be used in OpenWhisk using virtualenv. Developers install the packages locally and include the virutalenv folder in the archive for deployment.

Machine Learning libraries often use numerous shared libraries and compile native dependencies for performance. This can lead to hundreds of megabytes of dependencies.

Setting up a new virtualenv folder and installing pandas leads to an environment with nearly 100MB of dependencies.

1
2
3
4
5
6
7
8
9
$ virtualenv env
$ source env/bin/activate
$ pip install pandas
...
Installing collected packages: numpy, six, python-dateutil, pytz, pandas
Successfully installed numpy-1.13.1 pandas-0.20.3 python-dateutil-2.6.1 pytz-2017.2 six-1.10.0
$ du -h
...
84M   . <-- FOLDER SIZE 😱

Bundling these libraries within an archive file will not be possible due to the file size limit.

Custom OpenWhisk Runtime Images

Overcoming this limit can be achieved using a custom runtime image. The runtime will pre-install additional libraries during the build process and make them available during invocations.

OpenWhisk uses Docker for the runtime containers. Source files for the images are available on Github under the core folder. Here’s the Dockerfile for the Python runtime: https://github.com/apache/incubator-openwhisk/blob/master/core/pythonAction/Dockerfile.

Images for OpenWhisk runtimes are also available on Docker Hub under the OpenWhisk organisation.

Docker supports building new images from a parent image using the FROM directive. Inheriting from the existing runtime images means the Dockerfile for the new runtime only has to contain commands for installing extra dependencies.

Let’s build a new Python runtime which includes those libraries as shared packages.

Building Runtimes

Let’s create a new Dockerfile which installs additional packages into the OpenWhisk Python runtime.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM openwhisk/python3action

# lapack-dev is available in community repo.
RUN echo "http://dl-4.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories

# add package build dependencies
RUN apk add --no-cache \
        g++ \
        lapack-dev \
        gfortran

# add python packages
RUN pip install \
    numpy \
    pandas \
    scipy \
    sklearn

Running the Docker build command will create a new image with these extra dependencies.

1
2
3
4
5
6
7
$ docker build -t python_ml_runtime .
Sending build context to Docker daemon  83.01MB
Step 1/4 : FROM openwhisk/python3action
 ---> 46388e726fae
...
Successfully built cfc14a93863e
Successfully tagged python_ml_runtime:latest

Hosting images on Docker Hub requires registering a (free) account @ https://hub.docker.com/

Create a new tag from the python_ml_runtime image containing the Docker Hub username.

1
$ docker tag python_ml_runtime <YOUR_USERNAME>/python_ml_test

Push the image to Docker Hub to make it available to OpenWhisk.

1
$ docker push <YOUR_USERNAME>/python_ml_test

Testing It Out

Create a new Python file (main.py) with the following contents:

1
2
3
4
5
6
7
8
9
10
11
12
import numpy
import pandas
import sklearn
import scipy

def main(params):
    return {
        "numpy": numpy.__version__,
        "pandas": pandas.__version__,
        "sklearn": sklearn.__version__,
        "scipy": scipy.__version__
    }

Create a new OpenWhisk action using the Docker image from above and source file.

1
2
$ wsk action create lib-versions --docker <YOUR_USERNAME>/openwhisk_python_ml main.py
ok: created action lib-versions

Invoke the action to verify the modules are available and return the versions.

1
2
3
4
5
6
7
$ wsk action invoke lib-versions --result
{
    "numpy": "1.13.1",
    "pandas": "0.20.3",
    "scipy": "0.19.1",
    "sklearn": "0.18.2"
}

Yass. It works. 💃🕺

Serverless Machine Learning here we come…. 😉

Conclusions

Using custom runtimes with private source files is an amazing feature of OpenWhisk. It enables developers to run larger applications on the platform but also enables lots of other use cases. Almost any runtime, library or tool can now be used from the platform.

Here are some examples of where this approach could be used…

  • Installing global libraries to reduce archive file size under 48MB and speed up deployments.
  • Upgrading language runtimes, i.e. using Node.js 8 instead of 6.
  • Adding native dependencies or command-line tools to the environment, e.g. ffmpeg.

Building new runtimes is really simple using pre-existing base images published on Dockerhub.

The possibilities are endless!

Creating Swift Binaries for OpenWhisk

In the previous blog post, we explained how to write Serverless Swift functions using OpenWhisk actions.

Swift sources files are compiled into a binary by the platform before processing requests.

This compilation process adds a delay on the invocation time for “cold” runtimes. If the action has not been invoked for a while, the system is under heavy load or multiple invocations are received in parallel, a new runtime will need to be initialised.

Pre-compiled binaries can be deployed to remove this delay. Binaries must be compiled for the correct platform architecture and support execution through the OpenWhisk runtime.

There is now a Swift package to make the process of building pre-compiled binaries much easier.

Let’s have a look at how this works…

Swift Packages

Swift introduced a package manager in Swift 3.0. The package manager integrates with the build system to “automate the process of downloading, compiling, and linking dependencies”.

Swift uses a manifest file (Packages.swift) to define package properties including dependencies.

Example Swift Package

Here’s an example manifest file from a sample package with external dependencies.

1
2
3
4
5
6
7
8
9
10
11
12
import PackageDescription

let package = Package(
    name: "DeckOfPlayingCards",
    targets: [],
    dependencies: [
        .Package(url: "https://github.com/apple/example-package-fisheryates.git",
                 majorVersion: 1),
        .Package(url: "https://github.com/apple/example-package-playingcard.git",
                 majorVersion: 1),
    ]
)

Packages are referenced through a URL which resolves to a Git repository. Semantic versioning conventions are used to control the package version installed.

External packages are downloaded, compiled and linked in the project during the build process.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ swift build
Fetching https://github.com/apple/example-package-deckofplayingcards.git
Fetching https://github.com/apple/example-package-fisheryates.git
Fetching https://github.com/apple/example-package-playingcard.git
Cloning https://github.com/apple/example-package-fisheryates.git
Resolving https://github.com/apple/example-package-fisheryates.git at 2.0.3
Cloning https://github.com/apple/example-package-playingcard.git
Resolving https://github.com/apple/example-package-playingcard.git at 3.0.2
Cloning https://github.com/apple/example-package-deckofplayingcards.git
Resolving https://github.com/apple/example-package-deckofplayingcards.git at 3.0.3
Compile Swift Module 'PlayingCard' (3 sources)
Compile Swift Module 'FisherYates' (2 sources)
Compile Swift Module 'DeckOfPlayingCards' (1 sources)
Compile Swift Module 'Dealer' (1 sources)
Linking ./.build/debug/Dealer
$

OpenWhiskAction Package

OpenWhiskAction is a Swift package for registering Swift functions as OpenWhisk actions.

It bundles the Swift source files used to implement the runtime handler for OpenWhisk as a library. Using this package means developers do not have to manually import those source files into their projects.

usage

This package exposes a public function (OpenWhiskAction ) that should be called with a function reference (([String: Any]) -> [String: Any])) as a named parameter (main). The callback will be executed with the invocation parameters. Returned values will be serialised as the invocation response.

1
2
3
4
5
6
7
8
9
10
11
import OpenWhiskAction

func hello(args: [String:Any]) -> [String:Any] {
    if let name = args["name"] as? String {
      return [ "greeting" : "Hello \(name)!" ]
    } else {
      return [ "greeting" : "Hello stranger!" ]
    }
}

OpenWhiskAction(main: hello)

example

Let’s show an example of using the package to build a pre-compiled Swift action for OpenWhisk.

create new package

Create a new directory and use the swift package init command to generate the boilerplate package.

1
2
3
4
5
6
7
8
9
10
11
12
$ mkdir Action
$ cd Action/
$ swift package init
Creating library package: Action
Creating Package.swift
Creating .gitignore
Creating Sources/
Creating Sources/Action.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/ActionTests/
Creating Tests/ActionTests/ActionTests.swift

add package dependency

Add the OpenWhiskAction package as a dependency to the manifest file (Package.swift).

1
2
3
4
5
6
7
8
import PackageDescription

let package = Package(
    name: "Action",
    dependencies: [
        .Package(url: "https://github.com/jthomas/OpenWhiskAction.git", majorVersion: 0)
    ]
)

write serverless function

Create a new main.swift file under the Sources directory containing the following source code.

1
2
3
4
5
6
7
8
9
10
11
import OpenWhiskAction

func hello(args: [String:Any]) -> [String:Any] {
    if let name = args["name"] as? String {
      return [ "greeting" : "Hello \(name)!" ]
    } else {
      return [ "greeting" : "Hello stranger!" ]
    }
}

OpenWhiskAction(main: hello)

Swift’s build process will produce an executable if the package contains a main.swift file. That file will be compiled as the package binary.

compiling with docker

OpenWhisk Swift actions use a custom Docker image as the runtime environment. Compiling application binaries from this image will ensure it is compatible with the platform runtime.

This command will run the swift build command within a container from this image. The host filesystem is mounted into the container at /swift-package. Binaries and other build artifacts will be available in ./.build/release/ after the command has executed.

1
docker run --rm -it -v $(pwd):/swift-package openwhisk/swift3action bash -e -c "cd /swift-package && swift build -v -c release"

deploying to openwhisk

OpenWhisk actions can be created from a zip file containing action artifacts. The zip file will be expanded prior to execution. In the Swift environment, the Swift binary executed by the platform should be at ./.build/release/Action.

If an action is deployed from a zip file which contains this file, the runtime will execute this binary rather than compiling a new binary from source code within the zip file.

1
2
3
4
5
6
7
8
$ zip action.zip .build/release/Action
  adding: .build/release/Action (deflated 67%)
$ wsk action create swift-action --kind swift:3 action.zip
ok: created action swift-action
$ wsk action invoke --blocking --result -p name "Bernie Sanders" swift-action
{
    "greeting": "Hello Bernie Sanders!"
}

Using With The Serverless Framework

As shown in the previous blog post, The Serverless Framework supports the Swift runtime. Actions can either be created from Swift source files or pre-compiled binaries.

This example project demonstrates how to integrate pre-compiled binaries into a serverless framework application.

example project

The project contains two Swift source files under the Sources directory. Using the main.swift file name means these files will be compiled into separate binaries under the .build/release directory.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ tree .
.
├── Package.swift
├── README.md
├── Sources
│   ├── hello
│   │   └── main.swift
│   └── welcome
│       └── main.swift
├── package.json
└── serverless.yml

3 directories, 6 files

The package manifest (Package.swift) contains the OpenWhiskAction dependency.

serverless.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
service: swift-packages

provider:
  name: openwhisk
  runtime: swift

functions:
  hello:
    handler: .build/release/hello
  welcome:
    handler: .build/release/welcome

custom:
  scripts:
    hooks:
      'package:initialize': npm run-script compile
plugins:
  - serverless-openwhisk
  - serverless-plugin-scripts

This configuration file describes two actions (hello and welcome) using the swift runtime. The handler property for those actions refers to a binary, produced by the build process, rather than source file.

compile during deployment

Before using serverless deploy command to create our application, we need to compile binaries for the OpenWhisk runtime.

Manually running the Swift build command before each deployment is cumbersome and error-prone.

Let’s automate this process…

Using NPM’s scripts feature, the project exports a new command npm run-script compile which triggers the build process using the OpenWhisk Docker runtime for Swift.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "name": "openwhisk-swift-package-with-precompiled-binaries",
  "version": "1.0.0",
  "description": "Swift packages and pre-compiled binaries on OpenWhisk.",
  "main": "handler.js",
  "scripts": {
    "postinstall": "npm link serverless-openwhisk",
    "compile": "docker run --rm -it -v $(pwd):/swift-package openwhisk/swift3action bash -e -c 'cd /swift-package && swift build -v -c release'"
  },
  "keywords": [
    "serverless",
    "openwhisk"
  ],
  "dependencies": {
    "serverless-plugin-scripts": "^1.0.2"
  }
}

The serverless-plugin-scripts plugin provides a mechanism for running shell commands when framework commands are executed. Using this plugin we can use the package:initialize event to execute the npm run-script compile command.

1
2
3
4
custom:
  scripts:
    hooks:
      'package:initialize': npm run-script compile

The package:initialize event is fired when the serverless deploy command executes.

Swift binaries will be compiled prior to deployment without any manual steps from the developer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ serverless deploy
> openwhisk-swift-package-with-precompiled-binaries@1.0.0 compile /Users/james/code/bluemix/serverless-examples/openwhisk-swift-precompiled-binaries
> docker run --rm -it -v $(pwd):/swift-package openwhisk/swift3action bash -e -c 'cd /swift-package && swift build -v -c release'
...
Serverless: Packaging service...
Serverless: Compiling Functions...
Serverless: Compiling API Gateway definitions...
Serverless: Compiling Rules...
Serverless: Compiling Triggers & Feeds...
Serverless: Deploying Functions...
Serverless: Deployment successful!

Service Information
platform:   openwhisk.ng.bluemix.net
namespace:  _
service:    swift-packages

actions:
swift-packages-dev-hello    swift-packages-dev-welcome
...
$ serverless invoke -f hello
{
    "greeting": "Hello stranger!"
}
$ serverless invoke -f welcome
{
    "greeting": "Welcome stranger!"
}

Conclusion

OpenWhisk supports creating Swift actions from source files and pre-compiled binaries. Using binaries reduces the startup time for “cold” environments. This is important for latency sensitive applications like API endpoints.

Swift binaries for OpenWhisk must be compiled for the correct architecture and support execution through the platform runtime. Previous instruction for producing these binaries involved numerous manual and error-prone steps.

This process has now been improved through a new Swift package which wraps the runtime handler source files. Adding this dependency into the package manifest file means the downloading, compiling and linking of these source files will be handled by the Swift package manager.

Recent updates to the OpenWhisk provider plugin for The Serverless Framework also added support for pre-compiled Swift binaries. Combined with other plugins, the framework can now completely automate the process of building binaries for the Swift runtime.

Building binaries for Swift OpenWhisk actions has never been easier! 😎

Serverless Swift With OpenWhisk

Swift is one of the fastest growing programming languages with developers.

Swift has reached a Top 15 ranking faster than any other language we have tracked.

Created for building mobile applications, the language is now popular with backend development.

But for Swift developers beginning to build backend applications, they now find themselves having to manage computing infrastructure to run their applications in the cloud.

Enter serverless cloud platforms… ☁️☁️☁️

These services allow developers to push code, rather than VMs, into the cloud. The platforms allow you to connect external event sources like API requests or message queues to functions in your code. As events occur, your code is instantiated and executed to process each request. Developers are only billed for the milliseconds needed to process each request.

Serverless platforms let you run applications in the cloud without worrying about infrastructure. 😎

Apache OpenWhisk is currently the only serverless platform to support Swift language functions.

Let’s have a look at how you can use Swift with OpenWhisk before diving into how the platform implements this feature to give us some tips and tricks for Swift on OpenWhisk…

Swift On OpenWhisk

Using the CLI

Create a Swift file with the following source code in.

1
2
3
4
5
6
7
func main(args: [String:Any]) -> [String:Any] {
    if let name = args["name"] as? String {
        return [ "greeting" : "Hello \(name)!" ]
    } else {
        return [ "greeting" : "Hello stranger!" ]
    }
}

Swift actions must consume and return a dictionary. The dictionary passed as the function argument will contain event parameters. Returned dictionary values must support serialisation to JSON.

Create and invoke a new OpenWhisk action using the command-line utility.

1
2
3
4
5
6
7
8
9
10
$ wsk action create swift action.swift
ok: created action swift
$ wsk action invoke swift --result
{
    "greeting": "Hello stranger!"
}
$ wsk action invoke swift --result --param name World
{
    "greeting": "Hello World!"
}

The result flag will only show the action output in the console rather than the full API response.

The source file must have a function called main. Each invocation executes this function. The function name to invoke can be overridden as shown below.

1
2
3
func foo(args: [String:Any]) -> [String:Any] {
    return [ "greeting" : "Hello foo!" ]
}
1
2
3
4
5
6
$ wsk action create foobar action.swift --main foo
ok: created action foobar
$ wsk action invoke foobar --result
{
    "greeting": "Hello foo!"
}

Choosing the runtime for the action can be set using the kind flag. If the source file has the .swift extension this will be automatically set to swift:default.

OpenWhisk uses Swift 3.0.2 that runs on the Linux environment. There are open issues to support Swift 3.1 and Swift 4.

Using the Serverless Framework

The Serverless Framework is a popular open-source framework for building serverless applications. It provides CLI tools and a workflow for managing serverless development.

Developers use a YAML file to define their application functions, events and resources. The framework handles deploying the application to their serverless provider.

Having started as a tool for AWS Lambda, the framework recently added multi-provider support. It now also works with Apache OpenWhisk, Azure Functions and Google Cloud Functions.

Let’s look at an example of using this framework to create a new OpenWhisk Swift application. Using a provider name and runtime, the framework can scaffold a new serverless application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ serverless create -t openwhisk-swift -p swift-action
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/home/me/swift-action"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.16.0
 -------'

Serverless: Successfully generated boilerplate for template: "openwhisk-swift"
$ tree swift-action/
swift-action/
├── README.md
├── package.json
├── ping.swift
└── serverless.yml

0 directories, 4 files

The openwhisk-swift directory contains the boilerplate application ready to deploy. It includes a sample action (ping.swift) and the configuration file (serverless.yml).

1
2
3
4
5
6
7
8
9
10
11
func main(args: [String:Any]) -> [String:Any] {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
    let now = formatter.string(from: Date())

    if let name = args["name"] as? String {
      return [ "greeting" : "Hello \(name)! The time is \(now)" ]
    } else {
      return [ "greeting" : "Hello stranger! The time is \(now)" ]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
service: swift-action

provider:
  name: openwhisk
  runtime: swift

functions:
  hello:
    handler: ping.main

plugins:
  - serverless-openwhisk

Install the provider plugin using npm install and type serverless deploy to deploy this application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ serverless deploy
Serverless: Packaging service...
Serverless: Compiling Functions...
Serverless: Compiling API Gateway definitions...
Serverless: Compiling Rules...
Serverless: Compiling Triggers & Feeds...
Serverless: Deploying Functions...
Serverless: Deployment successful!

Service Information
platform: openwhisk.ng.bluemix.net
namespace:    _
service:  swift-action

actions:
swift-action-dev-hello
...
$ serverless invoke -f hello
{
    "greeting": "Hello stranger! The time is 2017-06-23 10:52:02"
}

For more information on using the Serverless Framework with OpenWhisk, please see this documentation: https://serverless.com/framework/docs/providers/openwhisk/.

How It Works

Swift is a statically typed compiled language. Unlike JavaScript or Python, Swift source code must be compiled into a binary for execution.

Swift actions in OpenWhisk can be created from Swift source files, rather than binaries, meaning the platform must run this compilation step.

Swift on Docker

OpenWhisk uses Docker containers to manage the action runtime environments. This Dockerfile documents the build steps for generating the Swift runtime image used in OpenWhisk.

Images for each of the OpenWhisk runtime environments are available on Docker Hub. Creating containers from these images allows you to explore the Swift runtime environment.

1
2
$ docker pull openwhisk/swift3action
$ docker run -it --rm openwhisk/swift3action bash

For more information on the API exposed by runtime containers to initialise and invoke actions, please see this blog post.

Building Swift actions

Swift runtime environments has a template package available in the /swift3Action/spm-build directory.

All the Swift sources files provided by the user are written into that package’s main.swift file. The following source code is appended to main.swift to support execution within the OpenWhisk runtime. It parses the input parameters from the environment, invokes the registered function name and returns the computation response as a JSON string.

Dependencies for the following packages are included in the existing Package.swift file. These packages can be used from the action source code without further configuration.

1
2
3
4
5
6
7
8
9
10
import PackageDescription

let package = Package(
    name: "Action",
        dependencies: [
          .Package(url: "https://github.com/IBM-Swift/Kitura-net.git", "1.0.1"),
            .Package(url: "https://github.com/IBM-Swift/SwiftyJSON.git", "14.2.0"),
            .Package(url: "https://github.com/IBM-Swift/swift-watson-sdk.git", "0.4.1")
        ]
)

During initialisation, the Swift build process is executed to generate the action binary.

This artifact (/swift3Action/spm-build/.build/release/Action) will be executed for each invocation received by the platform.

Container re-use

Containers used for action runtimes are re-used with subsequent requests. This means any initialisation cost, e.g. compiling Swift source code, will only be incurred once per runtime container.

Runtime containers are evicted from the cache ten minutes after the last activation. Future invocations for that runtime will use a new container and have to run the initialisation step again.

Additionally, runtimes containers cannot process concurrent requests. If a request arrives before the previous one has finished processing, a new environment will need to be initialised.

Improving cold start time

Swift build times are not known for being fast.

Build time is included in the request processing time for each new runtime container provisioned.

In an attempt to reduce this delay, OpenWhisk runs the minimum build steps necessary to compile the source code, rather than a full release build.

During the Docker build for the Swift runtime image, the full release build is executed for the empty action package. This generates object files and other intermediary build outputs which are stored in the build cache.

Logs from the build process are parsed to retrieve the individual compilation and linking commands for the main.swift file. These commands are written into a new shell script (/swift3Action/spm-build/swiftbuildandlink.sh).

When a new Swift runtime container is initialised, the source code for the action is written into the main.swift file. Rather than running a full re-build, the runtime just executes the shell script containing the compilation and linking steps. This re-uses the cached build objects and reduces compilation time.

Modifying package dependencies

Swift packages uses a manifest file (Packages.swift) to list package dependencies. Dependencies are automatically downloaded and compiling during the package build process.

The Swift environment used by OpenWhisk uses the package manifest shown above. This includes dependencies for JSON and HTTP libraries.

Swift actions can be created from Swift source code or zip files. Zip files are expanded into the package directory (/swift3action/spm-build) before initialisation.

If the zip file contains a new package manifest, this will overwrite the default manifest in the environment.

1
2
3
4
5
6
7
8
9
10
11
import PackageDescription

let package = Package(
    name: "Action",
        dependencies: [
          .Package(url: "https://github.com/IBM-Swift/Kitura-net.git", "1.0.1"),
            .Package(url: "https://github.com/IBM-Swift/SwiftyJSON.git", "14.2.0"),
            .Package(url: "https://github.com/IBM-Swift/swift-watson-sdk.git", "0.4.1"),
          .Package(url: "https://github.com/IBM-Swift/swift-html-entities", majorVersion: 3, minor: 0),
        ]
)

Running a full build will download new package dependencies and make them available for use in our action.

OpenWhisk uses a shell script (swiftbuildandlink.sh) to manage the build process during initialisation. This defaults to only running the compiler and linker commands for the main.swift file, rather than a full release build.

Including a replacement swiftbuildandlink.sh file in the zip file will allow us to modify the build command used, e.g. swift build -v -c release.

1
2
3
4
#!/bin/bash
echo "Release build running..."
swift build -v -c release
echo "Release build finished."

Downloading additional packages will add a significant delay to initialising new runtime containers.

If this is an issue, let’s look at skipping the compile step entirely…

Compiling binaries locally

Swift actions execute a binary that is available at the following path: /swift3action/spm-build/.build/release/Action.

The runtime uses the existence of this binary to control running the build process. If the file does not exist, the build step is executed. It ensures that compilation is only ran once per runtime container.

This also means that developers can include a locally compiled Swift binary inside the action zip file. During initialisation, the existence of this file will stop the build process from running.

If you want to use lots of additional Swift packages, the compile time penalty won’t have to be incurred during action invocations. This will dramatically speed up invocation times for “cold” actions.

Binaries must be compatible with the platform environment they are being executed within. OpenWhisk uses Swift 3.0.2 on Linux.

OpenWhisk publishes the runtime environments as Docker images. Using containers from these images to compile our action binaries will ensure the binary is compatible.

These instructions show you how to compile your source code into a compatible platform binary.

1
2
3
4
5
6
7
8
9
10
11
12
13
# run an interactive Swift action container
docker run -it -v `pwd`:/ow openwhisk/swift3action bash
cd /ow
# now inside the docker shell
# copy the source code and prepare to build it
cat /swift3Action/epilogue.swift >> main.swift
echo '_run_main(mainFunction:main)' >> main.swift
# build and link (the expensive step)
swift build -v -c release
# create the zip archive
zip action.zip .build/release/Action
# exit the docker shell
exit

The action.zip file can then be deployed as a new action using the following command-line.

1
wsk action create static-swift action.zip --kind swift:3

Conclusion

Swift is one of the fastest growing programming languages with developers. People are increasingly using it to develop backend APIs and services. Being able to use Swift on serverless cloud platforms means developers can focus on writing code, rather than managing infrastructure.

Apache OpenWhisk, an open-source serverless platform, supports Swift as a first-class language. Developers can provide Swift source code and have the platform execute these functions in response to external events.

Because OpenWhisk is open-source, we can discover how the platform executes the code using the Swift runtime. Understanding this process allows us to modify the build step to use additional Swift packages within our actions. We can also improve performance by skipping the compilation stage entirely by providing a native binary.

Python Packages in OpenWhisk

OpenWhisk’s Python runtime includes popular third-party libraries like requests, scrapy and simplejson. Developers don’t have to manually install packages to use those libraries.

Great, but what about using other libraries that aren’t pre-installed?

In a previous blog post, we showed how to deploy Node.js actions from zip files containing third-party modules. These modules are then made available in the Node.js runtime.

Recent updates to OpenWhisk allow us to use the same approach with the Python runtime!

Python Packages

Python packages can be installed using the pip tool. This can be used to install individual packages or a series of dependencies from an external file.

1
2
$ pip install blah
$ pip install -r requirements.txt

pip defaults to installing packages in a global location (site-packages) which is shared between all users. This can cause issues when different projects require different versions of the same package.

virtualenv

virtualenv is a tool that solves this issue by creating virtual python environments for projects. The virtual environment includes a custom site-packages folder to install packages into.

1
2
3
4
5
$ virtualenv env
Using base prefix '/Library/Frameworks/Python.framework/Versions/3.6'
New python executable in /private/tmp/env/bin/python3.6
Also creating executable in /private/tmp/env/bin/python
Installing setuptools, pip, wheel...done.

OpenWhisk recently added support for using virtualenv in the Python runtime.

custom packages on openwhisk

OpenWhisk actions can be created from a zip file containing source files and other resources.

If the archive includes a virtual Python environment folder, the platform runs the ./bin/activate_this.py script before executing Python actions. This script modifies the module search path to include the local site-packages folder.

This will only happen during “cold” activations.

This feature comes with the following restrictions.

  • Virtual Python environment must be in a folder called virtualenv under the top-level directory.
  • Packages must be available for the Python runtime being used in OpenWhisk (2.7 or 3.6).

Let’s look at an example of building an OpenWhisk Python action which uses an external Python package.

Python Package Example

The pyjokes package provides a library for generating (terrible) jokes for programmers. Let’s turn this package into an API (Jokes-as-a-Service!) using the Python runtime on OpenWhisk.

Start by creating a new directory for your project and set up the virtual Python environment.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ mkdir jokes; cd jokes
$ virtualenv virtualenv
Using base prefix '/Library/Frameworks/Python.framework/Versions/3.6'
New python executable in /tmp/jokes/virtualenv/bin/python3.6
Also creating executable in /tmp/jokes/virtualenv/bin/python
Installing setuptools, pip, wheel...done.
$ source virtualenv/bin/activate
(virtualenv) $ pip install pyjokes
Collecting pyjokes
  Using cached pyjokes-0.5.0-py2.py3-none-any.whl
Installing collected packages: pyjokes
Successfully installed pyjokes-0.5.0
(virtualenv) $

In the project directory, create a new file (__main__.py) and paste the following code.

1
2
3
4
import pyjokes

def joke(params):
    return {"joke": pyjokes.get_joke()}

Check the script works with the Python intepreter.

1
2
3
4
(virtualenv) $ python -i .
>>> joke({})
{'joke': 'What do you call a programmer from Finland? Nerdic.'}
>>>

Add the virtualenv folder and Python script to a new zip file.

1
2
3
4
5
6
7
8
$ zip -r jokes.zip virtualenv __main__.py
  adding: virtualenv/ (stored 0%)
  adding: virtualenv/.Python (deflated 65%)
  adding: virtualenv/bin/ (stored 0%)
  adding: virtualenv/bin/activate (deflated 63%)
  ...
$ ls
__main__.py  jokes.zip   virtualenv

Create a new OpenWhisk action for the Python runtime using the wsk cli.

1
2
$ wsk action create jokes --kind python:3 --main joke jokes.zip
ok: created action jokes

Invoking our new action will return (bad) jokes on-demand using the third-party Python package.

1
2
3
4
$ wsk action invoke jokes --blocking --result
{
    "joke": "Software salesmen and used-car salesmen differ in that the latter know when they are lying."
}

Installing Packages With Docker

In the example above, the Python runtime used in development (v3.6) matched the OpenWhisk runtime environment. Packages installed using virtualenv must be for the same major and minor versions of the Python runtime used by OpenWhisk.

OpenWhisk publishes the runtime environments as Docker images on Docker Hub.

Running containers from those runtime images provides a way to download packages for the correct environment.

1
2
3
4
5
6
7
8
9
10
11
$ docker run --rm -v "$PWD:/tmp" openwhisk/python3action sh \
  -c "cd tmp; virtualenv virtualenv; source virtualenv/bin/activate; pip install pyjokes;"
Using base prefix '/usr/local'
New python executable in /tmp/virtualenv/bin/python3.6
Also creating executable in /tmp/virtualenv/bin/python
Installing setuptools, pip, wheel...done.
Collecting pyjokes
  Downloading pyjokes-0.5.0-py2.py3-none-any.whl
Installing collected packages: pyjokes
Successfully installed pyjokes-0.5.0
$

This will leave you a virtualenv folder in the current directory with packages for the correct Python runtime.

Speeding Up Deployments

Peeking inside the virtualenv folder reveals a huge number of files to set up the virtual Python environment. If we just want to use a third-party package from the local site-packages folder, most of those files are unnecessary.

Adding this entire folder to the zip archive will unnecessarily inflate the file size. This will slow down deployments and increase execution time for cold activations. OpenWhisk also has a maximum size for action source code of 48MB.

Manually including individual site-packages folders, rather than the entire virtualenv directory, will ensure the archive file only contains packages being used. We must also add the Python script (virtualenv/bin/activate_this.py) executed by OpenWhisk to modify the module search path.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ zip -r jokes_small.zip virtualenv/bin/activate_this.py virtualenv/lib/python3.6/site-packages/pyjokes __main__.py
updating: virtualenv/bin/activate_this.py (deflated 54%)
updating: virtualenv/lib/python3.6/site-packages/pyjokes/ (stored 0%)
updating: virtualenv/lib/python3.6/site-packages/pyjokes/__init__.py (deflated 20%)
updating: virtualenv/lib/python3.6/site-packages/pyjokes/jokes_de.py (deflated 29%)
updating: virtualenv/lib/python3.6/site-packages/pyjokes/jokes_en.py (deflated 61%)
updating: virtualenv/lib/python3.6/site-packages/pyjokes/jokes_es.py (deflated 40%)
updating: virtualenv/lib/python3.6/site-packages/pyjokes/pyjokes.py (deflated 68%)
updating: __main__.py (deflated 18%)
$ ls -lh
total 40984
-rw-r--r--  1 james  wheel    74B 21 Apr 11:01 __main__.py
-rw-r--r--  1 james  wheel    20M 21 Apr 11:07 jokes.zip
-rw-r--r--  1 james  wheel   9.3K 21 Apr 13:36 jokes_small.zip
drwxr-xr-x  6 james  wheel   204B 21 Apr 11:25 virtualenv

The archive file is now less than ten kilobytes! 🏃

With The Serverless Framework

The Serverless Framework is a popular open-source framework for building serverless applications. This framework handles the configuration, packaging and deployment of your serverless application.

OpenWhisk is supported through a provider plugin. Recent versions of the plugin added support for the Python runtime environment.

Using the application configuration file for the framework, users can add include and exclude parameters to control the contents of the archive file before deployment.

Here’s an example of the configuration needed to only include the necessary files for the application above.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
service: pyjokes

provider:
  name: openwhisk
  runtime: python:3

functions:
  jokes:
    handler: handler.joke

plugins:
  - serverless-openwhisk

package:
  exclude:
    - virtualenv/**
    - '!virtualenv/bin/activate_this.py'
    - '!virtualenv/lib/python3.6/site-packages/pyjokes/**'

conclusion

Python has a huge community of third-party packages for everything from parsing JSON, making HTTP requests and even generating jokes. OpenWhisk already provided a number of the most popular packages within the Python runtime.

Users can install additional packages locally using the pip and virtualenv tools. Bundling those files within the deployment archive means they are extracted into the OpenWhisk Python runtime environment.

Recent changes to the Python runtime allows the platform to automatically add local package folders to the module search path.

This means Python functions running on OpenWhisk can now use any third-party library as if it was installed globally.

Hurrah 👌!

Building an SMS Bot for Slack.

This is smsbot.

It provides an integration with Slack that connects SMS messages into channels. People can text an external number and have their messages posted into the channel. Channel users can respond to the messages and have their response sent back to the sender using SMS.

smsbot was developed in under a few hours and less than one hundred lines of code using a serverless cloud platform.

Want to understand how it works? Let’s find out…

The first challenge was how to programmatically send and receive SMS messages.

Want to deploy smsbot yourself? Follow the instructions at the bottom or check out the Github repository.

Twilio

Twilio provides a platform for building SMS, voice and messaging applications using an API.

Developers can register phone numbers through the service that invoke webhooks for incoming calls and SMS messages. Webhooks are passed message details and return a custom markup language (TwilML) to encode the instructions on how to respond to the request.

The platform also provides a REST API to initiate phone calls and SMS messages.

We now have a way to handle text messages for our bot, how do we integrate a new bot in Slack?

Slack

Slack also provides a webhook-based mechanism to integrate custom bots into the platform. The platform has two different integrations…

Incoming Webhooks.

Provide a way to post messages into Slack from external sources. It provides a custom URL that supports HTTP requests with a JSON payload. These requests are turned into channel messages. The JSON payload is used to control the content and formatting of the message.

https://api.slack.com/incoming-webhooks

Outgoing Webhooks

Allow you to listen for messages in channels without using the full real-time API. Slack sends HTTP requests to registered URLs when specific trigger words appear in channel messages. The HTTP request body contains the message details.

https://api.slack.com/outgoing-webhooks

Now we just need a way to write simple HTTP services to listen for webhook requests…

OpenWhisk

OpenWhisk is an open-source serverless cloud platform. Serverless platforms make it easy to create microservices in the cloud without having to set up or manage any infrastructure.

Developers push their code directly into the platform. The platform will instantiate the runtime and invoke the code on-demand for each request. Serverless functions can be exposed as HTTP endpoints or connected to event sources like message queues or databases.

Serverless platforms make it easy to create simple HTTP services to handle webhook requests.

Web Actions

Web Actions are a new feature in OpenWhisk for exposing serverless functions as simple HTTP endpoints. Functions have access to the full HTTP request and can control the HTTP response returned. This method is suitable for simple public endpoints that do not need more enterprise features supported by the API gateway.

Web Actions are available at the following platform API path.

1
https://{APIHOST}/api/v1/experimental/web/{USER_NAMESPACE}/{PACKAGE}/{ACTION_NAME}.{TYPE}
  • APIHOST - platform endpoint e.g. openwhisk.ng.bluemix.net.
  • USER_NAMESPACE - must be explicit and cannot use the default namespace (_).
  • PACKAGE - action package or default.
  • ACTION_NAME - function identifier.
  • TYPE - .json, .html, .text or .http.

Example

Here’s a simple Web Actions that returns HTML content when invoked through the API.

1
2
3
4
5
6
7
8
function main(args) {
    var msg = "you didn&#39;t tell me who you are."
    if (args.name) {
        msg = `hello ${args.name}!`
    }
    return {body:
       `<html><body><h3><center>${msg}</center></h3></body></html>`}
}

Actions can be turned into web-accessible actions by setting a custom annotation.

1
$ wsk action create greeting source.js --annotation web-export true

The greeting function can then be invoked through a HTTP request to the following endpoint.

https://openwhisk.ng.bluemix.net/api/v1/experimental/web/user@host.com_dev/default/greeting.http?name=James

1
2
3
4
5
$ http post https://openwhisk.ng.bluemix.net/api/v1/experimental/web/user@host.com_dev/default/html_greeting.http?name=James
HTTP/1.1 200 OK
...

<html><body><h3><center>hello James!</center></h3></body></html>

Twilio <=> Slack

OpenWhisk Web Actions are a great solution for creating webhook endpoints. Connecting Twilio to Slack (and vice-versa) can be implemented using two different OpenWhisk Web Actions.

  • Twilio Webhook. Invoked for SMS messages. Uses the Slack Incoming Webhook to create a bot messages from content. ​
  • Slack Outgoing Webhook. Invoked for channel replies. Uses Twilio API to send replies as SMS messages.

Let’s have a look at the Twilio webhook first…

Twilio Webhook

When a new SMS message is received, we want to post this bot message into our Slack channel.

Twilio allows developers to configure webhooks for each registered phone number. The webhook endpoint will be invoked for each SMS message that is received. Twilio can either send a HTTP POST request, with parameters in the body, or a HTTP GET request, with URL query parameters.

OpenWhisk Web Actions support both formats. Request parameters will be available as properties on the function argument.

Here’s a simple Web Action that would log the message sender and content for each SMS received.

1
2
3
function main (params) {
  console.log(`Text message from ${params.From}: ${params.Body}`)
}

Creating Bot Messages From SMS

When an SMS message is received, we need to send a HTTP POST to the Incoming Webhook URL. The JSON body of the HTTP request is used to configure the channel message. Using the username, icon_emoji and and text properties allows us to customise our bot message.

OpenWhisk Actions in Node.js have numerous popular NPM modules pre-installed in the environment. This includes a HTTP client library. This code snippet demonstrates sending the HTTP request to create out bot message. The Slack Webhook URL is bound as a default parameter to the action.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const request = require('request')

const slack_message = text => ({
  username: 'smsbot',
  icon_emoji: ':phone:',
  text
})

function main (params) {
  return new Promise(function (resolve, reject) {
    request.post({
      body: slack_message(`Text message from ${params.From}: ${params.Body}`),
      json: true,
      url: params.slack.webhook
    }, err => {
      if (err) return reject(err);
      resolve();
    })
  })
}

Returning a Promise ensures the request is completed before the function exits.

Sending Acknowledgement Message

Returning TwilML content allows us to control the response from Twilio to the incoming message.

This snippet would send an SMS reply to sender with the content “Hello World!”.

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Message>Hello World!</Message>
</Response>

Twilio’s client library for Node.js can be used to programatically generate TwilML.

1
2
3
const twilio = require('twilio')
const resp = new twilio.TwimlResponse()
const twilml = resp.message('Thanks for letting us know.').toString()

Returning XML content as the HTTP response requires us to set the response headers, body and status code in the Web Action.

1
2
3
4
5
6
7
8
9
10
function main () {
  const xml = '...'
  return {
    headers: {
      'Content-Type': 'text/xml'
    },
    code: 200,
    body: xml
  }
}

Web Action Source

Adding the XML response code into the existing function completes the OpenWhisk Web Action required to handle incoming SMS messages.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const request = require('request')
const twilio = require('twilio')

const resp = new twilio.TwimlResponse()
const twilml = resp.message('Thanks for letting us know.').toString()

const response = {
  headers: {
    'Content-Type': 'text/xml'
  },
  code: 200,
  body: twilml
}

const slack_message = text => ({
  username: 'smsbot',
  icon_emoji: ':phone:',
  text
})

function main (params) {
  return new Promise(function (resolve, reject) {
    request.post({
      body: slack_message(`Text message from ${params.From}: ${params.Body}`),
      json: true,
      url: params.slack.webhook
    }, err => {
      if (err) return reject(err);
      resolve(response);
    })
  })
}

Register Webhook

Once we have deployed the Web Action, we can configure the Twilio SMS webhook endpoint to use following URL.

https://openwhisk.ng.bluemix.net/api/v1/experimental/web/user@email.com_dev/default/smsbot-dev-incoming.http

Slack Outgoing Webhook

When someone sends a channel message to the bot, smsbot should send that content as an SMS message to the last person who sent an SMS to the channel. An Outgoing Webhook will be used to trigger the bot.

Outgoing Webhooks have a configurable trigger word. Channel messages which start with this word are send as HTTP requests to the list of URLs registered for that webhook. We will use smsbot as our trigger word.

Request Parameters

Slack sends the following parameters for each channel message.

1
2
3
4
5
6
7
8
9
10
token=XXXXXXXXXXXXXXXXXX
team_id=T0001
team_domain=example
channel_id=C2147483705
channel_name=test
timestamp=1355517523.000005
user_id=U2147483697
user_name=Steve
text=googlebot: What is the air-speed velocity of an unladen swallow?
trigger_word=googlebot:

In OpenWhisk Web Actions, these parameters will be available on the function argument object.

Here’s a simple Web Action that would parse and log the message contents when the webhook is fired.

1
2
3
4
function main (params) {
  const msg = params.text.slice(params.trigger_word.length + 1)
  console.log('channel message:', msg)
}

When this webhook is fired, we need to add code to send an SMS message with the channel message.

Sending SMS Messages

Twilio’s API allows us to programatically send SMS messages from our registered numbers.

This snippet shows you how to use their Node.js client library to send sample message.

1
2
3
4
5
6
7
8
9
10
const twilio = require('twilio')
const creds = { account: '...', auth: '...' }

const client = twilio(creds.account, creds.auth)
const callback = (err, message) => {
  if (err) return console.log(err)
  console.log('sent sms message.')
}

client.messages.create({ to: '...', from: '...', body: 'hello world' }, callback)

The webhook should use this client library to send a message to the last person who send us an incoming message.

Reply to Message Sender

How can we determine who was the last person who sent a message to our bot? The Web Action processing the incoming messages is a separate service to the Web Action sending SMS messages.

Rather than setting up a database to share application state, the service can use Twilio’s API to retrieve the received message details.

1
2
3
4
5
6
7
8
const twilio = require('twilio')
const creds = { account: '...', auth: '...' }
const client = twilio(creds.account, creds.auth)

client.messages.list({to: '+44....'}, (err, data) => {
  const last = data.messages[0]
  console.log(`last message from: ${last.from}`)
})

SMSBot Channel Response

Outgoing Webhooks which respond with a JSON body will generate a new channel message.

1
2
3
4
5
{
  "username": "smsbot",
  "icon_emoji": ":phone:",
  "text": "sms sent to ..."
}

Web Action Source

Combing the channel message parsing code with the snippets for sending SMS messages and obtaining the last message sender completes the Web Action needed to handle the Outgoing Webhook.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const twilio = require('twilio')

const slack_message = text => ({
  username: 'smsbot',
  icon_emoji: ':phone:',
  text
})

function reply (params) {
  const client = twilio(params.twilio.account, params.twilio.auth)
  return new Promise((resolve, reject) => {
    client.messages.list({to: params.twilio.number}, (err, data) => {
    if (err) return Promise.reject(err)

    const last = data.messages[0]
    const msg = params.text.slice(params.trigger_word.length + 1)
    const options = { to: last.from, from: last.to, body: msg }
    client.messages.create(options, (err, message) => {
        if (err) return Promise.reject(err)
        resolve(slack_message(`sms reply sent to ${last.from}`))
      })
    })
  })
}

Twilio account credentials are bound as default parameters to the Web Action during deployment.

Deployment

smsbot is built using The Serverless Framework.

This framework makes building serverless applications really easy. The tool handles the entire configuration and deployment process for your serverless provider. OpenWhisk recently released integration with the framework through a provider plugin.

Let’s look at how to use the framework to deploy our serverless application…

OpenWhisk

Register for an account with an OpenWhisk provider, e.g. IBM Bluemix.

Set up the wsk CLI and run the command to authenticate against the platform endpoint.

1
wsk property set --apihost openwhisk.ng.bluemix.net --auth SECRET

Serverless Framework

Install the The Serverless Framework and the OpenWhisk provider plugin.

1
npm install --global serverless serverless-openwhisk

Source Code

Download the source code from Github and install the project dependencies.

1
2
3
$ git clone https://github.com/ibmets/smsbot.git
$ cd smsbot
$ npm install

Create a new file called credentials.yml with the following content.

1
2
3
4
5
6
7
twilio:
    account:
    auth:
    number:
numbers:
slack:
    webhook:

Twilio

Register an account with Twilio and provision a new phone number. Make a note of the phone number. Retrieve the account identifier and auth token from the Twilio console.

Fill in the account identifier, auth token and phone number in the credentials.yml file.

1
2
3
4
twilio:
    account: AC_USER_ID
    auth: AUTH_TOKEN
    number: '+441234567890'

Important: the twilio.number property value must be a quoted string.

Phone Numbers

During Twilio’s free trial, you will need manually verify each phone number that you want to send messages to.

Fill in all verified numbers in credentials.yml.

1
2
3
numbers:
    '+441234567890': Joe Smith
    '+441234567891': Jane Smith

Important: the numbers property values must be a quoted strings.

Incoming Webhook

Create a new Incoming Webhook integration for the Slack channel messages should appear in.

Fill in the slack.webhook property in credentials.yml with this url.

1
2
slack:
    webhook: https://hooks.slack.com/services/XXXX/YYYY/ZZZZ

Deploy Application

Use The Serverless Framework to deploy your application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ serverless deploy
Serverless: Packaging service...
Serverless: Compiling Functions...
Serverless: Compiling API Gateway definitions...
Serverless: Compiling Rules...
Serverless: Compiling Triggers & Feeds...
Serverless: Deploying Functions...
Serverless: Deployment successful!

Service Information
platform:   openwhisk.ng.bluemix.net
namespace:  _
service:    smsbot

actions:
smsbot-dev-incoming    smsbot-dev-reply

triggers:
**no triggers deployed**

rules:
**no rules deployed**

endpoints:
**no routes deployed**

Twilio Webhook

On the Phone Numbers page in the Twilio console, configure the ”Messaging” webhook URL.

Use this Web Action URL, replacing user@host.com_dev with your namespace.

https://openwhisk.ng.bluemix.net/api/v1/experimental/web/user@host.com_dev/default/smsbot-dev-incoming.http

Outgoing Webhook

Create a new Outgoing Webhook integration for the Slack channel messages should appear in. Use smsbot as the Trigger Word.

Use this Web Action URL, replacing user@host.com_dev with your namespace.

https://openwhisk.ng.bluemix.net/api/v1/experimental/web/user@host.com_dev/default/smsbot-dev-reply.json

Test it out!

Send a text message to the phone number you registered through Twilio. smsbot should post the contents into Slack and send an SMS response with the message ”Thanks for letting us know!”.

If you send a channel message starting with the trigger word (smsbot), the phone number should receive a new SMS message with the message text.

Awesome-sauce 😎.

Conclusions

OpenWhisk Web Actions provide a convenient way to expose serverless functions as simple HTTP APIs. This feature is ideal for implementing webhook endpoints.

Both Slack and Twilio provide webhook integration for developers to use their platforms. Using OpenWhisk Web Actions, we can write serverless functions that act as a bridge between these services. With less than a hundred lines of code, we’ve created a new slack bot that can connect users to channels using SMS messages.

Pretty cool, huh?! 👏👏👏

Openwhisk and the Serverless Framework

The Serverless Framework is the most popular open-source framework for building serverless applications.

Recent releases included support for using the framework with non-AWS providers. This feature makes it easier for developers to try different serverless platforms and move applications between providers.

Since last summer, I’ve been leading the technical effort to provide an OpenWhisk provider plugin for the framework.

OpenWhisk is the first non-AWS serverless provider to complete integration into the framework.

Getting Started

Documentation for the provider plugin is available on The Serverless Framework’s website.

Example projects using the framework to build applications for OpenWhisk are now available in this repository.

More details on the provider plugin can be found on the project repository.

Found an issue? Feature request? Need help? Please open issues on Github.

Video Demonstration

Serverless Meetup Presentation

Last week, I was invited to speak at the London’s Serverless Meetup about building multi-provider serverless applications using this feature.

Slides from the presentation are here:

There is a (low-fi) video recording from the event here:

Watch live video from serverlessldn on www.twitch.tv

OpenWhisk and Rust

This blog post is one of a series looking at using Docker Actions in OpenWhisk to support extra runtimes.

Let’s look at writing serverless functions for OpenWhisk using Rust.

Rust is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety.

Rust has been growing in popularity since it launched in 2010. Rust is a popular language for writing microservices due to the focus on the attention to safety and strong concurrency support.

None of the major serverless platform natively support Rust at the moment. OpenWhisk does not include this as a default runtime. However, recent updates to OpenWhisk provide a path for writing serverless functions with Rust.

Let’s re-write the example from the previous post in Rust and see how to get it running using this new approach…

Have you seen this post explaining how Docker-based Actions work? This post assumes you have already read that first.

Rust Language Actions

Rust has a build system that supports creating static binaries. These binaries contain the application source code and dependent libraries.

Using the same approach as the Go-based example, bundling this binary into a zip file allows us to overwrite the runtime stub prior to invocation.

Runtime binaries will be executed by the Python-based invoker for each invocation. Request parameters will be passed as a JSON string using the first command-line argument. The invoker expects the Action result to be written to standard output as a JSON string.

Action Source Code

Here’s a simple Rust function that returns a greeting string from an input parameter. It parses the JSON string provided on the command-line to look for a name parameter. If this isn’t present, it defaults to stranger. It returns a JSON object with the greeting string (msg) by writing to the console.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
extern crate rustc_serialize;
use rustc_serialize::json;
use rustc_serialize::json::Json;
use std::env;

#[derive(RustcDecodable, RustcEncodable)]
pub struct Greeting {
    message: String
}

fn main() {
    let mut name = "stranger".to_string();

    // first arg contains JSON parameters
    if let Some(arg1) = env::args().nth(1) {
        // parse JSON and extract 'name' field
        let params = Json::from_str(&arg1).unwrap();
        if let Some(params_obj) = params.as_object() {
            if let Some(params_name) = params_obj.get("name") {
                name = params_name.as_string().unwrap().to_string();
            }
        }
    };

    let greeting = Greeting {
        message: format!("Hello, {}!", name),
    };

    println!("{}", json::encode(&greeting).unwrap());
}

Set Up Project

Using Rust’s package management tool, create a new project for our serverless function.

Add the source code above into the src/main.rs file.

1
2
3
4
5
6
7
8
9
10
11
$ cargo new action; cd action
     Created library `action` project
$ mv src/lib.rs src/main.rs
$ vim src/main.rs
$ tree .
.
├── Cargo.toml
└── src
    └── main.rs

1 directory, 2 files

This function uses the rustc-serialize crate to handle parsing and producing JSON.

Add this identifier to the project’s dependencies listed in Cargo.toml.

1
2
3
4
5
6
7
[package]
name = "action"
version = "0.1.0"
authors = ["Me <me@email.com>"]

[dependencies]
rustc-serialize = "0.3"

Build and run the binary to test it works as expected.

1
2
3
4
5
6
7
8
9
10
11
$ cargo run
    Updating registry `https://github.com/rust-lang/crates.io-index`
   Compiling rustc-serialize v0.3.22
   Compiling action v0.1.0 (file:///private/tmp/test/action)
    Finished debug [unoptimized + debuginfo] target(s) in 7.0 secs
     Running `target/debug/action`
{"message":"Hello, stranger!"}
$ cargo run '{"name": "James"}'
    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/action {\"name\":\ \"James\"}`
{"message":"Hello, James!"}

Before we can deploy this binary to OpenWhisk, it must be compiled for the platform architecture.

Cross-Compiling Locally

Rust’s compiler uses LLVM under the covers, making it possible to generate machine code for different architectures. Cross-compiling for different platforms requires having the correct compiler, linker and libraries for that architecture installed.

Rust recently released a toolchain manager to simplify this process.

Install the Rust toolchain for the x86_64-unknown-linux-musl runtime.

1
2
3
$ rustup target add x86_64-unknown-linux-musl
info: downloading component 'rust-std' for 'x86_64-unknown-linux-musl'
info: installing component 'rust-std' for 'x86_64-unknown-linux-musl'

Add the configuration file to set the correct linker for the runtime.

1
2
3
$ cat .cargo/config
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"

We can now cross-compile the binary for the correct environment.

1
2
3
4
$ cargo build --target=x86_64-unknown-linux-musl --release
   Compiling rustc-serialize v0.3.22
   Compiling action v0.1.0 (file:///Users/james/code/bluemix/openwhisk-languages/rust/action)
    Finished release [optimized] target(s) in 9.30 secs

Checking the file type demonstrates we have built a static binary for the Linux x86_64 platform.

1
2
$ file target/x86_64-unknown-linux-musl/release/action
target/x86_64-unknown-linux-musl/release/action: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, not stripped

Cross-Compiling Using Docker

If you don’t want to install the Rust development toolchain, Docker can be used to start a container with the environment set up.

1
2
3
4
5
6
7
8
$ docker pull ekidd/rust-musl-builder
$ docker run -it -v $(pwd):/home/rust/src ekidd/rust-musl-builder cargo build --release
    Updating registry `https://github.com/rust-lang/crates.io-index`
 Downloading rustc-serialize v0.3.22
   Compiling action v0.1.0 (file:///home/rust/src)
    Finished release [optimized] target(s) in 1.80 secs
$ file target/x86_64-unknown-linux-musl/release/action
target/x86_64-unknown-linux-musl/release/action: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, not stripped

Create & Deploy Archive

Add the binary to a zip file, ensuring the file is named exec in the archive.

Use the wsk command-line to create a new Docker Action using this archive.

1
2
3
4
5
$ cp target/x86_64-unknown-linux-musl/release/action exec
$ zip action.zip exec
  adding: exec (deflated 64%)
$ wsk action create rust_test action.zip --docker
ok: created action rust_test

Invoking Action

Test the action from the command-line to verify it works.

1
2
3
4
5
6
7
8
$ wsk action invoke rust_test --blocking --result
{
    "msg": "Hello, Stranger!"
}
$ wsk action invoke rust_test --blocking --result --param name James
{
    "msg": "Hello, James!"
}

Success 😎.

Openwhisk and Go

In an earlier blog post, I explained how to use Go language binaries on OpenWhisk using Docker-based Actions. It relied on building Docker images for each serverless function and hosting them on Docker Hub.

Recent updates to Docker-based Actions have made this process much simpler. Developers don’t need to build and expose public images anymore.

Let’s re-visit the example from the previous post and see how to get it running using this new approach…

Have you seen this post explaining how Docker-based Actions work? This post assumes you have already read that first.

Go Language Actions

Go’s build system combines application source code and dependencies into a single execution binary. Bundling this binary into a zip file allows us to overwrite the runtime stub prior to invocation.

Runtime binaries will be executed by the Python-based invoker for each invocation. Request parameters will be passed as a JSON string using the first command-line argument. The invoker expects the Action result to be written to standard output as a JSON string.

Action Source Code

Here’s a simple Go function that returns a greeting string from an input parameter. It parses the JSON string provided on the command-line to look for a name parameter. If this isn’t present, it defaults to Stranger. It returns a JSON object with the greeting string (msg) by writing to the console.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "encoding/json"
import "fmt"
import "os"

func main() {
  // native actions receive 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)
  name, ok := obj["name"].(string)
  if !ok {
      name = "Stranger"
  }
  msg := map[string]string{"msg": ("Hello, " + name + "!")}
  res, _ := json.Marshal(msg)
  fmt.Println(string(res))
}

Building this locally allows us to test it works.

1
2
$ go run test.go '{"name": "James"}'
{"msg":"Hello, James!"}

Before we can deploy this binary to OpenWhisk, it must be compiled for the platform architecture.

Cross-Compiling Locally

Go 1.5 introduced much improved support for cross-compilation.

If you have the development environment installed locally, you can compile the binary for another platform by setting environment variables. The full list of supported architectures is available here.

OpenWhisk uses an Alpine Linux-based environment to execute Actions.

1
$ env GOOS=linux GOARCH=amd64 go build exec.go

Checking the file type demonstrates we have built a static binary for the Linux x86_64 platform.

1
2
$ file exec
exec: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

Cross-Compiling Using Docker

If you don’t want to install the Go development toolchain, Docker can be used to start a container with the environment set up.

1
2
3
4
5
6
7
8
$ docker pull golang
$ docker run -it -v $(pwd):/go/src golang
root@0a2f1655eece:/go# cd src/
root@0a2f1655eece:/go/src# go build exec.go
root@0a2f1655eece:/go/src# ls
exec  exec.go
$ file exec
exec: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

Create & Deploy Archive

Add the binary to a zip file, ensuring the file is named exec in the archive.

Use the wsk command-line to create a new Docker Action using this archive.

1
2
3
4
$ zip action.zip exec
  adding: exec (deflated 66%)
$ wsk action create go_test action.zip --docker
ok: created action go_test

Invoking Action

Test the action from the command-line to verify it works.

1
2
3
4
5
6
7
8
$ wsk action invoke go_test --blocking --result
{
    "msg": "Hello, Stranger!"
}
$ wsk action invoke go_test --blocking --result --param name James
{
    "msg": "Hello, James!"
}

Success 😎.

OpenWhisk Docker Actions

OpenWhisk recently announced the following changes to Docker-based Actions.

Developers can now deploy runtime files to the Action environment prior to invocation.

This makes it much easier to support (almost) any programming language in OpenWhisk. Awesome!

Let’s start by explaining how this new feature works…

Docker Actions

Docker Actions in OpenWhisk are built from the following repository using the python:2.7.12-alpine base image. This image is available on Docker Hub as openwhisk/dockerskeletion.

The image includes a Python application which implements the HTTP API used to handle platform requests, e.g. invoke the action with these parameters.

This service executes a file (/action/exec) for each invocation. Replacing this file allows us to control the runtime environment.

Request parameters are passed, using a JSON string, as the first command-line argument. Response values are interpreted as JSON written to stdout.

Developers can now include a zip file when creating Docker-based Actions. This archive will be extracted into the /action directory prior to invocations. If the archive contains a file named exec this will replace the exectuable file called by the invocation handler.

Testing It Out

Using the wsk command-line, developers can create Actions using this Docker image.

If the archive file is missing, the /action/exec path contains the the following stub file.

1
2
3
4
$ wsk action create skeleton --docker openwhisk/dockerskeleton
ok: created action skeleton
$ wsk action invoke skeleton --blocking --result
{ "error": "This is a stub action. Replace it with custom logic." }

Let’s update this stub file to return a custom greeting.

1
2
3
4
5
6
7
8
9
10
11
$ cat exec
#!/bin/bash
echo "{ \"hello\": \"ran without a docker pull!\" }"
$ ./exec
{ "hello": "ran without a docker pull!" }
$ zip exec.zip exec
  adding: exec (stored 0%)
$ wsk action create custom_docker_action exec.zip --docker
ok: created action custom_docker_action
$ wsk action invoke custom_docker_action --blocking --result
{ "hello": "ran without a docker pull!" }

The archive file could include a static binary, or even a complete runtime, to replace the exec stub.

All files in the archive file will be available under the /action directory.

Running Locally

The openwhisk/dockerskeleton image exposes a Python-based HTTP server on port 8080.

Pulling the openwhisk/dockerskeleton image from Docker Hub allows us to run it locally for development.

1
2
$ docker pull openwhisk/dockerskeleton
$ docker run -it -p 8080:8080 openwhisk/dockerskeleton

The platform uses the following HTTP endpoints to initialise and invoke Actions.

  • POST /init -> Set up Action source from JSON payload.
  • POST /run -> Invoke Action

Initialising The Environment

Before invoking Actions using this image, we need to deploy and unpack the archive file into the /action directory.

Reviewing the Python source code, the platform triggers this by sending a HTTP POST with the following JSON to /init endpoint.

1
2
3
4
5
6
{
  "value": {
    "binary": true,
    "code": "..."
  }
}

code contains the archive file as a base64 encoded string.

Let’s try this out using the action archive we created above.

1
2
3
4
5
6
7
8
$ base64 exec.zip  | echo "\"$(cat)\"" | jq '{value: {binary: true, code: .}}' > init.json
$ cat init.json
{
  "value": {
    "binary": true,
    "code": "UEsDBAoAAAAAAOlqMEr1+JNAQQAAAEEAAAAEABwAZXhlY1VUCQADRcl8WFDJfFh1eAsAAQT1AQAABBQAAAAjIS9iaW4vYmFzaAplY2hvICJ7IFwiaGVsbG9cIjogXCJyYW4gd2l0aG91dCBhIGRvY2tlciBwdWxsIVwiIH0iClBLAQIeAwoAAAAAAOlqMEr1+JNAQQAAAEEAAAAEABgAAAAAAAEAAADtgQAAAABleGVjVVQFAANFyXxYdXgLAAEE9QEAAAQUAAAAUEsFBgAAAAABAAEASgAAAH8AAAAAAA=="
  }
}

Now we can issue the HTTP request to push this archive into the container.

1
2
3
4
5
6
7
$ http post localhost:8080/init < init.json
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: text/html; charset=utf-8
Date: Mon, 16 Jan 2017 14:11:04 GMT

OK

Accessing the container filesystem allows us to verify the archive has been extracted correctly.

1
2
3
4
5
6
7
8
9
10
$ docker ps
CONTAINER ID        IMAGE                         COMMAND                  CREATED             STATUS              PORTS                    NAMES
b37a7dc1cab1        openwhisk/dockerskeleton      "/bin/bash -c 'cd ..."   About an hour ago   Up About an hour    0.0.0.0:8080->8080/tcp   relaxed_davinci
$ docker exec -it b37a7dc1cab1 /bin/sh
/ # cd /action
/action # ls
exec
/action # cat exec
#!/bin/bash
echo "{ \"hello\": \"ran without a docker pull!\" }"

Invocation Requests

Action invocations are triggered by sending a HTTP POST to the /run endpoint.

This endpoint expects the following JSON body.

1
2
3
4
5
{
  "value": {
    "foo": "bar"
  }
}

The inner object parameters under the value property are passed, as a JSON string, to the executable as the first command-line argument.

Sending this request to our container will trigger the shell script from our archive and return the JSON response.

1
2
3
4
5
6
7
8
9
$ echo "{}" | jq '{value: .}' | http post localhost:8080/run
HTTP/1.1 200 OK
Content-Length: 44
Content-Type: application/json
Date: Mon, 16 Jan 2017 14:17:15 GMT

{
    "hello": "ran without a docker pull!"
}

Conclusion

Recent updates to Docker-based Actions in OpenWhisk make it much easier to customise the runtime environment.

Being able to deploy arbitrary files into the runtime container, prior to invocation, simplifies the process of supporting new runtimes.

Hopefully this blog post has shown you how to get started with this feature.

Over the next few weeks, we’re going to show you how to use this approach to run lots of new programming languages on the platform. Stay tuned for updates…

NPM Modules in OpenWhisk

OpenWhisk now supports creating Node.js Actions from a zip file. The archive file will be extracted into the runtime environment by the platform. This allows us to split microservice logic across multiple files, use third-party NPM modules or include non-JavaScript assets (configuration files, images, HTML files).

“Hello World” Example

Let’s look at a “Hello World” example of registering a serverless function from a zip file. Our archive will contain two files, the package descriptor and a JavaScript file.

Here is the minimal package.json file required for loading a module from a directory.

package.json
1
2
3
{
  "main": "my_file.js"
}

In my_file.js, a function is returned through the main property on the exports object. This function implements the Action interface.

my_file.js
1
2
3
exports.main = function (params) {
  return {result: "Hello World"};
};

Creating a zip file from the current directory, we can deploy this Action through the command-line utility.

1
2
$ zip -r action.zip *
$ wsk action create hello_world --kind nodejs:default action.zip

When this Action is invoked, the archive will be unzipped into a temporary directory. OpenWhisk loads the directory as a Node.js module and invokes the function property on the module for each invocation.

1
2
3
4
$ wsk action invoke hello_world --result
{
    "result": "Hello world"
}

Using NPM Dependencies

Let’s look a more complicated example which uses an external NPM module in our Action.

index.js
1
2
3
4
5
6
7
8
const leftPad = require("left-pad")

function myAction(args) {
    const lines = args.lines || [];
    return { padded: lines.map(l => leftPad(l, 30, ".")) }
}

exports.main = myAction;

This module uses the extremely popular left-pad module to process an array of strings, passed through a request parameter. The resulting output is returned in the response.

Before using this module, we need to install the dependencies listed in package.json.

1
2
3
4
5
6
7
8
{
  "name": "my-action",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies" : {
    "left-pad" : "1.1.3"
  }
}

OpenWhisk does not automatically install dependencies listed in package.json in the runtime environment.

The developer has to run npm install locally and include the node_modules directory in the zip file.

  • Install NPM dependencies locally.
1
$ npm install
  • Create a .zip archive containing all files.
1
$ zip -r action.zip *
  • Create the action using command-line utility.
1
$ wsk action create packageAction --kind nodejs:default action.zip

Now we can test out our action to check it works….

1
2
3
4
5
6
7
8
$ wsk action invoke --blocking --result packageAction --param lines "[\"and now\", \"for something completely\", \"different\" ]"
{
    "padded": [
        ".......................and now",
        "......for something completely",
        ".....................different"
    ]
}

Native Module Dependencies

Node.js provides a mechanism for JavaScript modules to include native platform code as if they were ordinary modules. This is often used to improve performance by deferring operations to native C/C++ libraries. NPM handles compiling native code during the dependency install process.

Using modules with native dependencies in Actions requires the native code to be compiled for the platform runtime.

Compiling dependencies with Docker

One solution to this problem uses Docker to simulate the same runtime environment.

OpenWhisk uses Docker to manage the runtime environments for Actions. The buildpack-deps:trusty-curl image is used as the base image for all Node.js Actions.

Running a local container from this image will give access to the same runtime environment. Running npm install within this container will produce the node_modules directory with native code compiled for the correct architecture.

Action With Native Modules

Let’s look at an example…

index.js
1
2
3
4
5
6
7
8
9
const SHA3 = require('sha3');

function SHA(args) {
  const d = new SHA3.SHA3Hash();
  d.update(args.payload);
  return { sha: d.digest('hex') };
}

exports.main = SHA;

This module returns a function that calculates a SHA3 cryptographic hash for the invocation payload. The hex string for the hash is returned as the function response.

The NPM module (sha3) used to calculate the digest uses a C++ extension for the hashing algorithm.

package.json
1
2
3
4
5
6
7
8
{
  "name": "hashing-service",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "sha3": "^1.2.0"
  }
}

Action Runtime Environments

OpenWhisk uses a public Docker image as the base image for the Action environments. It then builds a custom image by installing Node.js and NPM for the particular runtime version.

Rather than building this image ourselves, we can use existing images published on Docker Hub.

NodeSource provides public Docker images pre-installed with different Node.js versions. Provided the base image (Ubuntu Trusty) and Node.js version (6.7) matches, the runtime environment will be the same.

Starting a local container from this image, we can use Docker’s host volume support to mount the local directory into the host container.

1
$ docker run -it -v "/action:/usr/src/app" nodesource/trusty:6.7 /bin/sh

Running npm install in the container, the sha3 dependency is compiled and installed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# npm install

> sha3@1.2.0 install /usr/src/app/node_modules/sha3
> node-gyp rebuild

make: Entering directory `/usr/src/app/node_modules/sha3/build'                                                       
make: Warning: File `sha3.target.mk' has modification time 0.19 s in the future
  CXX(target) Release/obj.target/sha3/src/addon.o
  CXX(target) Release/obj.target/sha3/src/displayIntermediateValues.o
  CXX(target) Release/obj.target/sha3/src/KeccakF-1600-reference.o
  CXX(target) Release/obj.target/sha3/src/KeccakNISTInterface.o
  CXX(target) Release/obj.target/sha3/src/KeccakSponge.o
  SOLINK_MODULE(target) Release/obj.target/sha3.node
  COPY Release/sha3.node
make: warning:  Clock skew detected.  Your build may be incomplete.
make: Leaving directory `/usr/src/app/node_modules/sha3/build'
my-action@1.0.0 /usr/src/app
`-- sha3@1.2.0
  `-- nan@2.4.0

The node_modules directory will be available on the host system after exiting the container. Repeat the steps above to archive the source files and deploy our serverless function.

1
2
3
$ zip -r action.zip *
$ wsk action create packageAction --kind nodejs:6 action.zip
ok: created action packageAction

Invoking the Action will now use the native code to produce hash values for the invocation parameters.

1
2
3
4
5
$ wsk action invoke packageAction -b -p payload "Hello" --result
{
    "sha": "c33fede18a1ae53ddb8663710f8054866beb714044fce759790459996196f101d94dfc7bd8268577f7ee3d2f8ff0cef4004a963222
7db84df62d2b40682d69e2"
}

Action Package Details

Upon invocation, OpenWhisk extracts the action’s zip file to a temporary directory in the runtime environment. It then loads the directory as a standard Node.js module, using require.

Node.js expects the directory to have a valid package.json file. The main property is used to define which JavaScript file is evaluated when the module is loaded. This file can assign values to the global exports object. These references are then returned when require is called for this module.

OpenWhisk expects the returned module object to have a property called main which references a function. This function will be executed for each invocation request.

Request parameters are passed as object properties on the first function argument. The function must return an object for the invocation response.

Other files included in the archive will be available in the current working directory. These can also be loaded as modules or read directly from the file-system.

Conclusions

OpenWhisk support for Action packages is a huge step forward for the platform. Node.js has an enormous ecosystem of third-party modules. Developers can now easily use any of these modules within their Actions.

This feature can also be used to include non-JS files within the runtime environment. It would be possible to use configuration files in JSON or static assets like HTML or CSS files.

The team are now working on providing support for other runtimes, watch this space…