Edited at

OpenWhisk シーケンスのソース

More than 1 year has passed since last update.

OpenWhiskは、IBMが提供するサーバレスアーキテクチャーです。

IBM Bluemix、またはOSSとして利用できます。

「トリガー」「アクション」「ルール」「シーケンス」の4つから構成されます。

概要図は以下を参照ください。

https://github.com/openwhisk/openwhisk/blob/master/docs/about.md

↓の自動化でトリガー、

 シーケンスへのリンクでシーケンス、

 をアクションと関連付けできます。

secence action.png


1.アクション例

AWS Lambda , Azure Function , Google Function も近いアーキテクチャーですが、

Docker、Swift対応やWatson連携だけではなく、シーケンス設定の分かりやすさに

とソースが見えることだと思います。

そのソースコードを見てみましょう。


Node.js

function main(params) {

return { "message": "you sent me " + params.message };

}


Python

import sys

def main(dict):

if 'message' in dict:

name = dict['message']

else:

name = 'stranger'

greeting = 'Hello ' + name + '!'

print(greeting)

return {'greeting':greeting}


Swift

func main(args: [String:Any]) -> [String:Any] {

if let message = args["message"] as? String {

return [ "greeting" : "Hello (message)!" ]

} else {

return [ "greeting" : "Hello stranger!" ]

}

}


C inside Docker

Dockerの場合は、Dockerhub内に記載します。

イメージを使用して

が dockerhub

に接続されました: openwhisk/example

*request parameter

{

"arg": "3"

}

*response

{

"args": {

"arg": "3"

},

"msg": "Hello from arbitrary C program!"

}


2.シーケンス例

シーケンスはNode.jsで実装されます。


Websocket

Websocket send sample です。

