1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const tslib_1 = require("tslib");
|
4 | const sdk_1 = require("@cto.ai/sdk");
|
5 | const debug_1 = tslib_1.__importDefault(require("debug"));
|
6 | const JSONStream_1 = tslib_1.__importDefault(require("JSONStream"));
|
7 | const through2_1 = tslib_1.__importDefault(require("through2"));
|
8 | const path = tslib_1.__importStar(require("path"));
|
9 | const fs = tslib_1.__importStar(require("fs-extra"));
|
10 | const CustomErrors_1 = require("../errors/CustomErrors");
|
11 | const Error_1 = require("./Error");
|
12 | const get_docker_1 = tslib_1.__importDefault(require("../utils/get-docker"));
|
13 | const debug = debug_1.default('ops:ImageService');
|
14 | class ImageService {
|
15 | constructor(error = new Error_1.ErrorService()) {
|
16 | this.error = error;
|
17 | this.log = console.log;
|
18 | this.checkLocalImage = async (opImageUrl) => {
|
19 | const docker = await get_docker_1.default(console, 'ImageService');
|
20 | const list = await docker.listImages();
|
21 | return list
|
22 | .map(this.imageFilterPredicate(opImageUrl))
|
23 | .find((repoTag) => !!repoTag);
|
24 | };
|
25 | this.imageFilterPredicate = (repo) => ({ RepoTags }) => {
|
26 | if (!RepoTags) {
|
27 | return;
|
28 | }
|
29 | return RepoTags.find((repoTag) => repoTag.includes(repo));
|
30 | };
|
31 | this.pull = async (op, authconfig) => {
|
32 | this.log(`š Pulling ${sdk_1.ux.colors.dim(op.name)} from registry...\n`);
|
33 | const docker = await get_docker_1.default(console, 'ImageServicePull');
|
34 | const stream = await docker.pull(op.image || '', { authconfig });
|
35 | if (!stream) {
|
36 | throw new Error('No stream');
|
37 | }
|
38 | const parser = await this.setParser(op, this.getProgressBarText);
|
39 | await new Promise(this.updateStatusBar(stream, parser));
|
40 | sdk_1.ux.spinner.stop(sdk_1.ux.colors.green('Done!'));
|
41 | const msg = `${sdk_1.ux.colors.italic.bold(`${op.name}:${op.id}`)}`;
|
42 | this.log(`\nš Saved ${msg} locally! \n`);
|
43 | };
|
44 | this.setParser = (op, getFn) => {
|
45 | const bar = sdk_1.ux.progress.init({
|
46 | format: sdk_1.ux.colors.callOutCyan('{bar} {percentage}% | Status: {speed} '),
|
47 | barCompleteChar: '\u2588',
|
48 | barIncompleteChar: '\u2591',
|
49 | });
|
50 | bar.start(100, 0, { speed: 'š Starting...' });
|
51 | const layers = {};
|
52 | const parser = through2_1.default.obj((chunk, _enc, callback) => {
|
53 | const { id, status, progressDetail: p } = chunk;
|
54 | const progress = p && p.current ? (p.current / p.total) * 100 : 0;
|
55 | const { speed } = getFn(status, op);
|
56 | if (id) {
|
57 | layers[id] = id;
|
58 | }
|
59 | if (speed) {
|
60 | bar.update(progress, { speed });
|
61 | }
|
62 | callback();
|
63 | });
|
64 | const _pipe = parser.pipe;
|
65 | parser.pipe = dest => _pipe(dest);
|
66 | return { parser, bar };
|
67 | };
|
68 | this.getProgressBarText = (status, { name }) => {
|
69 | const mapping = {
|
70 | [`Pulling from ${name}`]: `ā
Pulling from ${name}...`,
|
71 | 'Already exists': 'ā
Already exists!',
|
72 | Waiting: 'ā± Waiting...',
|
73 | Downloading: 'š Downloading...',
|
74 | 'Download complete': 'š Downloaded!',
|
75 | Extracting: 'š¦ Unpacking...',
|
76 | 'Pulling fs layer': 'š Pulling layers...',
|
77 | 'Pull complete': 'š Pull Complete!',
|
78 | };
|
79 | return { speed: mapping[status] };
|
80 | };
|
81 | this.updateStatusBar = (stream, { parser, bar }) => async (resolve, reject) => {
|
82 | const allData = [];
|
83 | const size = 100;
|
84 | stream
|
85 | .pipe(JSONStream_1.default.parse())
|
86 | .pipe(parser)
|
87 | .on('data', data => allData.push(data))
|
88 | .on('end', async (err) => {
|
89 | for (let i = 0; i < size; i++) {
|
90 | bar.update(100 - size / i);
|
91 | await sdk_1.ux.wait(5);
|
92 | }
|
93 | bar.update(100);
|
94 | bar.stop();
|
95 | debug('%O', err);
|
96 | return err ? reject(err) : resolve(allData);
|
97 | });
|
98 | };
|
99 | this.checkIfDockerfileExists = (opPath) => {
|
100 | const pathToDockerfile = path.join(path.resolve(opPath), 'Dockerfile');
|
101 | return fs.existsSync(pathToDockerfile);
|
102 | };
|
103 | this.build = async (tag, opPath, op) => {
|
104 | try {
|
105 | const dockerfileExists = this.checkIfDockerfileExists(opPath);
|
106 | if (!dockerfileExists) {
|
107 | 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`);
|
108 | }
|
109 | const all = [];
|
110 | const errors = [];
|
111 | const log = this.log;
|
112 | const parser = through2_1.default.obj(function (chunk, _enc, cb) {
|
113 | if (chunk.stream && chunk.stream !== '\n') {
|
114 | this.push(chunk.stream.replace('\n', ''));
|
115 | log(chunk.stream.replace('\n', ''));
|
116 | all.push(chunk);
|
117 | }
|
118 | else if (chunk.errorDetail) {
|
119 | debug(chunk.errorDetail);
|
120 | errors.push(chunk.errorDetail.message);
|
121 | }
|
122 | cb();
|
123 | });
|
124 | const _pipe = parser.pipe;
|
125 | parser.pipe = function (dest) {
|
126 | return _pipe(dest);
|
127 | };
|
128 | await new Promise(async function (resolve, reject) {
|
129 | const docker = await get_docker_1.default(console, 'build');
|
130 | if (docker) {
|
131 | const stream = await docker
|
132 | .buildImage({ context: opPath, src: op.src }, { t: tag })
|
133 | .catch(err => {
|
134 | debug('%O', err);
|
135 | throw new CustomErrors_1.DockerBuildImageError(err);
|
136 | });
|
137 | if (stream) {
|
138 | stream
|
139 | .pipe(JSONStream_1.default.parse())
|
140 | .pipe(parser)
|
141 | .on('data', (d, data) => {
|
142 | all.push(d);
|
143 | })
|
144 | .on('end', async function () {
|
145 | if (errors.length) {
|
146 | return reject(new CustomErrors_1.DockerBuildImageError(errors[0]));
|
147 | }
|
148 | log('\nā”ļø Verifying...');
|
149 | const bar = sdk_1.ux.progress.init();
|
150 | bar.start(100, 0);
|
151 | for (let i = 0; i < all.length; i++) {
|
152 | bar.update(100 - all.length / i);
|
153 | await sdk_1.ux.wait(50);
|
154 | }
|
155 | bar.update(100);
|
156 | bar.stop();
|
157 | log(`\nš» Run ${sdk_1.ux.colors.green('$')} ${sdk_1.ux.colors.italic.dim('ops run ' + op.name)} to test your op.`);
|
158 | log(`š¦ Run ${sdk_1.ux.colors.green('$')} ${sdk_1.ux.colors.italic.dim('ops publish ' + opPath)} to share your op. \n`);
|
159 | resolve();
|
160 | });
|
161 | }
|
162 | }
|
163 | });
|
164 | }
|
165 | catch (err) {
|
166 | debug('%O', err);
|
167 | this.error.handleError({ err });
|
168 | }
|
169 | };
|
170 | }
|
171 | }
|
172 | exports.ImageService = ImageService;
|