diff --git a/helpers/Dojo/ExerciceDockerCompose.ts b/helpers/Dojo/ExerciceDockerCompose.ts new file mode 100644 index 0000000000000000000000000000000000000000..bb873671189f394d2b9cbce62be61c97773b4d24 --- /dev/null +++ b/helpers/Dojo/ExerciceDockerCompose.ts @@ -0,0 +1,151 @@ +import EnonceFile from '../../../shared/types/Dojo/EnonceFile'; +import { TypedEmitter } from 'tiny-typed-emitter'; +import ExerciceRunningEvents from '../../types/Dojo/ExerciceRunningEvents'; +import { spawn } from 'child_process'; +import ExerciceCheckerError from '../../../shared/types/Dojo/ExerciceCheckerError'; + + +class ExerciceDockerCompose { + readonly events: TypedEmitter<ExerciceRunningEvents> = new TypedEmitter<ExerciceRunningEvents>(); + + public displayableLogs: string = ''; + public allLogs: string = ''; + + public isFinished: boolean = false; + public success: boolean = false; + public exitCode: number = -1; + + constructor(private projectName: string, private enonceFile: EnonceFile, private executionFolder: string, private composeFileOverride: Array<string> = []) { + this.events.on('logs', (log: string, _error: boolean, displayable: boolean) => { + this.allLogs += log; + this.displayableLogs += displayable ? log : ''; + }); + + this.events.on('finished', (success: boolean, exitCode: number) => { + this.isFinished = true; + this.success = success; + this.exitCode = exitCode; + }); + } + + run(doDown: boolean = false) { + (async () => { + let containerExitCode: number = -1; + + const dockerComposeCommand = `docker compose --project-name ${ this.projectName } --progress plain --file docker-compose.yml ${ this.composeFileOverride.map((file) => `--file ${ file }`).join(' ') }`; + + // Run the service + { + try { + this.events.emit('step', 'COMPOSE_RUN', 'Run Docker Compose file'); + + containerExitCode = await new Promise<number>((resolve, reject) => { + + this.events.emit('logs', '####################################################### Docker Compose & Main Container Logs #######################################################\n', false, false); + + const dockerCompose = spawn(`${ dockerComposeCommand } run --build --rm ${ this.enonceFile.result.container }`, { + cwd : this.executionFolder, + shell: true, + env : { + 'DOCKER_BUILDKIT' : '1', + 'BUILDKIT_PROGRESS': 'plain', ...process.env + } + }); + + dockerCompose.stdout.on('data', (data) => { + this.events.emit('logs', data.toString(), false, true); + }); + + dockerCompose.stderr.on('data', (data) => { + this.events.emit('logs', data.toString(), true, true); + }); + + dockerCompose.on('exit', (code) => { + code !== null ? resolve(code) : reject(); + }); + }); + } catch ( error ) { + this.events.emit('endStep', 'COMPOSE_RUN', `Error while running the docker compose file`, true); + this.events.emit('finished', false, ExerciceCheckerError.DOCKER_COMPOSE_RUN_ERROR); + return; + } + this.events.emit('endStep', 'COMPOSE_RUN', `Docker Compose file run successfully`, false); + } + + // Get linked services logs + { + try { + this.events.emit('step', 'COMPOSE_LOGS', 'Logs acquisition of linked services'); + + await new Promise<void>((resolve, reject) => { + + this.events.emit('logs', '####################################################### Other Services Logs #######################################################\n', false, false); + + const dockerCompose = spawn(`${ dockerComposeCommand } logs --timestamps`, { + cwd : this.executionFolder, + shell: true + }); + + dockerCompose.stdout.on('data', (data) => { + this.events.emit('logs', data.toString(), false, false); + }); + + dockerCompose.stderr.on('data', (data) => { + this.events.emit('logs', data.toString(), true, false); + }); + + dockerCompose.on('exit', (code) => { + code !== null ? resolve() : reject(); + }); + }); + } catch ( error ) { + this.events.emit('endStep', 'COMPOSE_LOGS', `Error while getting the linked services logs`, true); + this.events.emit('finished', false, ExerciceCheckerError.DOCKER_COMPOSE_LOGS_ERROR); + return; + } + this.events.emit('endStep', 'COMPOSE_LOGS', `Linked services logs acquired`, false); + } + + // Remove containers if asked + { + if ( doDown ) { + try { + this.events.emit('step', 'COMPOSE_DOWN', 'Stopping and removing containers'); + + await new Promise<void>((resolve, reject) => { + + this.events.emit('logs', '####################################################### Stop and remove containers #######################################################\n', false, false); + + const dockerCompose = spawn(`${ dockerComposeCommand } down --volumes`, { + cwd : this.executionFolder, + shell: true + }); + + dockerCompose.stdout.on('data', (data) => { + this.events.emit('logs', data.toString(), false, false); + }); + + dockerCompose.stderr.on('data', (data) => { + this.events.emit('logs', data.toString(), true, false); + }); + + dockerCompose.on('exit', (code) => { + code !== null ? resolve() : reject(); + }); + }); + } catch ( error ) { + this.events.emit('endStep', 'COMPOSE_DOWN', `Error stop and remove containers`, true); + this.events.emit('finished', false, ExerciceCheckerError.DOCKER_COMPOSE_DOWN_ERROR); + return; + } + this.events.emit('endStep', 'COMPOSE_DOWN', `Containers stopped and removed`, false); + } + } + + this.events.emit('finished', true, containerExitCode); + })(); + } +} + + +export default ExerciceDockerCompose; \ No newline at end of file