ws.send(payload ・・・ 部分がメッセージ送信となります。

/**

* Sends a payload message to the designated WebSocket URI

*

* @param uri String representation of the WebSocket uri

* @param payload Message to send to the WebSocket

* @return Standard OpenWhisk success/error response

*/

function main(params) {

if (!params.uri) {

return whisk.error('You must specify a uri parameter.');

}

var uri = params.uri;

console.log("URI param is " + params.uri);

if (!params.payload) {

return whisk.error('You must specify a payload parameter.');
}
var payload = params.payload;

console.log("Payload param is " + params.payload);

var WebSocket = require('ws');

var connectionEstablished = false;
var ws = new WebSocket(uri);

var connectionTimeout = 30 * 1000; // 30 seconds

var promise = new Promise(function(resolve, reject) {
setTimeout(function () {
if (!connectionEstablished) {
reject('Did not establish websocket connection to ' + uri + ' in a timely manner.');
}
}, connectionTimeout);

ws.on('open', function () {
connectionEstablished = true;

console.log("Sending payload: " + payload);
ws.send(payload, function (error) {
if (error) {
console.log("Error received communicating with websocket: " + error);
ws.close();
reject(error);
} else {
console.log("Send was successful.");
ws.close();
resolve({
'payload': payload
});
}
});
});

ws.on('error', function (error) {
console.log("Error communicating with websocket: " + error);
ws.close();
reject(error);
});
});

return promise;

}


Slack

Slackへの連携です。Slackの基本認証とメッセージが必須パラメータです。

var request = require('request');

/**

* Action to post to slack

* @param {string} url - Slack webhook url

* @param {string} channel - Slack channel to post the message to

* @param {string} username - name to post the message as

* @param {string} text - message to post

* @param {string} icon_emoji - (optional) emoji to use as the icon for the message

* @param {boolean} as_user - (optional) when the token belongs to a bot, whether to post as the bot itself

* @param {object} attachments - (optional) message attachments (see Slack documentation for format)

* @return {object} whisk async

*/

function main(params) {

if (checkParams(params)) {

var body = {

channel: params.channel,
username: params.username || 'Simple Message Bot',
text: params.text
};

if (params.icon_emoji) {
// guard against sending icon_emoji: undefined
body.icon_emoji = params.icon_emoji;
}

if (params.token) {
//
// this allows us to support /api/chat.postMessage
// e.g. users can pass params.url = https://slack.com/api/chat.postMessage
// and params.token = <their auth token>
//
body.token = params.token;
} else {
//
// the webhook api expects a nested payload
//
// notice that we need to stringify; this is due to limitations
// of the formData npm: it does not handle nested objects
//
console.log(body);
console.log("to: " + params.url);

body = {
payload: JSON.stringify(body)
};
}

if (params.as_user === true) {
body.as_user = true;
}

if (params.attachments) {
body.attachments = params.attachments;
}

var promise = new Promise(function (resolve, reject) {
request.post({
url: params.url,
formData: body
}, function (err, res, body) {
if (err) {
console.log('error: ', err, body);
reject(err);
} else {
console.log('success: ', params.text, 'successfully sent');
resolve();
}
});
});

return promise;

}

}

/**

Checks if all required params are set

*/

function checkParams(params) {

if (params.text === undefined) {

whisk.error('No text provided');

return false;

}

if (params.url === undefined) {

whisk.error('No Webhook URL provided');

return false;

}

if (params.channel === undefined) {

whisk.error('No channel provided');

return false;

}

return true;

}


Cloudant

ドキュメントNoSQLCloudantが変更された場合です。

/**

* Create and update attachment for document in Cloudant database:

* https://docs.cloudant.com/attachments.html#create-/-update

**/

function main(message) {

var cloudantOrError = getCloudantAccount(message);

if (typeof cloudantOrError !== 'object') {

return whisk.error('getCloudantAccount returned an unexpected object type.');

}

var cloudant = cloudantOrError;

var dbName = message.dbname;

var docId = message.docid;

var attName = message.attachmentname;

var att = message.attachment;

var contentType = message.contenttype;

var params = {};

if(!dbName) {

return whisk.error('dbname is required.');

}

if(!docId) {

return whisk.error('docid is required.');

}

if(!attName) {

return whisk.error('attachmentname is required.');

}

if(!att) {

return whisk.error('attachment is required.');

}

if(!contentType) {

return whisk.error('contenttype is required.');

}

//Add document revision to query if it exists

if(typeof message.docrev !== 'undefined') {

params.rev = message.docrev;

}

var cloudantDb = cloudant.use(dbName);

if (typeof message.params === 'object') {

params = message.params;

} else if (typeof message.params === 'string') {

try {

params = JSON.parse(message.params);

} catch (e) {

return whisk.error('params field cannot be parsed. Ensure it is valid JSON.');

}

}

return insert(cloudantDb, docId, attName, att, contentType, params);

}

/**

* Insert attachment for document in database.

*/

function insert(cloudantDb, docId, attName, att, contentType, params) {

return new Promise(function(resolve, reject) {

cloudantDb.attachment.insert(docId, attName, att, contentType, params, function(error, response) {

if (!error) {

console.log("success", response);

resolve(response);

} else {

console.log("error", error)

reject(error);

}

});

});

}

function getCloudantAccount(message) {

// full cloudant URL - Cloudant NPM package has issues creating valid URLs

// when the username contains dashes (common in Bluemix scenarios)

var cloudantUrl;

if (message.url) {

// use bluemix binding

cloudantUrl = message.url;

} else {

if (!message.host) {

whisk.error('cloudant account host is required.');

return;

}

if (!message.username) {

whisk.error('cloudant account username is required.');

return;

}

if (!message.password) {

whisk.error('cloudant account password is required.');

return;

}

cloudantUrl = "https://" + message.username + ":" + message.password + "@" + message.host;

}

return require('cloudant')({

url: cloudantUrl

});

}


Watson Text to speech

watson-developer-cloudを呼び出すため、認証情報、文字情報等を送ります。

var watson = require('watson-developer-cloud');

function isValidEncoding(encoding) {

return encoding === 'ascii' ||

encoding === 'utf8' ||

encoding === 'utf16le' ||

encoding === 'ucs2' ||

encoding === 'base64' ||

encoding === 'binary' ||

encoding === 'hex';

}

/**

* Synthesizes text to spoken audio.

* See https://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/text-to-speech/api/v1/

*

* @param voice The voice to be used for synthesis. Example: en-US_MichaelVoice

* @param accept The requested MIME type of the audio. Example: audio/wav

* @param payload The text to synthesized. Required.

* @param encoding The encoding of the speech binary data. Defaults to base64.

* @param username The Watson service username.

* @param password The Watson service password.

*

* @return {

* "payload": "",

* "encoding": "",

* "content_type": ""

* }

*/

function main(params) {

var voice = params.voice;

var accept = params.accept;

var payload = params.payload;

var encoding = isValidEncoding(params.encoding) ? params.encoding : 'base64';

var username = params.username;

var password = params.password;

console.log('params:', params);

var textToSpeech = watson.text_to_speech({

username: username,

password: password,

version: 'v1'

});

var promise = new Promise(function(resolve, reject) {

textToSpeech.synthesize({

voice: voice,

accept: accept,

text: payload,

}, function (err, res) {

if (err) {

reject(err);

} else {

resolve({

payload: res.toString(encoding),

encoding: encoding,

mimetype: accept

});

}

}, function (err) {

reject(err);

});

});

return promise;

}


Weather

Bluemix上の

Forcast 情報との連動です。

var request = require('request');

/**

* Get hourly weather forecast for a lat/long from the Weather API service.

*

* Must specify one of zipCode or latitude/longitude.

*

* @param username The Weather service API account username.

* @param username The Weather service API account password.

* @param latitude Latitude of coordinate to get forecast.

* @param longitude Longitude of coordinate to get forecast.

* @param zipCode ZIP code of desired forecast.

* @return The hourly forecast for the lat/long.

*/

function main(params) {

console.log('input params:', params);

var username = params.username;

var password = params.password;

var lat = params.latitude || '0';

var lon = params.longitude || '0';

var language = params.language || 'en-US';

var units = params.units || 'm';

var timePeriod = params.timePeriod || '10day';

var url = 'https://twcservice.mybluemix.net/api/weather/v1/geocode/' + lat + '/' + lon;

var qs = {language: language, units: units};

switch(timePeriod) {

case '48hour':
url += '/forecast/hourly/48hour.json';
break;
case 'current':
url += '/observations.json';
break;
case 'timeseries':
url += '/observations/timeseries.json';
qs.hours = '23';
break;
case '10day':
default:
url += '/forecast/daily/10day.json';
break;
}

console.log('url:', url);

var promise = new Promise(function(resolve, reject) {
request({
url: url,
qs: qs,
auth: {username: username, password: password},
timeout: 30000
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
var j = JSON.parse(body);
resolve(j);
} else {
console.log('error getting forecast');
console.log('http status code:', (response || {}).statusCode);
console.log('error:', error);
console.log('body:', body);
reject({
error: error,
response: response,
body: body
});
}
});
});

return promise;

}


シーケンス関連付

以下は、CloudandのDocumentDBに変更があった場合にアクションが起動する例です。

relation.png