apache / openwhisk-client-js

JavaScript client library for the Apache OpenWhisk platform
https://openwhisk.apache.org/
Apache License 2.0
83 stars 53 forks source link
apache cloud faas functions-as-a-service openwhisk serverless serverless-architectures serverless-functions

Apache OpenWhisk Client for JavaScript

License Continuous Integration codecov JavaScript Style Guide

JavaScript client library for the Apache OpenWhisk platform. Provides a wrapper around the OpenWhisk APIs (Swagger JSON).

installation

$ npm install openwhisk

usage

within openwhisk platform

This client library can use environment parameters to automatically configure the authentication credentials, platform endpoint and namespace. These parameters are defined within the Node.js runtime environment in OpenWhisk. Unless you want to override these values, you can construct the client instance without further configuration.

var openwhisk = require('openwhisk');

function action() {
  var ow = openwhisk();
  return ow.actions.invoke('sample')
}

exports.main = action

All methods return a Promise resolved asynchronously with the results. Failures are available through the catch method.

ow.resource.operation()
  .then(function () { /* success! */ })
  .catch(function (err) { /* failed! */ })

Users can override default constructor parameters by passing in explicit options as shown in the example below.

outside openwhisk platform

var openwhisk = require('openwhisk');
var options = {apihost: 'openwhisk.ng.bluemix.net', api_key: '...'};
var ow = openwhisk(options);
ow.actions.invoke('sample').then(result => console.log(result))

using 3rd party authentication handler

You can specify an authentication handler in options.auth_handler this is an object that provides a function getAuthHeader that returns a Promise resolving to a string to be used in the Authorization http header for every http request.

const authHandler = {
  getAuthHeader: ()=>{
    return Promise.resolve('Basic user:password')
  }
}
var openwhisk = require('openwhisk');
var options = {
  apihost: 'openwhisk.ng.bluemix.net',
  auth_handler: authHandler
}
var ow = openwhisk(options)
ow.actions.invoke('sample').then(result => console.log(result))

constructor options

Client constructor supports the following mandatory parameters:

Client constructor supports the following optional parameters:

environment variables

Client constructor will read values for the apihost, namespace, api_key, ignore_certs, apigw_token and apigw_space_guid options from the environment if the following parameters are set. Explicit options have precedence over environment values.

User-Agent

A User-Agent header may be specified to be passed along with all calls to OpenWhisk. This can be helpful, if you wish to discriminate client traffic to your OpenWhisk backend. By default, the header will have the value openwhisk-client-js. You may override this by passing along a 'User-Agent' field in the options structure of any API calls; note that this is not a constructor argument, but rather an option to the API calls themselves. For example, one might specify a myClient user agent to an action invocation as follows:

ow.actions.invoke({ 'User-Agent': 'myClient', name, params })

In some cases, you may need to have no User-Agent header. To override the default header behavior, you may pass noUserAgent: true in your options structure, e.g.

ow.actions.invoke({ noUserAgent: true, name, params })

Working with a Proxy

If you are working behind a firewall, HTTP(s) proxy details can be set by using the proxy option in the constructor parameters with the proxy service URI, e.g. http://proxy-server.com:3128. The proxy URI can also be set using the following environment parameters (to set a proxy without changing application code):

If you need more advanced proxy behaviour, rather than using Needle's default built-in HTTP agent, the agent constructor parameter can be used to set a custom http.Agent implementation, e.g proxy-agent

Examples

invoke action, blocking for result

const name = 'reverseWords'
const blocking = true, result = true
const params = {msg: 'these are some words to reverse'}

ow.actions.invoke({name, blocking, result, params}).then(result => {
  console.log('here is the reversed string', result.reversed)
}).catch(err => {
  console.error('failed to invoke actions', err)
})

fire trigger

const name = 'eventTrigger'
const params = {msg: 'event trigger message string'}
ow.triggers.invoke({name, params}).then(result => {
  console.log('trigger fired!')
}).catch(err => {
  console.error('failed to fire trigger', err)
})

create action from source file

const name = 'reverseWords'
const action = fs.readFileSync('source.js', {encoding: 'utf8'})

ow.actions.create({name, action}).then(result => {
  console.log('action created!')
}).catch(err => {
  console.error('failed to create action', err)
})

create action from zip package

