├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bin └── carapace ├── examples ├── app │ └── server.js ├── chdirserver.sh ├── coffee-script.coffee └── spawner.js ├── lib ├── carapace.js ├── cli.js └── plugins │ ├── chdir.js │ ├── coffee.js │ ├── heartbeat.js │ ├── net.js │ └── setuid.js ├── package.json └── test ├── fixtures ├── checkargs.js ├── checkchildargs.js ├── custom.js ├── eacces.js ├── multi-server.js └── pluginserver.js ├── helper └── macros.js ├── net ├── errors-test.js └── net-multiple-servers-test.js ├── simple ├── child-argument-test.js ├── use-custom-plugin-test.js ├── use-load-test.js ├── use-pluginserver-test.js └── use-run-test.js └── spawn ├── local-run-test.js ├── parent-custom-plugin-test.js ├── parent-pluginserver-test.js ├── parent-run-test.js └── process-run-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.node 2 | .DS_Store 3 | .lockScript 4 | .lock-wscript 5 | build/ 6 | node_modules/ 7 | npm-debug.log 8 | config.json 9 | *.sw[op] 10 | 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .lockScript 2 | .DS_Store 3 | .gitignore 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Nodejitsu Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # haibu-carapace 2 | 3 | Haibu Drone's Little Shell 4 | 5 | ## What is Carapace 6 | 7 | Carapace is an process wrapper for Node.js applications that is part of the [Haibu][1] Network. 8 | Carapace also provides a plugin system to simplify deployment and development of applications. 9 | 10 | ## What can I do with Carapace? 11 | 12 | By utilizing Carapace you can help automate deployments of applications into a custom environment. 13 | Combining Carapace with the [Forever][3] Daemon can allow you run the application in the environment indefinitely. 14 | 15 | ## Carapace CLI Options 16 | 17 | `carapace --plugin [plugin] --[plugin] [options] application [options]` 18 | 19 | #### *Plugins* 20 | `--plugin [plugin]` 21 | 22 | Plugin to use with the carapace instance 23 | 24 | #### *Plugin Options* 25 | `--[plugin] [options]` 26 | 27 | Option to be passed to the [plugin] 28 | 29 | #### *Application & Application's Options* 30 | `[application] [application's CLI options]` 31 | 32 | Any options that isn't consumed by the Carapace will automatically be passed to the application 33 | 34 | ## Default Plugins 35 | List of known plugins, and options (if any) used by them 36 | 37 | * chdir - directory to change into 38 | * heartbeat - time in micro-seconds between 'carapace::heartbeat' events 39 | * coffee - spawn `.coffee` files 40 | * setuid - set the uid of the spawned process 41 | * net - automatically listen on a new port if `EADDRINUSE` is thrown 42 | 43 | ## Installation 44 | 45 | ``` bash 46 | $ [sudo] npm install haibu-carapace 47 | ``` 48 | 49 | ## Run Tests 50 | All of the `carapace` tests are written in [vows][4] 51 | 52 | ``` bash 53 | $ npm test 54 | ``` 55 | 56 | #### Author: [Nodejitsu Inc.](http://www.nodejitsu.com) 57 | #### Maintainers: [Charlie Robbins](https://github.com/indexzero), [Bradley Meck](https://github.com/bmeck), [Jameson Lee](https://github.com/drjackal) 58 | 59 | [1]:https://github.com/nodejitsu/haibu 60 | [3]:https://github.com/indexzero/forever 61 | [4]:https://github.com/cloudhead/vows 62 | -------------------------------------------------------------------------------- /bin/carapace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'), 4 | _send = process.send || function(){}, 5 | async = require('async'), 6 | carapace = require('../lib/carapace'); 7 | 8 | // 9 | // Extract the carapace CLI arguments for this 10 | // process. 11 | // 12 | var argv = carapace.cli.argv(); 13 | carapace.script = argv._[0]; 14 | 15 | function onPluginError (info) { 16 | // 17 | // If any of the plugins have failed to load then short 18 | // circuit this carapace process because it requested them all. 19 | // 20 | console.log('Error loading plugin: ' + info.plugin); 21 | console.log(info.error.message); 22 | console.dir(info.error.stack.split('\n')); 23 | process.exit(1); 24 | } 25 | 26 | // 27 | // Setup the passthru arguments. 28 | // 29 | carapace.argv = carapace.cli.extract(null, carapace.script); 30 | 31 | function configure (next) { 32 | if (!Array.isArray(argv.plugin)) { 33 | argv.plugin = [argv.plugin]; 34 | } 35 | 36 | // 37 | // Listen for errors when loading plugins 38 | // 39 | carapace.on('plugin::error', onPluginError); 40 | 41 | var plugins = argv.plugin.map(function (plugin) { 42 | return carapace.load(plugin); 43 | }); 44 | 45 | carapace.use(plugins, function () { 46 | var names = argv.plugin.map(function (plugin) { 47 | return path.basename(plugin, '.js'); 48 | }); 49 | 50 | async.forEachSeries(names, function (plugin, next) { 51 | if (!carapace[plugin]) { 52 | return onPluginError({ 53 | plugin: plugin, 54 | error: new Error('No plugin `' + plugin + '` was found after loading.') 55 | }); 56 | } 57 | 58 | carapace[plugin](argv[plugin], function (err) { 59 | return !err ? next() : onPluginError({ 60 | plugin: plugin, 61 | error: err 62 | }); 63 | }); 64 | }, next); 65 | }); 66 | } 67 | 68 | function runAndReport () { 69 | carapace.run(carapace.script, true, function () { 70 | _send.call(process, 'carapace has wrapped: ' + carapace.script); 71 | 72 | function logArray (array, msg, delim) { 73 | return array && array.length 74 | ? _send.call(process, msg + array.join(delim)) 75 | : null; 76 | } 77 | 78 | logArray(carapace.argv, 'with arguments: ', ' '); 79 | logArray(Object.keys(carapace._module.exports), ' and exports: ', ', '); 80 | }); 81 | } 82 | 83 | return argv.plugin 84 | ? configure(runAndReport) 85 | : runAndReport(); 86 | 87 | -------------------------------------------------------------------------------- /examples/app/server.js: -------------------------------------------------------------------------------- 1 | var port, server; 2 | 3 | // default port to listen to 4 | port = 1337; 5 | 6 | // Unless a specific port is request via argv 7 | if (process.argv.length === 4) { 8 | // if we get a --port and the value passed is a number 9 | if (process.argv[2] === '--port' && Number(process.argv[3])) { 10 | // should set the passed value as the port 11 | port = Number(process.argv[3]); 12 | } 13 | } 14 | 15 | // start the http server 16 | server = require('http').createServer(function (req, res) { 17 | res.end(process.cwd()); 18 | }); 19 | 20 | // export before we leave 21 | exports.port = port; 22 | exports.server = server; 23 | 24 | // and start the server 25 | server.listen(port); 26 | -------------------------------------------------------------------------------- /examples/chdirserver.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ./bin/carapace --plugin chdir --chdir ./examples/app server.js --port 1337 4 | -------------------------------------------------------------------------------- /examples/coffee-script.coffee: -------------------------------------------------------------------------------- 1 | countdown = (num for num in [10..1]) 2 | console.error(countdown) 3 | -------------------------------------------------------------------------------- /examples/spawner.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | carapace = require('../lib/carapace'); 3 | 4 | var script = 'server.js', 5 | scriptPort = 31337; 6 | 7 | carapace.on('carapace::plugin::error', function (info) { 8 | console.log('Error loading plugin: ' + info.plugin); 9 | console.log(info.error.message); 10 | console.dir(info.error.stack.split('\n')) 11 | }); 12 | 13 | carapace.use([ 14 | carapace.plugins.heartbeat, 15 | carapace.plugins.chdir 16 | ], function () { 17 | carapace.chdir(path.join(__dirname, 'app')); 18 | carapace.run(script, ['--port', scriptPort], function afterRun() { 19 | carapace.heartbeat(function () { 20 | carapace.on('heartbeat', function () { 21 | console.log('still running'); 22 | }); 23 | }, 1000); 24 | console.log(script + ' running on port ' + scriptPort.toString()); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /lib/carapace.js: -------------------------------------------------------------------------------- 1 | /* 2 | * carapace.js: Top-level include for the haibu-carapace module. 3 | * 4 | * (C) 2011 Nodejitsu Inc. 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | var fs = require('fs'), 10 | net = require('net'), 11 | path = require('path'), 12 | events = require('eventemitter2'); 13 | 14 | var carapace = module.exports = new events.EventEmitter2({ 15 | delimeter: '::', 16 | wildcard: true 17 | }); 18 | 19 | if (process.send) { 20 | carapace.send = process.send.bind(process); 21 | delete process.send; 22 | } 23 | 24 | carapace.onAny(function (data) { 25 | // 26 | // Simple `EventEmitter` over `child_process.fork`. 27 | // 28 | if (carapace.send) { 29 | carapace.send({ event: this.event, data: data }); 30 | } 31 | }); 32 | 33 | // 34 | // Expose the `cli` module for default options 35 | // and liberal arguments parsing 36 | // 37 | carapace.cli = require('./cli'); 38 | carapace.bin = path.join(__dirname, '..', 'bin', 'carapace'); 39 | carapace.argv = []; 40 | 41 | // 42 | // Plugins list exposed through path names so that they 43 | // can be later required by `carapace.plugin()` or `carapace:plugin` events. 44 | // 45 | carapace.plugins = {}; 46 | 47 | // 48 | // Internal mapping of server instances to 49 | // ports that have been mapped via `.listen()`. 50 | // 51 | carapace.servers = {}; 52 | 53 | // 54 | // Internal state for managing various carapace operations: 55 | // * carapace.running: Value indicating if the target script has started. 56 | // * carapace.listening: Value indicating if the carapace `dnode` server has started. 57 | // 58 | carapace.running = false; 59 | carapace.listening = false; 60 | 61 | // 62 | // Store a set of reserved ports for which the `net.js` implementation 63 | // should actually throw an error, or not emit the `carapace::port` event. 64 | // 65 | carapace.ports = { 66 | throw: [], 67 | ignore: [] 68 | }; 69 | 70 | // 71 | // ### function use (plugins, callback) 72 | // #### @plugins {string|Array} List (or single) plugin add to the carapace. 73 | // #### @callback {function} Continuation to respond to when complete. 74 | // Enables the specified `plugins` in the carapace. 75 | // 76 | carapace.use = function (plugins, callback) { 77 | if (!Array.isArray(plugins)) { 78 | plugins = [plugins]; 79 | } 80 | 81 | plugins.forEach(function (plugin) { 82 | try { 83 | // 84 | // todo make this more flexible 85 | // this requires absolute path or node_module 86 | // 87 | require(plugin)(carapace); 88 | carapace.emit('plugin::loaded', { 89 | id: carapace.id, 90 | plugin: plugin 91 | }); 92 | } 93 | catch (ex) { 94 | carapace.emit('plugin::error', { 95 | id: carapace.id, 96 | plugin: plugin, 97 | error: ex 98 | }); 99 | } 100 | }); 101 | 102 | if (callback) { 103 | callback(); 104 | } 105 | 106 | return carapace; 107 | }; 108 | 109 | // 110 | // ### function run (script, argv, callback) 111 | // #### @script {string} Path to the script to run inside the carapace. 112 | // #### @argv {Array} Arguments to rewrite into process.argv 113 | // #### @override {Boolean} When true remove current process.argv and replace 114 | // with [script, argv]. If false remove all arguments that would be 115 | // overwritten by the new [script, argv] 116 | // #### @callback {function} Continuation to respond to when complete. 117 | // Runs the script in `argv[0]` with the rest of the arguments specified 118 | // in `argv` by transparently rewriting the current `process.argv`. 119 | // 120 | carapace.run = function (override, callback) { 121 | var error; 122 | 123 | Array.prototype.slice.call(arguments).forEach(function (a) { 124 | switch (typeof(a)) { 125 | case 'function': callback = a; break; 126 | case 'boolean': override = a; break; 127 | } 128 | }); 129 | 130 | if (!carapace.script) { 131 | error = new Error('Cannot spawn a script with no path.'); 132 | } 133 | else if (carapace.running) { 134 | error = new Error('Cannot spawn a new script, one is already running.'); 135 | } 136 | 137 | 138 | if (error) { 139 | return callback 140 | ? callback(error) 141 | : carapace.emit('error', { id: carapace.id, error: error }); 142 | } 143 | 144 | // 145 | // Rewrite `process.argv` so that `Module.runMain()` 146 | // will transparently locate and run the target script 147 | // and it will be completely unaware of the carapace. 148 | // 149 | carapace.cli.rewrite(carapace.script, carapace.argv, override); 150 | 151 | // 152 | // Clear the module cache so anything required by `haibu-carapace` 153 | // is reloaded as necessary. 154 | // 155 | require('module').Module._cache = {}; 156 | 157 | // 158 | // Setup `carapace.wrapped` contain information about 159 | // the wrapped script, then 160 | // 161 | carapace.wrapped = { 162 | script: carapace.script, 163 | argv: carapace.argv 164 | }; 165 | 166 | process.nextTick(function () { 167 | // 168 | // Next tick to prevent a leak from arguments 169 | // 170 | require('module').Module.runMain(); 171 | carapace._module = process.mainModule; 172 | 173 | carapace.running = true; 174 | carapace.emit('running', { 175 | id: carapace.id, 176 | script: carapace.script, 177 | argv: carapace.argv 178 | }); 179 | 180 | if (callback) { 181 | callback(); 182 | } 183 | }); 184 | 185 | return carapace; 186 | }; 187 | 188 | // 189 | // ### function load (script, callback) 190 | // #### @script {string} Path to the script to run inside the carapace 191 | // 192 | carapace.load = function (script) { 193 | if (script[0] === '.') { 194 | throw new Error('Cannot load relative paths into carapace. Provide an absolute path'); 195 | } 196 | 197 | var name = path.basename(script, '.js'); 198 | 199 | if (carapace.plugins[name]) { 200 | // if we already have the plugin, 201 | return carapace.plugins[name]; 202 | } 203 | 204 | if ('/' !== script[0]) { 205 | // 206 | // If it is not a relative or absolute path, make it absolute 207 | // from the current `process.cwd()`. 208 | // 209 | script = path.join(process.cwd(), script); 210 | } 211 | 212 | carapace.plugins[name] = script; 213 | carapace.emit('plugin::loaded', script); 214 | 215 | return script; 216 | }; 217 | 218 | // 219 | // Setup the filenames for each of the default plugins 220 | // 221 | fs.readdirSync(path.join(__dirname, 'plugins')).forEach(function (name) { 222 | carapace.load(path.join(__dirname, 'plugins', name)); 223 | }); 224 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | /* 2 | * cli.js: wrapper around `optimist` 3 | * 4 | * (C) 2011 Nodejitsu Inc. 5 | * 6 | */ 7 | 8 | var fs = require('fs'), 9 | path = require('path'), 10 | optimist = require('optimist'); 11 | 12 | var defaultOptions = exports.defaultOptions = { 13 | debug: { 14 | description: 'Indicate if carapace is in debug mode', 15 | boolean: true, 16 | default: false 17 | }, 18 | plugin: { 19 | description: 'Indicate if carapace should use the specified plugin', 20 | string: true 21 | }, 22 | setuid: { 23 | description: 'User to run as', 24 | string: true 25 | }, 26 | chdir: { 27 | description: 'Default path to change to in the jail', 28 | string: true, 29 | default: '.' 30 | }, 31 | heartbeat: { 32 | description : 'Default interval for heartbeat beats', 33 | number: true, 34 | default: 1000 35 | } 36 | }; 37 | 38 | // 39 | // For exporting out 40 | // 41 | exports.options = function (options, argv) { 42 | options = options || {}; 43 | return (argv ? optimist(argv) : optimist).options(defaultOptions).options(options); 44 | }; 45 | 46 | exports.extract = function (options, script) { 47 | // 48 | // Get current process arguments in clean array. If script is not given 49 | // then get the first non option argument from process.argv 50 | // 51 | var argv = [].concat(process.argv); 52 | script = script || exports.options(options).argv._[0]; 53 | 54 | // 55 | // Remove everything up to and including the script. 56 | // 57 | return argv.splice(argv.indexOf(script) + 1); 58 | }; 59 | 60 | exports.rewrite = function (script, argv, override) { 61 | argv = argv || []; 62 | 63 | if (!Array.isArray(argv)) { 64 | throw new Error('Cannot rewrite unparsed arguments'); 65 | } 66 | 67 | if (!~['.', '/'].indexOf(script[0])) { 68 | // 69 | // If it is not a relative or absolute path, make it absolute 70 | // from the current `process.cwd()`. 71 | // 72 | script = path.join(process.cwd(), script); 73 | } 74 | 75 | script = fs.realpathSync(require.resolve(script)); 76 | var insert = [script].concat(argv); 77 | 78 | // 79 | // Remove all arguments that would be overwritten by 80 | // the new [script, argv] 81 | // 82 | if (!override) { 83 | process.argv.splice(1, insert.length); 84 | process.argv.splice.apply(process.argv, [1, -1].concat(insert)); 85 | return; 86 | } 87 | 88 | process.argv = [process.argv[0]].concat(insert); 89 | }; 90 | 91 | exports.argv = function (argv) { 92 | return exports.options({}, argv).argv; 93 | }; 94 | -------------------------------------------------------------------------------- /lib/plugins/chdir.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = function chdirPlugin(carapace) { 4 | if (!carapace.chdir) { 5 | carapace.chdir = function (value, done) { 6 | try { process.chdir(path.resolve(value)) } 7 | catch (ex) { return done ? done(ex) : null } 8 | 9 | carapace.cli.defaultOptions['chdir'].default = value; 10 | carapace.cli.defaultOptions['chdir'].required = true; 11 | return done ? done() : null; 12 | }; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /lib/plugins/coffee.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | fs = require('fs'); 3 | 4 | module.exports = function coffeePlugin(carapace) { 5 | if (!carapace.coffee) { 6 | carapace.coffee = function (value, done) { 7 | var coffeeBin = fs.realpathSync(path.join(process.execPath, '..', 'coffee')); 8 | // 9 | // This will be immediately called when this plugin is passed to `.use()` 10 | // 11 | // Should change `process.argv[1]` to coffee and then rewrite the CLI arguments 12 | // as necessary so that the .coffee is handled correctly by the `coffee` binary. 13 | // 14 | var script = carapace.script; 15 | function replaceWithCoffee() { 16 | carapace.script = coffeeBin; 17 | carapace.argv.unshift(script); 18 | } 19 | if (value == 'true' || /\.coffee$/.test(script)) { 20 | replaceWithCoffee(); 21 | done(); 22 | } 23 | else { 24 | // 25 | // Check shebang ... ugg, needed for executables 26 | // 27 | var file = fs.createReadStream(script); 28 | var line = ''; 29 | var shebangLine = /^#!.*coffee$/; 30 | file.on('error', function () { 31 | done(); 32 | }) 33 | file.on('data', function (data) { 34 | data = data + ''; 35 | var lines = data.split(/\r?\n/); 36 | if (lines.length > 1) { 37 | file.destroy(); 38 | line += lines[0]; 39 | if (shebangLine.test(line)) { 40 | replaceWithCoffee(); 41 | } 42 | done(); 43 | return; 44 | } 45 | line += lines[0]; 46 | }); 47 | file.on('end', function () { 48 | done(); 49 | }); 50 | } 51 | }; 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /lib/plugins/heartbeat.js: -------------------------------------------------------------------------------- 1 | // 2 | // Fires off functions at end of a heartbeat event 3 | // 4 | 5 | var enabled = false; 6 | 7 | module.exports = function (carapace) { 8 | carapace.heartbeat = function heartbeat() { 9 | var args = Array.prototype.slice.call(arguments), 10 | interval = 1000, 11 | done; 12 | 13 | args.forEach(function (a) { 14 | switch (typeof a) { 15 | case 'number': interval = a; break; 16 | case 'function': done = a; break; 17 | default: break; 18 | } 19 | }); 20 | 21 | enabled = !enabled; 22 | 23 | if (!enabled) { 24 | return clearInterval(this.interval); 25 | } 26 | 27 | // 28 | // make sure we have/set the interval 29 | // 30 | if (!interval) { 31 | interval = carapace.cli.defaultOptions['heartbeat'].default; 32 | } 33 | else { 34 | carapace.cli.defaultOptions['heartbeat'].default = interval; 35 | } 36 | 37 | // 38 | // start the timer 39 | // 40 | this.interval = setInterval(function () { 41 | carapace.emit('heartbeat', carapace.id); 42 | }, interval); 43 | 44 | return done ? done() : null; 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /lib/plugins/net.js: -------------------------------------------------------------------------------- 1 | /* 2 | * net.js: Wrapper around node.js core `net` module for observing relevant events 3 | * 4 | * (C) 2011 Nodejitsu Inc. 5 | * 6 | */ 7 | 8 | var net = require('net'), 9 | semver = require('semver'), 10 | carapace = require('../carapace'); 11 | 12 | // 13 | // ### function override (version) 14 | // Helper function which overrides with the correct `net` module 15 | // override for current node version. 16 | // 17 | module.exports = function (carapace) { 18 | carapace.net = function (argv, next) { 19 | var version = process.version, 20 | methods = Object.keys(module.exports), 21 | method; 22 | 23 | for (var i = 0; i < methods.length; i++) { 24 | method = module.exports[methods[i]]; 25 | if (method.nodeVersion && semver.satisfies(version, method.nodeVersion)) { 26 | method(); 27 | return next(); 28 | } 29 | } 30 | throw new Error('No appropiate net override for node version ' + version); 31 | }; 32 | }; 33 | 34 | // 35 | // ### function toPort(x) 36 | // #### @x {Number|String} Object to convert to a port number. 37 | // Helper function from Node code to parse port arguments 38 | // passed to net.prototype.Server.listen 39 | // 40 | function toPort(x) { 41 | return (x = Number(x)) >= 0 ? x : false; 42 | } 43 | 44 | // 45 | // ### function nextPort (port) 46 | // #### @port {Number} Port to increment from. 47 | // Gets the next port in sequence from the 48 | // specified `port`. 49 | // 50 | function nextPort(port) { 51 | if (!port) { 52 | return 8000; 53 | } 54 | 55 | // 56 | // Find the next port that we are not supposed to ignore or cause errors on 57 | // 58 | var unavailable = carapace.ports.ignore.concat(carapace.ports.throw); 59 | do { 60 | port = port + 1; 61 | } while (unavailable.indexOf(port) !== -1); 62 | 63 | return port; 64 | } 65 | 66 | // 67 | // ### functon reservedPort 68 | // #### @desired {Number} Desired port to bind to. 69 | // Helper function which will emit an error if the 70 | // server (the `this` argument) is in `carapace.ports.throw` 71 | // 72 | function reservedPort(desired) { 73 | if (carapace.ports.throw.indexOf(desired) !== -1) { 74 | this.close(); 75 | // 76 | // Build fake error 77 | // 78 | var err = new Error('EADDRINUSE, Address already in use'); 79 | err.code = 'EADDRINUSE'; 80 | err.syscall = 'bind'; 81 | err.errno = 100; 82 | this.emit('error', err); 83 | return true; 84 | } 85 | 86 | return false; 87 | } 88 | 89 | // 90 | // ### function _listen2 () 91 | // Override for `node@0.[5-8].x` 92 | // 93 | module.exports._listen2 = function overrideNet() { 94 | var _listen2 = net.Server.prototype._listen2; 95 | 96 | net.Server.prototype._listen2 = function ourListen(address, port, addressType, desired) { 97 | var self = this; 98 | 99 | address = '0.0.0.0'; 100 | port = toPort(port); 101 | desired || (desired = port); 102 | 103 | // 104 | // Always throw if our desired port was one that should always throw 105 | // 106 | if (reservedPort.call(this, port)) { 107 | return; 108 | } 109 | 110 | // 111 | // Since desired is not on a throwing port 112 | // we want to skip ports in both throw and ignore 113 | // 114 | port || (port = 0); 115 | 116 | // 117 | // We rely on `error` event to check if address it taken/available, however 118 | // user may want to hook up to `error` in his application as well (and 119 | // usually exit when it's emitted). This messes things up. Here we store 120 | // `error` event listeners and remove them. They will be restored after we 121 | // start listening or get an error which is not `EADDRINUSE`. 122 | // 123 | var errorListeners = self.listeners('error').slice(0); 124 | self.removeAllListeners('error'); 125 | 126 | function restoreErrorListeners() { 127 | errorListeners.forEach(function (listener) { 128 | self.on('error', listener); 129 | }); 130 | } 131 | 132 | // 133 | // Problem with `listening` events is a bit different. We want to ensure 134 | // that we first emit `carapace::port` and *then* the proper `listening` 135 | // event (which can be consumed by user's app). 136 | // 137 | // (Also, not doing so breaks `net/net-multiple-servers-test` test.) 138 | // 139 | var listeningListeners = self.listeners('listening').slice(0); 140 | self.removeAllListeners('listening'); 141 | 142 | function restoreListeningListeners() { 143 | listeningListeners.forEach(function (listener) { 144 | self.on('listening', listener); 145 | }); 146 | } 147 | 148 | function onListen() { 149 | // 150 | // Yay, we made it! Lets restore `error` listeners and tell the world 151 | // that we totally owned this port. 152 | // 153 | 154 | self.removeListener('error', onError); 155 | 156 | restoreErrorListeners(); 157 | restoreListeningListeners(); 158 | 159 | // 160 | // Store the server that has listened on the `desired` port 161 | // on the carapace itself, indexed by port. 162 | // 163 | carapace.servers[desired] = self; 164 | 165 | carapace.emit('port', { 166 | id: carapace.id, 167 | addr: address, 168 | port: self.address().port, 169 | desired: desired 170 | }); 171 | 172 | process.nextTick(function () { 173 | self.emit('listening'); 174 | }); 175 | } 176 | 177 | function onError(err) { 178 | // 179 | // We failed to listen. Unless it's not EADDRINUSE lets try doing the 180 | // same thing, just with next port. 181 | // 182 | 183 | self.removeListener('listening', onListen); 184 | 185 | // 186 | // We can safely restore these listeners here. We're going to call 187 | // `ourFunction`, so they will be removed again anyway. 188 | // 189 | restoreErrorListeners(); 190 | restoreListeningListeners(); 191 | 192 | if (err.code !== 'EADDRINUSE' && err.code !== 'EACCES') { 193 | try { self.close() } 194 | catch (ex) { } 195 | 196 | self.emit('error', err); 197 | } 198 | else { 199 | ourListen.call(self, '0.0.0.0', nextPort(port), addressType, desired); 200 | } 201 | } 202 | 203 | self.once('listening', onListen); 204 | self.once('error', onError); 205 | 206 | // 207 | // Call original _listen2 function. 208 | // 209 | process.nextTick(function () { 210 | _listen2.call(self, address, port, addressType); 211 | }); 212 | }; 213 | }; 214 | 215 | // 216 | // Setup the valid `nodeVersion` for all of the 217 | // monkey patches to `require('net')`. 218 | // 219 | module.exports._listen2.nodeVersion = '0.5.x || 0.6.x || 0.7.x || 0.8.x || 0.9.x || 0.10.x'; 220 | -------------------------------------------------------------------------------- /lib/plugins/setuid.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = function setuidPlugin(carapace) { 4 | if (!carapace.setuid) { 5 | carapace.setuid = function (value, done) { 6 | try { process.setuid(value) } 7 | catch (ex) { return done ? done(ex) : null } 8 | 9 | carapace.cli.defaultOptions['setuid'].default = value; 10 | carapace.cli.defaultOptions['setuid'].required = true; 11 | return done ? done() : null; 12 | }; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "haibu-carapace", 3 | "main": "lib/carapace.js", 4 | "version": "0.7.1", 5 | "author": "Nodejitsu Inc. ", 6 | "repository": { 7 | "type": "git", 8 | "url": "http://github.com/nodejitsu/haibu-carapace.git" 9 | }, 10 | "maintainers": [ 11 | "bmeck ", 12 | "indexzero " 13 | ], 14 | "dependencies": { 15 | "async": "0.1.x", 16 | "eventemitter2": "0.4.x", 17 | "optimist": "0.3.x", 18 | "semver": "1.0.x" 19 | }, 20 | "devDependencies": { 21 | "eyes": "0.1.x", 22 | "request": "2.9.x", 23 | "vows": "0.6.x" 24 | }, 25 | "engines": { 26 | "node": ">= 0.8.0" 27 | }, 28 | "bin": { 29 | "carapace": "./bin/carapace" 30 | }, 31 | "scripts": { 32 | "test": "vows test/*/*-test.js --isolate --spec" 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /test/fixtures/checkargs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * checkargs.js: Test fixture for a custom plugin in haibu-carapace which uses custom arguments. 3 | * 4 | * (C) 2011 Nodejitsu Inc. 5 | * 6 | */ 7 | 8 | module.exports = function checkargsPlugin (carapace) { 9 | if (!carapace.checkargs) { 10 | carapace.checkargs = function () { 11 | setInterval(function () { 12 | carapace.emit('carapace::checkargs', { id: carapace.id, checkargs: process.argv['checkargs'] }); 13 | }, 1000); 14 | }; 15 | 16 | carapace.checkargs(); 17 | } 18 | }; -------------------------------------------------------------------------------- /test/fixtures/checkchildargs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Test client argument rewrite of haibu.carapace 3 | * 4 | */ 5 | 6 | console.log('%j', process.argv); 7 | process.exit(0); -------------------------------------------------------------------------------- /test/fixtures/custom.js: -------------------------------------------------------------------------------- 1 | /* 2 | * custom.js: Test fixture for a custom plugin in haibu-carapace. 3 | * 4 | * (C) 2011 Nodejitsu Inc. 5 | * 6 | */ 7 | 8 | var enabled = false; 9 | 10 | module.exports = function customPlugin (carapace) { 11 | if (!carapace.custom) { 12 | carapace.custom = function (args, done) { 13 | if (enabled) { 14 | return done ? done() : null; 15 | } 16 | 17 | enabled = true; 18 | 19 | this.interval = setInterval(function () { 20 | carapace.emit('custom', { id: carapace.id, custom: true }); 21 | }, 1000); 22 | 23 | if (done) { 24 | done(); 25 | } 26 | }; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /test/fixtures/eacces.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Test _doListen function in net.js of haibu.carapace when there are multiple servers 3 | * 4 | */ 5 | var net = require('net'), 6 | port = 80; 7 | 8 | var server1 = net.createServer(function (socket) { 9 | }); 10 | 11 | 12 | server1.addListener('error', function (err) { 13 | process.exit(101); 14 | }); 15 | 16 | // 17 | // start server1 to use the test port and address... 18 | // 19 | server1.listen(port, 'localhost', function () { 20 | ; 21 | }); 22 | -------------------------------------------------------------------------------- /test/fixtures/multi-server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Test _doListen function in net.js of haibu.carapace when there are multiple servers 3 | * 4 | */ 5 | var net = require('net'), 6 | port = 8000; 7 | 8 | var server1 = net.createServer(function (socket) { 9 | }); 10 | 11 | var server2 = net.createServer(function (socket) { 12 | }); 13 | 14 | var server3 = net.createServer(function (socket) { 15 | }); 16 | 17 | server1.addListener('error', function (err) { 18 | process.exit(101); 19 | }); 20 | 21 | server2.addListener('error', function (err) { 22 | process.exit(102); 23 | }); 24 | 25 | server3.addListener('error', function (err) { 26 | process.exit(103); 27 | }); 28 | 29 | // 30 | // start server1 to use the test port and address... 31 | // 32 | server1.listen(port, 'localhost', function () { 33 | var ready = false; 34 | 35 | // 36 | // Server1 is occupying the test port and address so now spawn up more servers 37 | // 38 | 39 | server2.listen(port, 'localhost', function () { 40 | if (ready) process.exit(0); 41 | ready = true; 42 | }); 43 | 44 | server3.listen(port, 'localhost', function () { 45 | if (ready) process.exit(0); 46 | ready = true; 47 | }); 48 | 49 | }); 50 | -------------------------------------------------------------------------------- /test/fixtures/pluginserver.js: -------------------------------------------------------------------------------- 1 | /* 2 | * pluginserver.js: Test fixture for a custom plugin in haibu-carapace that starts an HTTP server . 3 | * 4 | * (C) 2011 Nodejitsu Inc. 5 | * 6 | */ 7 | 8 | var http = require('http'); 9 | 10 | var enabled = false; 11 | 12 | module.exports = function pluginserver (carapace) { 13 | if (!carapace.pluginserver) { 14 | carapace.pluginserver = function (args, done) { 15 | if (enabled) { 16 | return done(); 17 | } 18 | 19 | var port = 1337, server; 20 | 21 | server = http.createServer(function (req, res) { 22 | res.end('from-pluginserver'); 23 | }); 24 | 25 | // 26 | // Append the port of the plugin server to `carapace.ports.ignore` 27 | // so that `haibu-carapace` will not emit `carapace::port` events when 28 | // it attempts to listen. 29 | // 30 | carapace.ports.ignore.push(port); 31 | server.listen(port, function () { 32 | if (done) { 33 | done(); 34 | } 35 | }); 36 | }; 37 | } 38 | }; -------------------------------------------------------------------------------- /test/helper/macros.js: -------------------------------------------------------------------------------- 1 | /* 2 | * macros.js: Test macros carapace module 3 | * 4 | * (C) 2011 Marak Squires, Charlie Robbins 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | var assert = require('assert'), 10 | path = require('path'), 11 | spawn = require('child_process').spawn, 12 | fork = require('child_process').fork, 13 | eyes = require('eyes'), 14 | request = require('request'), 15 | carapace = require('../../lib/carapace'); 16 | 17 | var macros = exports; 18 | 19 | macros.assertUse = function (plugins, vows) { 20 | var names = plugins.map(function (p) { return path.basename(p, '.js') }); 21 | 22 | var context = { 23 | topic: function () { 24 | // 25 | // should be an array 26 | // we have to do this because carapace, preloads these plugins 27 | // 28 | var scripts = plugins.map(function (plugin) { 29 | return carapace.load(plugin); 30 | }); 31 | 32 | carapace.use(scripts, this.callback.bind(null, null)); 33 | }, 34 | "should have load the plugin(s)": function () { 35 | assert.isArray(plugins); 36 | names.forEach(function (name) { 37 | assert.isFunction(carapace[name]); 38 | }); 39 | } 40 | }; 41 | 42 | return extendContext(context, vows); 43 | }; 44 | 45 | macros.assertRun = function (script, argv, vows) { 46 | var context = { 47 | topic: function () { 48 | carapace.on('running', this.callback.bind(carapace, null)); 49 | carapace.argv = argv || []; 50 | carapace.script = script; 51 | carapace.run(); 52 | }, 53 | "should fire the `carapace::running` event": function () { 54 | assert.equal(carapace.event, 'running'); 55 | }, 56 | "should rewrite process.argv transparently": function () { 57 | assert.equal(process.argv[1], script); 58 | } 59 | }; 60 | 61 | return extendContext(context, vows); 62 | }; 63 | 64 | macros.assertSpawn = function (PORT, script, argv, vows) { 65 | argv = argv.slice(0); 66 | 67 | var context = { 68 | topic: function () { 69 | var that = this, 70 | child = fork(carapace.bin, [script].concat(argv)); 71 | 72 | child.on('message', function onRunning(info) { 73 | if (info.event === 'running') { 74 | child.removeListener('message', onRunning); 75 | that.callback.call(null, null, child, info); 76 | } 77 | }); 78 | }, 79 | "should respond with the proper wrapped script output": function (_, child, info) { 80 | assert.equal(info.data.script, script); 81 | } 82 | } 83 | 84 | return extendContext(context, vows); 85 | }; 86 | 87 | macros.assertParentSpawn = function (options, /*PORT, script, argv, cwd,*/ vows) { 88 | options.argv = options.argv.slice(0); 89 | options.argv.push('--plugin', 'net'); 90 | options.argv.push(options.script); 91 | 92 | var context = { 93 | "when spawning a child carapace": { 94 | topic: function () { 95 | var that = this, 96 | child = fork(carapace.bin, options.argv, { silent: true }); 97 | 98 | child.on('message', function onPort (info) { 99 | if (info.data && info.data.port) { 100 | that.port = info.data.port; 101 | that.callback(null, info, child); 102 | child.removeListener('message', onPort); 103 | } 104 | }); 105 | }, 106 | "should emit the `port` event": { 107 | topic: function (info, child) { 108 | this.callback(null, info, child); 109 | }, 110 | "with the correct port": function (_, info, child) { 111 | assert.equal(info.data.port, this.port); 112 | }, 113 | "should correctly start the HTTP server": { 114 | topic: function (_, _, _, child) { 115 | request({ uri: 'http://localhost:' + this.port }, this.callback.bind(null, null, child)); 116 | }, 117 | "that responds with a cwd": function (_, child, err, res, body) { 118 | if (!options.keepalive) { 119 | child.kill(); 120 | } 121 | 122 | assert.equal(body, options.cwd); 123 | } 124 | } 125 | } 126 | } 127 | }; 128 | 129 | if (options.keepalive) { 130 | context['when spawning a child carapace'] = extendContext(context['when spawning a child carapace'], vows); 131 | } 132 | else { 133 | context = extendContext(context, vows); 134 | } 135 | 136 | return context; 137 | }; 138 | 139 | function extendContext (context, vows) { 140 | if (vows) { 141 | if (vows.topic) { 142 | console.log('Cannot include topic at top-level of nested vows:'); 143 | eyes.inspect(vows, 'vows'); 144 | process.exit(1); 145 | } 146 | 147 | Object.keys(vows).forEach(function (key) { 148 | context[key] = vows[key]; 149 | }); 150 | } 151 | 152 | return context; 153 | } 154 | -------------------------------------------------------------------------------- /test/net/errors-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * net-dolisten-test.js: Basic tests for the lib/net.js module 3 | * 4 | * (C) 2011 stolsma 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | var assert = require('assert'), 10 | path = require('path'), 11 | fork = require('child_process').fork, 12 | vows = require('vows'), 13 | helper = require('../helper/macros.js'), 14 | carapace = require('../../lib/carapace'); 15 | 16 | var script = path.join(__dirname, '..', 'fixtures', 'eacces.js'), 17 | argv = ['--plugin', 'net', '--setuid', 'nobody', script]; 18 | 19 | vows.describe('carapace/net/dolisten').addBatch({ 20 | "When using haibu-carapace": { 21 | "spawning the eacces.js script the child carapace": { 22 | topic: function () { 23 | var callback = this.callback; 24 | var that = this, 25 | result, 26 | child; 27 | 28 | result = { 29 | events: [], 30 | exitCode: -1 31 | }; 32 | 33 | child = fork(carapace.bin, argv, { silent: true }); 34 | 35 | child.on('message', function onPort (info) { 36 | if (info.event == 'port') { 37 | result.events.push({ 38 | event: info.event, 39 | info: info.data 40 | }); 41 | child.kill(); 42 | callback(null, result); 43 | } 44 | }); 45 | }, 46 | "should exit": { 47 | topic: function (info, child) { 48 | this.callback(null, info, child); 49 | }, 50 | "with the correct exit code": function (_, info, child) { 51 | assert.equal(info.exitCode, -1); 52 | }, 53 | "and emit the `port` event with the correct port": function (_, info, child) { 54 | assert.equal(info.events.length, 1); 55 | info.events.forEach(function (event, index) { 56 | assert.equal(event.info.addr, '0.0.0.0'); 57 | assert.equal(event.info.desired, 80); 58 | assert.equal(event.info.port, 1024); 59 | }); 60 | } 61 | } 62 | } 63 | } 64 | }).export(module); 65 | -------------------------------------------------------------------------------- /test/net/net-multiple-servers-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * net-dolisten-test.js: Basic tests for the lib/net.js module 3 | * 4 | * (C) 2011 stolsma 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | var assert = require('assert'), 10 | path = require('path'), 11 | fork = require('child_process').fork, 12 | vows = require('vows'), 13 | helper = require('../helper/macros.js'), 14 | carapace = require('../../lib/carapace'); 15 | 16 | var script = path.join(__dirname, '..', 'fixtures', 'multi-server.js'), 17 | testPort = 8000, 18 | argv = ['--plugin', 'net', script]; 19 | 20 | vows.describe('carapace/net/dolisten').addBatch({ 21 | "When using haibu-carapace": { 22 | "spawning the server-dolisten.js script the child carapace": { 23 | topic: function () { 24 | var that = this, 25 | result, 26 | child; 27 | 28 | result = { 29 | events: [], 30 | exitCode: -1 31 | }; 32 | 33 | child = fork(carapace.bin, argv, { silent: true }); 34 | child.on('exit', function (code) { 35 | result.exitCode = code; 36 | // process all events before asserting 37 | process.nextTick(function () { 38 | that.callback(null, result, child); 39 | }); 40 | }); 41 | 42 | child.on('message', function onPort (info) { 43 | info.event == 'port' && result.events.push({ 44 | event: info.event, 45 | info: info.data 46 | }); 47 | }); 48 | }, 49 | "should exit": { 50 | topic: function (info, child) { 51 | this.callback(null, info, child); 52 | }, 53 | "with the correct exit code": function (_, info, child) { 54 | assert.equal(info.exitCode, 0); 55 | }, 56 | "and 3x emit the `port` event with the correct port": function (_, info, child) { 57 | var desired = testPort, 58 | port = desired; 59 | 60 | assert.equal(info.events.length, 3); 61 | info.events.forEach(function (event, index) { 62 | assert.equal(event.info.desired, desired); 63 | assert.equal(event.info.port, port++); 64 | }); 65 | } 66 | } 67 | } 68 | } 69 | }).export(module); 70 | -------------------------------------------------------------------------------- /test/simple/child-argument-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * child-argument-test.js: Basic child argument rewrite tests 3 | * 4 | * (C) 2011 stolsma 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | var assert = require('assert'), 10 | path = require('path'), 11 | spawn = require('child_process').spawn, 12 | vows = require('vows'), 13 | helper = require('../helper/macros.js'), 14 | carapace = require('../../lib/carapace'); 15 | 16 | var script = path.join(__dirname, '..', 'fixtures' ,'checkchildargs.js'), 17 | testPort = 8000, 18 | checkargs = ['argument', '-a', 'aargument', '--test', 'testargument']; 19 | argv = [script]; 20 | 21 | vows.describe('carapace/simple/child-argument').addBatch({ 22 | "When using haibu-carapace": { 23 | "spawning the checkchildargs.js script via the child carapace": { 24 | topic: function () { 25 | var that = this, 26 | child, 27 | result; 28 | 29 | result = { 30 | arguments: '', 31 | exitCode: -1 32 | }; 33 | 34 | child = spawn(carapace.bin, argv.concat(checkargs)); 35 | 36 | child.stdout.on('data', function (data) { 37 | result.arguments += data; 38 | }); 39 | 40 | child.on('exit', function (code) { 41 | result.exitCode = code; 42 | 43 | // 44 | // Process all events before asserting 45 | // 46 | process.nextTick(function () { 47 | that.callback(null, result, child); 48 | }); 49 | }); 50 | }, 51 | "should exit": { 52 | topic: function (info, child) { 53 | this.callback(null, info, child); 54 | }, 55 | "with the correct exit code": function (_, info, child) { 56 | assert.equal(info.exitCode, 0); 57 | }, 58 | "and correct client arguments": function (_, info, child) { 59 | var childargs = JSON.parse(info.arguments), 60 | resultScript, 61 | node, 62 | 63 | // 64 | // First two are reference to node and the script itself 65 | // 66 | node = childargs.splice(0, 1); 67 | resultScript = childargs.splice(0, 1); 68 | assert.equal(resultScript, script); 69 | assert.deepEqual(childargs, checkargs); 70 | } 71 | } 72 | } 73 | } 74 | }).export(module); 75 | -------------------------------------------------------------------------------- /test/simple/use-custom-plugin-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * use-custom-plugin-test.js: Tests to ensure that custom-plugins load correctly 3 | * 4 | * (C) 2011 Nodejitsu Inc. 5 | * 6 | */ 7 | 8 | var assert = require('assert'), 9 | path = require('path'), 10 | vows = require('vows'), 11 | helper = require('../helper/macros.js'), 12 | carapace = require('../../lib/carapace'); 13 | 14 | vows.describe('carapace/simple/use-custom-plugin').addBatch({ 15 | "When using haibu-carapace": { 16 | "a custom plugin" : { 17 | "with an absolute path": helper.assertUse([path.join(__dirname, '..', 'fixtures', 'custom.js')], { 18 | "after the plugin is loaded": { 19 | topic: function () { 20 | carapace.custom(); 21 | carapace.once('custom', this.callback.bind(carapace, null)); 22 | }, 23 | "should emit the `custom` event": function (_, info) { 24 | assert.isTrue(info.custom); 25 | } 26 | } 27 | }), 28 | "with a relative path": function () { 29 | assert.throws(function () { carapace.load('../fixtures/relative.js') }); 30 | } 31 | } 32 | } 33 | }).export(module); 34 | -------------------------------------------------------------------------------- /test/simple/use-load-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * use-test.js: Basic tests for the carapace module 3 | * 4 | * (C) 2011 Nodejitsu Inc 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | var assert = require('assert'), 10 | vows = require('vows'), 11 | helper = require('../helper/macros.js'), 12 | carapace = require('../../lib/carapace'); 13 | 14 | vows.describe('carapace/simple/use').addBatch({ 15 | "When using haibu-carapace": { 16 | "use chdir plugins" : helper.assertUse(['chdir'], { 17 | "and use heartbeat" : helper.assertUse(['heartbeat']) 18 | }) 19 | } 20 | }).export(module); 21 | -------------------------------------------------------------------------------- /test/simple/use-pluginserver-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * use-pluginserver-test.js: Tests to ensure that custom plugins which start servers load correctly 3 | * 4 | * (C) 2011 Nodejitsu Inc. 5 | * 6 | */ 7 | 8 | var assert = require('assert'), 9 | path = require('path'), 10 | request = require('request'), 11 | vows = require('vows'), 12 | helper = require('../helper/macros.js'), 13 | carapace = require('../../lib/carapace'); 14 | 15 | vows.describe('carapace/simple/use-pluginserver').addBatch({ 16 | "When using haibu-carapace": { 17 | "a custom plugin that starts a server" : helper.assertUse([path.join(__dirname, '..', 'fixtures', 'pluginserver.js')], { 18 | "a request to the server started by pluginserver.js": { 19 | topic: function () { 20 | var that = this; 21 | 22 | carapace.pluginserver(null, function () { 23 | request({ uri: 'http://localhost:1337' }, that.callback); 24 | }); 25 | }, 26 | "should respond with `from-pluginserver`": function (err, res, body) { 27 | assert.isTrue(!err); 28 | assert.equal(body, 'from-pluginserver'); 29 | } 30 | } 31 | }) 32 | } 33 | }).export(module); 34 | -------------------------------------------------------------------------------- /test/simple/use-run-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * use-test.js: Basic tests for the carapace module 3 | * 4 | * (C) 2011 Nodejitsu Inc 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | var assert = require('assert'), 10 | vows = require('vows'), 11 | helper = require('../helper/macros.js'), 12 | carapace = require('../../lib/carapace'); 13 | 14 | vows.describe('carapace/simple/use-plugins').addBatch({ 15 | "When using haibu-carapace": { 16 | "load up chdir, heartbeat plugins" : helper.assertUse(['chdir', 'heartbeat'], { 17 | "and running the heartbeat plugin" : { 18 | topic : function () { 19 | carapace.once('heartbeat', this.callback.bind(carapace, null)); 20 | carapace.heartbeat(); 21 | }, 22 | "should see a heartbeat event" : function (_, event, data) { 23 | assert.isString(carapace.event); 24 | assert.equal(carapace.event, 'heartbeat'); 25 | } 26 | } 27 | }) 28 | } 29 | }).export(module); 30 | -------------------------------------------------------------------------------- /test/spawn/local-run-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * use-test.js: Basic tests for the carapace module 3 | * 4 | * (C) 2011 Nodejitsu Inc 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | var assert = require('assert'), 10 | path = require('path'), 11 | exec = require('child_process').exec, 12 | http = require('http'), 13 | request = require('request'), 14 | vows = require('vows'), 15 | helper = require('../helper/macros.js'), 16 | carapace = require('../../lib/carapace'); 17 | 18 | var script = path.join(__dirname, '..', '..', 'examples', 'app', 'server.js'); 19 | 20 | vows.describe('carapace/spawn/local').addBatch({ 21 | "When using haibu-carapace": { 22 | "with `net` plugin": { 23 | topic: function () { 24 | carapace.use(path.resolve(__dirname, '..', '..', 'lib', 'plugins', 'net')); 25 | carapace.net([], this.callback); 26 | }, 27 | "and running `./server.js`": helper.assertRun(script, null, { 28 | "should set the correct exports on carapace._module": function (_, _) { 29 | assert.equal(carapace._module.exports.port, 1337); 30 | }, 31 | "should emit the `port` event": { 32 | topic: function () { 33 | carapace.on('port', this.callback.bind(carapace, null)); 34 | }, 35 | "with the correct port": function (err, info) { 36 | assert.equal(carapace.event, 'port'); 37 | assert.equal(info.desired, 1337); 38 | }, 39 | "should correctly start the HTTP server": { 40 | topic: function (info) { 41 | request({ uri: 'http://localhost:' + info.port }, this.callback); 42 | }, 43 | "that responds with a cwd": function (err, res, body) { 44 | assert.equal(body, process.cwd()); 45 | } 46 | } 47 | } 48 | }) 49 | } 50 | } 51 | }).export(module); 52 | -------------------------------------------------------------------------------- /test/spawn/parent-custom-plugin-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * use-test.js: Basic tests for the carapace module 3 | * 4 | * (C) 2011 Nodejitsu Inc 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | var assert = require('assert'), 10 | path = require('path'), 11 | exec = require('child_process').exec, 12 | http = require('http'), 13 | request = require('request'), 14 | vows = require('vows'), 15 | helper = require('../helper/macros.js'), 16 | carapace = require('../../lib/carapace'); 17 | 18 | var jail = path.join(__dirname, '..', '..', 'examples', 'app'), 19 | custom = path.join(__dirname, '..', 'fixtures', 'custom.js'), 20 | options; 21 | 22 | options = { 23 | port: 5060, 24 | script: path.join(jail, 'server.js'), 25 | argv: ['--plugin', custom, '--plugin', 'heartbeat'], 26 | cwd: process.cwd(), 27 | keepalive: true 28 | }; 29 | 30 | vows.describe('carapace/spawn/custom-plugin').addBatch({ 31 | "When using haibu-carapace": { 32 | "spawning a child carapace with a custom plugin": helper.assertParentSpawn(options, { 33 | "after the plugin is loaded": { 34 | topic: function (info, child) { 35 | var that = this; 36 | child.once('message', function (info) { 37 | if (info.event === 'custom') { 38 | that.callback(null, child, info); 39 | } 40 | }); 41 | }, 42 | "should emit the `carapace::custom` event": function (_, child, info) { 43 | assert.isTrue(info.data.custom); 44 | child.kill(); 45 | } 46 | } 47 | }) 48 | } 49 | }).export(module); 50 | -------------------------------------------------------------------------------- /test/spawn/parent-pluginserver-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * use-test.js: Basic tests for the carapace module 3 | * 4 | * (C) 2011 Nodejitsu Inc 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | var assert = require('assert'), 10 | path = require('path'), 11 | request = require('request'), 12 | vows = require('vows'), 13 | helper = require('../helper/macros.js'); 14 | 15 | var jail = path.join(__dirname, '..', '..', 'examples', 'app'), 16 | custom = path.join(__dirname, '..', 'fixtures', 'pluginserver.js'), 17 | options, 18 | child; 19 | 20 | options = { 21 | script: path.join(jail, 'server.js'), 22 | argv: ['--plugin', custom], 23 | cwd: process.cwd(), 24 | keepalive: true 25 | }; 26 | 27 | vows.describe('carapace/spawn/custom-plugin').addBatch({ 28 | "When using haibu-carapace": { 29 | "spawning a child carapace with a custom plugin": helper.assertParentSpawn(options, { 30 | "a request to the server started by pluginserver.js": { 31 | topic: function (info, _child) { 32 | child = _child; 33 | request({ uri: 'http://127.0.0.1:1337' }, this.callback.bind(this, null, child)); 34 | }, 35 | "should respond with `from-pluginserver`": function (_, child, err, res, body) { 36 | assert.isTrue(!err); 37 | assert.equal(body, 'from-pluginserver'); 38 | }, 39 | // 40 | // Remark: There is not a good way to do asynchronous teardown in vows 41 | // currently, so this context is merely a stop-gap for that. 42 | // 43 | "when everything is over": { 44 | topic: function () { 45 | setTimeout(this.callback, 200); 46 | }, 47 | "kill the child process": function () { 48 | child.kill(); 49 | } 50 | } 51 | } 52 | }) 53 | } 54 | }).export(module); 55 | -------------------------------------------------------------------------------- /test/spawn/parent-run-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * use-test.js: Basic tests for the carapace module 3 | * 4 | * (C) 2011 Nodejitsu Inc 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | var assert = require('assert'), 10 | path = require('path'), 11 | exec = require('child_process').exec, 12 | http = require('http'), 13 | request = require('request'), 14 | vows = require('vows'), 15 | helper = require('../helper/macros.js'), 16 | carapace = require('../../lib/carapace'); 17 | 18 | var jail = path.join(__dirname, '..', '..', 'examples', 'app'), 19 | options; 20 | 21 | options = { 22 | argv: [], 23 | script: script = path.join(jail, 'server.js'), 24 | cwd: process.cwd() 25 | }; 26 | 27 | vows.describe('carapace/spawn/parent').addBatch({ 28 | "When using haibu-carapace": { 29 | "an initial spawn of the child": helper.assertParentSpawn(options) 30 | } 31 | }).addBatch({ 32 | "followed by a second spawn of the same child": helper.assertParentSpawn(options) 33 | }).export(module); 34 | -------------------------------------------------------------------------------- /test/spawn/process-run-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * use-test.js: Basic tests for the carapace module 3 | * 4 | * (C) 2011 Nodejitsu Inc 5 | * MIT LICENCE 6 | * 7 | */ 8 | 9 | var assert = require('assert'), 10 | path = require('path'), 11 | exec = require('child_process').exec, 12 | http = require('http'), 13 | request = require('request'), 14 | vows = require('vows'), 15 | helper = require('../helper/macros.js'), 16 | carapace = require('../../lib/carapace'); 17 | 18 | var jail = path.join(__dirname, '..', '..', 'examples', 'app'), 19 | script = path.join(jail, 'server.js'), 20 | argv = [], 21 | PORT = 5060; 22 | 23 | vows.describe('carapace/run/process').addBatch({ 24 | "When using haibu-carapace": { 25 | "and spawning `/.server.js` in a separate process": helper.assertSpawn(PORT, script, argv, { 26 | "should correctly start the HTTP server": { 27 | topic: function (child) { 28 | var that = this; 29 | request({ uri: 'http://localhost:1337' }, function () { 30 | child.kill(); 31 | that.callback.apply(null, arguments); 32 | }); 33 | }, 34 | "that responds with a cwd inside the app root": function (err, res, body) { 35 | assert.isNull(err); 36 | assert.equal(res.statusCode, 200); 37 | assert.equal(body, process.cwd()); 38 | } 39 | } 40 | }) 41 | } 42 | }).export(module); 43 | --------------------------------------------------------------------------------