UNPKG

@cto.ai/ops

Version:

šŸ’» CTO.ai - The CLI built for Teams šŸš€

193 lines (192 loc) • 9.02 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ImageService = void 0; const tslib_1 = require("tslib"); const JSONStream_1 = tslib_1.__importDefault(require("JSONStream")); const debug_1 = tslib_1.__importDefault(require("debug")); const fs = tslib_1.__importStar(require("fs-extra")); const path = tslib_1.__importStar(require("path")); const through2_1 = tslib_1.__importDefault(require("through2")); const cli_sdk_1 = require("@cto.ai/cli-sdk"); const opConfig_1 = require("../constants/opConfig"); const CustomErrors_1 = require("./../errors/CustomErrors"); const Error_1 = require("./../services/Error"); const get_docker_1 = tslib_1.__importDefault(require("./../utils/get-docker")); const debug = (0, debug_1.default)('ops:ImageService'); class ImageService { constructor(error = new Error_1.ErrorService()) { this.error = error; this.log = console.log; this.checkLocalImage = async (opImageUrl) => { const docker = await (0, get_docker_1.default)(console, 'ImageService'); const list = await docker.listImages(); return list .map(this.imageFilterPredicate(opImageUrl)) .find((repoTag) => !!repoTag); }; this.imageFilterPredicate = (repo) => ({ RepoTags }) => { if (!RepoTags) { return; } return RepoTags.find((repoTag) => repoTag.includes(repo)); }; this.pull = async (op, authconfig) => { const docker = await (0, get_docker_1.default)(console, 'ImageServicePull'); const stream = await docker .pull(op.image || '', { authconfig }) .catch(err => { throw new CustomErrors_1.ImagePullError(err); }); if (!stream) { throw new Error('No stream'); } this.log(`šŸ”‹ Pulling ${cli_sdk_1.ux.colors.dim(op.name)} from registry...\n`); const parser = this.setParser(op, this.getProgressBarText); await new Promise(this.updateStatusBar(stream, parser)); cli_sdk_1.ux.spinner.stop(cli_sdk_1.ux.colors.green('Done!')); const msg = `${cli_sdk_1.ux.colors.italic.bold(`${op.name}:${op.id}`)}`; this.log(`\nšŸ™Œ Saved ${msg} locally! \n`); }; this.setParser = (op, getFn) => { const bar = cli_sdk_1.ux.progress.init({ format: cli_sdk_1.ux.colors.callOutCyan('{bar} {percentage}% | Status: {speed} '), barCompleteChar: '\u2588', barIncompleteChar: '\u2591', }); bar.start(100, 0, { speed: 'šŸ Starting...' }); const layers = {}; const parser = through2_1.default.obj((chunk, _enc, callback) => { const { id, status, progressDetail: p } = chunk; const progress = p && p.current ? (p.current / p.total) * 100 : 0; const { speed } = getFn(status, op); if (id) { layers[id] = id; } if (speed) { bar.update(progress, { speed }); } callback(); }); const _pipe = parser.pipe; parser.pipe = dest => _pipe(dest); return { parser, bar }; }; this.getProgressBarText = (status, { name }) => { const mapping = { [`Pulling from ${name}`]: `āœ… Pulling from ${name}...`, 'Already exists': 'āœ… Already exists!', Waiting: 'ā± Waiting...', Downloading: 'šŸ‘‡ Downloading...', 'Download complete': 'šŸ‘‡ Downloaded!', Extracting: 'šŸ“¦ Unpacking...', 'Pulling fs layer': 'šŸ‘ Pulling layers...', 'Pull complete': 'šŸŽ‰ Pull Complete!', }; return { speed: mapping[status] }; }; this.updateStatusBar = (stream, { parser, bar }) => async (resolve, reject) => { const allData = []; const size = 100; stream .pipe(JSONStream_1.default.parse(undefined)) .pipe(parser) .on('data', data => allData.push(data)) .on('end', async (err) => { for (let i = 0; i < size; i++) { bar.update(100 - size / i); await cli_sdk_1.ux.wait(5); } bar.update(100); bar.stop(); debug('%O', err); return err ? reject(err) : resolve(allData); }); }; this.checkIfDockerfileExists = (opPath) => { const pathToDockerfile = path.join(path.resolve(opPath), 'Dockerfile'); return fs.existsSync(pathToDockerfile); }; this.build = async (tag, opPath, op, options) => { const ogPath = opPath; if (op.type === opConfig_1.JOB_TYPE) { opPath = ogPath + '/.ops/jobs/' + op.name; } try { const dockerfileExists = this.checkIfDockerfileExists(opPath); if (!dockerfileExists) { throw new Error(`Unable to build an image for this workflow. If you are inside your workflow directory, please run ${cli_sdk_1.ux.colors.green('$')} ${cli_sdk_1.ux.colors.italic.dim('ops run .')} or ${cli_sdk_1.ux.colors.green('$')} ${cli_sdk_1.ux.colors.italic.dim('ops build .')} instead.\n`); } const all = []; const errors = []; const log = this.log; const parser = through2_1.default.obj(function (chunk, _enc, cb) { if (chunk.stream && chunk.stream !== '\n') { this.push(chunk.stream.replace('\n', '')); log(chunk.stream.replace('\n', '')); all.push(chunk); } else if (chunk.errorDetail) { debug(chunk.errorDetail); errors.push(chunk.errorDetail.message); } cb(); }); const _pipe = parser.pipe; parser.pipe = function (dest) { return _pipe(dest); }; const docker = await (0, get_docker_1.default)(console, 'build'); // TODO: What error handling should we do if this is falsy if (!docker) { return; } const defaultOptions = { t: tag, nocache: false, pull: true, }; const buildOptions = Object.assign(Object.assign({}, defaultOptions), options); debug('build options ', buildOptions); const stream = await docker .buildImage({ context: opPath, src: op.src || fs.readdirSync(opPath) }, buildOptions) .catch(err => { debug('%O', err); throw new CustomErrors_1.DockerBuildImageError(err); }); // TODO: What error handling should we do if this is false if (!stream) { return; } await new Promise(function (resolve, reject) { stream .pipe(JSONStream_1.default.parse(undefined)) .pipe(parser) .on('data', (d, data) => { all.push(d); }) .on('end', function () { if (errors.length) { return reject(new CustomErrors_1.DockerBuildImageError(errors[0])); } let messagePath = ogPath; const parentFolder = ogPath.substring(0, ogPath.lastIndexOf('/')); if (process.env.PWD === ogPath) { messagePath = '.'; } else if (process.env.PWD === parentFolder) { messagePath = ogPath.substring(ogPath.lastIndexOf('/') + 1); } log(`\nšŸ’» Run ${cli_sdk_1.ux.colors.green('$')} ${cli_sdk_1.ux.colors.italic.dim('ops run ' + messagePath)} to test your workflow.`); log(`šŸ“¦ Run ${cli_sdk_1.ux.colors.green('$')} ${cli_sdk_1.ux.colors.italic.dim('ops publish ' + messagePath)} to share your workflow. \n`); resolve(undefined); }); }); } catch (err) { debug('%O', err); this.error.handleError({ err }); } }; } } exports.ImageService = ImageService;