firmament-docker
Version:
Typescript classes for performing Docker operations
413 lines • 22.3 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
const inversify_1 = require("inversify");
const docker_descriptors_1 = require("../interfaces/docker-descriptors");
const firmament_yargs_1 = require("firmament-yargs");
const _ = require("lodash");
const fs = require("fs");
const path = require('path');
const fileExists = require('file-exists');
const async = require('async');
const templateCatalogUrl = 'https://raw.githubusercontent.com/jreeme/firmament-docker/master/docker/templateCatalog.json';
let DockerMakeImpl = class DockerMakeImpl extends firmament_yargs_1.ForceErrorImpl {
constructor(commandUtil, spawn, safeJson, remoteCatalogGetter, dockerImageManagement, dockerUtil, dockerContainerManagement, positive, progressBar) {
super();
this.commandUtil = commandUtil;
this.spawn = spawn;
this.safeJson = safeJson;
this.remoteCatalogGetter = remoteCatalogGetter;
this.dockerImageManagement = dockerImageManagement;
this.dockerUtil = dockerUtil;
this.dockerContainerManagement = dockerContainerManagement;
this.positive = positive;
this.progressBar = progressBar;
}
buildTemplate(argv) {
let me = this;
let { fullInputPath, sortedContainerConfigs } = me.getSortedContainerConfigsFromJsonFile(argv.input);
const baseDir = path.dirname(fullInputPath);
me.commandUtil.log("Constructing Docker containers described in: '" + fullInputPath + "'");
me.processContainerConfigs(sortedContainerConfigs, baseDir, (err) => {
me.commandUtil.processExitWithError(err, `Finished.\n`);
});
}
makeTemplate(argv) {
let me = this;
const fullOutputPath = this.commandUtil.getConfigFilePath(argv.output, '.json');
if (argv.get === undefined) {
if (fs.existsSync(fullOutputPath)
&& !me.positive.areYouSure(`Config file '${fullOutputPath}' already exists. Overwrite? [Y/n] `, 'Operation canceled.', true, firmament_yargs_1.FailureRetVal.TRUE)) {
me.commandUtil.processExit();
}
else {
me.dockerUtil.writeJsonTemplateFile(argv.full
? docker_descriptors_1.DockerDescriptors.dockerContainerDefaultTemplate
: docker_descriptors_1.DockerDescriptors.dockerContainerConfigTemplate, fullOutputPath);
}
me.commandUtil.processExit();
}
else {
me.remoteCatalogGetter.getCatalogFromUrl(templateCatalogUrl, (err, remoteCatalog) => {
if (!argv.get.length) {
me.commandUtil.log('\nAvailable templates:\n');
remoteCatalog.entries.forEach(entry => {
me.commandUtil.log('> ' + entry.name);
});
me.commandUtil.processExit();
}
else {
let template = _.find(remoteCatalog.entries, entry => {
return entry.name === argv.get;
});
if (!template) {
me.commandUtil.processExitWithError(new Error(`\nTemplate catalog '${argv.get}' does not exist.\n`));
}
template.resources.forEach(resource => {
try {
let outputPath = path.resolve(process.cwd(), path.basename(resource.name));
fs.writeFileSync(outputPath, resource.text);
}
catch (err) {
me.commandUtil.processExitWithError(err);
}
});
me.commandUtil.processExit(0, `\nTemplate '${template.name}' written.\n`);
}
});
}
}
getSortedContainerConfigsFromJsonFile(inputPath) {
let me = this;
const fullInputPath = me.commandUtil.getConfigFilePath(inputPath, '.json');
if (!fileExists.sync(fullInputPath)) {
me.commandUtil.processExitWithError(new Error(`\n'${fullInputPath}' does not exist`));
}
const containerConfigs = me.safeJson.readFileSync(fullInputPath, undefined);
const sortedContainerConfigs = me.containerDependencySort(containerConfigs);
return { fullInputPath, sortedContainerConfigs };
}
processContainerConfigs(containerConfigs, baseDir, cb) {
let me = this;
let containerConfigsByImageName = {};
containerConfigs.forEach(containerConfig => {
containerConfigsByImageName[containerConfig.Image] = containerConfig;
});
async.waterfall([
(cb) => {
this.dockerContainerManagement.removeContainers(containerConfigs.map(containerConfig => containerConfig.name), cb);
},
(containerRemoveResults, cb) => {
this.dockerImageManagement.listImages(false, (err, images) => {
if (me.commandUtil.callbackIfError(cb, err)) {
return;
}
let repoTags = {};
images.forEach(dockerImage => {
repoTags[dockerImage.RepoTags[0]] = true;
});
let missingImageNames = [];
containerConfigs.forEach(containerConfig => {
let imageName = (containerConfig.Image.indexOf(':') == -1)
? containerConfig.Image + ':latest'
: containerConfig.Image;
if (!repoTags[imageName]) {
missingImageNames.push(imageName);
}
});
cb(null, _.uniq(missingImageNames));
});
},
(missingImageNames, cb) => {
async.mapLimit(missingImageNames, 4, (missingImageName, cb) => {
this.dockerImageManagement.pullImage(missingImageName, (taskId, status, current, total) => {
}, (err) => {
cb(null, err ? missingImageName : null);
});
}, (err, missingImageNames) => {
if (me.commandUtil.callbackIfError(cb, err)) {
return;
}
cb(null, missingImageNames.filter(missingImageName => {
return !!missingImageName;
}));
});
},
(missingImageNames, cb) => {
async.mapLimit(missingImageNames, 4, (missingImageName, cb) => {
const containerConfig = containerConfigsByImageName[missingImageName];
let dockerFilePath = path.resolve(baseDir, containerConfig.DockerFilePath);
let dockerImageName = containerConfig.Image;
this.dockerImageManagement.buildDockerFile(dockerFilePath, dockerImageName, (taskId, status, current, total) => {
}, (err) => {
cb(null, err
? new Error('Unable to build Dockerfile at "' + dockerFilePath + '" because: ' + err.message)
: null);
});
}, (err, errors) => {
if (me.commandUtil.callbackIfError(cb, err)) {
return;
}
errors = errors.filter(error => {
return !!error;
});
cb(me.commandUtil.logErrors(errors).length ? new Error() : null, errors);
});
},
(errs, cb) => {
try {
async.mapSeries(containerConfigs, (containerConfig, cb) => {
this.dockerContainerManagement.createContainer(containerConfig, (err, container) => {
me.commandUtil.logAndCallback('Container "' + containerConfig.name + '" created.', cb, err, container);
});
}, (err, containers) => {
if (me.commandUtil.callbackIfError(cb, err)) {
return;
}
let sortedContainerNames = containerConfigs.map(containerConfig => containerConfig.name);
this.dockerContainerManagement.startOrStopContainers(sortedContainerNames, true, () => {
cb(null, null);
});
});
}
catch (err) {
me.commandUtil.callbackIfError(cb, err);
}
},
function deployExpressApps(errs, cb) {
async.mapLimit(containerConfigs, 4, (containerConfig, cb) => {
async.mapSeries(containerConfig.ExpressApps || [], (expressApp, cb) => {
async.series([
(cb) => {
let cwd = process.cwd();
let serviceName = expressApp.ServiceName;
if (expressApp.DeployExisting) {
let serviceSourceFolders = fs.readdirSync(cwd).filter((fileName) => {
return fileName.substring(0, serviceName.length) === serviceName;
});
if (serviceSourceFolders.length > 1) {
let msg = 'DeployExisting was specified but there is more than one service named: ';
msg += serviceName + ': ' + serviceSourceFolders + '. Delete all but one and retry.';
cb(new Error(msg));
return;
}
else if (serviceSourceFolders.length > 0) {
expressApp.GitCloneFolder = cwd + '/' + serviceSourceFolders[0];
cb(null);
return;
}
}
let refTimeStamp = (new Date()).getTime();
let timeStamp = refTimeStamp;
while (timeStamp === refTimeStamp) {
timeStamp = (new Date()).getTime();
}
expressApp.GitCloneFolder = `${cwd}/${expressApp.ServiceName}${timeStamp}`;
cb(null);
},
(cb) => {
fs.stat(expressApp.GitCloneFolder, (err, stats) => {
if (err) {
me.gitClone(expressApp.GitUrl, expressApp.GitSrcBranchName, expressApp.GitCloneFolder, (err) => {
cb(err);
});
}
else {
cb(null);
}
});
},
(cb) => {
let retries = 20;
(function checkForStrongloop() {
me.remoteSlcCtlCommand('Looking for SLC PM ...', expressApp, ['info'], (err, result) => {
--retries;
const errorMsg = 'Strongloop not available';
const readyResult = /Driver Status:\s+running/;
if (err) {
setTimeout(checkForStrongloop, 3000);
}
else if (readyResult.test(result)) {
cb(null, 'Strongloop ready.');
}
else if (retries < 0) {
cb(new Error(errorMsg), errorMsg);
}
else {
setTimeout(checkForStrongloop, 3000);
}
});
})();
},
(cb) => {
let serviceName = expressApp.ServiceName;
let msg = 'Creating ' + serviceName;
me.remoteSlcCtlCommand(msg, expressApp, ['create', serviceName], cb);
},
(cb) => {
if (!expressApp.ClusterSize) {
cb(null);
return;
}
let clusterSize = expressApp.ClusterSize.toString();
me.remoteSlcCtlCommand('Setting cluster size to: ' + clusterSize, expressApp, ['set-size', expressApp.ServiceName, clusterSize], cb);
},
(cb) => {
if (!_.keys(expressApp.EnvironmentVariables).length) {
cb(null);
return;
}
let cmd = ['env-set', expressApp.ServiceName];
for (let environmentVariable in expressApp.EnvironmentVariables) {
cmd.push(environmentVariable
+ '='
+ expressApp.EnvironmentVariables[environmentVariable]);
}
me.remoteSlcCtlCommand('Setting environment variables', expressApp, cmd, cb);
},
(cb) => {
if (!expressApp.DoBowerInstall) {
cb(null);
return;
}
let cwd = expressApp.GitCloneFolder;
me.spawn.spawnShellCommandAsync(['bower', 'install', '--config.interactive=false'], { cwd }, (err, result) => {
me.commandUtil.log(result.toString());
}, cb);
},
(cb) => {
let cwd = expressApp.GitCloneFolder;
me.spawn.spawnShellCommandAsync(['npm', 'install', '--quiet'], { cwd }, (err, result) => {
me.commandUtil.log(result.toString());
}, cb);
},
(cb) => {
async.mapSeries(expressApp.Scripts || [], (script, cb) => {
let cwd = expressApp.GitCloneFolder + '/' + script.RelativeWorkingDir;
let cmd = [script.Command];
cmd = cmd.concat(script.Args);
me.spawn.spawnShellCommandAsync(cmd, { cwd }, (err, result) => {
me.commandUtil.log(result.toString());
}, (err, result) => {
cb(script.StopDeployOnFailure ? err : null, result);
});
}, (err, results) => {
cb(err, null);
});
},
(cb) => {
let cwd = expressApp.GitCloneFolder;
me.spawn.spawnShellCommandAsync(['slc', 'build', '--scripts'], { cwd }, (err, result) => {
me.commandUtil.log(result.toString());
}, cb);
},
(cb) => {
let cwd = expressApp.GitCloneFolder;
me.commandUtil.log('StrongLoop Deploying @ ' + cwd);
me.spawn.spawnShellCommandAsync(['slc', 'deploy', '--service=' + expressApp.ServiceName, expressApp.StrongLoopServerUrl], { cwd }, (err, result) => {
me.commandUtil.log(result.toString());
}, cb);
}
], cb);
}, cb);
}, cb);
}
], cb);
}
containerDependencySort(containerConfigs) {
const sortedContainerConfigs = [];
const objectToSort = {};
const containerConfigByNameMap = {};
containerConfigs.forEach(function (containerConfig) {
if (containerConfigByNameMap[containerConfig.name]) {
console.error('Same name is used by more than one container.');
}
containerConfigByNameMap[containerConfig.name] = containerConfig;
const dependencies = [];
if (containerConfig.HostConfig && containerConfig.HostConfig.Links) {
containerConfig.HostConfig.Links.forEach(function (link) {
const linkName = link.split(':')[0];
dependencies.push(linkName);
});
}
objectToSort[containerConfig.name] = dependencies;
});
const sortedContainerNames = this.topologicalDependencySort(objectToSort);
sortedContainerNames.forEach(function (sortedContainerName) {
sortedContainerConfigs.push(containerConfigByNameMap[sortedContainerName]);
});
return sortedContainerConfigs;
}
topologicalDependencySort(graph) {
const sorted = [], visited = {};
try {
Object.keys(graph).forEach(function visit(name, ancestors) {
if (visited[name]) {
return;
}
if (!Array.isArray(ancestors)) {
ancestors = [];
}
ancestors.push(name);
visited[name] = true;
const deps = graph[name];
deps.forEach(function (dep) {
if (ancestors.indexOf(dep) >= 0) {
console.error('Circular dependency "' + dep + '" is required by "' + name + '": ' + ancestors.join(' -> '));
}
visit(dep, ancestors.slice(0));
});
sorted.push(name);
});
}
catch (ex) {
throw new Error('Linked container dependency sort failed. You are probably trying to link to an unknown container.');
}
return sorted;
}
remoteSlcCtlCommand(msg, expressApp, cmd, cb) {
let cwd = expressApp.GitCloneFolder;
let serviceName = expressApp.ServiceName;
let serverUrl = expressApp.StrongLoopServerUrl;
this.commandUtil.log(msg + ' "' + serviceName + '" @ "' + cwd + '" via "' + serverUrl + '"');
const baseCmd = ['slc', 'ctl', '-C', serverUrl];
Array.prototype.push.apply(baseCmd, cmd);
this.spawn.spawnShellCommandAsync(baseCmd, { cwd, stdio: 'pipe', cacheStdOut: true, cacheStdErr: true }, (err, result) => {
this.commandUtil.log(result.toString());
}, (err, result) => {
this.commandUtil.log(result);
cb(err, result);
});
}
gitClone(gitUrl, gitBranch, localFolder, cb) {
this.spawn.spawnShellCommandAsync(['git', 'clone', '-b', gitBranch, '--single-branch', gitUrl, localFolder], { cwd: process.cwd(), stdio: 'pipe' }, (err, result) => {
this.commandUtil.log(result.toString());
}, cb);
}
};
DockerMakeImpl = __decorate([
inversify_1.injectable(),
__param(0, inversify_1.inject('CommandUtil')),
__param(1, inversify_1.inject('Spawn')),
__param(2, inversify_1.inject('SafeJson')),
__param(3, inversify_1.inject('RemoteCatalogGetter')),
__param(4, inversify_1.inject('DockerImageManagement')),
__param(5, inversify_1.inject('DockerUtil')),
__param(6, inversify_1.inject('DockerContainerManagement')),
__param(7, inversify_1.inject('Positive')),
__param(8, inversify_1.inject('ProgressBar')),
__metadata("design:paramtypes", [Object, Object, Object, Object, Object, Object, Object, Object, Object])
], DockerMakeImpl);
exports.DockerMakeImpl = DockerMakeImpl;
//# sourceMappingURL=docker-make-impl.js.map