Skip to content
This repository has been archived by the owner on Aug 11, 2021. It is now read-only.

Commit

Permalink
you can now set the variable in service.json, which can be used to st…
Browse files Browse the repository at this point in the history
…art multiple copies of the service.
  • Loading branch information
bcoe committed Oct 12, 2014
1 parent 386cb63 commit b70327a
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 39 deletions.
7 changes: 6 additions & 1 deletion README.md
Expand Up @@ -100,6 +100,7 @@ Here's an example:
},
"ndm-test2": {
"description": "the awesome service",
"processes": 3,
"module": "be-awesome",
"scripts": {
"start": "./bin/foo.js"
Expand All @@ -112,7 +113,7 @@ Here's an example:
}
},
"env": {
"APP": "my-test-app",
"APP": "my-test-app-%i",
"NODE_ENV": "production"
},
"args": {
Expand All @@ -126,6 +127,10 @@ Here's an example:
* **scripts:** scripts that can be executed by ndm. When generating service wrappers the `start` script is used.
* **env:** string environment variables available within the script executed by the ndm wrapper.
* **args:** command-line-arguments available to the script executed by the ndm wrapper.
* **processes:** how many copies of the service should be run.
* useful for taking advantage of multiple cpus, defaults to 1 process.
* **`%i`:** `%i` is a place-holder for the process # if you're running multiple processes.
* this can be useful if you want each process to bind to a different port, e.g., `800%i`.

Defaults for all services are in the top-level `env` and `args` fields. Each services can override and extend the defaults in its own options stanza.

Expand Down
104 changes: 69 additions & 35 deletions lib/service.js
Expand Up @@ -275,43 +275,53 @@ function parseServiceJson(serviceNameFilter, serviceJson) {
Object.keys(serviceJson).forEach(function(serviceName) {
if (serviceName === 'env' || serviceName === 'args') return;

var serviceConfig = serviceJson[serviceName];

// apply sane defaults as we create
// the services.
var service = new Service(_.extend(
{
module: serviceName,
name: serviceName
},
serviceConfig
));

// override env and args with global args and env.
service.env = _.extend({},
dropInterviewQuestions(serviceJson.env),
dropInterviewQuestions(serviceConfig.env)
);

service.args = serviceConfig.args;

if (_.isArray(serviceConfig.args)) {
// combine arrays of arguments, if both top-level args.
// and service-level args are an array.
if (_.isArray(serviceJson.args)) service.args = [].concat(serviceJson.args, service.args);
} else {
// merge objects together if top-level, and service level
// arguments are maps.
service.args = _.extend({},
dropInterviewQuestions(serviceJson.args),
dropInterviewQuestions(serviceConfig.args)
var serviceConfig = serviceJson[serviceName],
processCount = serviceConfig.processes || 1;

// if services have a process count > 1,
// we'll create multiple run-scripts for them.
_.range(processCount).forEach(function(i) {

// apply sane defaults as we create
// the services.
var service = new Service(_.extend(
{
module: serviceName,
name: i > 0 ? (serviceName + '-' + i) : serviceName
},
serviceConfig
));

// override env and args with global args and env.
service.env = _.extend({},
dropInterviewQuestions(serviceJson.env),
dropInterviewQuestions(serviceConfig.env)
);
}

// we can optionall filter to a specific service name.
if (serviceNameFilter && service.name !== serviceNameFilter && !config.globalPackage) return;

services.push(service);
service.args = serviceConfig.args;

if (_.isArray(serviceConfig.args)) {
// combine arrays of arguments, if both top-level args.
// and service-level args are an array.
if (_.isArray(serviceJson.args)) service.args = [].concat(serviceJson.args, service.args);
} else {
// merge objects together if top-level, and service level
// arguments are maps.
service.args = _.extend({},
dropInterviewQuestions(serviceJson.args),
dropInterviewQuestions(serviceConfig.args)
);
}

// we can optionaly filter to a specific service name.
if (serviceNameFilter && service.name !== serviceNameFilter && !config.globalPackage) return;

// replace placeholder variables
// in the service.json.
expandVariables(service, i);

services.push(service);
});
});

return services;
Expand All @@ -326,6 +336,30 @@ function dropInterviewQuestions(o) {
}, {});
}

// the %i placeholder can be used to reference the process
// number in args/env variables within service.json.
function expandVariables(service, processNumber) {
// first the simple case of env, which is always an object.
service.env = _.reduce(service.env, function(result, v, k) {
var value = typeof v === 'string' ? v.replace(/%i/g, processNumber) : v;
return (result[k] = value, result)
}, {});


// next, the more annoying usecase of args which can be
// an array on an object.
if (_.isArray(service.args)) {
service.args = _.map(service.args, function(arg) {
return typeof arg === 'string' ? arg.replace(/%i/g, processNumber) : arg;
});
} else {
service.args = _.reduce(service.args, function(result, v, k) {
var value = typeof v === 'string' ? v.replace(/%i/g, processNumber) : v;
return (result[k] = value, result)
}, {});
}
};

// look for a service manifest in cascade of logical places.
exports._serviceJsonPath = function(serviceNameFilter) {
var config = require('./config')(),
Expand Down
2 changes: 1 addition & 1 deletion node_modules/ndm-test/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion node_modules/ndm-test/service.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions test/fixtures/multi-process-service.json
@@ -0,0 +1,16 @@
{
"awesome": {
"processes": 3,
"env": {
"FOO": "bar",
"PORT": "500%i"
},
"args": ["--process", "%i"]
},
"dude": {
"processes": 1,
"args": {
"--port": "808%i"
}
}
}
45 changes: 44 additions & 1 deletion test/service-test.js
@@ -1,6 +1,7 @@
require('../lib/config')({headless: true}); // turn off output in tests.

var Lab = require('lab'),
var _ = require('lodash'),
Lab = require('lab'),
lab = exports.lab = Lab.script(),
describe = lab.describe,
it = lab.it,
Expand Down Expand Up @@ -105,6 +106,48 @@ lab.experiment('service', function() {
});
});

lab.experiment('multiple processes', function() {
it('creates multiple services when processes value is set', function(done) {
Config({
serviceJsonPath: './test/fixtures/multi-process-service.json'
});
var services = Service.allServices(),
serviceNames = _.map(services, function(service) {
return service.name;
});

expect(services.length).to.eql(4);
expect(serviceNames).to.include('awesome');
expect(serviceNames).to.include('awesome-1');
expect(serviceNames).to.include('awesome-2');
expect(serviceNames).to.include('dude');
done();
});

it('replaces %i with process count in env and args', function(done) {
Config({
serviceJsonPath: './test/fixtures/multi-process-service.json'
});
var services = Service.allServices(),
service1 = services[0], // the multiple awesome services.
service2 = services[1];
service3 = services[3]; // the dude service.

// %i replaced in env object.
Lab.expect(service1.env.PORT).to.eql('5000');
Lab.expect(service2.env.PORT).to.eql('5001');

// %i replaced in args array.
Lab.expect(service1.args).to.include('0');
Lab.expect(service2.args).to.include('1');

// %i replaced in args object.
Lab.expect(service3.args['--port']).to.eql('8080');

return done();
});
});

lab.experiment('commands', function() {
it('should generate appropriate start/stop/restart commands for OSX', function(done) {
Config({
Expand Down

0 comments on commit b70327a

Please sign in to comment.