containership.plugin.cloud
Version:
Offical plugin for interacting with ContainerShip Cloud
595 lines (526 loc) • 22.9 kB
JavaScript
;
const constants = require('./constants');
const github = require('./integrations/github');
const logger = require('./logger');
const providers = require('./providers');
const _ = require('lodash');
const async = require('async');
const colors = require('colors');
const fs = require('fs');
const glob = require('glob');
const prmpt = require('prompt');
const request = require('request');
const sprintf = require('sprintf-js').sprintf;
const tar = require('tar-fs');
module.exports = {
login: {
options: {
method: {
position: 1,
help: 'Signin method',
metavar: 'METHOD',
required: true,
choices: ['github', 'bitbucket']
}
},
callback: function(options) {
prmpt.message = '';
prmpt.delimiter = colors.white(':');
prmpt.start();
function get_credentials(callback) {
prmpt.get([{
name: 'username',
description: colors.white(options.method === 'github' ? 'Username' : 'Email'),
required: true
}, {
name: 'password',
description: colors.white('Password'),
hidden: true
}], callback);
}
function get_otp(callback) {
prmpt.get([{
name: 'otp',
description: colors.white('Authentication Code'),
required: true
}], callback);
}
function authenticate(auth, method, callback) {
const request_options = {
url: `${constants.environment.AUTH_API_BASE_URL}/v1/authenticate/${method}/authorization`,
method: 'POST'
};
if(method === 'github') {
request_options.json = {
authorization: auth.token
};
} else if(method === 'bitbucket') {
request_options.json = auth;
}
request(request_options, (err, response) => {
if(err || response.statusCode !== 201) {
return callback(new Error('Error generating ContainerShip auth token'));
} else {
config.load();
if(_.isUndefined(config.config.headers)) {
config.config.headers = {};
}
config.config.headers.authorization = `Bearer ${response.body.token}`;
if(response.body.organization) {
config.config.headers['x-containership-cloud-organization'] = response.body.organization.id;
}
config.set(config.config);
return callback();
}
});
}
get_credentials((err, credentials) => {
function github_authorize(otp, callback) {
if(_.isFunction(otp)) {
callback = otp;
otp = undefined;
}
github.authorize(otp, callback);
}
if(err) {
process.stderr.write(`${err.message}\n`);
process.exit(1);
}
if(options.method === 'github') {
github.authenticate(credentials);
github.get_user((err) => {
if(err && err.headers && err.headers['x-github-otp']) {
get_otp((err, credentials) => {
if(err) {
process.stderr.write(`${err.message}\n`);
process.exit(1);
}
github_authorize(credentials.otp, (err, auth) => {
if(err) {
process.stderr.write(`${err.message}\n`);
process.exit(1);
}
authenticate(auth, options.method, (err) => {
if(err) {
process.stderr.write(`${err.message}\n`);
process.exit(1);
} else {
process.stdout.write(colors.green('\nSuccessfully logged in!\n'));
}
});
});
});
} else if(err) {
process.stderr.write(`${err.message}\n`);
process.exit(1);
} else {
github_authorize((err, auth) => {
if(err) {
process.stderr.write(`${err.message}\n`);
process.exit(1);
}
authenticate(auth, options.method, (err) => {
if(err) {
process.stderr.write(`${err.message}\n`);
process.exit(1);
} else {
process.stdout.write(colors.green('\nSuccessfully logged in!\n'));
}
});
});
}
});
} else if(options.method === 'bitbucket') {
authenticate(credentials, options.method, (err) => {
if(err) {
process.stderr.write(`${err.message}\n`);
process.exit(1);
} else {
process.stdout.write(colors.green('\nSuccessfully logged in!\n'));
}
});
} else {
process.stderr.write(`Not a recognized login method.\n`);
process.exit(1);
}
});
}
},
logout: {
options: {},
callback: function() {
config.load();
delete config.config.headers['authorization'];
delete config.config.headers['x-containership-cloud-organization'];
delete config.config.headers['x-containership-cloud-cluster'];
config.set(config.config);
logger.info('Successfully logged out!');
}
},
'list-orgs': {
options: {},
callback: function() {
config.load();
let request_options = {
url: `${constants.environment.CLOUD_API_BASE_URL}/v2/account`,
method: 'GET',
json: true,
headers: _.pick(config.config.headers, 'authorization')
};
request(request_options, function(err, response) {
if(err || response.statusCode !== 200) {
process.stderr.write('Could not fetch organizations!');
process.exit(1);
}
let orgs = {};
async.forEach(response.body.organizations || [], function(organization, fn) {
let request_options = {
url: `${constants.environment.CLOUD_API_BASE_URL}/v2/organizations/${organization.id}`,
method: 'GET',
json: true,
headers: _.pick(config.config.headers, 'authorization')
};
request(request_options, function(err, response) {
const owners = _.keyBy(response.body.users, 'id');
if(err || response.statusCode !== 200) {
return fn();
} else {
orgs[organization.id] = {
owner: owners[response.body.owner].display_name,
description: response.body.description || organization
};
return fn();
}
});
}, function() {
logger.info(sprintf('%-50s %-50s %-50s',
'ID',
'ORGANIZATION',
'OWNER'
));
_.forEach(orgs, function(org_details, org) {
logger.info(sprintf('%-50s %-50s %-50s',
[org, config.config.headers['x-containership-cloud-organization'] === org ? '*' : ''].join(''),
org_details.description,
org_details.owner
));
});
});
});
}
},
'use-org': {
options: {
organization: {
position: 1,
help: 'ContainerShip organization to use',
metavar: 'ORGANIZATION',
required: true
}
},
callback: function(options) {
config.load();
let request_options = {
url: `${constants.environment.CLOUD_API_BASE_URL}/v2/account`,
method: 'GET',
json: true,
headers: _.pick(config.config.headers, 'authorization')
};
request(request_options, function(err, response) {
if(err || response.statusCode !== 200) {
process.stderr.write('Could not fetch organizations!');
process.exit(1);
}
if(_.find(response.body.organizations, org => org.id === options.organization)) {
config.config.headers['x-containership-cloud-organization'] = options.organization;
config.set(config.config);
logger.info(['Successfully switched to', options.organization, 'organization!'].join(' '));
} else {
process.stderr.write(['You do not belong to the', options.organization, 'organization!'].join(' '));
process.exit(1);
}
});
}
},
'create-cluster': {
options: {
provider: {
position: 1,
help: 'Provider on which to launch the cluster',
metavar: 'PROVIDER',
required: true,
choices: ['vagrant']
},
leaders: {
help: 'Number of leaders to create',
metavar: 'LEADERS',
default: 1
},
followers: {
help: 'Number of followers to create',
metavar: 'FOLLOWERS',
default: 2
},
plugins: {
help: 'List of ContainerShip plugins to install',
metavar: 'PLUGINS',
default: 'navigator,service-discovery'
}
},
callback: function(options) {
let provider = providers[options.provider];
provider.validate(function(err) {
if(err) {
throw err;
}
provider.create({
leaders: options.leaders,
followers: options.followers,
plugins: options.plugins
}, function(err) {
if(err) {
throw err;
}
});
});
}
},
'destroy-cluster': {
options: {
'cluster-id': {
position: 1,
help: 'ID of the cluster to destroy',
metavar: 'CLUSTER_ID',
required: true
}
},
callback: function(options) {
config.load();
// eslint-disable-next-line handle-callback-err
providers.vagrant.get_clusters(function(err, clusters) {
let request_options = {
url: `${constants.environment.CLOUD_API_BASE_URL}/v2/organizations/${config.config.headers['x-containership-cloud-organization']}/clusters`,
method: 'GET',
json: true,
headers: _.pick(config.config.headers, 'authorization')
};
request(request_options, function(err, response) {
if(!err && response.statusCode === 200) {
clusters.push(response.body);
clusters = _.flatten(clusters);
}
clusters = _.keyBy(clusters, 'id');
if(_.has(clusters, options['cluster-id']) && clusters[options['cluster-id']].provider === 'vag') {
providers.vagrant.validate(function(err) {
if(err) {
throw err;
}
providers.vagrant.destroy(function(err) {
if(err) {
throw err;
}
logger.info(['Successfully destroyed cluster:', options['cluster-id']].join(' '));
});
});
} else {
process.stderr.write('Unable to destroy cluster!');
process.exit(1);
}
});
});
}
},
'use-cluster': {
options: {
'cluster-id': {
position: 1,
help: 'ContainerShip cluster ID',
metavar: 'CLUSTER ID',
required: true
}
},
callback: function(options) {
config.load();
config.config['api-url'] = constants.environment.CLOUD_API_BASE_URL;
if( config.config.headers === undefined ) {
config.config.headers = {};
}
config.config.headers['x-containership-cloud-cluster'] = options['cluster-id'];
config.set(config.config);
logger.info(['Successfully updated config to use cluster', options['cluster-id']].join(' '));
}
},
'list-clusters': {
options: {},
callback: function() {
config.load();
// eslint-disable-next-line handle-callback-err
providers.vagrant.get_clusters(function(err, clusters) {
let request_options = {
url: `${constants.environment.CLOUD_API_BASE_URL}/v2/organizations/${config.config.headers['x-containership-cloud-organization']}/clusters`,
method: 'GET',
json: true,
headers: _.pick(config.config.headers, 'authorization')
};
request(request_options, function(err, response) {
if(!err && response.statusCode === 200) {
clusters.push(response.body);
clusters = _.flatten(clusters);
}
let providers = {
aws: 'Amazon Web Services',
do: 'Digital Ocean',
na: 'Not Available',
vag: 'Vagrant'
};
logger.info(sprintf('%-40s %-40s %-25s %-15s %-10s %-10s',
'ID',
'NAME',
'PROVIDER',
'APPLICATIONS',
'LEADERS',
'FOLLOWERS'
));
_.forEach(clusters, function(cluster) {
let hosts = _.groupBy(_.values(cluster.hosts), function(host) {
return host.mode;
});
logger.info(sprintf('%-40s %-40s %-25s %-15s %-10s %-10s',
cluster.id,
cluster.name || '',
providers[cluster.provider] || providers.na,
_.keys(cluster.applications).length,
hosts.leader && hosts.leader.length || '0',
hosts.follower && hosts.follower.length || '0'
));
});
});
});
}
},
build: {
options: {},
callback: function() {
let image = _.last(process.cwd().split('/'));
async.waterfall([
function(fn) {
fs.readFile([process.cwd(), '.gitignore'].join('/'), function(err, gitignore) {
if(err) {
return fn(undefined, new Buffer('# no gitignore present'));
} else {
return fn(undefined, gitignore);
}
});
},
function(gitignore, fn) {
let ignored = [];
let lines = gitignore.toString().split('\n');
_.forEach(lines, function(line) {
if(line.indexOf('#') !== 0 && line.length > 0) {
ignored.push(line);
}
});
return fn(undefined, ignored);
},
function(ignored, fn) {
let start_index = process.cwd().length + 1;
let packager = tar.pack(process.cwd(), {
ignore: function(file) {
let ignore = false;
file = file.substring(start_index, file.length);
_.forEach(ignored, function(pattern) {
let match = glob.sync(pattern);
if(!_.isEmpty(match) && match[0] === file) {
ignore = true;
}
});
return ignore;
}
});
return fn(undefined, packager);
},
function(packager, fn) {
config.load();
let options = {
url: `${constants.environment.BUILD_API_BASE_URL}/v1/organizations/${config.config.headers['x-containership-cloud-organization']}/${image}/builds`,
method: 'POST',
headers: {
authorization: config.config.headers.authorization
}
};
packager.pipe(request(options)
.on('response', function(response) {
let body;
response.on('data', function(data) {
try {
body = JSON.parse(data);
} catch(e) {
body = e.message;
}
});
response.on('end', function() {
if(response.statusCode === 201) {
return fn(null, body);
} else if(response.statusCode === 401) {
return fn(new Error('Authentication error! Your token may have expired. Please login again using the `cs cloud login` command'));
} else {
return fn(new Error(['Build returned status code: ', response.statusCode, '. ', body].join('')));
}
});
})
.on('error', function(err) {
return fn(err);
})
);
}
], function(err, build_id) {
if(err) {
process.stderr.write(err.message);
process.exit(1);
} else {
logger.info(['Starting build with ID:', build_id].join(' '));
let options = {
url: `${constants.environment.BUILD_API_BASE_URL}/v1/organizations/${config.config.headers['x-containership-cloud-organization']}/builds/${build_id}/logs`,
method: 'GET',
headers: {
authorization: config.config.headers.authorization
},
timeout: 300000
};
request(options).on('response', function(response) {
response.on('data', function(data) {
process.stdout.write(data.toString());
});
}).on('error', function(err) {
logger.error(err);
});
}
});
}
}
};
let config = {
load: function() {
try {
this.config = JSON.parse(fs.readFileSync([process.env['HOME'], '.containership', 'cli.json'].join('/')));
} catch(e) {
process.stdout.write('Could not load Containership config file. Does it exist?');
process.exit(1);
}
},
set: function(new_config) {
let config;
try {
config = JSON.parse(fs.readFileSync([process.env['HOME'], '.containership', 'cli.json'].join('/')));
} catch(e) {
config = {};
}
try {
fs.writeFileSync([process.env['HOME'], '.containership', 'cli.json'].join('/'), JSON.stringify(new_config));
this.config = config;
} catch(e) {
process.stdout.write('Could not write Containership config file');
process.exit(1);
}
}
};