balena-supervisor/test/lib/docker-helper.ts

90 lines
2.5 KiB
TypeScript
Raw Normal View History

import * as Docker from 'dockerode';
import * as tar from 'tar-stream';
2022-09-08 02:25:10 +00:00
import { strict as assert } from 'assert';
import { setTimeout } from 'timers/promises';
2022-09-08 02:25:10 +00:00
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
2022-09-08 02:25:10 +00:00
): Promise<string> {
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) => {
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]);
});
});
}
// 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 } } });
};