Compute Services

Using Node.js to code OpenWhisk actions

Share this post:

node.js on openwhisk hero mediumIn the months preceding the general availability of OpenWhisk on Bluemix, the development team made many improvements to the user experience of programming Node.js actions. This post covers the most important ones.

Pick your runtime

Through the new notion of kind, you can decide with which version of Node.js to run your actions.

When using the command line tool (wsk) to create or update an action, you specify kind using the --kind flag.

Two versions of Node.js are currently supported:

  • Node.js 0.12 (--kind nodejs)
  • Node.js 6 (--kind nodejs:6)

Let’s assume a file (version.js) with the following content:

function main() {
    return { 'version': process.version };
}

Now observe the effect the --kind option has when used to create/update an action:

$ wsk action create node_version version.js --kind nodejs
ok: created action node_version
$ wsk action invoke -br node_version
{
    "version": "v0.12.17"
}

And similarly, to use Node 6, we enter:

$ wsk action update node_version version.js --kind nodejs:6
ok: updated action node_version
$ wsk action invoke -br node_version
{
    "version": "v6.9.1"
}

While the exact versions may change over time and without notice, the argument --kind nodejs will always correspond to a version in the 0.12.x series; and --kind nodejs:6 will always correspond to a version in the 6.x.x series.

In addition to the two kinds described above, there is a special kind, nodejs:default, which currently is equivalent to nodejs:6. Over time, this kind will reflect advances in the ecosystem and resolve to the most recent stable version supported on OpenWhisk. Creating a Node.js action without specifying a kind is akin to using nodejs:default; so for now all OpenWhisk actions coded in Node.js today run on Node 6 by default.

We encourage users to update actions created with the Node.js 0.12.x runtime to use the most recent version.

A taste of ES6

One major advantage of Node 6 is its support for a large set of improvements to JavaScript introduced in the ECMAScript June 2015 specification (ES6). Node.js 6 reportedly covers 99% of the standard.

To mention some of the most visible features:

  • const and let statements allow for greater control over variable scopes.
  • destructuring assignments provide an expressive syntax for extracting values from objects and–since actions receive all their input arguments as a single dictionary–are particularly useful in OpenWhisk.
  • template literals allow for string interpolation.
  • arrow functions offer a compact form for defining anonymous functions.

As a concrete example, the following is a valid JavaScript action of the nodejs:6 kind:1

var main = args => {
    let { name = 'stranger' } = args
    return { greetings: `Hello ${name}!` }
}

The promised land

Since JavaScript Promises offer an encapsulated and composable way of representing asynchronous computations that may succeed or fail, they are great fit for use in OpenWhisk actions. Accordingly, OpenWhisk actions can now use Promises to compute and return results asynchronously.

In contrast to callback-passing versions, functions manipulating Promises are often easier to reason about and to compose.

With the addition of Promises, the signature for OpenWhisk JavaScript actions is now:

  • Accept a single dictionary as argument
  • Return either a dictionary or a Promise
    • a Promise resolved with a dictionary is a successful activation
    • a rejected Promise results in failed activation

For example, this action consumes a JSON API, reformats its response, and handles errors:

function main(args) {
  const request = require('request-promise')

  const reqPromise = request({
    uri: 'https://api.github.com/users/openwhisk/repos',
    json: true,
    headers: { 'User-Agent': 'OpenWhisk' }
  })

  return reqPromise
    .then(res => ({ repos: res.map(obj => obj.name) }))
    .catch(err => ({ error: `There was an error fetching repos: ${err}.`}))
}

Returning asynchronous results using Promises supercedes the previous mechanism of using a combination of whisk.async(), whisk.done() and whisk.error()--functions that are now marked as deprecated.

Packed and ready to go

Creating actions from an archived NPM module, and using them as packaged Javascript actions, may be the most important change for Node.js on OpenWhisk in terms of unlocking developer productivity.

JavaScript developers know the power of the NPM ecosystem and the magic of npm install. While OpenWhisk Node.js runtimes conveniently include some common libraries by default, specific users will often have unique requirements.

Here is the process for creatin an OpenWhisk packaged action:

  1. Develop the action as an NPM module, declaring meta-data and dependencies in package.json as usual
  2. Expose the action entry point as an export (for example, module.exports.main = ...)
  3. Run npm install locally
  4. Archive the module directory in a zip file
  5. Upload the zip file as a Node.js action

Running npm install locally ensures that users know exactly which versions of dependencies were in use when the action was created.

