2024-03-05 18:15:30 +00:00
|
|
|
import Docker from 'dockerode';
|
2022-08-25 21:15:48 +00:00
|
|
|
import * as tar from 'tar-stream';
|
2022-09-08 02:25:10 +00:00
|
|
|
import { strict as assert } from 'assert';
|
2022-11-28 22:40:20 +00:00
|
|
|
import { setTimeout } from 'timers/promises';
|
2022-09-08 02:25:10 +00:00
|
|
|
|
2022-10-31 21:31:43 +00:00
|
|
|
import { isStatusError } from '~/lib/errors';
|
|
|
|
|
2022-08-25 21:15:48 +00:00
|
|
|
// Creates an image from scratch with just some labels
|
|
|
|
export async function createDockerImage(
|
|
|
|
name: string,
|
|
|
|
labels: [string, ...string[]],
|
|
|
|
docker = new Docker(),
|
2022-10-19 15:05:52 +00:00
|
|
|
extra = [] as string[], // Additional instructions to add to the dockerfile
|
2022-09-08 02:25:10 +00:00
|
|
|
): Promise<string> {
|
2022-08-25 21:15:48 +00:00
|
|
|
const pack = tar.pack(); // pack is a streams2 stream
|
|
|
|
pack.entry(
|
|
|
|
{ name: 'Dockerfile' },
|
2022-10-19 15:05:52 +00:00
|
|
|
['FROM scratch']
|
|
|
|
.concat(labels.map((l) => `LABEL ${l}`))
|
|
|
|
.concat(extra)
|
|
|
|
.join('\n'),
|
2022-08-25 21:15:48 +00:00
|
|
|
(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) => {
|
2022-09-08 02:25:10 +00:00
|
|
|
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]);
|
|
|
|
});
|
2022-08-25 21:15:48 +00:00
|
|
|
});
|
|
|
|
}
|
2022-10-31 21:31:43 +00:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-28 22:40:20 +00:00
|
|
|
// Wait until containers are all removed
|
2024-02-29 22:00:39 +00:00
|
|
|
while ((await docker.listContainers({ all: true })).length > 0) {
|
|
|
|
await setTimeout(100);
|
2022-11-28 22:40:20 +00:00
|
|
|
}
|
|
|
|
|
2022-10-31 21:31:43 +00:00
|
|
|
// 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 } } });
|
|
|
|
};
|