deployment-engine/dist/apis/docker.js

226 lines
7.0 KiB
JavaScript
Raw Normal View History

2023-03-12 08:48:59 -04:00
import Docker from "dockerode";
import { writeFileSync } from "fs";
import * as process from "process";
import rimraf from "rimraf";
import * as tmp from 'tmp';
import getPort, { portNumbers } from 'get-port';
import { INTERNAL_IP } from "../config.js";
const docker = new Docker();
export async function createContainer(imageName, containerName) {
const port = await getPort({ host: INTERNAL_IP, port: portNumbers(52300, 52399) });
if (await isContainerNameInUse(containerName)) {
await stopAndRemoveContainerByName(containerName);
}
const containerOptions = {
Image: imageName,
name: containerName,
WorkingDir: '/app',
HostConfig: {
AutoRemove: false,
RestartPolicy: {
Name: "always"
},
PortBindings: {
'3000/tcp': [{ HostPort: '' + port }]
}
},
};
try {
const container = await docker.createContainer(containerOptions);
await container.start();
console.log("Started", containerName, "with port", port, '> 3000');
await checkContainerStatus(container.id);
return {
id: container.id,
port
};
}
catch (err) {
console.error("Error creating container:", err);
throw err;
}
}
export async function listContainers() {
return (await docker.listContainers())
.map(v => v.Names[0])
.filter((full) => {
return full.startsWith('deploy-');
});
}
export async function getContainerInfo(containerId) {
const docker = new Docker();
const container = docker.getContainer(containerId);
const containerInfo = await container.inspect();
return containerInfo;
}
export async function checkImageExists(imageName) {
const images = await docker.listImages();
const imageExists = images.some(image => {
if (image.RepoTags) {
return image.RepoTags.includes(imageName);
}
else {
return false;
}
});
return imageExists;
}
export async function pullImage(imageName) {
const imageExists = await checkImageExists(imageName);
if (!imageExists) {
console.log(`Pulling image ${imageName}`);
return new Promise((resolve, reject) => {
docker.pull(imageName, (err, stream) => {
if (err) {
reject(err);
}
docker.modem.followProgress(stream, (err, output) => {
if (err) {
reject(err);
}
console.log(`Image ${imageName} has been pulled`);
resolve();
});
});
});
}
else {
console.log(`Image ${imageName} already exists`);
return Promise.resolve();
}
}
export async function attachLogs(containerId) {
const container = docker.getContainer(containerId);
const logsStream = await container.logs({
follow: true,
stdout: true,
stderr: true,
});
return logsStream;
}
export async function isContainerNameInUse(name) {
const containers = await docker.listContainers({ all: true });
return containers.some((container) => container.Names.includes(`/${name}`));
}
async function stopAndRemoveContainerByName(name) {
const containers = await docker.listContainers({ all: true });
const matchingContainers = containers.filter(container => container.Names.includes(`/${name}`));
if (matchingContainers.length === 0) {
throw new Error(`No container with name '${name}' was found`);
}
const stoppedContainer = docker.getContainer(matchingContainers[0].Id);
try {
await stoppedContainer.stop();
}
catch (error) {
console.error(`Failed to stop container ${name}: ${error.message}`);
}
try {
await stoppedContainer.remove();
console.log(`Container ${name} removed`);
}
catch (error) {
console.error(`Failed to remove container ${name}: ${error.message}`);
}
}
async function checkContainerStatus(containerId) {
const container = docker.getContainer(containerId);
const containerInfo = await container.inspect();
const status = containerInfo.State.Status;
console.log(`Container status: ${status}`);
return status;
}
export async function runCommandInContainer(containerId, command) {
console.log('$', ...command);
const container = await docker.getContainer(containerId);
const exec = await container.exec({
Cmd: command,
AttachStdout: true,
AttachStderr: true,
});
const stream = await exec.start({});
await new Promise((resolve, reject) => {
container.modem.demuxStream(stream, process.stdout, process.stderr);
stream.on('end', resolve);
stream.on('error', reject);
});
}
export async function restartContainer(containerId) {
const docker = new Docker();
const container = await docker.getContainer(containerId);
console.log(`Restarting container ${containerId}...`);
await container.restart();
await checkContainerStatus(containerId);
}
export async function stopContainer(id) {
const docker = new Docker();
const container = await docker.getContainer(id);
await container.stop();
await checkContainerStatus(id);
}
export async function startContainer(id) {
const docker = new Docker();
const container = await docker.getContainer(id);
await container.start();
await checkContainerStatus(id);
}
function createDockerfile(cloneUrl, branch) {
return `
FROM node:lts-alpine3.17
RUN apk add git
WORKDIR /app
RUN git clone "${cloneUrl}" /app --depth 1 -b ${branch}
RUN yarn && yarn build
EXPOSE 3000
CMD yarn start`.trim();
}
export async function buildDockerImage(options) {
const { name: context } = tmp.dirSync();
const { user, name, branch, cloneUrl } = options;
const imageName = `${user}/${name}:${branch}`;
let id = null;
try {
writeFileSync(context + '/Dockerfile', createDockerfile(cloneUrl, branch));
const stream = await docker.buildImage({
src: ['Dockerfile'],
context,
}, {
//@ts-ignore
nocache: true,
t: imageName
});
//@ts-ignore
stream.on('data', d => {
const data = JSON.parse(d);
const text = data.stream ?? '';
process.stdout.write(text);
if (text.startsWith("Successfully built")) {
const potentialId = text.split(" ")[2];
id = potentialId;
}
});
await new Promise((resolve) => {
//@ts-ignore
docker.modem.followProgress(stream, () => {
resolve(void 0);
});
});
if (id === null)
throw new Error("Unable to generate image " + imageName);
console.log("Built Image", imageName);
}
catch (e) {
console.log('do we get here?');
await rimraf(context);
throw e;
}
await rimraf(context);
return imageName;
}