UNPKG

@enspirit/emb

Version:

A replacement for our Makefile-for-monorepos

121 lines (120 loc) 4.47 kB
import { taskManagerFactory } from '../../../index.js'; import { join } from 'node:path/posix'; import { Transform } from 'node:stream'; import * as z from 'zod'; import { ResourceFactory } from '../../../monorepo/resources/ResourceFactory.js'; import { AbstractOperation } from '../../../operations/index.js'; /** * https://docs.docker.com/reference/api/engine/version/v1.37/#tag/Image/operation/ImagePush */ const schema = z.object({ images: z .array(z.string()) .optional() .describe('The names of images to push (The name should be provided without tag. Use the `tag` parameter to specify which tag to push)'), tag: z.string().optional().describe('Tag of the images to push'), registry: z.string().optional().describe('Override the registry to push to'), retag: z .string() .optional() .describe('Override the original tag to push as a new tag'), }); export class PushImagesOperation extends AbstractOperation { out; constructor(out) { super(schema); this.out = out; } async _run(input) { const { monorepo } = this.context; const references = await Promise.all(monorepo.resources .filter((r) => r.type === 'docker/image') .map(async (config) => { const component = monorepo.component(config.component); const builder = ResourceFactory.factor(config.type, { config, monorepo, component, }); return builder.getReference(); })); const manager = taskManagerFactory(); const tasks = references.map((fullName) => { return { title: `Push ${fullName}`, task: async (ctx, task) => { const { imgName, tag } = await this.retagIfNecessary(fullName, input.retag, input.registry); task.title = `Pushing ${imgName}:${tag}`; return this.pushImage(imgName, tag, task.stdout()); }, }; }); return manager.run([ { title: 'Push imags', async task(ctx, task) { return task.newListr(tasks, { rendererOptions: { collapseSubtasks: false, collapseSkips: true, }, }); }, }, ]); } async retagIfNecessary(fullName, retag, registry) { let [imgName, tag] = fullName.split(':'); // Retag if necessary if (retag || registry) { const dockerImage = await this.context.docker.getImage(fullName); tag = retag || tag; imgName = registry ? join(registry, imgName) : imgName; await dockerImage.tag({ tag, repo: imgName, }); } return { imgName, tag }; } async pushImage(repo, tag, out) { const dockerImage = await this.context.docker.getImage(`${repo}:${tag}`); const stream = await dockerImage.push({ authconfig: { username: process.env.DOCKER_USERNAME, password: process.env.DOCKER_PASSWORD, }, }); const transform = new Transform({ transform(chunk, encoding, callback) { const lines = chunk.toString().split('\n'); lines.forEach((line) => { if (!line.trim()) { return; } try { const { status } = JSON.parse(line.trim()); out?.write(status + '\n'); } catch (error) { out?.write(error + '\n'); } }); callback(); }, }); stream.pipe(transform).pipe(process.stdout); await new Promise((resolve, reject) => { this.context.docker.modem.followProgress(stream, (err, data) => { if (err) { return reject(err); } const hasError = data.find((d) => Boolean(d.error)); if (hasError) { return reject(new Error(hasError.error)); } resolve(null); }); }); } }