const name = 'reverseWords'
const action = fs.readFileSync('package.zip')

ow.actions.create({name, action}).then(result => {
  console.log('action created!')
}).catch(err => {
  console.error('failed to create action', err)
})

create sequence from another action

const actionName = '/mynamespace/reverseWords'
const name = 'reverse'

ow.actions.create({ name, sequence: [ actionName ] })

retrieve action resource

const name = 'reverseWords'
ow.actions.get(name).then(action => {
  console.log('action resource', action)
}).catch(err => {
  console.error('failed to retrieve action', err)
})

chaining calls

ow.actions.list()
  .then(actions => ow.actions.invoke(actions))
  .then(result => { /* ... */ })

list packages

ow.packages.list().then(packages => {
  packages.forEach(package => console.log(package.name))
}).catch(err => {
  console.error('failed to list packages', err)
})

update package parameters

const name = 'myPackage'
const package = {
  parameters: [
    {key: "colour", value: "green"},
    {key: "name", value: "Freya"}
  ]
}

ow.packages.update({name, package}).then(package => {
  console.log('updated package:', package.name)
}).catch(err => {
  console.error('failed to update package', err)
})

bind a package from another namespace

const name = 'myBoundPackage'
const package = {
  binding: {
    namespace: "othernamespace", // namespace to bind from
    name: "otherpackage" // package to bind from
  }
}

ow.packages.update({name, package}).then(package => {
  console.log('bound package:', package.name)
}).catch(err => {
  console.error('failed to bind package', err)
})

create trigger feed from alarm package

// alarmTrigger MUST already exist in default namespace
const params = {cron: '*/8 * * * * *', trigger_payload: {name: 'James'}}
const name = '/whisk.system/alarms/alarm'
const trigger = 'alarmTrigger'
ow.feeds.create({name, trigger, params}).then(package => {
  console.log('alarm trigger feed created')
}).catch(err => {
  console.error('failed to create alarm trigger', err)
})

API Details

resource identifiers + namespaces

When passing resource identifiers as parameters you can either use a short name, without an explicit namespace, or a fully-qualified identifier, including namespace and package details.

If the namespace is missing from the resource identifier, the client will use the namespace from configuration options following this ordering.

list resources

ow.actions.list()
ow.activations.list()
ow.triggers.list()
ow.rules.list()
ow.namespaces.list()
ow.packages.list()

Query parameters for the API calls are supported (e.g. limit, skip, count etc.) by passing an object with the named parameters as the first argument.

ow.actions.list({skip: 100, limit: 50})

To count the number of resources without retrieving the resources you can use count query parameter.

ow.actions.list({count:true})

The following optional parameters are supported:

retrieve resource

ow.actions.get({name: '...'})
ow.activations.get({name: '...'})
ow.triggers.get({name: '...'})
ow.rules.get({name: '...'})
ow.packages.get({name: '...'})
ow.feeds.get({name: '...', trigger: '...'})

The following optional parameters are supported for all resource retrievals:

Optional parameters for action resource retrievals are shown below:

This method also supports passing the name property directly without wrapping within an object.

const name = "actionName"
ow.actions.get(name)

If you pass in an array for the first parameter, the get call will be executed for each array item. The function returns a Promise which resolves with the results when all operations have finished.

ow.actions.get(["a", {name: "b"}])

delete resource

ow.actions.delete({name: '...'})
ow.triggers.delete({name: '...'})
ow.rules.delete({name: '...'})
ow.packages.delete({name: '...'})
ow.feeds.delete({name: '...', trigger: '...'})

The following optional parameters are supported:

This method also supports passing the name property directly without wrapping within an object.

const name = "actionName"
ow.actions.delete(name)

If you pass in an array for the first parameter, the delete call will be executed for each array item. The function returns a Promise which resolves with the results when all operations have finished.

ow.actions.delete(["a", {name: "b"}])

invoke action

ow.actions.invoke({name: '...'})

The actionName parameter supports the following formats: actionName, package/actionName, /namespace/actionName, /namespace/package/actionName.

If actionName includes a namespace, this overrides any other namespace properties.

The following optional parameters are supported:

This method also supports passing the name property directly without wrapping within an object.

const name = "actionName"
ow.actions.invoke(name)

If you pass in an array for the first parameter, the invoke call will be executed for each array item. The function returns a Promise which resolves with the results when all operations have finished.

