containership
Version:
A simple container management platform
576 lines (506 loc) • 24.9 kB
JavaScript
;
const config = require('../lib/config');
const request = require('../lib/request');
const utils = require('../lib/utils');
const _ = require('lodash');
const async = require('async');
const flat = require('flat');
const C2C = require('c2c');
const url = require('url');
const colors = require('colors');
const protocols = {
http: require('http'),
https: require('https')
};
const create_edit_options = {
application: {
position: 1,
help: 'Name of the application',
metavar: 'APPLICATION',
required: true
},
engine: {
help: 'Engine used to start application',
metavar: 'ENGINE',
choices: ['docker'],
abbr: 'x'
},
image: {
help: 'Application image',
metavar: 'IMAGE',
abbr: 'i'
},
'env-var': {
list: true,
help: 'Environment variable for application',
metavar: 'ENV_VAR=VALUE',
abbr: 'e'
},
'network-mode': {
help: 'Application network mode',
metavar: 'NETWORK MODE',
abbr: 'n'
},
'container-port': {
help: 'Port application must listen on',
metavar: 'PORT',
abbr: 'p'
},
command: {
help: 'Application start command',
metavar: 'COMMAND',
abbr: 's'
},
volume: {
help: 'Volume to bind-mount for application',
metavar: 'HOST_PATH:CONTAINER_PATH',
list: true,
abbr: 'b'
},
tag: {
help: 'Tag to add to application',
metavar: 'NAME=VALUE',
list: true,
abbr: 't'
},
cpus: {
help: 'CPUs allocated to application',
metavar: 'CPUS',
abbr: 'c'
},
memory: {
help: 'Memory (mb) allocated to application',
metavar: 'MEMORY',
abbr: 'm'
},
privileged: {
help: 'Run application containers in privileged mode',
metavar: 'PRIVILEGED',
choices: [true, false]
},
respawn: {
help: 'Respawn application containers when they die',
metavar: 'RESPAWN',
choices: [true, false]
}
};
function parse_update_body(options) {
if(_.has(options, 'tag')) {
options.tags = utils.parse_tags(options.tag);
delete options.tag;
}
if(_.has(options, 'volume')) {
options.volumes = utils.parse_volumes(options.volume);
delete options.volume;
}
if(_.has(options, 'env-var')) {
options.env_vars = utils.parse_tags(options['env-var']);
delete options['env-var'];
}
if(_.has(options, 'network-mode')) {
options.network_mode = options['network-mode'];
delete options['network-mode'];
}
if(_.has(options, 'container-port')) {
options.container_port = options['container-port'];
delete options['container-port'];
}
return options;
}
module.exports = {
fetch: (/*core*/) => {
return {
commands: [
{
name: 'create-from-file',
options: {
'docker-compose': {
position: 1,
help: 'Path to Docker compose file',
metavar: 'DOCKER-COMPOSE',
default: './docker-compose.yml',
required: true
},
'containership-compose': {
position: 2,
help: 'Path to ContainerShip compose file',
metavar: 'CONTAINERSHIP-COMPOSE'
}
},
callback: (options) => {
let c2c;
try {
c2c = new C2C({
compose_path: options['docker-compose'],
containership_path: options['containership-compose']
});
} catch(err) {
process.stderr.write(err.message);
process.exit(1);
}
c2c.convert((err, json) => {
if(err) {
process.stderr.write(err.message);
process.exit(1);
}
request.post('applications', {}, json, (err, response) => {
if(err) {
process.stderr.write('Could not create applications!');
process.exit(1);
} else if(response.statusCode != 201) {
process.stderr.write(response.body.error);
process.exit(1);
} else {
process.stdout.write(`Successfully created ${_.keys(json).length} applications!`);
}
});
});
}
},
{
name: 'create',
options: create_edit_options,
callback: (options) => {
options = _.omit(options, ['0', '_']);
options = parse_update_body(options);
request.post(`applications/${options.application}`, {}, options, (err, response) => {
if(err) {
process.stderr.write(`Could not create application ${options.application}!`);
process.exit(1);
} else if(response.statusCode != 201) {
process.stderr.write(response.body.error);
process.exit(1);
} else {
process.stdout.write(`Successfully created application ${options.application}!`);
}
});
}
},
{
name: 'edit',
options: create_edit_options,
callback: (options) => {
options = _.omit(options, ['0', '_']);
options = parse_update_body(options);
request.put(`applications/${options.application}`, {}, options, (err, response) => {
if(err) {
process.stderr.write(`Could not update application ${options.application}!`);
process.exit(1);
} else if(response.statusCode != 200) {
process.stderr.write(response.body.error);
process.exit(1);
} else {
process.stdout.write(`Successfully updated application ${options.application}!`);
}
});
}
},
{
name: 'list',
options: {},
callback: (/*options*/) => {
request.get('applications', {}, (err, response) => {
if(err) {
process.stderr.write('Could not fetch applications!');
process.exit(1);
}
utils.println([
['%-50s', 'APPLICATION'],
['%-80s', 'IMAGE'],
['%-50s', 'COMMAND'],
['%-5s', 'CPUS'],
['%-10s', 'MEMORY'],
['%-6s', 'CONTAINERS'],
]);
_.forEach(response.body, (application) => {
const parsed_containers = _.groupBy(application.containers, (container) => container.status);
var loaded_containers = parsed_containers.loaded || [];
utils.println([
['%-50s', application.id],
['%-80s', application.image],
['%-50s', application.command],
['%-5s', application.cpus],
['%-10s', application.memory],
['%-6s', `${loaded_containers.length || 0}/${application.containers.length}`]
]);
});
});
}
},
{
name: 'show',
options: {
application: {
position: 1,
help: 'Name of the application to fetch',
metavar: 'APPLICATION',
required: true
},
},
callback: (options) => {
request.get(`applications/${options.application}`, {}, (err, response) => {
if(err) {
process.stderr.write(`Could not fetch application ${options.application}!`);
process.exit(1);
} else if(response.statusCode == 404) {
process.stderr.write(`Application ${options.application} does not exist!`);
process.exit(1);
} else if(response.statusCode != 200) {
process.stderr.write(response.body.error);
process.exit(1);
} else {
utils.println([ ['%-20s', 'ENGINE'], ['%-100s', response.body.engine] ]);
utils.println([ ['%-20s', 'IMAGE'], ['%-100s', response.body.image] ]);
utils.println([ ['%-20s', 'COMMAND'], ['%-100s', response.body.command] ]);
utils.println([ ['%-20s', 'CPUS'], ['%-100s', response.body.cpus] ]);
utils.println([ ['%-20s', 'MEMORY'], ['%-100s', response.body.memory] ]);
utils.println([ ['%-20s', 'NETWORK MODE'], ['%-100s', response.body.network_mode] ]);
utils.println([ ['%-20s', 'DISCOVERY PORT'], ['%-100s', response.body.discovery_port] ]);
utils.println([ ['%-20s', 'CONTAINER PORT'], ['%-100s', response.body.container_port || ''] ]);
utils.println();
utils.println([ ['%-20s', 'ENV VARS'], ['%-50s', 'NAME'], ['%-50s', 'VALUE'] ]);
_.forEach(response.body.env_vars, (val, key) => {
utils.println([ ['%-20s', ''], ['%-50s', key], ['%-50s', val] ]);
});
utils.println();
utils.println([ ['%-20s', 'TAGS'], ['%-50s', 'NAME'], ['%-50s', 'VALUE'] ]);
_.forEach(flat(response.body.tags), (val, key) => {
utils.println([ ['%-20s', ''], ['%-50s', key], ['%-50s', val] ]);
});
utils.println();
utils.println([ ['%-20s', 'CONTAINERS'], ['%-50s', 'ID'], ['%-50s', 'HOST'], ['%-20s', 'STATUS'] ]);
_.forEach(response.body.containers, function(container){
utils.println([ ['%-20s', ''], ['%-50s', container.id], ['%-50s', container.host], ['%-20s', container.status] ]);
});
}
});
}
},
{
name: 'scale-up',
options: {
application: {
position: 1,
help: 'Name of the application to scale up',
metavar: 'APPLICATION',
required: true
},
count: {
help: 'Number of containers to add',
metavar: 'NUM CONTAINERS',
default: 1
},
tag: {
help: 'Tag to add to new containers',
metavar: 'NAME=VALUE',
list: true
}
},
callback: (options) => {
if(!_.has(options, 'count')) {
options.count = 1;
}
if(_.has(options, 'tag')) {
options.tags = utils.parse_tags(options.tag);
delete options.tag;
} else {
options.tags = {};
}
request.post(`applications/${options.application}/containers`, { count: options.count }, { tags: options.tags }, (err, response) => {
if(err) {
process.stderr.write(`Could not scale up application ${options.application}!`);
process.exit(1);
} else if(response.statusCode == 404) {
process.stderr.write(`Application ${options.application} does not exist!`);
process.exit(1);
} else if(response.statusCode != 201) {
process.stderr.write(response.body.error);
process.exit(1);
} else {
process.stdout.write(`Successfully scaled up application ${options.application}!`);
}
});
}
},
{
name: 'scale-down',
options: {
application: {
position: 1,
help: 'Name of the application to scale down',
metavar: 'APPLICATION',
required: true
},
count: {
help: 'Number of containers to remove',
metavar: 'NUM CONTAINERS',
default: 1
}
},
callback: (options) => {
request.delete(`applications/${options.application}/containers`, { count: options.count }, (err, response) => {
if(err) {
process.stderr.write(`Could not scale down application ${options.application}!`);
process.exit(1);
} else if(response.statusCode == 404) {
process.stderr.write(`Application ${options.application} does not exist!`);
process.exit(1);
} else if(response.statusCode != 204) {
process.stderr.write(response.body.error);
process.exit(1);
} else {
process.stdout.write(`Successfully scaled down application ${options.application}!`);
}
});
}
},
{
name: 'delete',
options: {
application: {
position: 1,
help: 'Name of the application to delete',
metavar: 'APPLICATION',
required: true
}
},
callback: (options) => {
request.delete(`applications/${options.application}`, {}, (err, response) => {
if(err) {
process.stderr.write(`Could not delete application ${options.application}!`);
process.exit(1);
} else if(response.statusCode == 404) {
process.stderr.write(`Application ${options.application} does not exist!`);
process.exit(1);
} else if(response.statusCode == 204) {
process.stdout.write(`Successfully deleted application ${options.application}!`);
} else {
process.stderr.write(`Could not delete application ${options.application}!`);
process.exit(1);
}
});
}
},
{
name: 'logs',
options: {
application: {
position: 1,
help: 'Name of the application to fetch logs for',
metavar: 'APPLICATION',
required: true
},
'container-id': {
list: true,
help: 'container id to fetch logs for',
metavar: 'CONTAINER_ID'
},
all: {
help: 'fetch logs for all containers',
flag: true,
default: false
}
},
callback: (options) => {
const local_request = {
get: (path, qs, callback) => {
const options = {
url: `${config.config['api-url']}/${config.config['api-version']}/${path}`,
headers: config.config.headers || {},
method: 'GET',
qs: qs,
json: true,
strictSSL: config.config.strict_ssl || true
};
async.waterfall(_.map(config.config.middleware || [], (middleware) => {
return function(callback){
middleware(options, callback);
};
}), (/*err*/) => {
const req_opts = {
host: url.parse(options.url).hostname,
path: url.parse(options.url).path,
method: options.method,
headers: options.headers || {}
};
if(url.parse(options.url).port) {
req_opts.port = url.parse(options.url).port;
}
req_opts.headers['Content-Type'] = 'application/json';
let protocol = url.parse(options.url).protocol;
protocol = protocol.substring(0, protocol.length - 1);
const req = protocols[protocol].request(req_opts, (response) => {
return callback(undefined, response);
});
req.on('error', callback);
if(req_opts.method === 'POST') {
req.write(JSON.stringify(options.json));
}
req.end();
});
}
};
function get_application(callback) {
request.get(`applications/${options.application}`, {}, (err, response) => {
if(err) {
return callback(new Error(`Could not fetch application ${options.application}!`));
} else if(response.statusCode == 404) {
return callback(new Error(`Application ${options.application} does not exist!`));
} else if(response.statusCode == 200) {
return callback(undefined, _.pluck(response.body.containers, 'id'));
}
});
}
function fetch_logs() {
_.forEach(options['container-id'], (container_id) => {
local_request.get(`logs/${options.application}/containers/${container_id}`, {}, (err, response) => {
if(err || response.statusCode != 200) {
process.stderr.write(`Could not fetch logs for container ${container_id} of application ${options.application}!\n`);
} else {
response.on('data', (chunk) => {
try {
const json = JSON.parse(chunk);
if(json.type === 'stdout') {
process.stdout.write(`[${json.name}]\t ${json.data}\n`);
} else if(json.type === 'stderr') {
process.stdout.write(colors.red(`[${json.name}]\t ${json.data}\n`));
}
} catch(err) {
process.stderr.write(colors.red('Unable to parse log'));
}
});
}
});
});
}
if(options.all) {
get_application((err, container_ids) => {
if(err) {
process.stderr.write(err.message);
process.exit(1);
} else {
options['container-id'] = container_ids;
fetch_logs();
}
});
} else if(!options['container-id']) {
get_application((err, container_ids) => {
if(err) {
process.stderr.write(err.message);
process.exit(1);
} else {
process.stdout.write('Please pass any of the following container ids to fetch logs, or pass the --all flag to get logs for every container:\n');
_.forEach(container_ids, (container_id) => {
process.stdout.write(`${container_id}\n`);
});
}
});
} else {
fetch_logs();
}
}
}
]
};
}
};