@cto.ai/ops
Version:
š» CTO.ai Ops - The CLI built for Teams š
168 lines (167 loc) ⢠7.82 kB
JavaScript
;
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;