UNPKG

firmament-docker

Version:

Typescript classes for performing Docker operations

413 lines 22.3 kB
"use strict"; 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