diff --git a/.cfignore b/.cfignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.cfignore @@ -0,0 +1 @@ +node_modules/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..77a8d4f --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +logs/* +!.gitkeep +node_modules/ +tmp +.DS_Store +.idea diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..8ff7b66 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: node ./server/app.js \ No newline at end of file diff --git a/manifest.yml b/manifest.yml new file mode 100644 index 0000000..50e0c23 --- /dev/null +++ b/manifest.yml @@ -0,0 +1,6 @@ +--- +applications: +- name: cf-mysql-node-broker +memory: 256M +instances: 1 +host: cf-mysql-node-broker \ No newline at end of file diff --git a/npm-debug.log b/npm-debug.log new file mode 100644 index 0000000..e14c91a --- /dev/null +++ b/npm-debug.log @@ -0,0 +1,36 @@ +0 info it worked if it ends with ok +1 verbose cli [ 'node', '/usr/local/bin/npm', 'start' ] +2 info using npm@2.1.8 +3 info using node@v0.10.33 +4 verbose node symlink /usr/local/bin/node +5 verbose run-script [ 'prestart', 'start', 'poststart' ] +6 info prestart cf-mysql-node-broker@1.0.0 +7 info start cf-mysql-node-broker@1.0.0 +8 verbose unsafe-perm in lifecycle true +9 info cf-mysql-node-broker@1.0.0 Failed to exec start script +10 verbose stack Error: cf-mysql-node-broker@1.0.0 start: `node ./server/app.js` +10 verbose stack Exit status 8 +10 verbose stack at EventEmitter. (/usr/local/lib/node_modules/npm/lib/utils/lifecycle.js:212:16) +10 verbose stack at EventEmitter.emit (events.js:98:17) +10 verbose stack at ChildProcess. (/usr/local/lib/node_modules/npm/lib/utils/spawn.js:14:12) +10 verbose stack at ChildProcess.emit (events.js:98:17) +10 verbose stack at maybeClose (child_process.js:756:16) +10 verbose stack at Process.ChildProcess._handle.onexit (child_process.js:823:5) +11 verbose pkgid cf-mysql-node-broker@1.0.0 +12 verbose cwd /Users/xul15/Documents/work/nodejs_app/cf-mysql-node-broker +13 error Darwin 13.4.0 +14 error argv "node" "/usr/local/bin/npm" "start" +15 error node v0.10.33 +16 error npm v2.1.8 +17 error code ELIFECYCLE +18 error cf-mysql-node-broker@1.0.0 start: `node ./server/app.js` +18 error Exit status 8 +19 error Failed at the cf-mysql-node-broker@1.0.0 start script 'node ./server/app.js'. +19 error This is most likely a problem with the cf-mysql-node-broker package, +19 error not with npm itself. +19 error Tell the author that this fails on your system: +19 error node ./server/app.js +19 error You can get their info via: +19 error npm owner ls cf-mysql-node-broker +19 error There is likely additional logging output above. +20 verbose exit [ 1, true ] diff --git a/package.json b/package.json new file mode 100644 index 0000000..9086110 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "cf-mysql-node-broker", + "version": "1.0.0", + "description": "mysql service broker built on node.js for cloud foundry", + "main": "index.js", + "scripts": { + "start": "node ./server/app.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/komushi/cf-mysql-node-broker.git" + }, + "keywords": [ + "cf", + "cloud", + "foundry", + "service", + "broker" + ], + "author": "Lei Xu", + "license": "ISC", + "dependencies": { + "express": "~4.0.0", + "body-parser":"1.10.x", + "basic-auth":"~1.0.0", + "fs":"~0.0.2", + "js-yaml":"3.2.x", + "q":"1.1.x", + "mysql":"~2.5.0", + "cfenv":"1.0.x" + + }, + "bugs": { + "url": "https://github.com/komushi/cf-mysql-node-broker/issues" + }, + "homepage": "https://github.com/komushi/cf-mysql-node-broker" +} diff --git a/resources/mysql.yml b/resources/mysql.yml new file mode 100644 index 0000000..034b73c --- /dev/null +++ b/resources/mysql.yml @@ -0,0 +1,4 @@ +host: localhost +port: 3306 +user: root +password: \ No newline at end of file diff --git a/resources/settings.yml b/resources/settings.yml new file mode 100644 index 0000000..581dd01 --- /dev/null +++ b/resources/settings.yml @@ -0,0 +1,32 @@ +# This file should not be used in deployed environments. Instead, set +# the SETTINGS_PATH environment variable to point to a configuration +# file that contains these settings. + + +services: +- name: p-mysql + id: 3101b971-1044-4816-a7ac-9ded2e028079 + description: MySQL service for application development and testing + tags: + - mysql + - relational + max_db_per_node: 250 + metadata: + displayName: MySQL for Pivotal CF + imageUrl: data:image/png;base64,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 + longDescription: Provisioning a service instance creates a MySQL database. Binding applications to the instance creates unique credentials for each application to access the database + providerDisplayName: Pivotal Software + documentationUrl: http://docs.pivotal.io/p-mysql + supportUrl: http://support.pivotal.io + plans: + - name: 5mb + id: 2451fa22-df16-4c10-ba6e-1f682d3dcdc9 + description: Shared MySQL Server, 5mb persistent disk, 40 max concurrent connections + max_storage_mb: 5 # in MB + metadata: + cost: 0.0 + bullets: + - content: Shared MySQL server + - content: 5 MB storage + - content: 40 concurrent connections + diff --git a/server/app.js b/server/app.js new file mode 100644 index 0000000..d55955b --- /dev/null +++ b/server/app.js @@ -0,0 +1,44 @@ +console.log("process.cwd():" + process.cwd()); + +/**************************/ +/* config */ +var application_root = __dirname; +var express = require("express"); +var path = require("path"); +var Q = require("q"); +var bodyParser = require('body-parser'); +// var api = require('./controllers/catalogController'); +var routes = require('./routes/routes'); +var basicAuth = require('./utils/basicAuth'); +var app = express(); + +var allowCrossDomain = function(req, res, next) { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS'); + res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With'); + + // intercept OPTIONS method + if ('OPTIONS' == req.method) { + res.send(200); + } + else { + next(); + } +}; + +app.disable('etag'); +app.use(allowCrossDomain); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ + extended: true +})); +app.use(express.static(path.join(application_root, "public"))); +app.use('/v2', basicAuth.auth('test', 'test')); +app.use('/', routes); + +/* config */ +/**************************/ + +var server = app.listen((process.env.PORT || 9000), function() { + console.log('Express server listening on port ' + server.address().port); +}); diff --git a/server/controllers/catalogController.js b/server/controllers/catalogController.js new file mode 100644 index 0000000..5826b20 --- /dev/null +++ b/server/controllers/catalogController.js @@ -0,0 +1,12 @@ +var resourceManager = require('../utils/resourceManager'); + +exports.getCatalog = function(req, res) { + + resourceManager.getResource("./resources/settings.yml", function (err, jsonString) { + + res.set('Content-Type', 'application/json'); + res.send(jsonString); + }); + +}; + diff --git a/server/controllers/serviceInstanceController.js b/server/controllers/serviceInstanceController.js new file mode 100644 index 0000000..eb4c3d4 --- /dev/null +++ b/server/controllers/serviceInstanceController.js @@ -0,0 +1,22 @@ +var service = require('../services/serviceInstanceService.js'); + +exports.getMySQL = function(req, res) { + service.executeQuery("select 1 + 1 as result", function (err, result) { + + console.log("result:" + JSON.stringify(result)); + res.set('Content-Type', 'application/json'); + res.send(result); + }); + +}; + + +exports.create = function(req, res) { + service.update(req.params.id, function (err, result) { + + console.log("result:" + JSON.stringify(result)); + res.set('Content-Type', 'application/json'); + res.send(result); + }); + +}; diff --git a/server/routes/routes.js b/server/routes/routes.js new file mode 100644 index 0000000..2acf372 --- /dev/null +++ b/server/routes/routes.js @@ -0,0 +1,47 @@ +var express=require('express'); +var catalogController = require('../controllers/catalogController'); +var serviceInstanceController = require('../controllers/serviceInstanceController'); + +//configure routes + +var router=express.Router(); + +/**************************/ +/* REST API hello */ +router.route('/v2') + .get(function (req, res) { + res.send('REST API is running.'); + console.log("REST API is running."); +}); +/* REST API hello */ +/**************************/ + +/**************************/ +/* REST API /v2/catalog */ +router.route('/v2/catalog') + .get(function (req, res) { + console.log("Begin: /v2/catalog"); + + catalogController.getCatalog(req, res); + + console.log("End: /v2/catalog"); +}); + +/* REST API getCountByCat */ +/**************************/ + +/**************************/ +/* REST API /v2/catalog */ +router.route('/v2/service_instances/:instance_id') + .put(function (req, res) { + console.log("Begin: /v2/service_instances/:instance_id"); + + serviceInstanceController.getMySQL(req, res); + + console.log("End: /v2/service_instances/:instance_id"); +}); + +/* REST API getCountByCat */ +/**************************/ + +module.exports=router; \ No newline at end of file diff --git a/server/services/serviceInstanceService.js b/server/services/serviceInstanceService.js new file mode 100644 index 0000000..9c28d68 --- /dev/null +++ b/server/services/serviceInstanceService.js @@ -0,0 +1,21 @@ +var mysqlManager = require('../utils/mysqlManager'); + + +exports.executeQuery = function(queryText, next) { + + + mysqlManager.executeQuery(queryText, next); + + +}; + + +exports.update = function(instanceId, next) { + + + var queryText = "CREATE DATABASE " + instanceId; + + mysqlManager.executeQuery(queryText, next); + + +}; \ No newline at end of file diff --git a/server/utils/basicAuth.js b/server/utils/basicAuth.js new file mode 100644 index 0000000..8d913df --- /dev/null +++ b/server/utils/basicAuth.js @@ -0,0 +1,29 @@ + +/** + * Your utility library for express + */ + +var basicAuth = require('basic-auth'); + +/** + * Simple basic auth middleware for use with Express 4.x. + * + * @example + * app.use('/api-requiring-auth', basicAuth.auth('username', 'password')); + * + * @param {string} username Expected username + * @param {string} password Expected password + * @returns {function} Express 4 middleware requiring the given credentials + */ +exports.auth = function(username, password) { + return function(req, res, next) { + var user = basicAuth(req); + + if (!user || user.name !== username || user.pass !== password) { + res.set('WWW-Authenticate', 'Basic realm=Authorization Required'); + return res.send(401); + } + + next(); + }; +}; diff --git a/server/utils/mysqlManager.js b/server/utils/mysqlManager.js new file mode 100644 index 0000000..f442d65 --- /dev/null +++ b/server/utils/mysqlManager.js @@ -0,0 +1,125 @@ + +/** + * Your utility library for express + */ + + +var mysql = require('mysql'); +var resourceManager = require('../utils/resourceManager.js'); + + +var createPool = function (next) +{ + resourceManager.getResource("./resources/mysql.yml", function (err, jsonString) { + + var mysqlPool; + var error = null; + if (!err && jsonString) + { + var jsonObj = JSON.parse(jsonString); + mysqlPool = mysql.createPool({ + host : jsonObj.host, + port : jsonObj.port, + user : jsonObj.user, + password : jsonObj.password + }); + } + else + { + console.log(err); + error = err; + } + + return next(error, mysqlPool); + + }); + +}; + +var getConnection = function (pool, next) +{ + + pool.getConnection(function (err, connection) { + + var mysqlConn; + var error = null; + + if (!err && connection) + { + mysqlConn = connection; + } + else + { + console.log(err); + error = err; + } + + return next(error, mysqlConn); + + }); + +}; + +/** + * Simple basic auth middleware for use with Express 4.x. + * + * @example + * mgr.execute('select 1 as field1', function (error, result){}); + * + * @param {string} queryText + * @returns {function} + */ +exports.executeQuery = function(queryText, next){ + + + createPool(function (err, pool) { + + if (err) + { + return next(err, null); + } + + getConnection(pool, function(err, connection) { + + if (err) + { + return next(err, null); + } + + + + // Use the connection + connection.query(queryText, function(err, rows) { + + var error = null; + var result; + + // And done with the connection. + connection.release(); + + if (!err && rows) + { + result = rows; + } + else + { + console.log(err); + error = err; + } + + + + console.log(queryText); + console.log(rows); + + result = rows; + + return next(error, result); + }); + }); + + + }); + + +}; diff --git a/server/utils/resourceManager.js b/server/utils/resourceManager.js new file mode 100644 index 0000000..245372d --- /dev/null +++ b/server/utils/resourceManager.js @@ -0,0 +1,24 @@ +var yaml = require("js-yaml"); +var fs = require("fs"); + + +exports.getResource = function(resourcePath, next) { + + var resx; + var jsonString; + var error = null; + + try { + // the synchronous code that we want to catch thrown errors on + resx = yaml.load(fs.readFileSync(resourcePath)); + jsonString = JSON.stringify(resx); + } + catch (err) { + // handle the error safely + console.log(err); + error = err; + + } + + return next(error, jsonString); +};