make action's node.js runtime version configurable (#178)
diff --git a/.gitignore b/.gitignore
index 3af456f..4891796 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,8 @@
.gradle
build/
node_modules/
-action/*.zip
+actions/event-actions/*.zip
+actions/event-actions/package.json
.idea
*.iml
diff --git a/Dockerfile b/Dockerfile
index aa9ae23..2df3aac 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -14,8 +14,8 @@
apt-get install -y nodejs
# only package.json
-ADD package.json /cloudantTrigger/
-RUN cd /cloudantTrigger; npm install
+ADD package.json /
+RUN cd / && npm install --production
# App
ADD provider/. /cloudantTrigger/
diff --git a/actions/changes.js b/actions/changes.js
deleted file mode 100644
index 012bf90..0000000
--- a/actions/changes.js
+++ /dev/null
@@ -1,97 +0,0 @@
-var request = require('request');
-
-function main(msg) {
-
- let eventMap = {
- CREATE: 'post',
- READ: 'get',
- UPDATE: 'put',
- DELETE: 'delete'
- };
- // for creation -> CREATE
- // for reading -> READ
- // for updating -> UPDATE
- // for deletion -> DELETE
- var lifecycleEvent = msg.lifecycleEvent;
-
- var endpoint = msg.apihost;
- var webparams = createWebParams(msg);
-
- var url = `https://${endpoint}/api/v1/web/whisk.system/cloudantWeb/changesWebAction.http`;
-
- if (lifecycleEvent in eventMap) {
- var method = eventMap[lifecycleEvent];
- return requestHelper(url, webparams, method);
- } else {
- return Promise.reject('unsupported lifecycleEvent');
- }
-}
-
-function requestHelper(url, input, method) {
-
- return new Promise(function(resolve, reject) {
-
- var options = {
- method : method,
- url : url,
- json: true,
- rejectUnauthorized: false
- };
-
- if (method === 'get') {
- options.qs = input;
- } else {
- options.body = input;
- }
-
- request(options, function(error, response, body) {
-
- if (!error && response.statusCode === 200) {
- resolve(body);
- }
- else {
- if (response) {
- console.log('cloudant: Error invoking whisk action:', response.statusCode, body);
- reject(body);
- }
- else {
- console.log('cloudant: Error invoking whisk action:', error);
- reject(error);
- }
- }
- });
- });
-}
-
-function createWebParams(rawParams) {
- var namespace = process.env.__OW_NAMESPACE;
- var triggerName = ':' + namespace + ':' + parseQName(rawParams.triggerName, '/').name;
-
- var webparams = Object.assign({}, rawParams);
- delete webparams.lifecycleEvent;
- delete webparams.bluemixServiceName;
- delete webparams.apihost;
-
- webparams.triggerName = triggerName;
-
- return webparams;
-}
-
-function parseQName(qname, separator) {
- var parsed = {};
- var delimiter = separator || ':';
- var defaultNamespace = '_';
- if (qname && qname.charAt(0) === delimiter) {
- var parts = qname.split(delimiter);
- parsed.namespace = parts[1];
- parsed.name = parts.length > 2 ? parts.slice(2).join(delimiter) : '';
- } else {
- parsed.namespace = defaultNamespace;
- parsed.name = qname;
- }
- return parsed;
-}
-
-exports.main = main;
-
-
diff --git a/actions/changesWebAction.js b/actions/changesWebAction.js
deleted file mode 100644
index 1bade20..0000000
--- a/actions/changesWebAction.js
+++ /dev/null
@@ -1,494 +0,0 @@
-var request = require('request');
-var moment = require('moment');
-
-function main(params) {
-
- if (!params.authKey) {
- return sendError(400, 'no authKey parameter was provided');
- }
- if (!params.triggerName) {
- return sendError(400, 'no trigger name parameter was provided');
- }
-
- var triggerParts = parseQName(params.triggerName);
- var triggerID = `:${triggerParts.namespace}:${triggerParts.name}`;
-
- var triggerURL = `https://${params.apihost}/api/v1/namespaces/${triggerParts.namespace}/triggers/${triggerParts.name}`;
-
- var nano = require('nano')(params.DB_URL);
- var db = nano.db.use(params.DB_NAME);
- var workers = params.workers instanceof Array ? params.workers : [];
-
- if (params.__ow_method === "post") {
-
- // check for parameter errors
- if (!params.dbname) {
- return sendError(400, 'cloudant trigger feed: missing dbname parameter');
- }
- if (!params.host) {
- return sendError(400, 'cloudant trigger feed: missing host parameter');
- }
- if (!params.username) {
- return sendError(400, 'cloudant trigger feed: missing username parameter');
- }
- if (!params.password) {
- return sendError(400, 'cloudant trigger feed: missing password parameter');
- }
-
- var query_params;
-
- if (params.filter) {
- query_params = params.query_params;
- if (typeof queryParams === 'string') {
- try {
- query_params = JSON.parse(params.query_params);
- }
- catch (e) {
- return sendError(400, 'The query_params parameter cannot be parsed. Ensure it is valid JSON.');
- }
- }
- if (query_params && typeof query_params !== 'object') {
- return sendError(400, 'The query_params parameter is not a valid JSON Object');
- }
- }
- else if (params.query_params) {
- return sendError(400, 'The query_params parameter is only allowed if the filter parameter is defined');
- }
-
- var newTrigger = {
- id: triggerID,
- host: params.host,
- port: params.port,
- protocol: params.protocol || 'https',
- dbname: params.dbname,
- user: params.username,
- pass: params.password,
- apikey: params.authKey,
- since: params.since,
- maxTriggers: params.maxTriggers || -1,
- filter: params.filter,
- query_params: query_params,
- status: {
- 'active': true,
- 'dateChanged': Date.now()
- }
- };
-
- return new Promise(function (resolve, reject) {
- verifyTriggerAuth(triggerURL, params.authKey, false)
- .then(() => {
- return verifyUserDB(newTrigger);
- })
- .then(() => {
- return getWorkerID(db, workers);
- })
- .then((worker) => {
- console.log('trigger will be assigned to worker ' + worker);
- newTrigger.worker = worker;
- return createTrigger(db, triggerID, newTrigger);
- })
- .then(() => {
- resolve({
- statusCode: 200,
- headers: {'Content-Type': 'application/json'},
- body: {'status': 'success'}
- });
- })
- .catch(err => {
- reject(err);
- });
- });
-
- }
- else if (params.__ow_method === "get") {
- return new Promise(function (resolve, reject) {
- verifyTriggerAuth(triggerURL, params.authKey, false)
- .then(() => {
- return getTrigger(db, triggerID);
- })
- .then(doc => {
- var body = {
- config: {
- name: doc.id.split(':')[2],
- namespace: doc.id.split(':')[1],
- host: doc.host,
- port: doc.port,
- protocol: doc.protocol,
- dbname: doc.dbname,
- username: doc.user,
- password: doc.pass,
- since: doc.since,
- filter: doc.filter,
- query_params: doc.query_params,
- },
- status: {
- active: doc.status.active,
- dateChanged: moment(doc.status.dateChanged).utc().valueOf(),
- dateChangedISO: moment(doc.status.dateChanged).utc().format(),
- reason: doc.status.reason
- }
- };
- resolve({
- statusCode: 200,
- headers: {'Content-Type': 'application/json'},
- body: body
- });
- })
- .catch(err => {
- reject(err);
- });
- });
- }
- else if (params.__ow_method === "put") {
- return new Promise(function (resolve, reject) {
- verifyTriggerAuth(triggerURL, params.authKey, true)
- .then(() => {
- return getTrigger(db, triggerID);
- })
- .then(trigger => {
- if (trigger.status && trigger.status.active === false) {
- reject(sendError(400, `${params.triggerName} cannot be updated because it is disabled`));
- }
- if (params.filter || params.query_params) {
- var updatedParams = {
- filter: trigger.filter,
- query_params: trigger.query_params
- };
-
- if (params.filter) {
- updatedParams.filter = params.filter;
- }
- if (params.query_params) {
- if (updatedParams.filter) {
- var query_params = params.query_params;
- if (typeof query_params === 'string') {
- try {
- query_params = JSON.parse(params.query_params);
- }
- catch (e) {
- reject(sendError(400, 'The query_params parameter cannot be parsed. Ensure it is valid JSON.'));
- }
- }
- if (typeof query_params !== 'object') {
- reject(sendError(400, 'The query_params parameter is not a valid JSON Object'));
- }
- updatedParams.query_params = query_params;
- } else {
- reject(sendError(400, 'The query_params parameter is only allowed if the filter parameter is defined'));
- }
- }
- return updateTrigger(db, triggerID, trigger, updatedParams);
- } else {
- reject(sendError(400, 'At least one of filter or query_params parameters must be supplied'));
- }
- })
- .then(() => {
- resolve({
- statusCode: 200,
- headers: {'Content-Type': 'application/json'},
- body: {'status': 'success'}
- });
- })
- .catch(err => {
- reject(err);
- });
- });
- }
- else if (params.__ow_method === "delete") {
-
- return new Promise(function (resolve, reject) {
- verifyTriggerAuth(triggerURL, params.authKey, true)
- .then(() => {
- return disableTrigger(db, triggerID, 0);
- })
- .then(() => {
- return deleteTrigger(db, triggerID, 0);
- })
- .then(() => {
- resolve({
- statusCode: 200,
- headers: {'Content-Type': 'application/json'},
- body: {'status': 'success'}
- });
- })
- .catch(err => {
- reject(err);
- });
- });
- }
- else {
- return sendError(400, 'unsupported lifecycle event');
- }
-}
-
-function getWorkerID(db, availabeWorkers) {
-
- return new Promise((resolve, reject) => {
- var workerID = availabeWorkers[0] || 'worker0';
-
- if (availabeWorkers.length > 1) {
- db.view('triggerViews', 'triggers_by_worker', {reduce: true, group: true}, function (err, body) {
- if (!err) {
- var triggersByWorker = {};
-
- availabeWorkers.forEach(worker => {
- triggersByWorker[worker] = 0;
- });
-
- body.rows.forEach(row => {
- if (row.key in triggersByWorker) {
- triggersByWorker[row.key] = row.value;
- }
- });
-
- // find which worker has the least number of assigned triggers
- for (var worker in triggersByWorker) {
- if (triggersByWorker[worker] < triggersByWorker[workerID]) {
- workerID = worker;
- }
- }
- resolve(workerID);
- } else {
- reject(err);
- }
- });
- }
- else {
- resolve(workerID);
- }
- });
-}
-
-function createTrigger(triggerDB, triggerID, newTrigger) {
-
- return new Promise(function(resolve, reject) {
-
- triggerDB.insert(newTrigger, triggerID, function (err) {
- if (!err) {
- resolve();
- }
- else {
- reject(sendError(err.statusCode, 'error creating cloudant trigger.', err.message));
- }
- });
- });
-}
-
-function getTrigger(triggerDB, triggerID) {
-
- return new Promise(function(resolve, reject) {
-
- triggerDB.get(triggerID, function (err, existing) {
- if (err) {
- reject(err);
- } else {
- resolve(existing);
- }
- });
- });
-}
-
-function disableTrigger(triggerDB, triggerID, retryCount) {
-
- return new Promise(function(resolve, reject) {
-
- triggerDB.get(triggerID, function (err, existing) {
- if (!err) {
- var updatedTrigger = existing;
- updatedTrigger.status = {'active': false};
-
- triggerDB.insert(updatedTrigger, triggerID, function (err) {
- if (err) {
- if (err.statusCode === 409 && retryCount < 5) {
- setTimeout(function () {
- disableTrigger(triggerDB, triggerID, (retryCount + 1))
- .then(() => {
- resolve();
- })
- .catch(err => {
- reject(err);
- });
- }, 1000);
- }
- else {
- reject(sendError(err.statusCode, 'there was an error while marking the trigger for delete in the database.', err.message));
- }
- }
- else {
- resolve();
- }
- });
- }
- else {
- reject(sendError(err.statusCode, 'could not find the trigger in the database'));
- }
- });
- });
-}
-
-function deleteTrigger(triggerDB, triggerID, retryCount) {
-
- return new Promise(function(resolve, reject) {
-
- triggerDB.get(triggerID, function (err, existing) {
- if (!err) {
- triggerDB.destroy(existing._id, existing._rev, function (err) {
- if (err) {
- if (err.statusCode === 409 && retryCount < 5) {
- setTimeout(function () {
- deleteTrigger(triggerDB, triggerID, (retryCount + 1))
- .then(resolve)
- .catch(err => {
- reject(err);
- });
- }, 1000);
- }
- else {
- reject(sendError(err.statusCode, 'there was an error while deleting the trigger from the database.', err.message));
- }
- }
- else {
- resolve();
- }
- });
- }
- else {
- reject(sendError(err.statusCode, 'could not find the trigger in the database'));
- }
- });
- });
-}
-
-function updateTrigger(triggerDB, triggerID, existing, params) {
-
- return new Promise(function(resolve, reject) {
- var message = 'Automatically disabled trigger while updating';
- var status = {
- 'active': false,
- 'dateChanged': Date.now(),
- 'reason': {'kind': 'AUTO', 'statusCode': undefined, 'message': message}
- };
- existing.status = status;
- triggerDB.insert(existing, triggerID, function (err) {
- if (err) {
- reject(sendError(err.statusCode, 'there was an error while disabling the trigger in the database.', err.message));
- }
- else {
- resolve();
- }
- });
- })
- .then(() => {
- return getTrigger(triggerDB, triggerID);
- })
- .then(trigger => {
- for (var key in params) {
- trigger[key] = params[key];
- }
- var status = {
- 'active': true,
- 'dateChanged': Date.now()
- };
- trigger.status = status;
-
- return new Promise(function(resolve, reject) {
- triggerDB.insert(trigger, triggerID, function (err) {
- if (err) {
- reject(sendError(err.statusCode, 'there was an error while updating and re-enabling the trigger in the database.', err.message));
- }
- else {
- resolve();
- }
- });
- });
- });
-}
-
-function verifyTriggerAuth(triggerURL, authKey, isDelete) {
- var auth = authKey.split(':');
-
- return new Promise(function(resolve, reject) {
-
- request({
- method: 'get',
- url: triggerURL,
- auth: {
- user: auth[0],
- pass: auth[1]
- },
- rejectUnauthorized: false
- }, function(err, response) {
- if (err) {
- reject(sendError(400, 'Trigger authentication request failed.', err.message));
- }
- else if(response.statusCode >= 400 && !(isDelete && response.statusCode === 404)) {
- reject(sendError(response.statusCode, 'Trigger authentication request failed.'));
- }
- else {
- resolve();
- }
- });
- });
-}
-
-function verifyUserDB(triggerObj) {
- var dbURL = `${triggerObj.protocol}://${triggerObj.user}:${triggerObj.pass}@${triggerObj.host}`;
-
- // add port if specified
- if (triggerObj.port) {
- dbURL += ':' + triggerObj.port;
- }
-
- return new Promise(function(resolve, reject) {
- try {
- var nanoConnection = require('nano')(dbURL);
- var userDB = nanoConnection.use(triggerObj.dbname);
- userDB.info(function(err, body) {
- if (!err) {
- resolve();
- }
- else {
- reject(sendError(err.statusCode, 'error connecting to database ' + triggerObj.dbname, err.message));
- }
- });
- }
- catch(err) {
- reject(sendError(400, 'error connecting to database ' + triggerObj.dbname, err.message));
- }
-
- });
-}
-
-function sendError(statusCode, error, message) {
- var params = {error: error};
- if (message) {
- params.message = message;
- }
-
- return {
- statusCode: statusCode,
- headers: { 'Content-Type': 'application/json' },
- body: params
- };
-}
-
-
-function parseQName(qname, separator) {
- var parsed = {};
- var delimiter = separator || ':';
- var defaultNamespace = '_';
- if (qname && qname.charAt(0) === delimiter) {
- var parts = qname.split(delimiter);
- parsed.namespace = parts[1];
- parsed.name = parts.length > 2 ? parts.slice(2).join(delimiter) : '';
- } else {
- parsed.namespace = defaultNamespace;
- parsed.name = qname;
- }
- return parsed;
-}
-
-
-exports.main = main;
-
-
diff --git a/actions/event-actions/changes.js b/actions/event-actions/changes.js
new file mode 100644
index 0000000..ddeffb5
--- /dev/null
+++ b/actions/event-actions/changes.js
@@ -0,0 +1,31 @@
+const common = require('./lib/common');
+
+function main(msg) {
+
+ let eventMap = {
+ CREATE: 'post',
+ READ: 'get',
+ UPDATE: 'put',
+ DELETE: 'delete'
+ };
+ // for creation -> CREATE
+ // for reading -> READ
+ // for updating -> UPDATE
+ // for deletion -> DELETE
+ var lifecycleEvent = msg.lifecycleEvent;
+
+ var endpoint = msg.apihost;
+ var webparams = common.createWebParams(msg);
+
+ var url = `https://${endpoint}/api/v1/web/whisk.system/cloudantWeb/changesWebAction.http`;
+
+ if (lifecycleEvent in eventMap) {
+ var method = eventMap[lifecycleEvent];
+ return common.requestHelper(url, webparams, method);
+ } else {
+ return Promise.reject('unsupported lifecycleEvent');
+ }
+}
+
+
+exports.main = main;
diff --git a/actions/event-actions/changesFeed_package.json b/actions/event-actions/changesFeed_package.json
new file mode 100644
index 0000000..d1738c6
--- /dev/null
+++ b/actions/event-actions/changesFeed_package.json
@@ -0,0 +1,5 @@
+{
+ "name": "changesFeed",
+ "version": "1.0.0",
+ "main": "changes.js"
+}
diff --git a/actions/event-actions/changesWebAction.js b/actions/event-actions/changesWebAction.js
new file mode 100644
index 0000000..af2b0e8
--- /dev/null
+++ b/actions/event-actions/changesWebAction.js
@@ -0,0 +1,272 @@
+const moment = require('moment');
+const common = require('./lib/common');
+const Database = require('./lib/Database');
+
+function main(params) {
+
+ if (!params.triggerName) {
+ return common.sendError(400, 'no trigger name parameter was provided');
+ }
+
+ var triggerParts = common.parseQName(params.triggerName);
+ var triggerData = {
+ apikey: params.authKey,
+ name: triggerParts.name,
+ namespace: triggerParts.namespace,
+ additionalData: common.constructObject(params.additionalData),
+ };
+ var triggerID = `:${triggerParts.namespace}:${triggerParts.name}`;
+
+ var workers = params.workers instanceof Array ? params.workers : [];
+ var db;
+
+ if (params.__ow_method === "post") {
+
+ // check for parameter errors
+ if (!params.dbname) {
+ return common.sendError(400, 'cloudant trigger feed: missing dbname parameter');
+ }
+ if (!params.host) {
+ return common.sendError(400, 'cloudant trigger feed: missing host parameter');
+ }
+ if (!params.username) {
+ return common.sendError(400, 'cloudant trigger feed: missing username parameter');
+ }
+ if (!params.password) {
+ return common.sendError(400, 'cloudant trigger feed: missing password parameter');
+ }
+
+ var query_params;
+
+ if (params.filter) {
+ query_params = params.query_params;
+ if (typeof queryParams === 'string') {
+ try {
+ query_params = JSON.parse(params.query_params);
+ }
+ catch (e) {
+ return common.sendError(400, 'The query_params parameter cannot be parsed. Ensure it is valid JSON.');
+ }
+ }
+ if (query_params && typeof query_params !== 'object') {
+ return common.sendError(400, 'The query_params parameter is not a valid JSON Object');
+ }
+ }
+ else if (params.query_params) {
+ return common.sendError(400, 'The query_params parameter is only allowed if the filter parameter is defined');
+ }
+
+ var newTrigger = {
+ id: triggerID,
+ host: params.host,
+ port: params.port,
+ protocol: params.protocol || 'https',
+ dbname: params.dbname,
+ user: params.username,
+ pass: params.password,
+ apikey: triggerData.apikey,
+ since: params.since,
+ maxTriggers: params.maxTriggers || -1,
+ filter: params.filter,
+ query_params: query_params,
+ status: {
+ 'active': true,
+ 'dateChanged': Date.now()
+ },
+ additionalData: triggerData.additionalData
+ };
+
+ return new Promise(function (resolve, reject) {
+ common.verifyTriggerAuth(triggerData, false)
+ .then(() => {
+ db = new Database(params.DB_URL, params.DB_NAME);
+ return verifyUserDB(newTrigger);
+ })
+ .then(() => {
+ return db.getWorkerID(workers);
+ })
+ .then((worker) => {
+ console.log('trigger will be assigned to worker ' + worker);
+ newTrigger.worker = worker;
+ return db.createTrigger(triggerID, newTrigger);
+ })
+ .then(() => {
+ resolve({
+ statusCode: 200,
+ headers: {'Content-Type': 'application/json'},
+ body: {'status': 'success'}
+ });
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+
+ }
+ else if (params.__ow_method === "get") {
+ return new Promise(function (resolve, reject) {
+ common.verifyTriggerAuth(triggerData, false)
+ .then(() => {
+ db = new Database(params.DB_URL, params.DB_NAME);
+ return db.getTrigger(triggerID);
+ })
+ .then(doc => {
+ var body = {
+ config: {
+ name: doc.id.split(':')[2],
+ namespace: doc.id.split(':')[1],
+ host: doc.host,
+ port: doc.port,
+ protocol: doc.protocol,
+ dbname: doc.dbname,
+ username: doc.user,
+ password: doc.pass,
+ since: doc.since,
+ filter: doc.filter,
+ query_params: doc.query_params,
+ },
+ status: {
+ active: doc.status.active,
+ dateChanged: moment(doc.status.dateChanged).utc().valueOf(),
+ dateChangedISO: moment(doc.status.dateChanged).utc().format(),
+ reason: doc.status.reason
+ }
+ };
+ resolve({
+ statusCode: 200,
+ headers: {'Content-Type': 'application/json'},
+ body: body
+ });
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+ }
+ else if (params.__ow_method === "put") {
+
+ return new Promise(function (resolve, reject) {
+ var updatedParams = {};
+
+ common.verifyTriggerAuth(triggerData, false)
+ .then(() => {
+ db = new Database(params.DB_URL, params.DB_NAME);
+ return db.getTrigger(triggerID);
+ })
+ .then(trigger => {
+ if (trigger.status && trigger.status.active === false) {
+ return reject(common.sendError(400, `${params.triggerName} cannot be updated because it is disabled`));
+ }
+
+ if (params.filter || params.query_params) {
+ updatedParams.filter = trigger.filter;
+ updatedParams.query_params = trigger.query_params;
+ }
+ else {
+ return reject(common.sendError(400, 'At least one of filter or query_params parameters must be supplied'));
+ }
+
+ if (params.filter) {
+ updatedParams.filter = params.filter;
+ }
+
+ if (params.query_params) {
+ if (updatedParams.filter) {
+ var query_params = params.query_params;
+ if (typeof query_params === 'string') {
+ try {
+ query_params = JSON.parse(params.query_params);
+ }
+ catch (e) {
+ return reject(common.sendError(400, 'The query_params parameter cannot be parsed. Ensure it is valid JSON.'));
+ }
+ }
+ if (typeof query_params !== 'object') {
+ return reject(common.sendError(400, 'The query_params parameter is not a valid JSON Object'));
+ }
+ updatedParams.query_params = query_params;
+ }
+ else {
+ return reject(common.sendError(400, 'The query_params parameter is only allowed if the filter parameter is defined'));
+ }
+ }
+ return db.disableTrigger(triggerID, trigger, 0, 'updating');
+ })
+ .then(triggerID => {
+ return db.getTrigger(triggerID);
+ })
+ .then(trigger => {
+ return db.updateTrigger(triggerID, trigger, updatedParams, 0);
+ })
+ .then(() => {
+ resolve({
+ statusCode: 200,
+ headers: {'Content-Type': 'application/json'},
+ body: {'status': 'success'}
+ });
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+ }
+ else if (params.__ow_method === "delete") {
+
+ return new Promise(function (resolve, reject) {
+ common.verifyTriggerAuth(triggerData, true)
+ .then(() => {
+ db = new Database(params.DB_URL, params.DB_NAME);
+ return db.getTrigger(triggerID);
+ })
+ .then(trigger => {
+ return db.disableTrigger(triggerID, trigger, 0, 'deleting');
+ })
+ .then(triggerID => {
+ return db.deleteTrigger(triggerID, 0);
+ })
+ .then(() => {
+ resolve({
+ statusCode: 200,
+ headers: {'Content-Type': 'application/json'},
+ body: {'status': 'success'}
+ });
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+ }
+ else {
+ return common.sendError(400, 'unsupported lifecycleEvent');
+ }
+}
+
+function verifyUserDB(triggerObj) {
+ var dbURL = `${triggerObj.protocol}://${triggerObj.user}:${triggerObj.pass}@${triggerObj.host}`;
+
+ // add port if specified
+ if (triggerObj.port) {
+ dbURL += ':' + triggerObj.port;
+ }
+
+ return new Promise(function(resolve, reject) {
+ try {
+ var nanoConnection = require('nano')(dbURL);
+ var userDB = nanoConnection.use(triggerObj.dbname);
+ userDB.info(function(err, body) {
+ if (!err) {
+ resolve();
+ }
+ else {
+ reject(common.sendError(err.statusCode, 'error connecting to database ' + triggerObj.dbname, err.message));
+ }
+ });
+ }
+ catch(err) {
+ reject(common.sendError(400, 'error connecting to database ' + triggerObj.dbname, err.message));
+ }
+
+ });
+}
+
+exports.main = main;
diff --git a/actions/event-actions/changesWeb_package.json b/actions/event-actions/changesWeb_package.json
new file mode 100644
index 0000000..f2b3fc6
--- /dev/null
+++ b/actions/event-actions/changesWeb_package.json
@@ -0,0 +1,5 @@
+{
+ "name": "changesWebAction",
+ "version": "1.0.0",
+ "main": "changesWebAction.js"
+}
diff --git a/actions/event-actions/lib/Database.js b/actions/event-actions/lib/Database.js
new file mode 100644
index 0000000..962b2df
--- /dev/null
+++ b/actions/event-actions/lib/Database.js
@@ -0,0 +1,195 @@
+const common = require('./common');
+
+// constructor for DB object - a thin, promise-loving wrapper around nano
+module.exports = function(dbURL, dbName) {
+ var nano = require('nano')(dbURL);
+ this.db = nano.db.use(dbName);
+ var utilsDB = this;
+
+ this.getWorkerID = function(availabeWorkers) {
+
+ return new Promise((resolve, reject) => {
+ var workerID = availabeWorkers[0] || 'worker0';
+
+ if (availabeWorkers.length > 1) {
+ utilsDB.db.view('triggerViews', 'triggers_by_worker', {reduce: true, group: true}, function (err, body) {
+ if (!err) {
+ var triggersByWorker = {};
+
+ availabeWorkers.forEach(worker => {
+ triggersByWorker[worker] = 0;
+ });
+
+ body.rows.forEach(row => {
+ if (row.key in triggersByWorker) {
+ triggersByWorker[row.key] = row.value;
+ }
+ });
+
+ // find which worker has the least number of assigned triggers
+ for (var worker in triggersByWorker) {
+ if (triggersByWorker[worker] < triggersByWorker[workerID]) {
+ workerID = worker;
+ }
+ }
+ resolve(workerID);
+ } else {
+ reject(err);
+ }
+ });
+ }
+ else {
+ resolve(workerID);
+ }
+ });
+ };
+
+ this.createTrigger = function(triggerID, newTrigger) {
+
+ return new Promise(function(resolve, reject) {
+
+ utilsDB.db.insert(newTrigger, triggerID, function (err) {
+ if (!err) {
+ resolve();
+ }
+ else {
+ reject(common.sendError(err.statusCode, 'error creating cloudant trigger.', err.message));
+ }
+ });
+ });
+ };
+
+ this.getTrigger = function(triggerID) {
+
+ return new Promise(function(resolve, reject) {
+
+ utilsDB.db.get(triggerID, function (err, existing) {
+ if (err) {
+ var qName = triggerID.split(':');
+ var name = '/' + qName[1] + '/' + qName[2];
+ reject(common.sendError(err.statusCode, 'could not find trigger ' + name + ' in the database'));
+ } else {
+ resolve(existing);
+ }
+ });
+ });
+ };
+
+ this.disableTrigger = function(triggerID, trigger, retryCount, crudMessage) {
+
+ if (retryCount === 0) {
+ //check if it is already disabled
+ if (trigger.status && trigger.status.active === false) {
+ return Promise.resolve(triggerID);
+ }
+
+ var message = `Automatically disabled trigger while ${crudMessage}`;
+ var status = {
+ 'active': false,
+ 'dateChanged': Date.now(),
+ 'reason': {'kind': 'AUTO', 'statusCode': undefined, 'message': message}
+ };
+ trigger.status = status;
+ }
+
+ return new Promise(function(resolve, reject) {
+
+ utilsDB.db.insert(trigger, triggerID, function (err) {
+ if (err) {
+ if (err.statusCode === 409 && retryCount < 5) {
+ setTimeout(function () {
+ utilsDB.disableTrigger(triggerID, trigger, (retryCount + 1))
+ .then(id => {
+ resolve(id);
+ })
+ .catch(err => {
+ reject(err);
+ });
+ }, 1000);
+ }
+ else {
+ reject(common.sendError(err.statusCode, 'there was an error while disabling the trigger in the database.', err.message));
+ }
+ }
+ else {
+ resolve(triggerID);
+ }
+ });
+ });
+
+ };
+
+ this.deleteTrigger = function(triggerID, retryCount) {
+
+ return new Promise(function(resolve, reject) {
+
+ utilsDB.db.get(triggerID, function (err, existing) {
+ if (!err) {
+ utilsDB.db.destroy(existing._id, existing._rev, function (err) {
+ if (err) {
+ if (err.statusCode === 409 && retryCount < 5) {
+ setTimeout(function () {
+ utilsDB.deleteTrigger(triggerID, (retryCount + 1))
+ .then(resolve)
+ .catch(err => {
+ reject(err);
+ });
+ }, 1000);
+ }
+ else {
+ reject(common.sendError(err.statusCode, 'there was an error while deleting the trigger from the database.', err.message));
+ }
+ }
+ else {
+ resolve();
+ }
+ });
+ }
+ else {
+ var qName = triggerID.split(':');
+ var name = '/' + qName[1] + '/' + qName[2];
+ reject(common.sendError(err.statusCode, 'could not find trigger ' + name + ' in the database'));
+ }
+ });
+ });
+ };
+
+ this.updateTrigger = function(triggerID, trigger, params, retryCount) {
+
+ if (retryCount === 0) {
+ for (var key in params) {
+ trigger[key] = params[key];
+ }
+ var status = {
+ 'active': true,
+ 'dateChanged': Date.now()
+ };
+ trigger.status = status;
+ }
+
+ return new Promise(function(resolve, reject) {
+ utilsDB.db.insert(trigger, triggerID, function (err) {
+ if (err) {
+ if (err.statusCode === 409 && retryCount < 5) {
+ setTimeout(function () {
+ utilsDB.updateTrigger(triggerID, trigger, params, (retryCount + 1))
+ .then(id => {
+ resolve(id);
+ })
+ .catch(err => {
+ reject(err);
+ });
+ }, 1000);
+ }
+ else {
+ reject(common.sendError(err.statusCode, 'there was an error while updating the trigger in the database.', err.message));
+ }
+ }
+ else {
+ resolve(triggerID);
+ }
+ });
+ });
+ };
+
+};
diff --git a/actions/event-actions/lib/common.js b/actions/event-actions/lib/common.js
new file mode 100644
index 0000000..e5ba69a
--- /dev/null
+++ b/actions/event-actions/lib/common.js
@@ -0,0 +1,137 @@
+const request = require('request');
+const openwhisk = require('openwhisk');
+const config = require('./config');
+
+function requestHelper(url, input, method) {
+
+ return new Promise(function(resolve, reject) {
+
+ var options = {
+ method : method,
+ url : url,
+ json: true,
+ rejectUnauthorized: false
+ };
+
+ if (method === 'get') {
+ options.qs = input;
+ } else {
+ options.body = input;
+ }
+
+ request(options, function(error, response, body) {
+
+ if (!error && response.statusCode === 200) {
+ resolve(body);
+ }
+ else {
+ if (response) {
+ console.log('cloudant: Error invoking whisk action:', response.statusCode, body);
+ reject(body);
+ }
+ else {
+ console.log('cloudant: Error invoking whisk action:', error);
+ reject(error);
+ }
+ }
+ });
+ });
+}
+
+function createWebParams(rawParams) {
+ var namespace = process.env.__OW_NAMESPACE;
+ var triggerName = ':' + namespace + ':' + parseQName(rawParams.triggerName, '/').name;
+
+ var webparams = Object.assign({}, rawParams);
+ delete webparams.lifecycleEvent;
+ delete webparams.bluemixServiceName;
+ delete webparams.apihost;
+
+ webparams.triggerName = triggerName;
+ webparams.authKey = process.env.__OW_API_KEY;
+ config.addAdditionalData(webparams);
+
+ return webparams;
+}
+
+function verifyTriggerAuth(triggerData, isDelete) {
+ var owConfig = config.getOpenWhiskConfig(triggerData);
+ var ow = openwhisk(owConfig);
+
+ return new Promise(function(resolve, reject) {
+ ow.triggers.get(triggerData.name)
+ .then(() => {
+ resolve();
+ })
+ .catch(err => {
+ if (err.statusCode) {
+ var statusCode = err.statusCode;
+ if (!(isDelete && statusCode === 404)) {
+ reject(sendError(statusCode, 'Trigger authentication request failed.'));
+ }
+ else {
+ resolve();
+ }
+ }
+ else {
+ reject(sendError(400, 'Trigger authentication request failed.', err.message));
+ }
+ });
+ });
+}
+
+function parseQName(qname, separator) {
+ var parsed = {};
+ var delimiter = separator || ':';
+ var defaultNamespace = '_';
+ if (qname && qname.charAt(0) === delimiter) {
+ var parts = qname.split(delimiter);
+ parsed.namespace = parts[1];
+ parsed.name = parts.length > 2 ? parts.slice(2).join(delimiter) : '';
+ } else {
+ parsed.namespace = defaultNamespace;
+ parsed.name = qname;
+ }
+ return parsed;
+}
+
+function sendError(statusCode, error, message) {
+ var params = {error: error};
+ if (message) {
+ params.message = message;
+ }
+
+ return {
+ statusCode: statusCode,
+ headers: { 'Content-Type': 'application/json' },
+ body: params
+ };
+}
+
+function constructObject(data) {
+ var jsonObject;
+ if (data) {
+ if (typeof data === 'string') {
+ try {
+ jsonObject = JSON.parse(data);
+ }
+ catch (e) {
+ console.log('error parsing ' + data);
+ }
+ }
+ if (typeof data === 'object') {
+ jsonObject = data;
+ }
+ }
+ return jsonObject;
+}
+
+
+module.exports = {
+ 'requestHelper': requestHelper,
+ 'createWebParams': createWebParams,
+ 'verifyTriggerAuth': verifyTriggerAuth,
+ 'parseQName': parseQName,
+ 'sendError': sendError,
+ 'constructObject': constructObject
+};
diff --git a/actions/event-actions/lib/config.js b/actions/event-actions/lib/config.js
new file mode 100644
index 0000000..550f42a
--- /dev/null
+++ b/actions/event-actions/lib/config.js
@@ -0,0 +1,13 @@
+function getOpenWhiskConfig(triggerData) {
+ return {ignore_certs: true, namespace: triggerData.namespace, api_key: triggerData.apikey};
+}
+
+function addAdditionalData(params) {
+ //insert code here to store additional trigger data in the database
+ //for example, params.additionalData = {dateCreated: Date.now()};
+}
+
+module.exports = {
+ 'addAdditionalData': addAdditionalData,
+ 'getOpenWhiskConfig': getOpenWhiskConfig
+};
diff --git a/installCatalog.sh b/installCatalog.sh
index cfa5521..d83fffe 100755
--- a/installCatalog.sh
+++ b/installCatalog.sh
@@ -12,9 +12,8 @@
: ${OPENWHISK_HOME:?"OPENWHISK_HOME must be set and non-empty"}
WSK_CLI="$OPENWHISK_HOME/bin/wsk"
-if [ $# -eq 0 ]
-then
-echo "Usage: ./installCatalog.sh <authkey> <edgehost> <dburl> <dbprefix> <apihost> <workers>"
+if [ $# -eq 0 ]; then
+ echo "Usage: ./installCatalog.sh <authkey> <edgehost> <dburl> <dbprefix> <apihost> <workers>"
fi
AUTH="$1"
@@ -23,6 +22,7 @@
DB_NAME="${4}cloudanttrigger"
APIHOST="$5"
WORKERS="$6"
+ACTION_RUNTIME_VERSION=${ACTION_RUNTIME_VERSION:="nodejs:6"}
# If the auth key file exists, read the key in the file. Otherwise, take the
# first argument as the key itself.
@@ -48,7 +48,7 @@
echo Installing Cloudant package.
-$WSK_CLI -i --apihost "$EDGEHOST" package update --auth "$AUTH" --shared yes cloudant \
+$WSK_CLI -i --apihost "$EDGEHOST" package update --auth "$AUTH" --shared yes cloudant \
-a description "Cloudant database service" \
-a parameters '[ {"name":"bluemixServiceName", "required":false, "bindTime":true}, {"name":"username", "required":true, "bindTime":true, "description": "Your Cloudant username"}, {"name":"password", "required":true, "type":"password", "bindTime":true, "description": "Your Cloudant password"}, {"name":"host", "required":true, "bindTime":true, "description": "This is usually your username.cloudant.com"}, {"name":"dbname", "required":false, "description": "The name of your Cloudant database"}, {"name":"overwrite", "required":false, "type": "boolean"} ]' \
-p bluemixServiceName 'cloudantNoSQLDB' \
@@ -58,127 +58,139 @@
-p dbname '' \
-p apihost "$APIHOST"
-if [ -n "$WORKERS" ];
-then
- $WSK_CLI -i --apihost "$EDGEHOST" package update --auth "$AUTH" --shared no cloudantWeb \
- -p DB_URL "$DB_URL" \
- -p DB_NAME "$DB_NAME" \
- -p apihost "$APIHOST" \
- -p workers "$WORKERS"
-else
- $WSK_CLI -i --apihost "$EDGEHOST" package update --auth "$AUTH" --shared no cloudantWeb \
- -p DB_URL "$DB_URL" \
- -p DB_NAME "$DB_NAME" \
- -p apihost "$APIHOST"
+# make changesFeed.zip
+cd actions/event-actions
+
+if [ -e changesFeed.zip ]; then
+ rm -rf changesFeed.zip
fi
-# Cloudant feed action
+cp -f changesFeed_package.json package.json
+zip -r changesFeed.zip lib package.json changes.js
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/changes "$PACKAGE_HOME/actions/changes.js" \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/changes "$PACKAGE_HOME/actions/event-actions/changesFeed.zip" \
-t 90000 \
-a feed true \
-a description 'Database change feed' \
-a parameters '[ {"name":"dbname", "required":true, "updatable":false}, {"name": "filter", "required":false, "updatable":true, "type": "string", "description": "The name of your Cloudant database filter"}, {"name": "query_params", "required":false, "updatable":true, "description": "JSON Object containing query parameters that are passed to the filter"} ]' \
-a sampleInput '{ "dbname": "mydb", "filter": "mailbox/by_status", "query_params": {"status": "new"} }'
-# Cloudant web feed action
+COMMAND=" -i --apihost $EDGEHOST package update --auth $AUTH --shared no cloudantWeb \
+ -p DB_URL $DB_URL \
+ -p DB_NAME $DB_NAME \
+ -p apihost $APIHOST"
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudantWeb/changesWebAction "$PACKAGE_HOME/actions/changesWebAction.js" \
+if [ -n "$WORKERS" ]; then
+ COMMAND+=" -p workers $WORKERS"
+fi
+
+$WSK_CLI $COMMAND
+
+# make changesWebAction.zip
+cp -f changesWeb_package.json package.json
+
+if [ -e changesWebAction.zip ]; then
+ rm -rf changesWebAction.zip
+fi
+
+zip -r changesWebAction.zip lib package.json changesWebAction.js
+
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudantWeb/changesWebAction "$PACKAGE_HOME/actions/event-actions/changesWebAction.zip" \
-a description 'Create/Delete a trigger in cloudant provider Database' \
--web true
# Cloudant account actions
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/create-database \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/create-database \
"$PACKAGE_HOME/actions/account-actions/create-database.js" \
-a description 'Create Cloudant database' \
-a parameters '[ {"name":"dbname", "required":true} ]'
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/read-database \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/read-database \
"$PACKAGE_HOME/actions/account-actions/read-database.js" \
-a description 'Read Cloudant database' \
-a parameters '[ {"name":"dbname", "required":true} ]'
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/delete-database \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/delete-database \
"$PACKAGE_HOME/actions/account-actions/delete-database.js" \
-a description 'Delete Cloudant database' \
-a parameters '[ {"name":"dbname", "required":true} ]'
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/list-all-databases \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/list-all-databases \
"$PACKAGE_HOME/actions/account-actions/list-all-databases.js" \
-a description 'List all Cloudant databases'
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/read-updates-feed \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/read-updates-feed \
"$PACKAGE_HOME/actions/account-actions/read-updates-feed.js" \
-a description 'Read updates feed from Cloudant account (non-continuous)' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"params", "required":false} ]'
# Cloudant database actions
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/create-document \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/create-document \
"$PACKAGE_HOME/actions/database-actions/create-document.js" \
-a description 'Create document in database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"doc", "required":true, "description": "The JSON document to insert"}, {"name":"params", "required":false} ]' \
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/read \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/read \
"$PACKAGE_HOME/actions/database-actions/read-document.js" \
-a description 'Read document from database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"id", "required":true, "description": "The Cloudant document id to fetch"}, {"name":"params", "required":false}]' \
-p id ''
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/read-document \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/read-document \
"$PACKAGE_HOME/actions/database-actions/read-document.js" \
-a description 'Read document from database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true, "description": "The Cloudant document id to fetch"}, {"name":"params", "required":false}]' \
-p docid ''
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/write \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/write \
"$PACKAGE_HOME/actions/database-actions/write-document.js" \
-a description 'Write document in database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"doc", "required":true} ]' \
-p doc '{}'
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/update-document \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/update-document \
"$PACKAGE_HOME/actions/database-actions/update-document.js" \
-a description 'Update document in database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"doc", "required":true}, {"name":"params", "required":false} ]' \
-p doc '{}'
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/delete-document \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/delete-document \
"$PACKAGE_HOME/actions/database-actions/delete-document.js" \
-a description 'Delete document from database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true, "description": "The Cloudant document id to delete"}, {"name":"docrev", "required":true, "description": "The document revision number"} ]' \
-p docid '' \
-p docrev ''
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/list-documents \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/list-documents \
"$PACKAGE_HOME/actions/database-actions/list-documents.js" \
-a description 'List all docs from database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"params", "required":false} ]'
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/list-design-documents \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/list-design-documents \
"$PACKAGE_HOME/actions/database-actions/list-design-documents.js" \
-a description 'List design documents from database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"includedocs", "required":false} ]' \
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/create-query-index \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/create-query-index \
"$PACKAGE_HOME/actions/database-actions/create-query-index.js" \
-a description 'Create a Cloudant Query index into database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"index", "required":true} ]' \
-p index ''
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/list-query-indexes \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/list-query-indexes \
"$PACKAGE_HOME/actions/database-actions/list-query-indexes.js" \
-a description 'List Cloudant Query indexes from database' \
-a parameters '[ {"name":"dbname", "required":true} ]' \
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/exec-query-find \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/exec-query-find \
"$PACKAGE_HOME/actions/database-actions/exec-query-find.js" \
-a description 'Execute query against Cloudant Query index' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"query", "required":true} ]' \
-p query ''
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/exec-query-search \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/exec-query-search \
"$PACKAGE_HOME/actions/database-actions/exec-query-search.js" \
-a description 'Execute query against Cloudant search' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"indexname", "required":true}, {"name":"search", "required":true} ]' \
@@ -186,39 +198,39 @@
-p indexname '' \
-p search ''
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/exec-query-view \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/exec-query-view \
"$PACKAGE_HOME/actions/database-actions/exec-query-view.js" \
-a description 'Call view in design document from database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"viewname", "required":true}, {"name":"params", "required":false} ]' \
-p docid '' \
-p viewname ''
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/manage-bulk-documents \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/manage-bulk-documents \
"$PACKAGE_HOME/actions/database-actions/manage-bulk-documents.js" \
-a description 'Create, Update, and Delete documents in bulk' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"docs", "required":true}, {"name":"params", "required":false} ]' \
-p docs '{}'
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/delete-view \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/delete-view \
"$PACKAGE_HOME/actions/database-actions/delete-view.js" \
-a description 'Delete view from design document' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"viewname", "required":true}, {"name":"params", "required":false} ]' \
-p docid '' \
-p viewname ''
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/delete-query-index \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/delete-query-index \
"$PACKAGE_HOME/actions/database-actions/delete-query-index.js" \
-a description 'Delete index from design document' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"indexname", "required":true}, {"name":"params", "required":false} ]' \
-p docid '' \
-p indexname ''
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/read-changes-feed \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/read-changes-feed \
"$PACKAGE_HOME/actions/database-actions/read-changes-feed.js" \
-a description 'Read Cloudant database changes feed (non-continuous)' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"params", "required":false} ]'
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/create-attachment \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/create-attachment \
"$PACKAGE_HOME/actions/database-actions/create-update-attachment.js" \
-a description 'Create document attachment in database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"docrev", "required":true}, {"name":"attachment", "required":true}, {"name":"attachmentname", "required":true}, {"name":"contenttype", "required":true}, {"name":"params", "required":false} ]' \
@@ -228,14 +240,14 @@
-p attachmentname '' \
-p contenttype ''
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/read-attachment \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/read-attachment \
"$PACKAGE_HOME/actions/database-actions/read-attachment.js" \
-a description 'Read document attachment from database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"attachmentname", "required":true}, {"name":"params", "required":false} ]' \
-p docid '' \
-p attachmentname ''
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/update-attachment \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/update-attachment \
"$PACKAGE_HOME/actions/database-actions/create-update-attachment.js" \
-a description 'Update document attachment in database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"docrev", "required":true}, {"name":"attachment", "required":true}, {"name":"attachmentname", "required":true}, {"name":"contenttype", "required":true}, {"name":"params", "required":false} ]' \
@@ -245,7 +257,7 @@
-p attachmentname '' \
-p contenttype ''
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/delete-attachment \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/delete-attachment \
"$PACKAGE_HOME/actions/database-actions/delete-attachment.js" \
-a description 'Delete document attachment from database' \
-a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"docrev", "required":true}, {"name":"attachmentname", "required":true}, {"name":"params", "required":false} ]' \
diff --git a/provider/lib/authHandler.js b/provider/lib/authHandler.js
new file mode 100644
index 0000000..9e25242
--- /dev/null
+++ b/provider/lib/authHandler.js
@@ -0,0 +1,13 @@
+function handleAuth(triggerData) {
+
+ var auth = triggerData.apikey.split(':');
+ return Promise.resolve({
+ user: auth[0],
+ pass: auth[1]
+ });
+
+}
+
+module.exports = {
+ 'handleAuth': handleAuth
+};
diff --git a/provider/lib/health.js b/provider/lib/health.js
index c2055a3..f329ba9 100644
--- a/provider/lib/health.js
+++ b/provider/lib/health.js
@@ -1,6 +1,5 @@
var si = require('systeminformation');
var v8 = require('v8');
-var request = require('request');
var _ = require('lodash');
var URL = require('url').URL;
var constants = require('./constants.js');
@@ -47,8 +46,6 @@
this.monitor = function(apikey, monitoringInterval) {
var method = 'monitor';
- var auth = apikey.split(':');
-
if (triggerName) {
monitorStatus = Object.assign({}, utils.monitorStatus);
utils.monitorStatus = {};
@@ -63,8 +60,12 @@
var existingCanaryID = canaryDocID;
//delete the trigger
- var uri = utils.uriHost + '/api/v1/namespaces/_/triggers/' + triggerName;
- deleteTrigger(existingTriggerID, uri, auth, 0);
+ var triggerData = {
+ apikey: apikey,
+ uri: utils.uriHost + '/api/v1/namespaces/_/triggers/' + triggerName,
+ triggerID: existingTriggerID
+ };
+ deleteTrigger(triggerData, 0);
//delete the canary doc
deleteDocFromDB(existingCanaryID, 0);
@@ -81,7 +82,7 @@
var triggerURL = utils.uriHost + '/api/v1/namespaces/_/triggers/' + triggerName;
var triggerID = `:_:${triggerName}`;
- createTrigger(triggerURL, auth)
+ createTrigger(triggerURL, apikey)
.then(info => {
logger.info(method, triggerID, info);
var newTrigger = createCloudantTrigger(triggerID, apikey);
@@ -126,17 +127,13 @@
return newTrigger;
}
- function createTrigger(triggerURL, auth) {
+ function createTrigger(triggerURL, apikey) {
var method = 'createTrigger';
return new Promise(function(resolve, reject) {
- request({
+ utils.authRequest({apikey: apikey}, {
method: 'put',
uri: triggerURL,
- auth: {
- user: auth[0],
- pass: auth[1]
- },
json: true,
body: {}
}, function (error, response) {
@@ -163,23 +160,20 @@
});
}
- function deleteTrigger(triggerID, uri, auth, retryCount) {
+ function deleteTrigger(triggerData, retryCount) {
var method = 'deleteTrigger';
- request({
+ var triggerID = triggerData.triggerID;
+ utils.authRequest(triggerData, {
method: 'delete',
- uri: uri,
- auth: {
- user: auth[0],
- pass: auth[1]
- },
+ uri: triggerData.uri
}, function (error, response) {
logger.info(method, triggerID, 'http delete request, STATUS:', response ? response.statusCode : undefined);
if (error || response.statusCode >= 400) {
if (!error && response.statusCode === 409 && retryCount < 5) {
logger.info(method, 'attempting to delete trigger again', triggerID, 'Retry Count:', (retryCount + 1));
setTimeout(function () {
- deleteTrigger(triggerID, uri, auth, (retryCount + 1));
+ deleteTrigger(triggerData, (retryCount + 1));
}, 1000);
} else {
logger.error(method, triggerID, 'trigger delete request failed');
diff --git a/provider/lib/utils.js b/provider/lib/utils.js
index 808beb4..3e3ab61 100644
--- a/provider/lib/utils.js
+++ b/provider/lib/utils.js
@@ -1,6 +1,7 @@
var request = require('request');
var HttpStatus = require('http-status-codes');
var constants = require('./constants.js');
+var authHandler = require('./authHandler');
module.exports = function(logger, triggerDB, redisClient) {
@@ -26,41 +27,41 @@
this.monitorStatus = {};
// Add a trigger: listen for changes and dispatch.
- this.createTrigger = function(dataTrigger) {
+ this.createTrigger = function(triggerData) {
var method = 'createTrigger';
// both couch and cloudant should have their URLs in the username:password@host format
- var dbURL = `${dataTrigger.protocol}://${dataTrigger.user}:${dataTrigger.pass}@${dataTrigger.host}`;
+ var dbURL = `${triggerData.protocol}://${triggerData.user}:${triggerData.pass}@${triggerData.host}`;
// add port if specified
- if (dataTrigger.port) {
- dbURL += ':' + dataTrigger.port;
+ if (triggerData.port) {
+ dbURL += ':' + triggerData.port;
}
try {
var nanoConnection = require('cloudant-nano')(dbURL);
- var triggeredDB = nanoConnection.use(dataTrigger.dbname);
+ var triggeredDB = nanoConnection.use(triggerData.dbname);
// Listen for changes on this database.
- var feed = triggeredDB.follow({since: dataTrigger.since, include_docs: false});
- if (dataTrigger.filter) {
- feed.filter = dataTrigger.filter;
+ var feed = triggeredDB.follow({since: triggerData.since, include_docs: false});
+ if (triggerData.filter) {
+ feed.filter = triggerData.filter;
}
- if (dataTrigger.query_params) {
- feed.query_params = dataTrigger.query_params;
+ if (triggerData.query_params) {
+ feed.query_params = triggerData.query_params;
}
- dataTrigger.feed = feed;
- self.triggers[dataTrigger.id] = dataTrigger;
+ triggerData.feed = feed;
+ self.triggers[triggerData.id] = triggerData;
feed.on('change', function (change) {
- var triggerHandle = self.triggers[dataTrigger.id];
+ var triggerHandle = self.triggers[triggerData.id];
if (triggerHandle && shouldFireTrigger(triggerHandle) && hasTriggersRemaining(triggerHandle)) {
- logger.info(method, 'Trigger', dataTrigger.id, 'got change from', dataTrigger.dbname);
+ logger.info(method, 'Trigger', triggerData.id, 'got change from', triggerData.dbname);
try {
- fireTrigger(dataTrigger.id, change);
+ fireTrigger(triggerData.id, change);
} catch (e) {
- logger.error(method, 'Exception occurred while firing trigger', dataTrigger.id, e);
+ logger.error(method, 'Exception occurred while firing trigger', triggerData.id, e);
}
}
});
@@ -69,21 +70,21 @@
return new Promise(function(resolve, reject) {
feed.on('error', function (err) {
- logger.error(method,'Error occurred for trigger', dataTrigger.id, '(db ' + dataTrigger.dbname + '):', err);
+ logger.error(method,'Error occurred for trigger', triggerData.id, '(db ' + triggerData.dbname + '):', err);
reject(err);
});
feed.on('confirm', function () {
- logger.info(method, 'Added cloudant data trigger', dataTrigger.id, 'listening for changes in database', dataTrigger.dbname);
- if (isMonitoringTrigger(dataTrigger.monitor, dataTrigger.id)) {
+ logger.info(method, 'Added cloudant data trigger', triggerData.id, 'listening for changes in database', triggerData.dbname);
+ if (isMonitoringTrigger(triggerData.monitor, triggerData.id)) {
self.monitorStatus.triggerStarted = "success";
}
- resolve(dataTrigger.id);
+ resolve(triggerData.id);
});
});
} catch (err) {
- logger.info(method, 'caught an exception for trigger', dataTrigger.id, err);
+ logger.info(method, 'caught an exception for trigger', triggerData.id, err);
return Promise.reject(err);
}
@@ -105,7 +106,8 @@
maxTriggers: maxTriggers,
triggersLeft: maxTriggers,
filter: newTrigger.filter,
- query_params: newTrigger.query_params
+ query_params: newTrigger.query_params,
+ additionalData: newTrigger.additionalData
};
return trigger;
@@ -181,27 +183,27 @@
function fireTrigger(triggerIdentifier, change) {
var method = 'fireTrigger';
- var dataTrigger = self.triggers[triggerIdentifier];
- var triggerObj = parseQName(dataTrigger.id);
+ var triggerData = self.triggers[triggerIdentifier];
+ var triggerObj = parseQName(triggerData.id);
var form = change;
- form.dbname = dataTrigger.dbname;
+ form.dbname = triggerData.dbname;
- logger.info(method, 'firing trigger', dataTrigger.id, 'with db update');
+ logger.info(method, 'firing trigger', triggerData.id, 'with db update');
var host = 'https://' + self.routerHost + ':' + 443;
var uri = host + '/api/v1/namespaces/' + triggerObj.namespace + '/triggers/' + triggerObj.name;
- var auth = dataTrigger.apikey.split(':');
+ var auth = triggerData.apikey.split(':');
- postTrigger(dataTrigger, form, uri, auth, 0)
+ postTrigger(triggerData, form, uri, auth, 0)
.then(triggerId => {
logger.info(method, 'Trigger', triggerId, 'was successfully fired');
- if (isMonitoringTrigger(dataTrigger.monitor, triggerId)) {
+ if (isMonitoringTrigger(triggerData.monitor, triggerId)) {
self.monitorStatus.triggerFired = "success";
}
- if (dataTrigger.triggersLeft === 0) {
- if (dataTrigger.monitor) {
- deleteTrigger(triggerId, dataTrigger.monitor);
+ if (triggerData.triggersLeft === 0) {
+ if (triggerData.monitor) {
+ deleteTrigger(triggerId, triggerData.monitor);
}
else {
disableTrigger(triggerId, undefined, 'Automatically disabled after reaching max triggers');
@@ -214,45 +216,41 @@
});
}
- function postTrigger(dataTrigger, form, uri, auth, retryCount) {
+ function postTrigger(triggerData, form, uri, auth, retryCount) {
var method = 'postTrigger';
return new Promise(function(resolve, reject) {
// only manage trigger fires if they are not infinite
- if (dataTrigger.maxTriggers !== -1) {
- dataTrigger.triggersLeft--;
+ if (triggerData.maxTriggers !== -1) {
+ triggerData.triggersLeft--;
}
- request({
+ self.authRequest(triggerData, {
method: 'post',
uri: uri,
- auth: {
- user: auth[0],
- pass: auth[1]
- },
json: form
}, function(error, response) {
try {
- logger.info(method, dataTrigger.id, 'http post request, STATUS:', response ? response.statusCode : response);
+ logger.info(method, triggerData.id, 'http post request, STATUS:', response ? response.statusCode : response);
if (error || response.statusCode >= 400) {
// only manage trigger fires if they are not infinite
- if (dataTrigger.maxTriggers !== -1) {
- dataTrigger.triggersLeft++;
+ if (triggerData.maxTriggers !== -1) {
+ triggerData.triggersLeft++;
}
- logger.error(method, 'there was an error invoking', dataTrigger.id, response ? response.statusCode : error);
+ logger.error(method, 'there was an error invoking', triggerData.id, response ? response.statusCode : error);
if (!error && shouldDisableTrigger(response.statusCode)) {
//disable trigger
var message = 'Automatically disabled after receiving a ' + response.statusCode + ' status code when firing the trigger';
- disableTrigger(dataTrigger.id, response.statusCode, message);
- reject('Disabled trigger ' + dataTrigger.id + ' due to status code: ' + response.statusCode);
+ disableTrigger(triggerData.id, response.statusCode, message);
+ reject('Disabled trigger ' + triggerData.id + ' due to status code: ' + response.statusCode);
}
else {
if (retryCount < retryAttempts ) {
var timeout = response && response.statusCode === 429 && retryCount === 0 ? 60000 : 1000 * Math.pow(retryCount + 1, 2);
- logger.info(method, 'attempting to fire trigger again', dataTrigger.id, 'Retry Count:', (retryCount + 1));
+ logger.info(method, 'attempting to fire trigger again', triggerData.id, 'Retry Count:', (retryCount + 1));
setTimeout(function () {
- postTrigger(dataTrigger, form, uri, auth, (retryCount + 1))
+ postTrigger(triggerData, form, uri, auth, (retryCount + 1))
.then(triggerId => {
resolve(triggerId);
})
@@ -261,12 +259,12 @@
});
}, timeout);
} else {
- reject('Unable to reach server to fire trigger ' + dataTrigger.id);
+ reject('Unable to reach server to fire trigger ' + triggerData.id);
}
}
} else {
- logger.info(method, 'fired', dataTrigger.id, dataTrigger.triggersLeft, 'triggers left');
- resolve(dataTrigger.id);
+ logger.info(method, 'fired', triggerData.id, triggerData.triggersLeft, 'triggers left');
+ resolve(triggerData.id);
}
}
catch(err) {
@@ -294,16 +292,11 @@
var triggerObj = parseQName(triggerIdentifier);
var host = 'https://' + self.routerHost + ':' + 443;
var triggerURL = host + '/api/v1/namespaces/' + triggerObj.namespace + '/triggers/' + triggerObj.name;
- var auth = doc.apikey.split(':');
logger.info(method, 'Checking if trigger', triggerIdentifier, 'still exists');
- request({
+ self.authRequest(doc, {
method: 'get',
- url: triggerURL,
- auth: {
- user: auth[0],
- pass: auth[1]
- }
+ url: triggerURL
}, function (error, response) {
//disable trigger in database if trigger is dead
if (!error && shouldDisableTrigger(response.statusCode)) {
@@ -499,4 +492,18 @@
}
}
+ this.authRequest = function(triggerData, options, cb) {
+ var method = 'authRequest';
+
+ authHandler.handleAuth(triggerData)
+ .then(auth => {
+ options.auth = auth;
+ request(options, cb);
+ })
+ .catch(err => {
+ logger.error(method, err);
+ request(options, cb);
+ });
+ };
+
};