Packaged actions by example

Consider a directory with two files, package.json and index.js:

package.json:
{
  "name": "test-action",
  "version": "1.0.0",
  "description": "An action written as an npm package.",
  "main": "index.js",
  "author": "OpenWhisk team",
  "license": "Apache-2.0",
  "dependencies": {
    "prog-quote": "2.0.0"
  }
}

index.js:

function entryPoint(args) {
    const pq = require('prog-quote')();
    return pq.next().value;
}

module.exports.main = entryPoint;

Running npm install will fetch all dependencies into node_modules:

$ npm install
...
$ find node_modules/ | wc -l
      44

With all dependencies available locally, we package the action in a .zip file (other archive formats are not currently supported):

$ zip -r action.zip 

We can now create the OpenWhisk action, specifying the kind, as wsk cannot infer it from the file extension:

$ wsk action create quotes action.zip --kind nodejs:default

We confirm that the action works as expected:

$ wsk action invoke -br quotes
{
  "author": "Ken Thompson",
  "quote": "When in doubt, use brute force."
}

Tips:

  • Take care with what goes in the archive. For example, avoid  devDependencies, which can include large number of files, potentially slowing down both creating and invoking actions.
  • For local debugging, unzip the action archive in a clean directory, and from node run require('.').main({ ... }). This is essentially what the OpenWhisk runtime does.
  • If you depend on binary (native) libraries, either directly or transitively, your actions may not be able to run in the OpenWhisk environment. This blog post explains how you can work around binary incompatibilities.

Reflexive invocations

Actions have been able to programmatically invoke other actions or to fire triggers. These mechanisms are useful when writing custom advanced pipelines, for example. The whisk object was magical, however, in that the facility exposed through whisk.invoke() and whisk.fire() was only available when the action code executed in the OpenWhisk runtime. This made local testing difficult.

Those whisk functions are now deprecated in favor of using the NPM openwhisk package, which is available by default in all Node.js runtimes. When running on OpenWhisk, the package is automatically configured to use the same credentials that invoke the action. When running in other environments, you can configure the package through environment variables.

See the openwhisk package documentation for details.

In combination of the deprecation of whisk.async() in favor of Promises, the NPM openwhisk package mean that the whisk object is no longer required, which simplifies testing.

Enter here

Finally, it is now possible to choose alternative action entry points. By default, the runtime looks for a function called main (or an an export called main for packaged actions). But you can override this behavior in creating an action.

As an example, we have a file hello.js with these contents:

function greet(args) {
  return { msg: `Hello ${args.name}!`}
}

We can use the --main flag as follows:

$ wsk action create hello hello.js --main greet
ok: created action hello
$ wsk action invoke -br hello -p name reader
{
    "msg": "Hello reader!"
}

Similarly, for packaged actions, the --main flag defines the name of the exported function to use as an entry point.

Summary

JavaScript support in OpenWhisk has improved considerably, allowing you:

  • to pick which version of the Node.js runtime to run your actions with
  • to use Promises to compute and return asynchronous results
  • to write actions as packages with NPM Dependencies
  • to locally test actions since no special whisk object is needed
  • to define alternative action entry points

OpenWhisk continues to evolve with JavaScript practices. As a direct consequence, some features being replaced with modern alternatives are or will soon be deprecated.

We recommend this blog post on migration strategies for OpenWhisk Node.js actions.


 

  1. Note that main must be declared either as a function or as a var, due to the scoping rules for const and let which make such declarations invisible to the action runtime.
More stories

Securing your Python app with OpenID Connect (OIDC)

Some weeks back I introduced to a tutorial on how to analyse GitHub traffic. The tutorial combines serverless technology and Cloud Foundry to automatically retrieve statistics and store them in Db2. The data can then be accessed and analyzed using a Python Flask app. Today, I am going to show you how the web site is protected using OpenID Connect and IBM Cloud App ID.

Continue reading

Pod security policies in IBM Cloud Kubernetes Service

You can now use Kubernetes pod security policies in your IBM Cloud Kubernetes Service clusters. These policies enable the cluster administrator to configure who is authorized to create and update pods. For many cluster administrators, this is an important security feature to leverage.

Continue reading

Custom login page for App ID integration

When developing an application that integrates with App ID, the standard hosted login page has a few options to change the colours or logo. In some cases, this isn't enough and direct customisation is necessary. There exists a handy guide for a custom App ID login screen in mobile applications, however for web applications a little more effort is required.

Continue reading