import * as Docker from 'dockerode'; import * as tar from 'tar-stream'; import { strict as assert } from 'assert'; import { setTimeout } from 'timers/promises'; import { isStatusError } from '~/lib/errors'; // Creates an image from scratch with just some labels export async function createDockerImage( name: string, labels: [string, ...string[]], docker = new Docker(), extra = [] as string[], // Additional instructions to add to the dockerfile ): Promise { const pack = tar.pack(); // pack is a streams2 stream pack.entry( { name: 'Dockerfile' }, ['FROM scratch'] .concat(labels.map((l) => `LABEL ${l}`)) .concat(extra) .join('\n'), (err) => { if (err) { throw err; } pack.finalize(); }, ); // Create an empty image const stream = await docker.buildImage(pack, { t: name }); return await new Promise((resolve, reject) => { docker.modem.followProgress(stream, (err: any, res: any) => { if (err) { reject(err); } const ids = res .map((evt: any) => evt?.aux?.ID ?? null) .filter((id: string | null) => !!id); assert(ids.length > 0, 'expected at least an image id after building'); resolve(ids[ids.length - 1]); }); }); } // Clean up all Docker relics from tests export const cleanupDocker = async (docker = new Docker()) => { // Remove all containers // Some containers may still be running so a prune won't suffice try { const containers = await docker.listContainers({ all: true }); await Promise.all( containers.map(({ Id }) => docker.getContainer(Id).remove({ force: true }), ), ); } catch (e: unknown) { // Sometimes a container is already in the process of being removed // This is safe to ignore since we're removing them anyway. if (isStatusError(e) && e.statusCode !== 409) { throw e; } } // Wait until containers are all removed while (true) { if ((await docker.listContainers({ all: true })).length > 0) { await setTimeout(100); } else { break; } } // Remove all networks except defaults const networks = await docker.listNetworks(); await Promise.all( networks .filter(({ Name }) => !['bridge', 'host', 'none'].includes(Name)) // exclude docker default network from the cleanup .map(({ Name }) => docker.getNetwork(Name).remove()), ); // Remove all volumes const { Volumes } = await docker.listVolumes(); await Promise.all(Volumes.map(({ Name }) => docker.getVolume(Name).remove())); // Remove all images await docker.pruneImages({ filters: { dangling: { false: true } } }); };