UNPKG

@cto.ai/ops

Version:

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

168 lines (167 loc) • 7.82 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const sdk_1 = require("@cto.ai/sdk"); const debug_1 = tslib_1.__importDefault(require("debug")); const JSONStream_1 = tslib_1.__importDefault(require("JSONStream")); const through2_1 = tslib_1.__importDefault(require("through2")); const path = tslib_1.__importStar(require("path")); const fs = tslib_1.__importStar(require("fs-extra")); const CustomErrors_1 = require("../errors/CustomErrors"); const Error_1 = require("./Error"); const get_docker_1 = tslib_1.__importDefault(require("../utils/get-docker")); const debug = 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 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 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 ${sdk_1.ux.colors.dim(op.name)} from registry...\n`); const parser = await this.setParser(op, this.getProgressBarText); await new Promise(this.updateStatusBar(stream, parser)); sdk_1.ux.spinner.stop(sdk_1.ux.colors.green('Done!')); const msg = `${sdk_1.ux.colors.italic.bold(`${op.name}:${op.id}`)}`; this.log(`\nšŸ™Œ Saved ${msg} locally! \n`); }; this.setParser = (op, getFn) => { const bar = sdk_1.ux.progress.init({ format: 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()) .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 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) => { try { const dockerfileExists = this.checkIfDockerfileExists(opPath); if (!dockerfileExists) { throw new Error(`Unable to build an image for this op. If you are inside your op directory, please run ${sdk_1.ux.colors.green('$')} ${sdk_1.ux.colors.italic.dim('ops run .')} or ${sdk_1.ux.colors.green('$')} ${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); }; await new Promise(async function (resolve, reject) { const docker = await get_docker_1.default(console, 'build'); if (docker) { const stream = await docker .buildImage({ context: opPath, src: op.src }, { t: tag, pull: true }) .catch(err => { debug('%O', err); throw new CustomErrors_1.DockerBuildImageError(err); }); if (stream) { stream .pipe(JSONStream_1.default.parse()) .pipe(parser) .on('data', (d, data) => { all.push(d); }) .on('end', async function () { if (errors.length) { return reject(new CustomErrors_1.DockerBuildImageError(errors[0])); } log(`\nšŸ’» Run ${sdk_1.ux.colors.green('$')} ${sdk_1.ux.colors.italic.dim('ops run ' + op.name)} to test your op.`); log(`šŸ“¦ Run ${sdk_1.ux.colors.green('$')} ${sdk_1.ux.colors.italic.dim('ops publish ' + opPath)} to share your op. \n`); resolve(); }); } } }); } catch (err) { debug('%O', err); this.error.handleError({ err }); } }; } } exports.ImageService = ImageService;