2023-10-23 23:16:24 +02:00
|
|
|
import { execaCommand } from "execa"
|
|
|
|
import ms from "ms"
|
2021-06-09 20:31:45 +02:00
|
|
|
|
2023-10-23 23:16:24 +02:00
|
|
|
import { parseCommandOutput } from "../utils/parseCommandOutput.js"
|
2022-09-22 16:16:21 +02:00
|
|
|
|
2022-08-30 15:48:07 +02:00
|
|
|
export interface DockerRunResult {
|
|
|
|
stdout: string
|
|
|
|
}
|
|
|
|
|
2021-12-07 11:35:47 +01:00
|
|
|
export class Docker {
|
2023-10-23 23:16:24 +02:00
|
|
|
public static readonly CONTAINER_BASE_TAG = "programming-challenges"
|
2022-09-22 16:16:21 +02:00
|
|
|
public static readonly SIGSEGV_EXIT_CODE = 139
|
2023-10-23 23:16:24 +02:00
|
|
|
public static readonly MAXIMUM_TIMEOUT = "1 minute"
|
2022-09-22 16:16:21 +02:00
|
|
|
public static readonly MAXIMUM_TIMEOUT_MILLISECONDS = ms(
|
2023-10-23 23:16:24 +02:00
|
|
|
Docker.MAXIMUM_TIMEOUT,
|
2022-09-22 16:16:21 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
public getContainerTag(id: string): string {
|
|
|
|
return `${Docker.CONTAINER_BASE_TAG}:${id}`
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getImages(): Promise<string[]> {
|
|
|
|
try {
|
|
|
|
const { stdout } = await execaCommand(
|
|
|
|
`docker images -q --filter=reference="${Docker.CONTAINER_BASE_TAG}:*"`,
|
2023-10-23 23:16:24 +02:00
|
|
|
{ shell: true },
|
2022-09-22 16:16:21 +02:00
|
|
|
)
|
|
|
|
return parseCommandOutput(stdout)
|
|
|
|
} catch {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
}
|
2021-11-30 21:42:43 +01:00
|
|
|
|
2022-09-22 16:16:21 +02:00
|
|
|
public async removeImages(): Promise<void> {
|
2021-06-09 20:31:45 +02:00
|
|
|
try {
|
2022-09-22 16:16:21 +02:00
|
|
|
const images = await this.getImages()
|
|
|
|
if (images.length === 0) {
|
|
|
|
return
|
|
|
|
}
|
2023-10-23 23:16:24 +02:00
|
|
|
await execaCommand(`docker rmi -f ${images.join(" ")}`, {
|
|
|
|
shell: true,
|
2022-09-22 16:16:21 +02:00
|
|
|
})
|
|
|
|
} catch {}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async build(id: string): Promise<void> {
|
|
|
|
try {
|
|
|
|
await execaCommand(`docker build --tag=${this.getContainerTag(id)} ./`)
|
|
|
|
} catch (error: any) {
|
|
|
|
throw new Error(`Docker build failed.\n${error.message as string}`)
|
2021-06-09 20:31:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-22 16:16:21 +02:00
|
|
|
public async run(input: string, id: string): Promise<DockerRunResult> {
|
2022-04-23 18:41:14 +02:00
|
|
|
const subprocess = execaCommand(
|
2022-09-22 16:16:21 +02:00
|
|
|
`docker run --interactive --rm ${this.getContainerTag(id)}`,
|
2021-06-09 20:31:45 +02:00
|
|
|
{
|
2023-10-23 23:16:24 +02:00
|
|
|
input,
|
|
|
|
},
|
2021-06-09 20:31:45 +02:00
|
|
|
)
|
2021-12-06 19:04:16 +01:00
|
|
|
try {
|
|
|
|
const { stdout, stderr } = await subprocess
|
2023-07-20 22:00:11 +02:00
|
|
|
if (stderr.length > 0) {
|
2021-12-06 19:04:16 +01:00
|
|
|
throw new Error(stderr)
|
|
|
|
}
|
2022-08-30 15:48:07 +02:00
|
|
|
return {
|
2023-10-23 23:16:24 +02:00
|
|
|
stdout,
|
2022-08-30 15:48:07 +02:00
|
|
|
}
|
2021-12-06 19:04:16 +01:00
|
|
|
} catch (error: any) {
|
|
|
|
if (error.exitCode === Docker.SIGSEGV_EXIT_CODE) {
|
2022-04-24 20:27:51 +02:00
|
|
|
throw new Error(
|
2023-10-23 23:16:24 +02:00
|
|
|
"Docker run failed.\nSIGSEGV indicates a segmentation fault (attempts to access a memory location that it's not allowed to access).",
|
2022-04-24 20:27:51 +02:00
|
|
|
)
|
2021-12-06 19:04:16 +01:00
|
|
|
}
|
2022-09-22 16:16:21 +02:00
|
|
|
throw new Error(`Docker run failed.\n${error.message as string}`)
|
2021-06-09 20:31:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const docker = new Docker()
|