Add support for IAM based Cloudant DB instances (#184)
diff --git a/.gitignore b/.gitignore
index 4891796..009fe45 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
actions/event-actions/package.json
.idea
*.iml
+package-lock.json
# Eclipse
bin/
diff --git a/Dockerfile b/Dockerfile
index 2df3aac..e5e8a21 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,17 +1,4 @@
-FROM ubuntu:14.04
-
-ENV DEBIAN_FRONTEND noninteractive
-
-# Initial update and some basics.
-# This odd double update seems necessary to get curl to download without 404 errors.
-RUN apt-get update --fix-missing && \
- apt-get install -y wget && \
- apt-get update && \
- apt-get install -y curl && \
- apt-get update && \
- apt-get remove -y nodejs && \
- curl -sL https://deb.nodesource.com/setup_8.x | bash - && \
- apt-get install -y nodejs
+FROM node:8.12.0
# only package.json
ADD package.json /
@@ -23,4 +10,4 @@
EXPOSE 8080
# Run the app
-CMD ["/bin/bash", "-c", "node /cloudantTrigger/app.js >> /logs/cloudantTrigger_logs.log 2>&1"]
+CMD ["/bin/bash", "-c", "node /cloudantTrigger/app.js"]
diff --git a/actions/event-actions/changesWebAction.js b/actions/event-actions/changesWebAction.js
index af2b0e8..1e171ea 100644
--- a/actions/event-actions/changesWebAction.js
+++ b/actions/event-actions/changesWebAction.js
@@ -29,11 +29,10 @@
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');
+ if (!params.iamApiKey) {
+ if (!params.username || !params.password) {
+ return common.sendError(400, 'cloudant trigger feed: Must specify parameter/s of iamApiKey or username/password');
+ }
}
var query_params;
@@ -56,30 +55,34 @@
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) {
+ var newTrigger;
+
common.verifyTriggerAuth(triggerData, false)
.then(() => {
db = new Database(params.DB_URL, params.DB_NAME);
+
+ 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,
+ iamApiKey: params.iamApiKey,
+ iamUrl: params.iamUrl || 'https://iam.bluemix.net/identity/token'
+ };
return verifyUserDB(newTrigger);
})
.then(() => {
@@ -124,6 +127,8 @@
since: doc.since,
filter: doc.filter,
query_params: doc.query_params,
+ iamApiKey: doc.iamApiKey,
+ iamUrl: doc.iamUrl
},
status: {
active: doc.status.active,
@@ -242,17 +247,28 @@
}
function verifyUserDB(triggerObj) {
- var dbURL = `${triggerObj.protocol}://${triggerObj.user}:${triggerObj.pass}@${triggerObj.host}`;
- // add port if specified
- if (triggerObj.port) {
- dbURL += ':' + triggerObj.port;
+ var Cloudant = require('@cloudant/cloudant');
+ var cloudant;
+
+ if (triggerObj.iamApiKey) {
+ var dbURL = `${triggerObj.protocol}://${triggerObj.host}`;
+ if (triggerObj.port) {
+ dbURL += ':' + triggerObj.port;
+ }
+ cloudant = new Cloudant({ url: dbURL, plugins: { iamauth: { iamApiKey: triggerObj.iamApiKey, iamTokenUrl: triggerObj.iamUrl } } });
+ }
+ else {
+ var url = `${triggerObj.protocol}://${triggerObj.user}:${triggerObj.pass}@${triggerObj.host}`;
+ if (triggerObj.port) {
+ url += ':' + triggerObj.port;
+ }
+ cloudant = Cloudant(url);
}
return new Promise(function(resolve, reject) {
try {
- var nanoConnection = require('nano')(dbURL);
- var userDB = nanoConnection.use(triggerObj.dbname);
+ var userDB = cloudant.use(triggerObj.dbname);
userDB.info(function(err, body) {
if (!err) {
resolve();
diff --git a/actions/event-actions/changesWeb_package.json b/actions/event-actions/changesWeb_package.json
index f2b3fc6..5c46d8d 100644
--- a/actions/event-actions/changesWeb_package.json
+++ b/actions/event-actions/changesWeb_package.json
@@ -1,5 +1,8 @@
{
"name": "changesWebAction",
"version": "1.0.0",
- "main": "changesWebAction.js"
+ "main": "changesWebAction.js",
+ "dependencies" : {
+ "@cloudant/cloudant": "3.0.0"
+ }
}
diff --git a/actions/event-actions/lib/Database.js b/actions/event-actions/lib/Database.js
index 962b2df..9bc180d 100644
--- a/actions/event-actions/lib/Database.js
+++ b/actions/event-actions/lib/Database.js
@@ -2,8 +2,8 @@
// 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 cloudant = require('@cloudant/cloudant')(dbURL);
+ this.db = cloudant.db.use(dbName);
var utilsDB = this;
this.getWorkerID = function(availabeWorkers) {
diff --git a/installCatalog.sh b/installCatalog.sh
index 7ce6d5a..7787ec7 100755
--- a/installCatalog.sh
+++ b/installCatalog.sh
@@ -51,12 +51,8 @@
$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"} ]' \
+ -a parameters '[ {"name":"bluemixServiceName", "required":false, "bindTime":true}, {"name":"username", "required":false, "bindTime":true, "description": "Your Cloudant username"}, {"name":"password", "required":false, "type":"password", "bindTime":true, "description": "Your Cloudant password"}, {"name":"host", "required":true, "bindTime":true, "description": "This is usually your username.cloudant.com"}, {"name":"iamApiKey", "required":false}, {"name":"iamUrl", "required":false}, {"name":"dbname", "required":false, "description": "The name of your Cloudant database"}, {"name":"overwrite", "required":false, "type": "boolean"} ]' \
-p bluemixServiceName 'cloudantNoSQLDB' \
- -p host '' \
- -p username '' \
- -p password '' \
- -p dbname '' \
-p apihost "$APIHOST"
# make changesFeed.zip
@@ -73,7 +69,7 @@
-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 parameters '[ {"name":"dbname", "required":true, "updatable":false}, {"name":"iamApiKey", "required":false, "updatable":false}, {"name":"iamUrl", "required":false, "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"} }'
COMMAND=" -i --apihost $EDGEHOST package update --auth $AUTH --shared no cloudantWeb \
@@ -89,12 +85,13 @@
# make changesWebAction.zip
cp -f changesWeb_package.json package.json
+npm install
if [ -e changesWebAction.zip ]; then
rm -rf changesWebAction.zip
fi
-zip -r changesWebAction.zip lib package.json changesWebAction.js
+zip -r changesWebAction.zip lib package.json changesWebAction.js node_modules
$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' \
diff --git a/package.json b/package.json
index 2b8ecb0..6214f12 100644
--- a/package.json
+++ b/package.json
@@ -12,12 +12,12 @@
"moment": "^2.11.1",
"lodash": "^3.10.1",
"request": "^2.83.0",
- "cloudant-nano": "6.7.0",
+ "@cloudant/cloudant": "3.0.0",
"json-stringify-safe": "^5.0.1",
"http-status-codes": "^1.0.5",
"request-promise": "^1.0.2",
- "redis":"^2.7.1",
+ "redis": "^2.7.1",
"bluebird": "^3.5.0",
"systeminformation": "^3.19.0"
}
-}
\ No newline at end of file
+}
diff --git a/provider/app.js b/provider/app.js
index fc9149d..2488931 100644
--- a/provider/app.js
+++ b/provider/app.js
@@ -49,11 +49,11 @@
var method = 'createDatabase';
logger.info(method, 'creating the trigger database');
- var nano = require('cloudant-nano')(dbProtocol + '://' + dbUsername + ':' + dbPassword + '@' + dbHost);
+ var cloudant = require('@cloudant/cloudant')(dbProtocol + '://' + dbUsername + ':' + dbPassword + '@' + dbHost);
- if (nano !== null) {
+ if (cloudant !== null) {
return new Promise(function (resolve, reject) {
- nano.db.create(databaseName, function (err, body) {
+ cloudant.db.create(databaseName, function (err, body) {
if (!err) {
logger.info(method, 'created trigger database:', databaseName);
}
@@ -74,7 +74,7 @@
}
};
- createDesignDoc(nano.db.use(databaseName), viewDDName, viewDD)
+ createDesignDoc(cloudant.db.use(databaseName), viewDDName, viewDD)
.then(db => {
var filterDD = {
filters: {
@@ -114,7 +114,7 @@
});
}
else {
- Promise.reject('nano provider did not get created. check db URL: ' + dbHost);
+ Promise.reject('cloudant provider did not get created. check db URL: ' + dbHost);
}
}
@@ -181,7 +181,7 @@
// Initialize the Provider Server
function init(server) {
var method = 'init';
- var nanoDb;
+ var cloudantDb;
var providerUtils;
if (server !== null) {
@@ -194,11 +194,11 @@
createDatabase()
.then(db => {
- nanoDb = db;
+ cloudantDb = db;
return createRedisClient();
})
.then(client => {
- providerUtils = new ProviderUtils(logger, nanoDb, client);
+ providerUtils = new ProviderUtils(logger, cloudantDb, client);
return providerUtils.initRedis();
})
.then(() => {
diff --git a/provider/lib/utils.js b/provider/lib/utils.js
index 4f08530..aa12c6d 100644
--- a/provider/lib/utils.js
+++ b/provider/lib/utils.js
@@ -30,17 +30,26 @@
this.createTrigger = function(triggerData) {
var method = 'createTrigger';
- // both couch and cloudant should have their URLs in the username:password@host format
- var dbURL = `${triggerData.protocol}://${triggerData.user}:${triggerData.pass}@${triggerData.host}`;
+ var Cloudant = require('@cloudant/cloudant');
+ var cloudantConnection;
- // add port if specified
- if (triggerData.port) {
- dbURL += ':' + triggerData.port;
+ if (triggerData.iamApiKey) {
+ var dbURL = `${triggerData.protocol}://${triggerData.host}`;
+ if (triggerData.port) {
+ dbURL += ':' + triggerData.port;
+ }
+ cloudantConnection = new Cloudant({ url: dbURL, plugins: { iamauth: { iamApiKey: triggerData.iamApiKey, iamTokenUrl: triggerData.iamUrl } } });
+ }
+ else {
+ var url = `${triggerData.protocol}://${triggerData.user}:${triggerData.pass}@${triggerData.host}`;
+ if (triggerData.port) {
+ url += ':' + triggerData.port;
+ }
+ cloudantConnection = Cloudant(url);
}
try {
- var nanoConnection = require('cloudant-nano')(dbURL);
- var triggeredDB = nanoConnection.use(triggerData.dbname);
+ var triggeredDB = cloudantConnection.use(triggerData.dbname);
// Listen for changes on this database.
var feed = triggeredDB.follow({since: triggerData.since, include_docs: false});
@@ -107,7 +116,9 @@
triggersLeft: maxTriggers,
filter: newTrigger.filter,
query_params: newTrigger.query_params,
- additionalData: newTrigger.additionalData
+ additionalData: newTrigger.additionalData,
+ iamApiKey: newTrigger.iamApiKey,
+ iamUrl: newTrigger.iamUrl
};
return trigger;
diff --git a/tests/src/test/scala/system/packages/CloudantFeedTests.scala b/tests/src/test/scala/system/packages/CloudantFeedTests.scala
index 00c370a..7e70d8c 100644
--- a/tests/src/test/scala/system/packages/CloudantFeedTests.scala
+++ b/tests/src/test/scala/system/packages/CloudantFeedTests.scala
@@ -132,7 +132,7 @@
"host" -> myCloudantCreds.host().toJson),
expectedExitCode = 246)
}
- feedCreationResult.stderr should include("cloudant trigger feed: missing password parameter")
+ feedCreationResult.stderr should include("cloudant trigger feed: Must specify parameter/s of iamApiKey or username/password")
}
@@ -163,7 +163,7 @@
"host" -> myCloudantCreds.host().toJson),
expectedExitCode = 246)
}
- feedCreationResult.stderr should include("cloudant trigger feed: missing username parameter")
+ feedCreationResult.stderr should include("cloudant trigger feed: Must specify parameter/s of iamApiKey or username/password")
}
diff --git a/tests/src/test/scala/system/packages/CloudantFeedWebTests.scala b/tests/src/test/scala/system/packages/CloudantFeedWebTests.scala
index da9d518..499e61a 100644
--- a/tests/src/test/scala/system/packages/CloudantFeedWebTests.scala
+++ b/tests/src/test/scala/system/packages/CloudantFeedWebTests.scala
@@ -70,13 +70,13 @@
it should "reject post of a trigger due to missing username argument" in {
val params = JsObject(requiredParams.fields - "username")
- makePostCallWithExpectedResult(params, JsObject("error" -> JsString("cloudant trigger feed: missing username parameter")), 400)
+ makePostCallWithExpectedResult(params, JsObject("error" -> JsString("cloudant trigger feed: Must specify parameter/s of iamApiKey or username/password")), 400)
}
it should "reject post of a trigger due to missing password argument" in {
val params = JsObject(requiredParams.fields - "password")
- makePostCallWithExpectedResult(params, JsObject("error" -> JsString("cloudant trigger feed: missing password parameter")), 400)
+ makePostCallWithExpectedResult(params, JsObject("error" -> JsString("cloudant trigger feed: Must specify parameter/s of iamApiKey or username/password")), 400)
}
it should "reject post of a trigger due to missing dbname argument" in {