OpenWhiskは、IBMが提供するサーバレスアーキテクチャーです。
IBM Bluemix、またはOSSとして利用できます。
「トリガー」「アクション」「ルール」「シーケンス」の4つから構成されます。
概要図は以下を参照ください。
https://github.com/openwhisk/openwhisk/blob/master/docs/about.md
↓の自動化でトリガー、
シーケンスへのリンクでシーケンス、
をアクションと関連付けできます。
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に変更があった場合にアクションが起動する例です。