ow.actions.invoke(["a", {name: "b", blocking: true}])

create & update action

ow.actions.create({name: '...', action: 'function main() {};'})
ow.actions.update({name: '...', action: 'function main() {};'})

The following mandatory parameters are supported:

The following optional parameters are supported:

If you pass in an array for the first parameter, the create call will be executed for each array item. The function returns a Promise which resolves with the results when all operations have finished.

ow.actions.create([{...}, {...}])

create & update action sequence

ow.actions.create({name: '...', sequence: ["action_name", "next_action", ...]})
ow.actions.update({name: '...', sequence: ["action_name", "next_action", ...]})

The following mandatory parameters are supported:

The following optional parameters are supported:

If you pass in an array for the first parameter, the create call will be executed for each array item. The function returns a Promise which resolves with the results when all operations have finished.

ow.actions.create([{...}, {...}])

fire trigger

ow.triggers.invoke({name: '...'})

The following optional parameters are supported:

This method also supports passing the name property directly without wrapping within an object.

const name = "actionName"
ow.triggers.invoke(name)

If you pass in an array for the first parameter, the invoke call will be executed for each array item. The function returns a Promise which resolves with the results when all operations have finished.

ow.triggers.invoke(["a", {name: "b", blocking: true}])

create & update trigger

ow.triggers.create({name: '...'})
ow.triggers.update({name: '...'})

The following optional parameters are supported:

create & update packages

ow.packages.create({name: '...'})
ow.packages.update({name: '...'})

The following optional parameters are supported:

create & update rule

ow.rules.create({name: '...', action: '...', trigger: '...'})
ow.rules.update({name: '...', action: '...', trigger: '...'})

trigger and action identifiers will have the default namespace (/_/) appended in the request, unless a fully qualified name is passed in (/custom_ns/action_or_trigger_name).

The following optional parameters are supported:

enable & disable rule

ow.rules.enable({name: '...'})
ow.rules.disable({name: '...'})

The following optional parameters are supported:

create & update feeds

ow.feeds.create({feedName: '...', trigger: '...'})
ow.feeds.update({feedName: '...', trigger: '...'})

The following optional parameters are supported:

api gateway

OpenWhisk supports a built-in API gateway service and external third-party providers.

This client library defaults to using the platform service. If the apigw_token parameter is passed into the client constructor, the implementation will switch to the IBM Bluemix API Gateway.

The interface for managing routes through the library does not change between providers.

retrieve route

ow.routes.get({basepath: '...'})
ow.routes.get({name: '...'})

This method is a wrapper for the list method. It throws an error if the base path or name parameter is missing.

list routes

ow.routes.list()

The following optional parameters are supported to filter the result set:

relpath is only valid when basepath is also specified. name and basepath cannot be used together.

delete routes

ow.routes.delete({basepath: '...'})
ow.routes.delete({name: '...'})

The following optional parameters are supported to filter the result set:

add route

ow.routes.create({relpath: '...', operation: '...', action: '...'})

action supports normal (actionName) and fully-qualified (/namespace/actionName) formats.

The following optional parameters are supported:

add route (swagger)

ow.routes.create({swagger: '{...}'})

Swagger parameter must be a well-formed JSON string, containing a valid Swagger API definition, which follows the OpenWhisk API Gateway route schema.

No other parameters are supported when creating the route from a JSON Swagger document.

Debugging

Setting an environment parameter (DEBUG=needle) will dump the HTTP requests from the client library and responses received to stderr.

DEBUG=needle node script.js

This parameter can also be set dynamically at runtime, provided this happens before the openwhisk module is required.

process.env.DEBUG='needle';
var openwhisk = require('openwhisk');

Development

unit tests

$ npm run test:unit

integration tests

Please see the instructions for setting up the integration test environment prior to running these tests.

$ npm run test:integration

Note: The test integration runs in secure mode by default, which means that all trusted signers must be present and available to the client process. If your local environment is using self-signed certificates, you can use the following command to start the script in insecure mode:

__OW_INSECURE=true npm run test-integration

This will disable SSL/TLS verification for all SSL communication.

code coverage

Code coverage data for the unit and integration tests can be created using the following commands:

Generated data in stored in the .nyc_output directory.

Running the npm run coverage:report command will generate the output reports.