mirror of
https://github.com/caprover/caprover
synced 2026-09-24 15:45:48 +00:00
Change folder structure and starting to refactor files
This commit is contained in:
@@ -1,748 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
const program = require('commander');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
const inquirer = require('inquirer');
|
||||
const configstore = require('configstore');
|
||||
const request = require('request');
|
||||
const commandExistsSync = require('command-exists').sync;
|
||||
const ProgressBar = require('progress');
|
||||
const ora = require('ora');
|
||||
const {exec} = require('child_process');
|
||||
|
||||
const packagejson = require('./package.json');
|
||||
|
||||
const configs = new configstore(packagejson.name, {
|
||||
captainMachines: [],
|
||||
apps: []
|
||||
});
|
||||
|
||||
const BRANCH_TO_PUSH = 'branchToPush';
|
||||
const APP_NAME = 'appName';
|
||||
const MACHINE_TO_DEPLOY = 'machineToDeploy';
|
||||
const EMPTY_STRING = '';
|
||||
|
||||
console.log(' ');
|
||||
console.log(' ');
|
||||
|
||||
program
|
||||
.description('Deploy current directory to a Captain machine.')
|
||||
.option('-t, --tarFile <value>', 'Specify file to be uploaded (rather than using git archive)')
|
||||
.option('-d, --default', 'Run with default options')
|
||||
.option('-s, --stateless', 'Run deploy stateless')
|
||||
.option('-h, --host <value>', 'Only for stateless mode: Host of the captain machine')
|
||||
.option('-a, --appName <value>', 'Only for stateless mode: Name of the app')
|
||||
.option('-p, --pass <value>', 'Only for stateless mode: Password for Captain')
|
||||
.option('-b, --branch <value>', 'Only for stateless mode: Branch name (default master)')
|
||||
.parse(process.argv);
|
||||
|
||||
|
||||
if (program.args.length) {
|
||||
console.error(chalk.red('Unrecognized commands:'));
|
||||
program.args.forEach(function (arg) {
|
||||
console.log(chalk.red(arg));
|
||||
});
|
||||
console.error(chalk.red('Deploy does not require any options. '));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function printErrorAndExit(error) {
|
||||
console.log(chalk.bold.red(error));
|
||||
console.log(' ');
|
||||
console.log(' ');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
function getSuppliedTarFile() {
|
||||
return program.tarFile;
|
||||
}
|
||||
|
||||
if (!getSuppliedTarFile()) {
|
||||
|
||||
if (!fs.pathExistsSync('./.git')) {
|
||||
printErrorAndExit('**** ERROR: You are not in a git root directory. This command will only deploys the current directory ****');
|
||||
}
|
||||
|
||||
if (!fs.pathExistsSync('./captain-definition')) {
|
||||
printErrorAndExit('**** ERROR: captain-definition file cannot be found. Please see docs! ****');
|
||||
}
|
||||
|
||||
const contents = fs.readFileSync('./captain-definition', 'utf8');
|
||||
let contentsJson = null;
|
||||
|
||||
try {
|
||||
contentsJson = JSON.parse(contents);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log('');
|
||||
printErrorAndExit('**** ERROR: captain-definition file is not a valid JSON! ****');
|
||||
}
|
||||
|
||||
if (!contentsJson.schemaVersion) {
|
||||
printErrorAndExit('**** ERROR: captain-definition needs schemaVersion. Please see docs! ****');
|
||||
}
|
||||
|
||||
if (!contentsJson.templateId && !contentsJson.dockerfileLines) {
|
||||
printErrorAndExit('**** ERROR: captain-definition needs templateId or dockerfileLines. Please see docs! ****');
|
||||
}
|
||||
|
||||
if (contentsJson.templateId && contentsJson.dockerfileLines) {
|
||||
printErrorAndExit('**** ERROR: captain-definition needs templateId or dockerfileLines, NOT BOTH! Please see docs! ****');
|
||||
}
|
||||
}
|
||||
|
||||
let listOfMachines = [{
|
||||
name: '-- CANCEL --',
|
||||
value: EMPTY_STRING,
|
||||
short: EMPTY_STRING
|
||||
}];
|
||||
|
||||
let machines = configs.get('captainMachines');
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
let m = machines[i];
|
||||
listOfMachines.push({
|
||||
name: m.name + ' at ' + m.baseUrl,
|
||||
value: m.name,
|
||||
short: m.name + ' at ' + m.baseUrl
|
||||
})
|
||||
}
|
||||
|
||||
// Gets default value for propType that is stored in a directory.
|
||||
// Replaces getAppForDirectory
|
||||
function getPropForDirectory(propType) {
|
||||
let apps = configs.get('apps');
|
||||
for (let i = 0; i < apps.length; i++) {
|
||||
let app = apps[i];
|
||||
if (app.cwd === process.cwd()) {
|
||||
return app[propType];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Sets default value for propType that is stored in a directory to propValue.
|
||||
// Replaces saveAppForDirectory
|
||||
function savePropForDirectory(propType, propValue) {
|
||||
let apps = configs.get('apps');
|
||||
for (let i = 0; i < apps.length; i++) {
|
||||
let app = apps[i];
|
||||
if (app.cwd === process.cwd()) {
|
||||
app[propType] = propValue;
|
||||
configs.set('apps', apps);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
apps.push({
|
||||
cwd: process.cwd(),
|
||||
[propType]: propValue
|
||||
});
|
||||
|
||||
configs.set('apps', apps);
|
||||
}
|
||||
|
||||
|
||||
function getDefaultMachine() {
|
||||
let machine = getPropForDirectory(MACHINE_TO_DEPLOY);
|
||||
if (machine) {
|
||||
return machine.name;
|
||||
}
|
||||
if (listOfMachines.length == 2) {
|
||||
return 1;
|
||||
}
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
|
||||
console.log('Preparing deployment to Captain...');
|
||||
console.log(' ');
|
||||
|
||||
|
||||
const questions = [
|
||||
{
|
||||
type: 'list',
|
||||
name: 'captainNameToDeploy',
|
||||
default: getDefaultMachine(),
|
||||
message: 'Select the Captain Machine you want to deploy to:',
|
||||
choices: listOfMachines
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
default: getPropForDirectory(BRANCH_TO_PUSH) || 'master',
|
||||
name: BRANCH_TO_PUSH,
|
||||
message: 'Enter the "git" branch you would like to deploy:',
|
||||
when: function (answers) {
|
||||
return !!answers.captainNameToDeploy;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
default: getPropForDirectory(APP_NAME),
|
||||
name: APP_NAME,
|
||||
message: 'Enter the Captain app name this directory will be deployed to:',
|
||||
when: function (answers) {
|
||||
return !!answers.captainNameToDeploy;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirmedToDeploy',
|
||||
message: 'Note that uncommitted files and files in gitignore (if any) will not be pushed to server. Please confirm so that deployment process can start.',
|
||||
default: true,
|
||||
when: function (answers) {
|
||||
return !!answers.captainNameToDeploy;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
let defaultInvalid = false;
|
||||
|
||||
if (program.default) {
|
||||
|
||||
if (!getDefaultMachine() || !getPropForDirectory(BRANCH_TO_PUSH) || !getPropForDirectory(APP_NAME)) {
|
||||
console.log('Default deploy failed. Please select deploy options.');
|
||||
defaultInvalid = true;
|
||||
}
|
||||
else {
|
||||
console.log('Deploying to ' + getPropForDirectory(MACHINE_TO_DEPLOY).name);
|
||||
deployTo(getPropForDirectory(MACHINE_TO_DEPLOY), getPropForDirectory(BRANCH_TO_PUSH), getPropForDirectory(APP_NAME));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const isStateless = program.stateless && program.host && program.appName && program.pass;
|
||||
|
||||
if (isStateless) {
|
||||
// login first
|
||||
console.log('Trying to login to', program.host)
|
||||
requestLoginAuth(program.host, program.pass, function (authToken) {
|
||||
// deploy
|
||||
console.log('Starting stateless deploy to', program.host, program.branch, program.appName);
|
||||
deployTo({
|
||||
baseUrl: program.host,
|
||||
authToken,
|
||||
}, program.branch || 'master', program.appName);
|
||||
});
|
||||
}
|
||||
else if (!program.default || defaultInvalid) {
|
||||
|
||||
inquirer.prompt(questions).then(function (answers) {
|
||||
|
||||
console.log(' ');
|
||||
console.log(' ');
|
||||
|
||||
if (!answers.confirmedToDeploy) {
|
||||
console.log('Operation cancelled by the user...');
|
||||
console.log(' ');
|
||||
}
|
||||
else {
|
||||
let machines = configs.get('captainMachines');
|
||||
let machineToDeploy = null;
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
if (machines[i].name === answers.captainNameToDeploy) {
|
||||
console.log('Deploying to ' + answers.captainNameToDeploy);
|
||||
machineToDeploy = machines[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(' ');
|
||||
deployTo(machineToDeploy, answers.branchToPush, answers.appName);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function deployTo(machineToDeploy, branchToPush, appName) {
|
||||
|
||||
function checkAuthAndSendFile(zipFileFullPath, gitHash) {
|
||||
function isAuthTokenValidCallback(isValid) {
|
||||
if (isValid) {
|
||||
sendFileToCaptain(machineToDeploy, zipFileFullPath, appName, gitHash, branchToPush);
|
||||
}
|
||||
else {
|
||||
|
||||
requestLogin(machineToDeploy.name, machineToDeploy.baseUrl, function callback(machineToDeployNew) {
|
||||
|
||||
deployTo(machineToDeployNew, branchToPush, appName);
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
isAuthTokenValid(machineToDeploy, appName, isAuthTokenValidCallback);
|
||||
}
|
||||
|
||||
if (getSuppliedTarFile()) {
|
||||
checkAuthAndSendFile(path.join(process.cwd(), getSuppliedTarFile()), 'sendviatarfile');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!commandExistsSync('git')) {
|
||||
console.log(chalk.red('"git" command not found...'));
|
||||
console.log(chalk.red('Captain needs "git" to create tar file of your source files...'));
|
||||
console.log(' ');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let zipFileNameToDeploy = 'temporary-captain-to-deploy.tar';
|
||||
let zipFileFullPath = path.join(process.cwd(), zipFileNameToDeploy);
|
||||
|
||||
console.log('Saving tar file to:');
|
||||
console.log(zipFileFullPath);
|
||||
console.log(' ');
|
||||
|
||||
try {
|
||||
fs.removeSync(zipFileFullPath);
|
||||
} catch (ignoreError) {
|
||||
|
||||
}
|
||||
|
||||
exec('git archive --format tar --output "' + zipFileFullPath + '" ' + branchToPush, function (err, stdout, stderr) {
|
||||
if (err) {
|
||||
console.log(chalk.red('TAR file failed'));
|
||||
console.log(chalk.red(err + ' '));
|
||||
console.log(' ');
|
||||
fs.removeSync(zipFileFullPath);
|
||||
return;
|
||||
}
|
||||
|
||||
exec('git rev-parse ' + branchToPush, function (err, stdout, stderr) {
|
||||
|
||||
const gitHash = (stdout || '').trim();
|
||||
|
||||
if (err || !(/^[a-f0-9]{40}$/.test(gitHash))) {
|
||||
console.log(chalk.red('Cannot find hash of last commit on this branch: ' + branchToPush));
|
||||
console.log(chalk.red(gitHash + ' '));
|
||||
console.log(chalk.red(err + ' '));
|
||||
console.log(' ');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Pushing last commit on ' + branchToPush + ': ' + gitHash);
|
||||
|
||||
checkAuthAndSendFile(zipFileFullPath, gitHash);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function sendFileToCaptain(machineToDeploy, zipFileFullPath, appName, gitHash, branchToPush) {
|
||||
|
||||
console.log('Uploading file to ' + machineToDeploy.baseUrl);
|
||||
|
||||
const fileSize = fs.statSync(zipFileFullPath).size;
|
||||
const fileStream = fs.createReadStream(zipFileFullPath);
|
||||
|
||||
const barOpts = {
|
||||
width: 20,
|
||||
total: fileSize,
|
||||
clear: true
|
||||
};
|
||||
const bar = new ProgressBar(' uploading [:bar] :percent (ETA :etas)', barOpts);
|
||||
fileStream.on('data', function (chunk) {
|
||||
bar.tick(chunk.length);
|
||||
});
|
||||
|
||||
let spinner;
|
||||
|
||||
fileStream.on('end', function () {
|
||||
console.log(' ');
|
||||
console.log('This might take several minutes. PLEASE BE PATIENT...');
|
||||
spinner = ora('Building your source code...').start();
|
||||
spinner.color = 'yellow';
|
||||
});
|
||||
|
||||
|
||||
let options = {
|
||||
url: machineToDeploy.baseUrl + '/api/v1/user/appData/' + appName + '/?detached=1',
|
||||
headers: {
|
||||
'x-namespace': 'captain',
|
||||
'x-captain-auth': machineToDeploy.authToken
|
||||
},
|
||||
method: 'POST',
|
||||
formData: {
|
||||
sourceFile: fileStream,
|
||||
gitHash: gitHash
|
||||
}
|
||||
};
|
||||
|
||||
function callback(error, response, body) {
|
||||
|
||||
if (spinner) {
|
||||
spinner.stop();
|
||||
}
|
||||
|
||||
if (fs.pathExistsSync(zipFileFullPath)) {
|
||||
if (!getSuppliedTarFile()) {
|
||||
fs.removeSync(zipFileFullPath);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
if (!error && response.statusCode === 200) {
|
||||
|
||||
let data = JSON.parse(body);
|
||||
|
||||
if (data.status === 1106) {
|
||||
// expired token
|
||||
requestLogin(machineToDeploy.name, machineToDeploy.baseUrl, function callback(machineToDeployNew) {
|
||||
|
||||
deployTo(machineToDeployNew, branchToPush, appName);
|
||||
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.status !== 100 && data.status !== 101) {
|
||||
throw new Error(JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
savePropForDirectory(APP_NAME, appName);
|
||||
savePropForDirectory(BRANCH_TO_PUSH, branchToPush);
|
||||
savePropForDirectory(MACHINE_TO_DEPLOY, machineToDeploy);
|
||||
|
||||
if (data.status === 100) {
|
||||
console.log(chalk.green('Deployed successfully: ') + appName);
|
||||
console.log(' ');
|
||||
} else if (data.status === 101) {
|
||||
console.log(chalk.green('Building started: ') + appName);
|
||||
console.log(' ');
|
||||
startFetchingBuildLogs(machineToDeploy, appName);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
throw new Error(response ? JSON.stringify(response, null, 2) : 'Response NULL');
|
||||
|
||||
} catch (error) {
|
||||
|
||||
console.error(chalk.red('\nSomething bad happened. Cannot deploy "' + appName + '"\n'));
|
||||
|
||||
if (error.message) {
|
||||
try {
|
||||
var errorObj = JSON.parse(error.message);
|
||||
if (errorObj.status) {
|
||||
console.error(chalk.red('\nError code: ' + errorObj.status));
|
||||
console.error(chalk.red('\nError message:\n\n ' + errorObj.description));
|
||||
} else {
|
||||
throw new Error("NOT API ERROR");
|
||||
}
|
||||
} catch (ignoreError) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.red(error));
|
||||
}
|
||||
console.log(' ');
|
||||
}
|
||||
}
|
||||
|
||||
request(options, callback);
|
||||
|
||||
}
|
||||
|
||||
var lastLineNumberPrinted = -10000; // we want to show all lines to begin with!
|
||||
|
||||
function startFetchingBuildLogs(machineToDeploy, appName) {
|
||||
|
||||
let options = {
|
||||
url: machineToDeploy.baseUrl + '/api/v1/user/appData/' + appName,
|
||||
headers: {
|
||||
'x-namespace': 'captain',
|
||||
'x-captain-auth': machineToDeploy.authToken
|
||||
},
|
||||
method: 'GET'
|
||||
};
|
||||
|
||||
function onLogRetrieved(data) {
|
||||
|
||||
if (data) {
|
||||
var lines = data.logs.lines;
|
||||
var firstLineNumberOfLogs = data.logs.firstLineNumber;
|
||||
var firstLinesToPrint = 0;
|
||||
if (firstLineNumberOfLogs > lastLineNumberPrinted) {
|
||||
|
||||
if (firstLineNumberOfLogs < 0) {
|
||||
// This is the very first fetch, probably firstLineNumberOfLogs is around -50
|
||||
firstLinesToPrint = -firstLineNumberOfLogs;
|
||||
} else {
|
||||
console.log('[[ TRUNCATED ]]');
|
||||
}
|
||||
|
||||
} else {
|
||||
firstLinesToPrint = lastLineNumberPrinted - firstLineNumberOfLogs;
|
||||
}
|
||||
|
||||
lastLineNumberPrinted = firstLineNumberOfLogs + lines.length;
|
||||
|
||||
for (var i = firstLinesToPrint; i < lines.length; i++) {
|
||||
console.log((lines[i] || '').trim());
|
||||
}
|
||||
}
|
||||
|
||||
if (data && !data.isAppBuilding) {
|
||||
console.log(' ');
|
||||
if (!data.isBuildFailed) {
|
||||
console.log(chalk.green('Deployed successfully: ') + appName);
|
||||
console.log(chalk.magenta('App is available at ')
|
||||
+ (machineToDeploy.baseUrl.replace('//captain.', '//' + appName + '.').replace('https://', 'http://'))
|
||||
);
|
||||
} else {
|
||||
console.error(chalk.red('\nSomething bad happened. Cannot deploy "' + appName + '"\n'));
|
||||
}
|
||||
console.log(' ');
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
startFetchingBuildLogs(machineToDeploy, appName);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
|
||||
function callback(error, response, body) {
|
||||
|
||||
try {
|
||||
|
||||
if (!error && response.statusCode === 200) {
|
||||
|
||||
let data = JSON.parse(body);
|
||||
|
||||
if (data.status !== 100) {
|
||||
throw new Error(JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
onLogRetrieved(data.data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
throw new Error(response ? JSON.stringify(response, null, 2) : 'Response NULL');
|
||||
|
||||
} catch (error) {
|
||||
|
||||
console.error(chalk.red('\nSomething while retrieving app build logs.. "' + error + '"\n'));
|
||||
|
||||
onLogRetrieved(null);
|
||||
}
|
||||
}
|
||||
|
||||
request(options, callback);
|
||||
}
|
||||
|
||||
function isAuthTokenValid(machineToDeploy, appName, isAuthTokenValidCallback) {
|
||||
|
||||
let options = {
|
||||
url: machineToDeploy.baseUrl + '/api/v1/user/appDefinitions/',
|
||||
headers: {
|
||||
'x-namespace': 'captain',
|
||||
'x-captain-auth': machineToDeploy.authToken
|
||||
},
|
||||
method: 'GET'
|
||||
};
|
||||
|
||||
|
||||
function callback(error, response, body) {
|
||||
|
||||
try {
|
||||
|
||||
if (!error && response.statusCode === 200) {
|
||||
|
||||
let data = JSON.parse(body);
|
||||
|
||||
if (data.status === 1106 || data.status === 1105) {
|
||||
isAuthTokenValidCallback(false);
|
||||
}
|
||||
else {
|
||||
isAuthTokenValidCallback(true);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// This is just a sanity check. We only fire FALSE (i.e. expired) if we know it's expired or password is wrong
|
||||
isAuthTokenValidCallback(true);
|
||||
}
|
||||
}
|
||||
|
||||
request(options, callback);
|
||||
}
|
||||
|
||||
function requestLoginAuth(serverAddress, password, authCallback) {
|
||||
let options = {
|
||||
url: serverAddress + '/api/v1/login',
|
||||
headers: {
|
||||
'x-namespace': 'captain'
|
||||
},
|
||||
method: 'POST',
|
||||
form: {
|
||||
password: password
|
||||
}
|
||||
};
|
||||
|
||||
function callback(error, response, body) {
|
||||
|
||||
try {
|
||||
|
||||
if (!error && response.statusCode === 200) {
|
||||
|
||||
let data = JSON.parse(body);
|
||||
|
||||
if (data.status !== 100) {
|
||||
throw new Error(JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
authCallback(data.token);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
throw new Error(response ? JSON.stringify(response, null, 2) : 'Response NULL');
|
||||
|
||||
} catch (error) {
|
||||
|
||||
if (error.message) {
|
||||
try {
|
||||
var errorObj = JSON.parse(error.message);
|
||||
if (errorObj.status) {
|
||||
console.error(chalk.red('\nError code: ' + errorObj.status));
|
||||
console.error(chalk.red('\nError message:\n\n ' + errorObj.description));
|
||||
} else {
|
||||
throw new Error("NOT API ERROR");
|
||||
}
|
||||
} catch (ignoreError) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.red(error));
|
||||
}
|
||||
console.log(' ');
|
||||
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
request(options, callback);
|
||||
}
|
||||
|
||||
function requestLogin(serverName, serverAddress, loginCallback) {
|
||||
|
||||
console.log('Your auth token is not valid anymore. Try to login again.');
|
||||
|
||||
const questions = [
|
||||
{
|
||||
type: 'password',
|
||||
name: 'captainPassword',
|
||||
message: 'Please enter your password for ' + serverAddress,
|
||||
validate: function (value) {
|
||||
|
||||
if (value && value.trim()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ('Please enter your password for ' + serverAddress);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function updateAuthTokenInConfigStoreAndReturn(authToken) {
|
||||
|
||||
let machines = configs.get('captainMachines');
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
if (machines[i].name === serverName) {
|
||||
var baseUrl = machines[i].authToken = authToken;
|
||||
configs.set('captainMachines', machines);
|
||||
console.log('You are now logged back in to ' + serverAddress);
|
||||
return machines[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inquirer.prompt(questions).then(function (passwordAnswers) {
|
||||
|
||||
var password = passwordAnswers.captainPassword;
|
||||
|
||||
let options = {
|
||||
url: serverAddress + '/api/v1/login',
|
||||
headers: {
|
||||
'x-namespace': 'captain'
|
||||
},
|
||||
method: 'POST',
|
||||
form: {
|
||||
password: password
|
||||
}
|
||||
};
|
||||
|
||||
function callback(error, response, body) {
|
||||
|
||||
try {
|
||||
|
||||
if (!error && response.statusCode === 200) {
|
||||
|
||||
let data = JSON.parse(body);
|
||||
|
||||
if (data.status !== 100) {
|
||||
throw new Error(JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
var newMachineToDeploy = updateAuthTokenInConfigStoreAndReturn(data.token);
|
||||
|
||||
loginCallback(newMachineToDeploy);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
throw new Error(response ? JSON.stringify(response, null, 2) : 'Response NULL');
|
||||
|
||||
} catch (error) {
|
||||
|
||||
if (error.message) {
|
||||
try {
|
||||
var errorObj = JSON.parse(error.message);
|
||||
if (errorObj.status) {
|
||||
console.error(chalk.red('\nError code: ' + errorObj.status));
|
||||
console.error(chalk.red('\nError message:\n\n ' + errorObj.description));
|
||||
} else {
|
||||
throw new Error("NOT API ERROR");
|
||||
}
|
||||
} catch (ignoreError) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.red(error));
|
||||
}
|
||||
console.log(' ');
|
||||
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
request(options, callback);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
const program = require('commander');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
const inquirer = require('inquirer');
|
||||
const configstore = require('configstore');
|
||||
const request = require('request');
|
||||
|
||||
const packagejson = require('./package.json');
|
||||
|
||||
|
||||
const configs = new configstore(packagejson.name, {
|
||||
captainMachines: []
|
||||
});
|
||||
|
||||
|
||||
program
|
||||
.description('List all Captain machines currently logged in.')
|
||||
.parse(process.argv);
|
||||
|
||||
if (program.args.length) {
|
||||
console.error(chalk.red('Unrecognized commands:'));
|
||||
program.args.forEach(function (arg) {
|
||||
console.log(chalk.red(arg));
|
||||
});
|
||||
console.error(chalk.red('List does not require any options. '));
|
||||
process.exit(1);
|
||||
}
|
||||
console.log();
|
||||
console.log('Logged in Captain Machines:');
|
||||
console.log();
|
||||
let machines = configs.get('captainMachines');
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
|
||||
console.log('>> ' + chalk.greenBright(machines[i].name) + ' at ' + chalk.cyan(machines[i].baseUrl));
|
||||
|
||||
}
|
||||
console.log();
|
||||
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
const program = require('commander');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
const inquirer = require('inquirer');
|
||||
const configstore = require('configstore');
|
||||
const request = require('request');
|
||||
|
||||
const packagejson = require('./package.json');
|
||||
|
||||
|
||||
const configs = new configstore(packagejson.name, {
|
||||
captainMachines: []
|
||||
});
|
||||
|
||||
|
||||
program
|
||||
.description('Login to a CaptainDuckDuck machine. You can be logged in to multiple machines simultaneously.')
|
||||
.parse(process.argv);
|
||||
|
||||
|
||||
function cleanUpUrl(url) {
|
||||
if (!url) {
|
||||
return url;
|
||||
}
|
||||
url = url.trim();
|
||||
url = url.replace('http://', '').replace('https://', '');
|
||||
if (!url || !url.length) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (url.substr(url.length - 1, 1) === '/') {
|
||||
url = url.substr(0, url.length - 1);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
if (program.args.length) {
|
||||
console.error(chalk.red('Unrecognized commands:'));
|
||||
program.args.forEach(function (arg) {
|
||||
console.log(chalk.red(arg));
|
||||
});
|
||||
console.error(chalk.red('Login does not require any options. '));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function findDefaultCaptainName() {
|
||||
|
||||
let machines = configs.get('captainMachines');
|
||||
let currentSuffix = machines.length + 1;
|
||||
|
||||
function getCaptainFullName(suffix) {
|
||||
if (suffix < 10) {
|
||||
suffix = '0' + suffix;
|
||||
}
|
||||
return 'captain-' + suffix;
|
||||
}
|
||||
|
||||
function isSuffixValid(suffixNumber) {
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
let m = machines[i];
|
||||
if (m.name === getCaptainFullName(suffixNumber)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
while (!isSuffixValid(currentSuffix)) {
|
||||
currentSuffix++;
|
||||
}
|
||||
|
||||
return getCaptainFullName(currentSuffix);
|
||||
}
|
||||
|
||||
console.log('Login to a Captain Machine');
|
||||
|
||||
const SAMPLE_DOMAIN = 'captain.captainroot.yourdomain.com';
|
||||
|
||||
const questions = [
|
||||
{
|
||||
type: 'input',
|
||||
default: SAMPLE_DOMAIN,
|
||||
name: 'captainAddress',
|
||||
message: 'Enter address of the Captain machine. \nIt is captain.[your-captain-root-domain] :',
|
||||
validate: function (value) {
|
||||
|
||||
if (value===SAMPLE_DOMAIN){
|
||||
return 'Enter a valid URL';
|
||||
}
|
||||
|
||||
if (!cleanUpUrl(value)) {
|
||||
return 'This is an invalid URL: ' + value;
|
||||
}
|
||||
|
||||
let machines = configs.get('captainMachines');
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
if (cleanUpUrl(machines[i].baseUrl) === cleanUpUrl(value)) {
|
||||
return value + ' already exist as ' + machines[i].name + '. If you want to replace the existing entry, you have to first use <logout> command, and then re-login.'
|
||||
}
|
||||
}
|
||||
|
||||
if (value && value.trim()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'Please enter a valid address.';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'captainHasRootSsl',
|
||||
message: 'Is HTTPS activated for this Captain machine?',
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'captainPassword',
|
||||
message: 'Enter your password:',
|
||||
validate: function (value) {
|
||||
|
||||
if (value && value.trim()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'Please enter your password.';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'captainName',
|
||||
message: 'Enter a name for this Captain machine:',
|
||||
default: findDefaultCaptainName(),
|
||||
validate: function (value) {
|
||||
|
||||
let machines = configs.get('captainMachines');
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
if (machines[i].name === value) {
|
||||
return value + ' already exist. If you want to replace the existing entry, you have to first use <logout> command, and then re-login.'
|
||||
}
|
||||
}
|
||||
|
||||
if (value.match(/^[-\d\w]+$/i)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'Please enter a Captain Name.';
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
inquirer.prompt(questions).then(function (answers) {
|
||||
|
||||
|
||||
console.log(' ');
|
||||
console.log(' ');
|
||||
|
||||
let baseUrl = (answers.captainHasRootSsl ? 'https://' : 'http://') + cleanUpUrl(answers.captainAddress);
|
||||
|
||||
let options = {
|
||||
url: baseUrl + '/api/v1/login',
|
||||
headers: {
|
||||
'x-namespace': 'captain'
|
||||
},
|
||||
method: 'POST',
|
||||
form: {
|
||||
password: answers.captainPassword
|
||||
}
|
||||
};
|
||||
|
||||
function setCaptainMachine(data) {
|
||||
let machines = configs.get('captainMachines');
|
||||
machines.push(data);
|
||||
configs.set('captainMachines', machines);
|
||||
}
|
||||
|
||||
function callback(error, response, body) {
|
||||
|
||||
let captainNameEnterByUser = answers.captainName;
|
||||
|
||||
try {
|
||||
|
||||
if (!error && response.statusCode === 200) {
|
||||
|
||||
let data = JSON.parse(body);
|
||||
|
||||
if (data.status !== 100) {
|
||||
throw new Error(JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
console.log(chalk.green('Logged in successfully to ') + baseUrl);
|
||||
console.log(chalk.green('Authorization token is now saved as ' + captainNameEnterByUser));
|
||||
console.log(' ');
|
||||
|
||||
|
||||
setCaptainMachine({
|
||||
authToken: data.token,
|
||||
baseUrl: baseUrl,
|
||||
name: captainNameEnterByUser
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
throw new Error(response ? JSON.stringify(response, null, 2) : 'Response NULL');
|
||||
|
||||
} catch (error) {
|
||||
console.error(chalk.red('Something bad happened. Cannot save "' + captainNameEnterByUser + '"'));
|
||||
let errorMessage = error.message ? error.message : error;
|
||||
console.error(chalk.red(errorMessage));
|
||||
console.log(' ');
|
||||
}
|
||||
}
|
||||
|
||||
request(options, callback);
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
const program = require('commander');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
const inquirer = require('inquirer');
|
||||
const configstore = require('configstore');
|
||||
const request = require('request');
|
||||
|
||||
const packagejson = require('./package.json');
|
||||
|
||||
|
||||
const configs = new configstore(packagejson.name, {
|
||||
captainMachines: []
|
||||
});
|
||||
|
||||
|
||||
program
|
||||
.description('Logout from a specific Captain machine.')
|
||||
.parse(process.argv);
|
||||
|
||||
|
||||
if (program.args.length) {
|
||||
console.error(chalk.red('Unrecognized commands:'));
|
||||
program.args.forEach(function (arg) {
|
||||
console.log(chalk.red(arg));
|
||||
});
|
||||
console.error(chalk.red('Logout does not require any options. '));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let listOfMachines = [{
|
||||
name: '-- CANCEL --',
|
||||
value: '',
|
||||
short: ''
|
||||
}];
|
||||
let machines = configs.get('captainMachines');
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
let m = machines[i];
|
||||
listOfMachines.push({
|
||||
name: m.name + ' at ' + m.baseUrl,
|
||||
value: m.name,
|
||||
short: m.name + ' at ' + m.baseUrl
|
||||
})
|
||||
}
|
||||
|
||||
console.log('Logout from a Captain Machine and clear auth info');
|
||||
|
||||
const questions = [
|
||||
{
|
||||
type: 'list',
|
||||
name: 'captainNameToLogout',
|
||||
message: 'Select the Captain Machine you want to logout from:',
|
||||
choices: listOfMachines
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirmedToLogout',
|
||||
message: 'Are you sure you want to logout from this Captain machine?',
|
||||
default: false,
|
||||
when: function (answers) {
|
||||
return !!answers.captainNameToLogout;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
inquirer.prompt(questions).then(function (answers) {
|
||||
|
||||
console.log(' ');
|
||||
console.log(' ');
|
||||
|
||||
if (!answers.captainNameToLogout) {
|
||||
console.log('Operation cancelled by the user...');
|
||||
}
|
||||
else {
|
||||
let machines = configs.get('captainMachines');
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
if (machines[i].name === answers.captainNameToLogout) {
|
||||
var baseUrl = machines[i].baseUrl;
|
||||
machines.splice(i,1);
|
||||
configs.set('captainMachines', machines);
|
||||
console.log('You are now logged out from ' + answers.captainNameToLogout + ' at ' + baseUrl + '...');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(' ');
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,466 +0,0 @@
|
||||
const program = require('commander');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
const inquirer = require('inquirer');
|
||||
const configstore = require('configstore');
|
||||
const request = require('request');
|
||||
const spinnerUtil = require('./utils/spinner');
|
||||
|
||||
const packagejson = require('./package.json');
|
||||
|
||||
|
||||
const configs = new configstore(packagejson.name, {
|
||||
captainMachines: []
|
||||
});
|
||||
|
||||
|
||||
program
|
||||
.description('Easy setup for your Captain server.')
|
||||
.parse(process.argv);
|
||||
|
||||
|
||||
if (program.args.length) {
|
||||
console.error(chalk.red('Unrecognized commands:'));
|
||||
program.args.forEach(function (arg) {
|
||||
console.log(chalk.red(arg));
|
||||
});
|
||||
console.error(chalk.red('This command does not require any options. '));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function findDefaultCaptainName() {
|
||||
|
||||
let machines = configs.get('captainMachines');
|
||||
let currentSuffix = machines.length + 1;
|
||||
|
||||
function getCaptainFullName(suffix) {
|
||||
if (suffix < 10) {
|
||||
suffix = '0' + suffix;
|
||||
}
|
||||
return 'captain-' + suffix;
|
||||
}
|
||||
|
||||
function isSuffixValid(suffixNumber) {
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
let m = machines[i];
|
||||
if (m.name === getCaptainFullName(suffixNumber)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
while (!isSuffixValid(currentSuffix)) {
|
||||
currentSuffix++;
|
||||
}
|
||||
|
||||
return getCaptainFullName(currentSuffix);
|
||||
}
|
||||
|
||||
function isIpAddress(ipaddress) {
|
||||
if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipaddress)) {
|
||||
return (true)
|
||||
}
|
||||
return (false)
|
||||
}
|
||||
|
||||
|
||||
console.log(' ');
|
||||
console.log('Setup your Captain server');
|
||||
console.log(' ');
|
||||
|
||||
const SAMPLE_IP = '123.123.123.123';
|
||||
|
||||
var authTokenFromLogin = null;
|
||||
var ipAddressOfServer = null;
|
||||
var customDomainFromUser = null;
|
||||
var newPasswordFirstTry = null;
|
||||
var oldPassword = 'captain42';
|
||||
|
||||
function setCustomDomain(baseApiUrl, customDomain) {
|
||||
|
||||
return apiEndpointWithLoading('Changing Domain...')('POST', baseApiUrl, '/api/v1/user/system/changerootdomain/', {
|
||||
rootDomain: customDomain
|
||||
});
|
||||
}
|
||||
|
||||
function enableHttps(baseApiUrl, emailAddress) {
|
||||
|
||||
return apiEndpointWithLoading('Enabling SSL...')('POST', baseApiUrl, '/api/v1/user/system/enablessl/', {
|
||||
emailAddress: emailAddress
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function changePass(baseApiUrl, newPass) {
|
||||
|
||||
return apiEndpointWithLoading('Changing Password...')('POST', baseApiUrl, '/api/v1/user/changepassword/', {
|
||||
oldPassword: oldPassword,
|
||||
newPassword: newPass
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function forceHttps(baseApiUrl) {
|
||||
|
||||
return apiEndpointWithLoading('Forcing SSL...')('POST', baseApiUrl, '/api/v1/user/system/forcessl/', {
|
||||
isEnabled: true
|
||||
});
|
||||
}
|
||||
|
||||
// returns promise that resolves to auth token
|
||||
// rejects with ErrorCode or null
|
||||
function login(baseApiUrl, password) {
|
||||
|
||||
return apiEndpointWithLoading('Login...')('POST', baseApiUrl, '/api/v1/login', {
|
||||
password: password
|
||||
}).then(function (data) {
|
||||
return data.token;
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function apiEndpointWithLoading(message) {
|
||||
return (method, baseApiUrl, endpoint, dataToSend) => {
|
||||
return apiEndpoint(method, baseApiUrl, endpoint, dataToSend, message)
|
||||
}
|
||||
}
|
||||
|
||||
function apiEndpoint(method, baseApiUrl, endpoint, dataToSend, message) {
|
||||
|
||||
let options = {
|
||||
url: baseApiUrl + endpoint,
|
||||
headers: {
|
||||
'x-namespace': 'captain',
|
||||
'x-captain-auth': authTokenFromLogin
|
||||
},
|
||||
method: method,
|
||||
form: dataToSend
|
||||
};
|
||||
|
||||
return new Promise(function (res, rej) {
|
||||
|
||||
let spinner;
|
||||
if (message) {
|
||||
spinner = spinnerUtil.start(message);
|
||||
}
|
||||
var callback = function (error, response, body) {
|
||||
try {
|
||||
|
||||
if (!error && response.statusCode === 200) {
|
||||
|
||||
let data = JSON.parse(body);
|
||||
|
||||
if (data.status !== 100) {
|
||||
if (spinner) {
|
||||
spinnerUtil.fail(spinner);
|
||||
}
|
||||
rej(data);
|
||||
return;
|
||||
}
|
||||
if (spinner) {
|
||||
spinnerUtil.succeed(spinner);
|
||||
}
|
||||
res(data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
throw new Error(response ? JSON.stringify(response, null, 2) : 'Response NULL');
|
||||
|
||||
} catch (error) {
|
||||
if (spinner) {
|
||||
spinnerUtil.fail(spinner);
|
||||
}
|
||||
console.error(chalk.red('Something bad happened. Cannot connect to "' + baseApiUrl + endpoint + '"'));
|
||||
let errorMessage = error.message ? error.message : error;
|
||||
console.error(chalk.red(errorMessage));
|
||||
console.log(' ');
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
request(options, callback);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
const questions = [
|
||||
{
|
||||
type: 'list',
|
||||
name: 'hasInstalledCaptain',
|
||||
message: 'Have you already installed Captain on your server by running the following line:'
|
||||
+ '\nmkdir /captain && docker run -v /var/run/docker.sock:/var/run/docker.sock dockersaturn/captainduckduck ?',
|
||||
default: 'Yes',
|
||||
choices: ['Yes', 'No'],
|
||||
filter: function (value) {
|
||||
return new Promise(function (res, rej) {
|
||||
|
||||
if (value === 'Yes') {
|
||||
res(true);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('');
|
||||
console.log(' Cannot start the setup process if Captain is not installed.');
|
||||
console.log(' Please read tutorial on CaptainDuckDuck.com'
|
||||
+ ' to learn how to install CaptainDuckDuck on a server.');
|
||||
console.log('');
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
default: SAMPLE_IP,
|
||||
name: 'captainAddress',
|
||||
message: 'Enter IP address of your captain server:',
|
||||
filter: function (value) {
|
||||
return new Promise(function (res, rej) {
|
||||
|
||||
if (value === SAMPLE_IP) {
|
||||
rej('Enter a valid IP Address');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isIpAddress(value.trim())) {
|
||||
rej('This is an invalid IP Address: ' + value);
|
||||
return;
|
||||
}
|
||||
|
||||
ipAddressOfServer = value.trim();
|
||||
|
||||
//login using captain42.
|
||||
|
||||
return login('http://' + ipAddressOfServer + ':3000', oldPassword)
|
||||
.then(function (authTokenFetched) {
|
||||
authTokenFromLogin = authTokenFetched;
|
||||
res(ipAddressOfServer);
|
||||
})
|
||||
.catch(function (error) {
|
||||
|
||||
// if error is anything but password wrong, exit here...
|
||||
if (error.status == 1105) {
|
||||
authTokenFromLogin = null;
|
||||
res(ipAddressOfServer);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
if (error.status) {
|
||||
console.log(chalk.red('Error: ' + error.status));
|
||||
console.log(chalk.red('Error: ' + error.description));
|
||||
} else {
|
||||
console.log(chalk.red('Error: ' + error));
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
|
||||
|
||||
});
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'captainOriginalPassword',
|
||||
message: 'Enter your current password:',
|
||||
when: function () {
|
||||
return !authTokenFromLogin;
|
||||
},
|
||||
filter: function (value) {
|
||||
return new Promise(function (res, rej) {
|
||||
|
||||
console.log('');
|
||||
|
||||
return login('http://' + ipAddressOfServer + ':3000', value)
|
||||
.then(function (authTokenFetched) {
|
||||
authTokenFromLogin = authTokenFetched;
|
||||
oldPassword = value;
|
||||
res(value);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('');
|
||||
|
||||
if (error.status) {
|
||||
console.log(chalk.red('Error: ' + error.status));
|
||||
console.log(chalk.red('Error: ' + error.description));
|
||||
} else {
|
||||
console.log(chalk.red('Error: ' + error));
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'captainRootDomain',
|
||||
message: 'Enter a root domain for this Captain server. For example, enter test.yourdomain.com if you' +
|
||||
' setup your DNS to point *.test.yourdomain.com to ip address of your server' + ': ',
|
||||
filter: function (value) {
|
||||
return new Promise(function (res, rej) {
|
||||
|
||||
value = value.trim();
|
||||
|
||||
customDomainFromUser = 'captain.' + value;
|
||||
|
||||
return setCustomDomain('http://' + ipAddressOfServer + ':3000', value)
|
||||
.then(function () {
|
||||
res(customDomainFromUser);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('');
|
||||
if (error.status) {
|
||||
console.log(chalk.red('Error: ' + error.status));
|
||||
console.log(chalk.red('Error: ' + error.description));
|
||||
} else {
|
||||
console.log(chalk.red('Error: ' + error));
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'emailAddress',
|
||||
message: 'Enter your "valid" email address to enable HTTPS: ',
|
||||
filter: function (value) {
|
||||
return new Promise(function (res, rej) {
|
||||
|
||||
console.log('');
|
||||
value = value.trim();
|
||||
|
||||
return enableHttps('http://' + customDomainFromUser, value)
|
||||
.then(function () {
|
||||
return forceHttps('https://' + customDomainFromUser);
|
||||
})
|
||||
.then(function () {
|
||||
res(value);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('');
|
||||
if (error.status) {
|
||||
console.log(chalk.red('Error: ' + error.status));
|
||||
console.log(chalk.red('Error: ' + error.description));
|
||||
} else {
|
||||
console.log(chalk.red('Error: ' + error));
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'newPasswordFirstTry',
|
||||
message: 'Enter a new password:',
|
||||
filter: function (value) {
|
||||
return new Promise(function (res, rej) {
|
||||
|
||||
newPasswordFirstTry = value;
|
||||
res(value);
|
||||
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'newPassword',
|
||||
message: 'Enter a new password:',
|
||||
filter: function (value) {
|
||||
return new Promise(function (res, rej) {
|
||||
|
||||
if (newPasswordFirstTry !== value) {
|
||||
rej('Passwords do not match');
|
||||
//process.exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
return changePass('https://' + customDomainFromUser, value)
|
||||
.then(function () {
|
||||
return login('https://' + customDomainFromUser, value)
|
||||
})
|
||||
.then(function (token) {
|
||||
authTokenFromLogin = token;
|
||||
res(value);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log('');
|
||||
if (error.status) {
|
||||
console.log(chalk.red('Error: ' + error.status));
|
||||
console.log(chalk.red('Error: ' + error.description));
|
||||
} else {
|
||||
console.log(chalk.red('Error: ' + error));
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log(chalk.red('IMPORTANT!! Server setup is completed by password is not changed.'));
|
||||
console.log(chalk.red('You CANNOT use serversetup anymore. To continue: '));
|
||||
console.log(chalk.red('- Go to https://' + customDomainFromUser
|
||||
+ ' login with default password and change the password in settings.'));
|
||||
console.log(chalk.red('- In terminal (here), type captainduckduck login and enter this as your root domain: ' + customDomainFromUser));
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'captainName',
|
||||
message: 'Enter a name for this Captain machine:',
|
||||
default: findDefaultCaptainName(),
|
||||
validate: function (value) {
|
||||
|
||||
let machines = configs.get('captainMachines');
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
if (machines[i].name === value) {
|
||||
return value + ' already exist. If you want to replace the existing entry, you have to first use <logout> command, and then re-login.'
|
||||
}
|
||||
}
|
||||
|
||||
if (value.match(/^[-\d\w]+$/i)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'Please enter a Captain Name.';
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
inquirer.prompt(questions).then(function (answers) {
|
||||
|
||||
|
||||
var captainAddress = 'https://' + customDomainFromUser;
|
||||
let machines = configs.get('captainMachines');
|
||||
machines.push({
|
||||
authToken: authTokenFromLogin,
|
||||
baseUrl: captainAddress,
|
||||
name: answers.captainName
|
||||
});
|
||||
configs.set('captainMachines', machines);
|
||||
|
||||
console.log(' ');
|
||||
console.log(' ');
|
||||
console.log('Captain is available at ' + captainAddress);
|
||||
console.log(' ');
|
||||
console.log('For more details and docs see http://www.captainduckduck.com');
|
||||
console.log(' ');
|
||||
console.log(' ');
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
+41
-15
@@ -1,22 +1,48 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const packagejson = require('./package.json');
|
||||
const updateNotifier = require('update-notifier');
|
||||
const packagejson = require("./package.json")
|
||||
const updateNotifier = require("update-notifier")
|
||||
|
||||
updateNotifier({ pkg: packagejson }).notify({ isGlobal: true });
|
||||
updateNotifier({ pkg: packagejson }).notify({ isGlobal: true })
|
||||
|
||||
const program = require("commander")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
const chalk = require("chalk")
|
||||
// Command actions
|
||||
const list = require("./lib/list")
|
||||
const logout = require("./lib/logout")
|
||||
|
||||
const program = require('commander');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
// Setup
|
||||
program.version(packagejson.version + "").description(packagejson.description)
|
||||
|
||||
program
|
||||
.version(packagejson.version + '')
|
||||
.description(packagejson.description)
|
||||
.command('serversetup', 'Performs necessary actions and prepares your Captain server.')
|
||||
.command('login', 'Login to a CaptainDuckDuck machine. You can be logged in to multiple machines simultaneously.')
|
||||
.command('logout', 'Logout from a specific Captain machine.')
|
||||
.command('list', 'List all Captain machines currently logged in.')
|
||||
.command('deploy', 'Deploy your app (current directory) to a specific Captain machine. You\'ll be prompted to choose your Captain machine.')
|
||||
.parse(process.argv);
|
||||
.command(
|
||||
"serversetup",
|
||||
"Performs necessary actions and prepares your Captain server."
|
||||
)
|
||||
.command(
|
||||
"login",
|
||||
"Login to a CaptainDuckDuck machine. You can be logged in to multiple machines simultaneously."
|
||||
)
|
||||
.command(
|
||||
"deploy",
|
||||
"Deploy your app (current directory) to a specific Captain machine. You'll be prompted to choose your Captain machine."
|
||||
)
|
||||
|
||||
// Commands
|
||||
program
|
||||
.command("logout")
|
||||
.description("Logout from a specific Captain machine.")
|
||||
.action(() => {
|
||||
logout()
|
||||
})
|
||||
|
||||
program
|
||||
.command("list")
|
||||
.description("List all Captain machines currently logged in.")
|
||||
.action(() => {
|
||||
list()
|
||||
})
|
||||
|
||||
program.parse(process.argv)
|
||||
|
||||
@@ -0,0 +1,440 @@
|
||||
#!/usr/bin/env node
|
||||
const program = require("commander")
|
||||
const fs = require("fs-extra")
|
||||
const path = require("path")
|
||||
const chalk = require("chalk")
|
||||
const inquirer = require("inquirer")
|
||||
const configstore = require("configstore")
|
||||
const request = require("request")
|
||||
const commandExistsSync = require("command-exists").sync
|
||||
const { exec } = require("child_process")
|
||||
const { sendFileToCaptain } = require("./utils/fileHandler")
|
||||
const {
|
||||
requestLogin,
|
||||
requestLoginAuth,
|
||||
isAuthTokenValid
|
||||
} = require("./utils/loginHandler")
|
||||
const {
|
||||
validateIsGitRepository,
|
||||
validateDefinitionFile
|
||||
} = require("./utils/validationsHandler")
|
||||
const { printErrorAndExit } = require("./utils/errorHandler")
|
||||
const packagejson = require("./package.json")
|
||||
const configs = new configstore(packagejson.name, {
|
||||
captainMachines: [],
|
||||
apps: []
|
||||
})
|
||||
const BRANCH_TO_PUSH = "branchToPush"
|
||||
const APP_NAME = "appName"
|
||||
const MACHINE_TO_DEPLOY = "machineToDeploy"
|
||||
const EMPTY_STRING = ""
|
||||
|
||||
console.log("\n")
|
||||
|
||||
program
|
||||
.description("Deploy current directory to a Captain machine.")
|
||||
.option(
|
||||
"-t, --tarFile <value>",
|
||||
"Specify file to be uploaded (rather than using git archive)"
|
||||
)
|
||||
.option("-d, --default", "Run with default options")
|
||||
.option("-s, --stateless", "Run deploy stateless")
|
||||
.option(
|
||||
"-h, --host <value>",
|
||||
"Only for stateless mode: Host of the captain machine"
|
||||
)
|
||||
.option("-a, --appName <value>", "Only for stateless mode: Name of the app")
|
||||
.option("-p, --pass <value>", "Only for stateless mode: Password for Captain")
|
||||
.option(
|
||||
"-b, --branch <value>",
|
||||
"Only for stateless mode: Branch name (default master)"
|
||||
)
|
||||
.parse(process.argv)
|
||||
|
||||
if (program.args.length) {
|
||||
console.error(chalk.red("Unrecognized commands:"))
|
||||
program.args.forEach(function(arg) {
|
||||
console.log(chalk.red(arg))
|
||||
})
|
||||
console.error(chalk.red("Deploy does not require any options. "))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function getSuppliedTarFile() {
|
||||
return program.tarFile
|
||||
}
|
||||
|
||||
if (!getSuppliedTarFile()) {
|
||||
validateIsGitRepository()
|
||||
|
||||
validateDefinitionFile()
|
||||
|
||||
const contents = fs.readFileSync("./captain-definition", "utf8")
|
||||
let contentsJson = null
|
||||
|
||||
try {
|
||||
contentsJson = JSON.parse(contents)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
console.log("")
|
||||
printErrorAndExit(
|
||||
"**** ERROR: captain-definition file is not a valid JSON! ****"
|
||||
)
|
||||
}
|
||||
|
||||
if (!contentsJson.schemaVersion) {
|
||||
printErrorAndExit(
|
||||
"**** ERROR: captain-definition needs schemaVersion. Please see docs! ****"
|
||||
)
|
||||
}
|
||||
|
||||
if (!contentsJson.templateId && !contentsJson.dockerfileLines) {
|
||||
printErrorAndExit(
|
||||
"**** ERROR: captain-definition needs templateId or dockerfileLines. Please see docs! ****"
|
||||
)
|
||||
}
|
||||
|
||||
if (contentsJson.templateId && contentsJson.dockerfileLines) {
|
||||
printErrorAndExit(
|
||||
"**** ERROR: captain-definition needs templateId or dockerfileLines, NOT BOTH! Please see docs! ****"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let listOfMachines = [
|
||||
{
|
||||
name: "-- CANCEL --",
|
||||
value: EMPTY_STRING,
|
||||
short: EMPTY_STRING
|
||||
}
|
||||
]
|
||||
|
||||
let machines = configs.get("captainMachines")
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
let m = machines[i]
|
||||
listOfMachines.push({
|
||||
name: m.name + " at " + m.baseUrl,
|
||||
value: m.name,
|
||||
short: m.name + " at " + m.baseUrl
|
||||
})
|
||||
}
|
||||
|
||||
// Gets default value for propType that is stored in a directory.
|
||||
// Replaces getAppForDirectory
|
||||
function getPropForDirectory(propType) {
|
||||
let apps = configs.get("apps")
|
||||
for (let i = 0; i < apps.length; i++) {
|
||||
let app = apps[i]
|
||||
if (app.cwd === process.cwd()) {
|
||||
return app[propType]
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getDefaultMachine() {
|
||||
let machine = getPropForDirectory(MACHINE_TO_DEPLOY)
|
||||
if (machine) {
|
||||
return machine.name
|
||||
}
|
||||
if (listOfMachines.length == 2) {
|
||||
return 1
|
||||
}
|
||||
return EMPTY_STRING
|
||||
}
|
||||
|
||||
console.log("Preparing deployment to Captain...\n")
|
||||
|
||||
const questions = [
|
||||
{
|
||||
type: "list",
|
||||
name: "captainNameToDeploy",
|
||||
default: getDefaultMachine(),
|
||||
message: "Select the Captain Machine you want to deploy to:",
|
||||
choices: listOfMachines
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
default: getPropForDirectory(BRANCH_TO_PUSH) || "master",
|
||||
name: BRANCH_TO_PUSH,
|
||||
message: "Enter the 'git' branch you would like to deploy:",
|
||||
when: function(answers) {
|
||||
return !!answers.captainNameToDeploy
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
default: getPropForDirectory(APP_NAME),
|
||||
name: APP_NAME,
|
||||
message: "Enter the Captain app name this directory will be deployed to:",
|
||||
when: function(answers) {
|
||||
return !!answers.captainNameToDeploy
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "confirm",
|
||||
name: "confirmedToDeploy",
|
||||
message:
|
||||
"Note that uncommitted files and files in gitignore (if any) will not be pushed to server. Please confirm so that deployment process can start.",
|
||||
default: true,
|
||||
when: function(answers) {
|
||||
return !!answers.captainNameToDeploy
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
let defaultInvalid = false
|
||||
|
||||
if (program.default) {
|
||||
if (
|
||||
!getDefaultMachine() ||
|
||||
!getPropForDirectory(BRANCH_TO_PUSH) ||
|
||||
!getPropForDirectory(APP_NAME)
|
||||
) {
|
||||
console.log("Default deploy failed. Please select deploy options.")
|
||||
defaultInvalid = true
|
||||
} else {
|
||||
console.log("Deploying to " + getPropForDirectory(MACHINE_TO_DEPLOY).name)
|
||||
deployTo(
|
||||
getPropForDirectory(MACHINE_TO_DEPLOY),
|
||||
getPropForDirectory(BRANCH_TO_PUSH),
|
||||
getPropForDirectory(APP_NAME)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const isStateless =
|
||||
program.stateless && program.host && program.appName && program.pass
|
||||
|
||||
if (isStateless) {
|
||||
// login first
|
||||
console.log("Trying to login to", program.host)
|
||||
requestLoginAuth(program.host, program.pass, function(authToken) {
|
||||
// deploy
|
||||
console.log(
|
||||
"Starting stateless deploy to",
|
||||
program.host,
|
||||
program.branch,
|
||||
program.appName
|
||||
)
|
||||
deployTo(
|
||||
{
|
||||
baseUrl: program.host,
|
||||
authToken
|
||||
},
|
||||
program.branch || "master",
|
||||
program.appName
|
||||
)
|
||||
})
|
||||
} else if (!program.default || defaultInvalid) {
|
||||
inquirer.prompt(questions).then(function(answers) {
|
||||
console.log(" ")
|
||||
console.log(" ")
|
||||
|
||||
if (!answers.confirmedToDeploy) {
|
||||
console.log("Operation cancelled by the user...")
|
||||
console.log(" ")
|
||||
} else {
|
||||
let machines = configs.get("captainMachines")
|
||||
let machineToDeploy = null
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
if (machines[i].name === answers.captainNameToDeploy) {
|
||||
console.log("Deploying to " + answers.captainNameToDeploy)
|
||||
machineToDeploy = machines[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
console.log(" ")
|
||||
deployTo(machineToDeploy, answers.branchToPush, answers.appName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function deployTo(machineToDeploy, branchToPush, appName) {
|
||||
function checkAuthAndSendFile(zipFileFullPath, gitHash) {
|
||||
function isAuthTokenValidCallback(isValid) {
|
||||
if (isValid) {
|
||||
sendFileToCaptain(
|
||||
machineToDeploy,
|
||||
zipFileFullPath,
|
||||
appName,
|
||||
gitHash,
|
||||
branchToPush
|
||||
)
|
||||
} else {
|
||||
requestLogin(
|
||||
machineToDeploy.name,
|
||||
machineToDeploy.baseUrl,
|
||||
function callback(machineToDeployNew) {
|
||||
deployTo(machineToDeployNew, branchToPush, appName)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
isAuthTokenValid(machineToDeploy, appName, isAuthTokenValidCallback)
|
||||
}
|
||||
|
||||
if (getSuppliedTarFile()) {
|
||||
checkAuthAndSendFile(
|
||||
path.join(process.cwd(), getSuppliedTarFile()),
|
||||
"sendviatarfile"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (!commandExistsSync("git")) {
|
||||
console.log(chalk.red('"git" command not found...'))
|
||||
console.log(
|
||||
chalk.red(
|
||||
'Captain needs "git" to create tar file of your source files...'
|
||||
)
|
||||
)
|
||||
console.log(" ")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let zipFileNameToDeploy = "temporary-captain-to-deploy.tar"
|
||||
let zipFileFullPath = path.join(process.cwd(), zipFileNameToDeploy)
|
||||
|
||||
console.log("Saving tar file to:")
|
||||
console.log(zipFileFullPath)
|
||||
console.log(" ")
|
||||
|
||||
try {
|
||||
fs.removeSync(zipFileFullPath)
|
||||
} catch (ignoreError) {}
|
||||
|
||||
exec(
|
||||
'git archive --format tar --output "' +
|
||||
zipFileFullPath +
|
||||
'" ' +
|
||||
branchToPush,
|
||||
function(err, stdout, stderr) {
|
||||
if (err) {
|
||||
console.log(chalk.red("TAR file failed"))
|
||||
console.log(chalk.red(err + " "))
|
||||
console.log(" ")
|
||||
fs.removeSync(zipFileFullPath)
|
||||
return
|
||||
}
|
||||
|
||||
exec("git rev-parse " + branchToPush, function(err, stdout, stderr) {
|
||||
const gitHash = (stdout || "").trim()
|
||||
|
||||
if (err || !/^[a-f0-9]{40}$/.test(gitHash)) {
|
||||
console.log(
|
||||
chalk.red(
|
||||
"Cannot find hash of last commit on this branch: " + branchToPush
|
||||
)
|
||||
)
|
||||
console.log(chalk.red(gitHash + " "))
|
||||
console.log(chalk.red(err + " "))
|
||||
console.log(" ")
|
||||
return
|
||||
}
|
||||
|
||||
console.log("Pushing last commit on " + branchToPush + ": " + gitHash)
|
||||
|
||||
checkAuthAndSendFile(zipFileFullPath, gitHash)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
var lastLineNumberPrinted = -10000 // we want to show all lines to begin with!
|
||||
|
||||
function startFetchingBuildLogs(machineToDeploy, appName) {
|
||||
let options = {
|
||||
url: machineToDeploy.baseUrl + "/api/v1/user/appData/" + appName,
|
||||
headers: {
|
||||
"x-namespace": "captain",
|
||||
"x-captain-auth": machineToDeploy.authToken
|
||||
},
|
||||
method: "GET"
|
||||
}
|
||||
|
||||
function onLogRetrieved(data) {
|
||||
if (data) {
|
||||
var lines = data.logs.lines
|
||||
var firstLineNumberOfLogs = data.logs.firstLineNumber
|
||||
var firstLinesToPrint = 0
|
||||
if (firstLineNumberOfLogs > lastLineNumberPrinted) {
|
||||
if (firstLineNumberOfLogs < 0) {
|
||||
// This is the very first fetch, probably firstLineNumberOfLogs is around -50
|
||||
firstLinesToPrint = -firstLineNumberOfLogs
|
||||
} else {
|
||||
console.log("[[ TRUNCATED ]]")
|
||||
}
|
||||
} else {
|
||||
firstLinesToPrint = lastLineNumberPrinted - firstLineNumberOfLogs
|
||||
}
|
||||
|
||||
lastLineNumberPrinted = firstLineNumberOfLogs + lines.length
|
||||
|
||||
for (var i = firstLinesToPrint; i < lines.length; i++) {
|
||||
console.log((lines[i] || "").trim())
|
||||
}
|
||||
}
|
||||
|
||||
if (data && !data.isAppBuilding) {
|
||||
console.log(" ")
|
||||
if (!data.isBuildFailed) {
|
||||
console.log(chalk.green("Deployed successfully: ") + appName)
|
||||
console.log(
|
||||
chalk.magenta("App is available at ") +
|
||||
machineToDeploy.baseUrl
|
||||
.replace("//captain.", "//" + appName + ".")
|
||||
.replace("https://", "http://")
|
||||
)
|
||||
} else {
|
||||
console.error(
|
||||
chalk.red(
|
||||
'\nSomething bad happened. Cannot deploy "' + appName + '"\n'
|
||||
)
|
||||
)
|
||||
}
|
||||
console.log(" ")
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
startFetchingBuildLogs(machineToDeploy, appName)
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
function callback(error, response, body) {
|
||||
try {
|
||||
if (!error && response.statusCode === 200) {
|
||||
let data = JSON.parse(body)
|
||||
|
||||
if (data.status !== 100) {
|
||||
throw new Error(JSON.stringify(data, null, 2))
|
||||
}
|
||||
|
||||
onLogRetrieved(data.data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
response ? JSON.stringify(response, null, 2) : "Response NULL"
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
'\nSomething while retrieving app build logs.. "' + error + '"\n'
|
||||
)
|
||||
)
|
||||
|
||||
onLogRetrieved(null)
|
||||
}
|
||||
}
|
||||
|
||||
request(options, callback)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
const chalk = require("chalk")
|
||||
const configstore = require("configstore")
|
||||
const packagejson = require("../package.json")
|
||||
const configs = new configstore(packagejson.name, {
|
||||
captainMachines: []
|
||||
})
|
||||
|
||||
function _displayMachine(machine) {
|
||||
console.log(
|
||||
">> " +
|
||||
chalk.greenBright(machine.name) +
|
||||
" at " +
|
||||
chalk.cyan(machine.baseUrl)
|
||||
)
|
||||
}
|
||||
|
||||
function list() {
|
||||
console.log("\nLogged in Captain Machines:\n")
|
||||
|
||||
const machines = configs.get("captainMachines")
|
||||
|
||||
machines.map(machine => {
|
||||
_displayMachine(machine)
|
||||
})
|
||||
|
||||
console.log("")
|
||||
}
|
||||
|
||||
module.exports = list
|
||||
@@ -0,0 +1,239 @@
|
||||
const program = require("commander")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
const chalk = require("chalk")
|
||||
const inquirer = require("inquirer")
|
||||
const configstore = require("configstore")
|
||||
const request = require("request")
|
||||
const packagejson = require("./package.json")
|
||||
const configs = new configstore(packagejson.name, {
|
||||
captainMachines: []
|
||||
})
|
||||
|
||||
program
|
||||
.description(
|
||||
"Login to a CaptainDuckDuck machine. You can be logged in to multiple machines simultaneously."
|
||||
)
|
||||
.parse(process.argv)
|
||||
|
||||
function cleanUpUrl(url) {
|
||||
if (!url) {
|
||||
return url
|
||||
}
|
||||
|
||||
url = url.trim()
|
||||
|
||||
url = url.replace("http://", "").replace("https://", "")
|
||||
|
||||
if (!url || !url.length) {
|
||||
return url
|
||||
}
|
||||
|
||||
if (url.substr(url.length - 1, 1) === "/") {
|
||||
url = url.substr(0, url.length - 1)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
if (program.args.length) {
|
||||
console.error(chalk.red("Unrecognized commands:"))
|
||||
|
||||
program.args.forEach(function(arg) {
|
||||
console.log(chalk.red(arg))
|
||||
})
|
||||
|
||||
console.error(chalk.red("Login does not require any options. "))
|
||||
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function findDefaultCaptainName() {
|
||||
const machines = configs.get("captainMachines")
|
||||
let currentSuffix = machines.length + 1
|
||||
|
||||
function getCaptainFullName(suffix) {
|
||||
if (suffix < 10) {
|
||||
suffix = "0" + suffix
|
||||
}
|
||||
|
||||
return "captain-" + suffix
|
||||
}
|
||||
|
||||
function isSuffixValid(suffixNumber) {
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
const m = machines[i]
|
||||
|
||||
if (m.name === getCaptainFullName(suffixNumber)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
while (!isSuffixValid(currentSuffix)) {
|
||||
currentSuffix++
|
||||
}
|
||||
|
||||
return getCaptainFullName(currentSuffix)
|
||||
}
|
||||
|
||||
console.log("Login to a Captain Machine")
|
||||
|
||||
const SAMPLE_DOMAIN = "captain.captainroot.yourdomain.com"
|
||||
const questions = [
|
||||
{
|
||||
type: "input",
|
||||
default: SAMPLE_DOMAIN,
|
||||
name: "captainAddress",
|
||||
message:
|
||||
"Enter address of the Captain machine. \nIt is captain.[your-captain-root-domain] :",
|
||||
validate: function(value) {
|
||||
if (value === SAMPLE_DOMAIN) {
|
||||
return "Enter a valid URL"
|
||||
}
|
||||
|
||||
if (!cleanUpUrl(value)) {
|
||||
return "This is an invalid URL: " + value
|
||||
}
|
||||
|
||||
const machines = configs.get("captainMachines")
|
||||
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
if (cleanUpUrl(machines[i].baseUrl) === cleanUpUrl(value)) {
|
||||
return (
|
||||
value +
|
||||
" already exist as " +
|
||||
machines[i].name +
|
||||
". If you want to replace the existing entry, you have to first use <logout> command, and then re-login."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (value && value.trim()) {
|
||||
return true
|
||||
}
|
||||
|
||||
return "Please enter a valid address."
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "confirm",
|
||||
name: "captainHasRootSsl",
|
||||
message: "Is HTTPS activated for this Captain machine?",
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: "password",
|
||||
name: "captainPassword",
|
||||
message: "Enter your password:",
|
||||
validate: function(value) {
|
||||
if (value && value.trim()) {
|
||||
return true
|
||||
}
|
||||
|
||||
return "Please enter your password."
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
name: "captainName",
|
||||
message: "Enter a name for this Captain machine:",
|
||||
default: findDefaultCaptainName(),
|
||||
validate: function(value) {
|
||||
const machines = configs.get("captainMachines")
|
||||
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
if (machines[i].name === value) {
|
||||
return (
|
||||
value +
|
||||
" already exist. If you want to replace the existing entry, you have to first use <logout> command, and then re-login."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (value.match(/^[-\d\w]+$/i)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return "Please enter a Captain Name."
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
function setCaptainMachine(data) {
|
||||
const machines = configs.get("captainMachines")
|
||||
|
||||
machines.push(data)
|
||||
|
||||
configs.set("captainMachines", machines)
|
||||
}
|
||||
|
||||
inquirer.prompt(questions).then(function(answers) {
|
||||
console.log("\n")
|
||||
|
||||
const baseUrl =
|
||||
(answers.captainHasRootSsl ? "https://" : "http://") +
|
||||
cleanUpUrl(answers.captainAddress)
|
||||
const options = {
|
||||
url: baseUrl + "/api/v1/login",
|
||||
headers: {
|
||||
"x-namespace": "captain"
|
||||
},
|
||||
method: "POST",
|
||||
form: {
|
||||
password: answers.captainPassword
|
||||
}
|
||||
}
|
||||
|
||||
function callback(error, response, body) {
|
||||
const captainNameEnterByUser = answers.captainName
|
||||
|
||||
try {
|
||||
if (!error && response.statusCode === 200) {
|
||||
const data = JSON.parse(body)
|
||||
|
||||
if (data.status !== 100) {
|
||||
throw new Error(JSON.stringify(data, null, 2))
|
||||
}
|
||||
|
||||
console.log(chalk.green("Logged in successfully to ") + baseUrl)
|
||||
|
||||
console.log(
|
||||
chalk.green(
|
||||
`Authorization token is now saved as ${captainNameEnterByUser} \n`
|
||||
)
|
||||
)
|
||||
|
||||
setCaptainMachine({
|
||||
authToken: data.token,
|
||||
baseUrl: baseUrl,
|
||||
name: captainNameEnterByUser
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
response ? JSON.stringify(response, null, 2) : "Response NULL"
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Something bad happened. Cannot save "${captainNameEnterByUser}"`
|
||||
)
|
||||
)
|
||||
|
||||
const errorMessage = error.message ? error.message : error
|
||||
|
||||
console.error(`${chalk.red(errorMessage)} \n`)
|
||||
}
|
||||
}
|
||||
|
||||
request(options, callback)
|
||||
})
|
||||
@@ -0,0 +1,79 @@
|
||||
const inquirer = require("inquirer")
|
||||
const configstore = require("configstore")
|
||||
const packagejson = require("../package.json")
|
||||
const configs = new configstore(packagejson.name, {
|
||||
captainMachines: []
|
||||
})
|
||||
const machines = configs.get("captainMachines")
|
||||
|
||||
function getMachineList() {
|
||||
const firstItemInOption = [
|
||||
{
|
||||
name: "-- CANCEL --",
|
||||
value: "",
|
||||
short: ""
|
||||
}
|
||||
]
|
||||
const listOfMachines = machines.map(machine => {
|
||||
return {
|
||||
name: `${machine.name} at ${machine.baseUrl}`,
|
||||
value: `${machine.name}`,
|
||||
short: `${machine.name} at ${machine.baseUrl}`
|
||||
}
|
||||
})
|
||||
|
||||
return [...firstItemInOption, ...listOfMachines]
|
||||
}
|
||||
|
||||
function generateQuestions() {
|
||||
const listOfMachines = getMachineList()
|
||||
|
||||
return [
|
||||
{
|
||||
type: "list",
|
||||
name: "captainNameToLogout",
|
||||
message: "Select the Captain Machine you want to logout from:",
|
||||
choices: listOfMachines
|
||||
},
|
||||
{
|
||||
type: "confirm",
|
||||
name: "confirmedToLogout",
|
||||
message: "Are you sure you want to logout from this Captain machine?",
|
||||
default: false,
|
||||
when: answers => !!answers.captainNameToLogout
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function removeMachineFromLocalStorage(machineName) {
|
||||
const removedMachine = machines.filter(
|
||||
machine => machine.name === machineName
|
||||
)
|
||||
const newMachines = machines.filter(machine => machine !== machineName)
|
||||
|
||||
configs.set("captainMachines", newMachines)
|
||||
|
||||
console.log(
|
||||
`You are now logged out from ${removedMachine.name} at ${
|
||||
removedMachine.baseUrl
|
||||
}...\n`
|
||||
)
|
||||
}
|
||||
|
||||
function logout() {
|
||||
const questions = generateQuestions()
|
||||
|
||||
console.log("Logout from a Captain Machine and clear auth info")
|
||||
|
||||
inquirer.prompt(questions).then(answers => {
|
||||
if (!answers.captainNameToLogout) {
|
||||
console.log("\nOperation cancelled by the user...\n")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
removeMachineFromLocalStorage(answers.captainNameToLogout)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = logout
|
||||
@@ -0,0 +1,514 @@
|
||||
const program = require("commander")
|
||||
const chalk = require("chalk")
|
||||
const inquirer = require("inquirer")
|
||||
const configstore = require("configstore")
|
||||
const request = require("request")
|
||||
const spinnerUtil = require("./utils/spinner")
|
||||
const packagejson = require("./package.json")
|
||||
const configs = new configstore(packagejson.name, {
|
||||
captainMachines: []
|
||||
})
|
||||
|
||||
program.description("Easy setup for your Captain server.").parse(process.argv)
|
||||
|
||||
if (program.args.length) {
|
||||
console.error(chalk.red("Unrecognized commands:"))
|
||||
|
||||
program.args.forEach(function(arg) {
|
||||
console.log(chalk.red(arg))
|
||||
})
|
||||
|
||||
console.error(chalk.red("This command does not require any options. "))
|
||||
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function findDefaultCaptainName() {
|
||||
const machines = configs.get("captainMachines")
|
||||
let currentSuffix = machines.length + 1
|
||||
|
||||
function getCaptainFullName(suffix) {
|
||||
if (suffix < 10) {
|
||||
suffix = "0" + suffix
|
||||
}
|
||||
|
||||
return "captain-" + suffix
|
||||
}
|
||||
|
||||
function isSuffixValid(suffixNumber) {
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
const m = machines[i]
|
||||
|
||||
if (m.name === getCaptainFullName(suffixNumber)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
while (!isSuffixValid(currentSuffix)) {
|
||||
currentSuffix++
|
||||
}
|
||||
|
||||
return getCaptainFullName(currentSuffix)
|
||||
}
|
||||
|
||||
function isIpAddress(ipaddress) {
|
||||
if (
|
||||
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(
|
||||
ipaddress
|
||||
)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
console.log("\nSetup your Captain server\n")
|
||||
|
||||
const SAMPLE_IP = "123.123.123.123"
|
||||
let authTokenFromLogin = null
|
||||
let ipAddressOfServer = null
|
||||
let customDomainFromUser = null
|
||||
let newPasswordFirstTry = null
|
||||
let oldPassword = "captain42"
|
||||
|
||||
function setCustomDomain(baseApiUrl, customDomain) {
|
||||
return apiEndpointWithLoading("Changing Domain...")(
|
||||
"POST",
|
||||
baseApiUrl,
|
||||
"/api/v1/user/system/changerootdomain/",
|
||||
{
|
||||
rootDomain: customDomain
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function enableHttps(baseApiUrl, emailAddress) {
|
||||
return apiEndpointWithLoading("Enabling SSL...")(
|
||||
"POST",
|
||||
baseApiUrl,
|
||||
"/api/v1/user/system/enablessl/",
|
||||
{
|
||||
emailAddress: emailAddress
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function changePass(baseApiUrl, newPass) {
|
||||
return apiEndpointWithLoading("Changing Password...")(
|
||||
"POST",
|
||||
baseApiUrl,
|
||||
"/api/v1/user/changepassword/",
|
||||
{
|
||||
oldPassword: oldPassword,
|
||||
newPassword: newPass
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function forceHttps(baseApiUrl) {
|
||||
return apiEndpointWithLoading("Forcing SSL...")(
|
||||
"POST",
|
||||
baseApiUrl,
|
||||
"/api/v1/user/system/forcessl/",
|
||||
{
|
||||
isEnabled: true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// returns promise that resolves to auth token
|
||||
// rejects with ErrorCode or null
|
||||
function login(baseApiUrl, password) {
|
||||
return apiEndpointWithLoading("Login...")(
|
||||
"POST",
|
||||
baseApiUrl,
|
||||
"/api/v1/login",
|
||||
{
|
||||
password: password
|
||||
}
|
||||
).then(function(data) {
|
||||
return data.token
|
||||
})
|
||||
}
|
||||
|
||||
function apiEndpointWithLoading(message) {
|
||||
return (method, baseApiUrl, endpoint, dataToSend) => {
|
||||
return apiEndpoint(method, baseApiUrl, endpoint, dataToSend, message)
|
||||
}
|
||||
}
|
||||
|
||||
function apiEndpoint(method, baseApiUrl, endpoint, dataToSend, message) {
|
||||
const options = {
|
||||
url: baseApiUrl + endpoint,
|
||||
headers: {
|
||||
"x-namespace": "captain",
|
||||
"x-captain-auth": authTokenFromLogin
|
||||
},
|
||||
method: method,
|
||||
form: dataToSend
|
||||
}
|
||||
|
||||
return new Promise(function(res, rej) {
|
||||
let spinner
|
||||
|
||||
if (message) {
|
||||
spinner = spinnerUtil.start(message)
|
||||
}
|
||||
|
||||
var callback = function(error, response, body) {
|
||||
try {
|
||||
if (!error && response.statusCode === 200) {
|
||||
const data = JSON.parse(body)
|
||||
|
||||
if (data.status !== 100) {
|
||||
if (spinner) {
|
||||
spinnerUtil.fail(spinner)
|
||||
}
|
||||
|
||||
rej(data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (spinner) {
|
||||
spinnerUtil.succeed(spinner)
|
||||
}
|
||||
|
||||
res(data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
response ? JSON.stringify(response, null, 2) : "Response NULL"
|
||||
)
|
||||
} catch (error) {
|
||||
if (spinner) {
|
||||
spinnerUtil.fail(spinner)
|
||||
}
|
||||
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Something bad happened. Cannot connect to "${baseApiUrl} ${endpoint}"`
|
||||
)
|
||||
)
|
||||
|
||||
const errorMessage = error.message ? error.message : error
|
||||
|
||||
console.error(`${chalk.red(errorMessage)}\n`)
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
request(options, callback)
|
||||
})
|
||||
}
|
||||
|
||||
const questions = [
|
||||
{
|
||||
type: "list",
|
||||
name: "hasInstalledCaptain",
|
||||
message:
|
||||
"Have you already installed Captain on your server by running the following line:" +
|
||||
"\nmkdir /captain && docker run -v /var/run/docker.sock:/var/run/docker.sock dockersaturn/captainduckduck ?",
|
||||
default: "Yes",
|
||||
choices: ["Yes", "No"],
|
||||
filter: function(value) {
|
||||
return new Promise(function(res, rej) {
|
||||
if (value === "Yes") {
|
||||
res(true)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
console.log(
|
||||
"\n\nCannot start the setup process if Captain is not installed."
|
||||
)
|
||||
|
||||
console.log(
|
||||
"Please read tutorial on CaptainDuckDuck.com\n to learn how to install CaptainDuckDuck on a server.\n"
|
||||
)
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
default: SAMPLE_IP,
|
||||
name: "captainAddress",
|
||||
message: "Enter IP address of your captain server:",
|
||||
filter: function(value) {
|
||||
return new Promise(function(res, rej) {
|
||||
if (value === SAMPLE_IP) {
|
||||
rej("Enter a valid IP Address")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (!isIpAddress(value.trim())) {
|
||||
rej("This is an invalid IP Address: " + value)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ipAddressOfServer = value.trim()
|
||||
|
||||
//login using captain42.
|
||||
|
||||
return login("http://" + ipAddressOfServer + ":3000", oldPassword)
|
||||
.then(function(authTokenFetched) {
|
||||
authTokenFromLogin = authTokenFetched
|
||||
|
||||
res(ipAddressOfServer)
|
||||
})
|
||||
.catch(function(error) {
|
||||
// if error is anything but password wrong, exit here...
|
||||
if (error.status == 1105) {
|
||||
authTokenFromLogin = null
|
||||
|
||||
res(ipAddressOfServer)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
console.log("")
|
||||
|
||||
if (error.status) {
|
||||
console.log(chalk.red(`Error: ${error.status}`))
|
||||
|
||||
console.log(chalk.red(`Error: ${error.description}`))
|
||||
} else {
|
||||
console.log(chalk.red(`Error: ${error}`))
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "password",
|
||||
name: "captainOriginalPassword",
|
||||
message: "Enter your current password:",
|
||||
when: function() {
|
||||
return !authTokenFromLogin
|
||||
},
|
||||
filter: function(value) {
|
||||
return new Promise(function(res, rej) {
|
||||
console.log("")
|
||||
|
||||
return login("http://" + ipAddressOfServer + ":3000", value)
|
||||
.then(function(authTokenFetched) {
|
||||
authTokenFromLogin = authTokenFetched
|
||||
|
||||
oldPassword = value
|
||||
|
||||
res(value)
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log("")
|
||||
|
||||
if (error.status) {
|
||||
console.log(chalk.red("Error: " + error.status))
|
||||
|
||||
console.log(chalk.red("Error: " + error.description))
|
||||
} else {
|
||||
console.log(chalk.red("Error: " + error))
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
name: "captainRootDomain",
|
||||
message:
|
||||
"Enter a root domain for this Captain server. For example, enter test.yourdomain.com if you" +
|
||||
" setup your DNS to point *.test.yourdomain.com to ip address of your server" +
|
||||
": ",
|
||||
filter: function(value) {
|
||||
return new Promise(function(res, rej) {
|
||||
value = value.trim()
|
||||
|
||||
customDomainFromUser = "captain." + value
|
||||
|
||||
return setCustomDomain("http://" + ipAddressOfServer + ":3000", value)
|
||||
.then(function() {
|
||||
res(customDomainFromUser)
|
||||
})
|
||||
.catch(function(error) {
|
||||
if (error.status) {
|
||||
console.log(chalk.red(`Error: ${error.status}`))
|
||||
|
||||
console.log(chalk.red(`Error: ${error.description}`))
|
||||
} else {
|
||||
console.log(chalk.red(`Error: ${error}`))
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
name: "emailAddress",
|
||||
message: "Enter your 'valid' email address to enable HTTPS: ",
|
||||
filter: function(value) {
|
||||
return new Promise(function(res, rej) {
|
||||
console.log("")
|
||||
|
||||
value = value.trim()
|
||||
|
||||
return enableHttps("http://" + customDomainFromUser, value)
|
||||
.then(function() {
|
||||
return forceHttps("https://" + customDomainFromUser)
|
||||
})
|
||||
.then(function() {
|
||||
res(value)
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log("")
|
||||
|
||||
if (error.status) {
|
||||
console.log(chalk.red(`Error: ${error.status}`))
|
||||
|
||||
console.log(chalk.red(`Error: ${error.description}`))
|
||||
} else {
|
||||
console.log(chalk.red(`Error: ${error}`))
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "password",
|
||||
name: "newPasswordFirstTry",
|
||||
message: "Enter a new password:",
|
||||
filter: function(value) {
|
||||
return new Promise(function(res, rej) {
|
||||
newPasswordFirstTry = value
|
||||
|
||||
res(value)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "password",
|
||||
name: "newPassword",
|
||||
message: "Enter a new password:",
|
||||
filter: function(value) {
|
||||
return new Promise(function(res, rej) {
|
||||
if (newPasswordFirstTry !== value) {
|
||||
rej("Passwords do not match")
|
||||
//process.exit(0);
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return changePass("https://" + customDomainFromUser, value)
|
||||
.then(function() {
|
||||
return login("https://" + customDomainFromUser, value)
|
||||
})
|
||||
.then(function(token) {
|
||||
authTokenFromLogin = token
|
||||
|
||||
res(value)
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log("")
|
||||
|
||||
if (error.status) {
|
||||
console.log(chalk.red(`Error: ${error.status}`))
|
||||
|
||||
console.log(chalk.red(`Error: ${error.description}`))
|
||||
} else {
|
||||
console.log(chalk.red(`Error: ${error}`))
|
||||
}
|
||||
|
||||
console.log(
|
||||
chalk.red(
|
||||
"\nIMPORTANT!! Server setup is completed by password is not changed."
|
||||
)
|
||||
)
|
||||
|
||||
console.log(
|
||||
chalk.red("You CANNOT use serversetup anymore. To continue: ")
|
||||
)
|
||||
|
||||
console.log(
|
||||
chalk.red(
|
||||
"- Go to https://" +
|
||||
customDomainFromUser +
|
||||
" login with default password and change the password in settings."
|
||||
)
|
||||
)
|
||||
|
||||
console.log(
|
||||
chalk.red(
|
||||
"- In terminal (here), type captainduckduck login and enter this as your root domain: " +
|
||||
customDomainFromUser
|
||||
)
|
||||
)
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
name: "captainName",
|
||||
message: "Enter a name for this Captain machine:",
|
||||
default: findDefaultCaptainName(),
|
||||
validate: function(value) {
|
||||
const machines = configs.get("captainMachines")
|
||||
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
if (machines[i].name === value) {
|
||||
return `${value} already exist. If you want to replace the existing entry, you have to first use <logout> command, and then re-login.`
|
||||
}
|
||||
}
|
||||
|
||||
if (value.match(/^[-\d\w]+$/i)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return "Please enter a Captain Name."
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
inquirer.prompt(questions).then(function(answers) {
|
||||
var captainAddress = "https://" + customDomainFromUser
|
||||
|
||||
const machines = configs.get("captainMachines")
|
||||
|
||||
machines.push({
|
||||
authToken: authTokenFromLogin,
|
||||
baseUrl: captainAddress,
|
||||
name: answers.captainName
|
||||
})
|
||||
|
||||
configs.set("captainMachines", machines)
|
||||
|
||||
console.log(`\n\nCaptain is available at ${captainAddress}`)
|
||||
|
||||
console.log(
|
||||
"\nFor more details and docs see http://www.captainduckduck.com\n\n"
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
const chalk = require("chalk")
|
||||
|
||||
function printErrorAndExit(error) {
|
||||
console.log(`${chalk.bold.red(error)}\n\n`)
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
module.exports = printErrorAndExit
|
||||
@@ -0,0 +1,178 @@
|
||||
const fs = require("fs-extra")
|
||||
const chalk = require("chalk")
|
||||
const request = require("request")
|
||||
const ProgressBar = require("progress")
|
||||
const ora = require("ora")
|
||||
const configstore = require("configstore")
|
||||
const packagejson = require("./package.json")
|
||||
const { requestLogin } = require("./utils/loginHandler")
|
||||
const configs = new configstore(packagejson.name, {
|
||||
captainMachines: [],
|
||||
apps: []
|
||||
})
|
||||
|
||||
function sendFileToCaptain(
|
||||
machineToDeploy,
|
||||
zipFileFullPath,
|
||||
appName,
|
||||
gitHash,
|
||||
branchToPush
|
||||
) {
|
||||
console.log("Uploading file to " + machineToDeploy.baseUrl)
|
||||
|
||||
const fileSize = fs.statSync(zipFileFullPath).size
|
||||
const fileStream = fs.createReadStream(zipFileFullPath)
|
||||
const barOpts = {
|
||||
width: 20,
|
||||
total: fileSize,
|
||||
clear: true
|
||||
}
|
||||
const bar = new ProgressBar(
|
||||
" uploading [:bar] :percent (ETA :etas)",
|
||||
barOpts
|
||||
)
|
||||
|
||||
fileStream.on("data", function(chunk) {
|
||||
bar.tick(chunk.length)
|
||||
})
|
||||
|
||||
let spinner
|
||||
|
||||
fileStream.on("end", function() {
|
||||
console.log(" ")
|
||||
|
||||
console.log("This might take several minutes. PLEASE BE PATIENT...")
|
||||
|
||||
spinner = ora("Building your source code...").start()
|
||||
|
||||
spinner.color = "yellow"
|
||||
})
|
||||
|
||||
const options = {
|
||||
url:
|
||||
machineToDeploy.baseUrl +
|
||||
"/api/v1/user/appData/" +
|
||||
appName +
|
||||
"/?detached=1",
|
||||
headers: {
|
||||
"x-namespace": "captain",
|
||||
"x-captain-auth": machineToDeploy.authToken
|
||||
},
|
||||
method: "POST",
|
||||
formData: {
|
||||
sourceFile: fileStream,
|
||||
gitHash: gitHash
|
||||
}
|
||||
}
|
||||
|
||||
function callback(error, response, body) {
|
||||
if (spinner) {
|
||||
spinner.stop()
|
||||
}
|
||||
|
||||
if (fs.pathExistsSync(zipFileFullPath) && !getSuppliedTarFile()) {
|
||||
fs.removeSync(zipFileFullPath)
|
||||
}
|
||||
|
||||
try {
|
||||
if (!error && response.statusCode === 200) {
|
||||
const data = JSON.parse(body)
|
||||
|
||||
if (data.status === 1106) {
|
||||
// expired token
|
||||
requestLogin(
|
||||
machineToDeploy.name,
|
||||
machineToDeploy.baseUrl,
|
||||
function callback(machineToDeployNew) {
|
||||
deployTo(machineToDeployNew, branchToPush, appName)
|
||||
}
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (data.status !== 100 && data.status !== 101) {
|
||||
throw new Error(JSON.stringify(data, null, 2))
|
||||
}
|
||||
|
||||
savePropForDirectory(APP_NAME, appName)
|
||||
|
||||
savePropForDirectory(BRANCH_TO_PUSH, branchToPush)
|
||||
|
||||
savePropForDirectory(MACHINE_TO_DEPLOY, machineToDeploy)
|
||||
|
||||
if (data.status === 100) {
|
||||
console.log(chalk.green("Deployed successfully: ") + appName)
|
||||
console.log(" ")
|
||||
} else if (data.status === 101) {
|
||||
console.log(chalk.green("Building started: ") + appName)
|
||||
console.log(" ")
|
||||
|
||||
startFetchingBuildLogs(machineToDeploy, appName)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
response ? JSON.stringify(response, null, 2) : "Response NULL"
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(
|
||||
chalk.red('\nSomething bad happened. Cannot deploy "' + appName + '"\n')
|
||||
)
|
||||
|
||||
if (error.message) {
|
||||
try {
|
||||
var errorObj = JSON.parse(error.message)
|
||||
if (errorObj.status) {
|
||||
console.error(chalk.red("\nError code: " + errorObj.status))
|
||||
console.error(
|
||||
chalk.red("\nError message:\n\n " + errorObj.description)
|
||||
)
|
||||
} else {
|
||||
throw new Error("NOT API ERROR")
|
||||
}
|
||||
} catch (ignoreError) {
|
||||
console.error(chalk.red(error.message))
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.red(error))
|
||||
}
|
||||
console.log(" ")
|
||||
}
|
||||
}
|
||||
|
||||
request(options, callback)
|
||||
}
|
||||
|
||||
// Sets default value for propType that is stored in a directory to propValue.
|
||||
// Replaces saveAppForDirectory
|
||||
function savePropForDirectory(propType, propValue) {
|
||||
const apps = configs.get("apps")
|
||||
|
||||
for (let i = 0; i < apps.length; i++) {
|
||||
const app = apps[i]
|
||||
|
||||
if (app.cwd === process.cwd()) {
|
||||
app[propType] = propValue
|
||||
|
||||
configs.set("apps", apps)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
apps.push({
|
||||
cwd: process.cwd(),
|
||||
[propType]: propValue
|
||||
})
|
||||
|
||||
configs.set("apps", apps)
|
||||
}
|
||||
|
||||
module.exports = sendFileToCaptain
|
||||
@@ -0,0 +1,194 @@
|
||||
const inquirer = require("inquirer")
|
||||
|
||||
function requestLogin(serverName, serverAddress, loginCallback) {
|
||||
console.log("Your auth token is not valid anymore. Try to login again.")
|
||||
|
||||
const questions = [
|
||||
{
|
||||
type: "password",
|
||||
name: "captainPassword",
|
||||
message: "Please enter your password for " + serverAddress,
|
||||
validate: function(value) {
|
||||
if (value && value.trim()) {
|
||||
return true
|
||||
}
|
||||
|
||||
return "Please enter your password for " + serverAddress
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
inquirer.prompt(questions).then(function(passwordAnswers) {
|
||||
var password = passwordAnswers.captainPassword
|
||||
|
||||
let options = {
|
||||
url: serverAddress + "/api/v1/login",
|
||||
headers: {
|
||||
"x-namespace": "captain"
|
||||
},
|
||||
method: "POST",
|
||||
form: {
|
||||
password: password
|
||||
}
|
||||
}
|
||||
|
||||
function callback(error, response, body) {
|
||||
try {
|
||||
if (!error && response.statusCode === 200) {
|
||||
let data = JSON.parse(body)
|
||||
|
||||
if (data.status !== 100) {
|
||||
throw new Error(JSON.stringify(data, null, 2))
|
||||
}
|
||||
|
||||
var newMachineToDeploy = updateAuthTokenInConfigStoreAndReturn(
|
||||
data.token
|
||||
)
|
||||
|
||||
loginCallback(newMachineToDeploy)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
response ? JSON.stringify(response, null, 2) : "Response NULL"
|
||||
)
|
||||
} catch (error) {
|
||||
if (error.message) {
|
||||
try {
|
||||
var errorObj = JSON.parse(error.message)
|
||||
if (errorObj.status) {
|
||||
console.error(chalk.red("\nError code: " + errorObj.status))
|
||||
console.error(
|
||||
chalk.red("\nError message:\n\n " + errorObj.description)
|
||||
)
|
||||
} else {
|
||||
throw new Error("NOT API ERROR")
|
||||
}
|
||||
} catch (ignoreError) {
|
||||
console.error(chalk.red(error.message))
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.red(error))
|
||||
}
|
||||
console.log(" ")
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
request(options, callback)
|
||||
})
|
||||
}
|
||||
|
||||
function requestLoginAuth(serverAddress, password, authCallback) {
|
||||
let options = {
|
||||
url: serverAddress + "/api/v1/login",
|
||||
headers: {
|
||||
"x-namespace": "captain"
|
||||
},
|
||||
method: "POST",
|
||||
form: {
|
||||
password: password
|
||||
}
|
||||
}
|
||||
|
||||
function callback(error, response, body) {
|
||||
try {
|
||||
if (!error && response.statusCode === 200) {
|
||||
let data = JSON.parse(body)
|
||||
|
||||
if (data.status !== 100) {
|
||||
throw new Error(JSON.stringify(data, null, 2))
|
||||
}
|
||||
|
||||
authCallback(data.token)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
response ? JSON.stringify(response, null, 2) : "Response NULL"
|
||||
)
|
||||
} catch (error) {
|
||||
if (error.message) {
|
||||
try {
|
||||
var errorObj = JSON.parse(error.message)
|
||||
if (errorObj.status) {
|
||||
console.error(chalk.red("\nError code: " + errorObj.status))
|
||||
console.error(
|
||||
chalk.red("\nError message:\n\n " + errorObj.description)
|
||||
)
|
||||
} else {
|
||||
throw new Error("NOT API ERROR")
|
||||
}
|
||||
} catch (ignoreError) {
|
||||
console.error(chalk.red(error.message))
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.red(error))
|
||||
}
|
||||
console.log(" ")
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
request(options, callback)
|
||||
}
|
||||
|
||||
function isAuthTokenValid(machineToDeploy, appName, isAuthTokenValidCallback) {
|
||||
let options = {
|
||||
url: machineToDeploy.baseUrl + "/api/v1/user/appDefinitions/",
|
||||
headers: {
|
||||
"x-namespace": "captain",
|
||||
"x-captain-auth": machineToDeploy.authToken
|
||||
},
|
||||
method: "GET"
|
||||
}
|
||||
|
||||
function callback(error, response, body) {
|
||||
try {
|
||||
if (!error && response.statusCode === 200) {
|
||||
let data = JSON.parse(body)
|
||||
|
||||
if (data.status === 1106 || data.status === 1105) {
|
||||
isAuthTokenValidCallback(false)
|
||||
} else {
|
||||
isAuthTokenValidCallback(true)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// This is just a sanity check. We only fire FALSE (i.e. expired) if we know it's expired or password is wrong
|
||||
isAuthTokenValidCallback(true)
|
||||
}
|
||||
}
|
||||
|
||||
request(options, callback)
|
||||
}
|
||||
|
||||
function updateAuthTokenInConfigStoreAndReturn(authToken) {
|
||||
let machines = configs.get("captainMachines")
|
||||
for (let i = 0; i < machines.length; i++) {
|
||||
if (machines[i].name === serverName) {
|
||||
var baseUrl = (machines[i].authToken = authToken)
|
||||
configs.set("captainMachines", machines)
|
||||
console.log("You are now logged back in to " + serverAddress)
|
||||
return machines[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
requestLogin,
|
||||
requestLoginAuth,
|
||||
isAuthTokenValid
|
||||
}
|
||||
+10
-10
@@ -1,24 +1,24 @@
|
||||
const ora = require('ora');
|
||||
const ora = require("ora")
|
||||
|
||||
function start(message) {
|
||||
return ora(message).start()
|
||||
return ora(message).start()
|
||||
}
|
||||
|
||||
function stop(spinner) {
|
||||
spinner.stop();
|
||||
spinner.stop()
|
||||
}
|
||||
|
||||
function succeed(spinner) {
|
||||
spinner.succeed();
|
||||
spinner.succeed()
|
||||
}
|
||||
|
||||
function fail(spinner) {
|
||||
spinner.fail();
|
||||
spinner.fail()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
start: start,
|
||||
stop: stop,
|
||||
succeed: succeed,
|
||||
fail: fail,
|
||||
}
|
||||
start: start,
|
||||
stop: stop,
|
||||
succeed: succeed,
|
||||
fail: fail
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
const fs = require("fs-extra")
|
||||
const printErrorAndExit = require("./errorHandler")
|
||||
|
||||
function validateIsGitRepository() {
|
||||
if (!fs.pathExistsSync("./.git")) {
|
||||
printErrorAndExit(
|
||||
"**** ERROR: You are not in a git root directory. This command will only deploys the current directory ****"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function validateDefinitionFile() {
|
||||
if (!fs.pathExistsSync("./captain-definition")) {
|
||||
printErrorAndExit(
|
||||
"**** ERROR: captain-definition file cannot be found. Please see docs! ****"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateIsGitRepository,
|
||||
validateDefinitionFile
|
||||
}
|
||||
Reference in New Issue
Block a user