diff --git a/config/ClientsSharedConfig.ts b/config/ClientsSharedConfig.ts index c8fe801ff4fd90027248e224acf82a15dddd5ea3..d509a4dd26a15981d959cc03569edb213e708c3b 100644 --- a/config/ClientsSharedConfig.ts +++ b/config/ClientsSharedConfig.ts @@ -13,7 +13,7 @@ interface ClientsConfig { class ClientsSharedConfig { - private static config: ClientsSharedConfig | undefined = undefined; + private static readonly config: ClientsSharedConfig | undefined = undefined; public apiURL!: string; @@ -121,8 +121,8 @@ class ClientsSharedConfig { this.assignment = { filename : getEnvVar('ASSIGNMENT_FILENAME', ''), neededFiles: JSON.parse(getEnvVar('EXERCISE_NEEDED_FILES', '[]')), - name : process.env.DOJO_ASSIGNMENT_NAME || '', - secret : process.env.DOJO_ASSIGNMENT_SECRET || '' + name : process.env.DOJO_ASSIGNMENT_NAME ?? '', + secret : process.env.DOJO_ASSIGNMENT_SECRET ?? '' }; this.dockerCompose = { diff --git a/helpers/Dojo/AssignmentValidator.ts b/helpers/Dojo/AssignmentValidator.ts index d831a1c530275e603b787f89a1df221af294cfb1..5e28dccb75de417a8cd0eeee6f1c3afdc6c4dea0 100644 --- a/helpers/Dojo/AssignmentValidator.ts +++ b/helpers/Dojo/AssignmentValidator.ts @@ -337,7 +337,7 @@ class AssignmentValidator { this.newStep('ASSIGNMENT_RUN', 'Please wait while we are running the assignment...'); - const exerciseDockerCompose = new ExerciseDockerCompose(ClientsSharedConfig.dockerCompose.projectName, this.assignmentFile, this.folderAssignment); + const exerciseDockerCompose = new ExerciseDockerCompose(ClientsSharedConfig.dockerCompose.projectName, this.assignmentFile, this.folderAssignment, this.doDown); try { await new Promise<void>((resolve, reject) => { @@ -357,7 +357,7 @@ class AssignmentValidator { exitCode !== 0 ? resolve() : reject(exitCode); }); - exerciseDockerCompose.run(this.doDown); + exerciseDockerCompose.run(); }); } catch ( error ) { this.fatalErrorMessage = 'Assignment is already solved'; diff --git a/helpers/Dojo/ExerciseDockerCompose.ts b/helpers/Dojo/ExerciseDockerCompose.ts index d06489d1d2d27fbedf224c8f543d3bbd09bb0f98..c72eb80b5f90485439b578482adaf1f854618417 100644 --- a/helpers/Dojo/ExerciseDockerCompose.ts +++ b/helpers/Dojo/ExerciseDockerCompose.ts @@ -16,6 +16,11 @@ class ExerciseDockerCompose { public success: boolean = false; public exitCode: number = -1; + private containerExitCode: number = -1; + + private readonly filesOverrideArguments: string; + private readonly dockerComposeCommand: string; + private currentStep: string = 'NOT_RUNNING'; private readonly projectName: string; @@ -23,12 +28,19 @@ class ExerciseDockerCompose { private readonly executionFolder: string; private readonly composeFileOverride: Array<string> = []; - constructor(projectName: string, assignmentFile: AssignmentFile, executionFolder: string, composeFileOverride: Array<string> = []) { + private readonly doDown: boolean; + + constructor(projectName: string, assignmentFile: AssignmentFile, executionFolder: string, doDown: boolean = false, composeFileOverride: Array<string> = []) { this.projectName = projectName; this.assignmentFile = assignmentFile; this.executionFolder = executionFolder; this.composeFileOverride = composeFileOverride; + this.filesOverrideArguments = this.composeFileOverride.map(file => `--file "${ file }"`).join(' '); + this.dockerComposeCommand = `docker compose --project-name ${ this.projectName } --progress plain --file docker-compose.yml ${ this.filesOverrideArguments }`; + + this.doDown = doDown; + this.events.on('logs', (log: string, _error: boolean, displayable: boolean) => { this.allLogs += log; this.displayableLogs += displayable ? log : ''; @@ -74,118 +86,135 @@ class ExerciseDockerCompose { }); } - run(doDown: boolean = false) { - (async () => { - let containerExitCode: number = -1; - - const filesOverrideArguments = this.composeFileOverride.map(file => `--file "${ file }"`).join(' '); - const dockerComposeCommand = `docker compose --project-name ${ this.projectName } --progress plain --file docker-compose.yml ${ filesOverrideArguments }`; - - // Run the service - { - try { - this.newStep('COMPOSE_RUN', 'Running Docker Compose file'); - - containerExitCode = await new Promise<number>((resolve, reject) => { + /** + * Step 1: Run the docker compose file + * @private + */ + private async serviceRun() { + try { + this.newStep('COMPOSE_RUN', 'Running Docker Compose file'); - this.log('####################################################### Docker Compose & Main Container Logs #######################################################\n', false, false); + this.containerExitCode = await new Promise<number>((resolve, reject) => { - const dockerCompose = spawn(`${ dockerComposeCommand } run --build --rm ${ this.assignmentFile.result.container }`, { - cwd : this.executionFolder, - shell: true, - env : { - 'DOCKER_BUILDKIT' : '1', - 'BUILDKIT_PROGRESS': 'plain', ...process.env - } - }); + this.log('####################################################### Docker Compose & Main Container Logs #######################################################\n', false, false); - this.registerChildProcess(dockerCompose, resolve, reject, true, false); - }); - } catch ( error ) { - this.endStep(`Error while running the docker compose file`, true); - this.finished(false, ExerciseCheckerError.DOCKER_COMPOSE_RUN_ERROR); - return; - } - this.endStep(`Docker Compose file run successfully`, false); - } + const dockerCompose = spawn(`${ this.dockerComposeCommand } run --build --rm ${ this.assignmentFile.result.container }`, { + cwd : this.executionFolder, + shell: true, + env : { + 'DOCKER_BUILDKIT' : '1', + 'BUILDKIT_PROGRESS': 'plain', ...process.env + } + }); + + this.registerChildProcess(dockerCompose, resolve, reject, true, false); + }); + } catch ( error ) { + this.endStep(`Error while running the docker compose file`, true); + this.finished(false, ExerciseCheckerError.DOCKER_COMPOSE_RUN_ERROR); + throw error; + } + this.endStep(`Docker Compose file run successfully`, false); + } - // Get linked services logs - { - try { - this.newStep('COMPOSE_LOGS', 'Linked services logs acquisition'); + /** + * Step 2: Get the logs of the linked services + * @private + */ + private async getLogs() { + try { + this.newStep('COMPOSE_LOGS', 'Linked services logs acquisition'); + + await new Promise<number>((resolve, reject) => { + + this.log('####################################################### Other Services Logs #######################################################\n', false, false); + + const dockerCompose = spawn(`${ this.dockerComposeCommand } logs --timestamps`, { + cwd : this.executionFolder, + shell: true + }); + + this.registerChildProcess(dockerCompose, resolve, reject, false, true); + }); + } catch ( error ) { + this.endStep(`Error while getting the linked services logs`, true); + this.finished(false, ExerciseCheckerError.DOCKER_COMPOSE_LOGS_ERROR); + throw error; + } + this.endStep(`Linked services logs acquired`, false); + } - await new Promise<number>((resolve, reject) => { + /** + * Step 3: Remove the containers if asked + * @private + */ + private async removeContainers() { + if ( this.doDown ) { + try { + this.newStep('COMPOSE_DOWN', 'Stopping and removing containers'); - this.log('####################################################### Other Services Logs #######################################################\n', false, false); + await new Promise<number>((resolve, reject) => { - const dockerCompose = spawn(`${ dockerComposeCommand } logs --timestamps`, { - cwd : this.executionFolder, - shell: true - }); + this.log('####################################################### Stop and remove containers #######################################################\n', false, false); - this.registerChildProcess(dockerCompose, resolve, reject, false, true); + const dockerCompose = spawn(`${ this.dockerComposeCommand } down --volumes`, { + cwd : this.executionFolder, + shell: true }); - } catch ( error ) { - this.endStep(`Error while getting the linked services logs`, true); - this.finished(false, ExerciseCheckerError.DOCKER_COMPOSE_LOGS_ERROR); - return; - } - this.endStep(`Linked services logs acquired`, false); + + this.registerChildProcess(dockerCompose, resolve, reject, false, true); + }); + } catch ( error ) { + this.endStep(`Error while stopping and removing containers`, true); + this.finished(false, ExerciseCheckerError.DOCKER_COMPOSE_DOWN_ERROR); + throw error; } + this.endStep(`Containers stopped and removed`, false); + } + } - // Remove containers if asked - { - if ( doDown ) { - try { - this.newStep('COMPOSE_DOWN', 'Stopping and removing containers'); + /** + * Step 4: Remove the dangling images if asked + * @private + */ + private async removeImages() { + if ( this.doDown ) { + try { + this.newStep('COMPOSE_REMOVE_DANGLING', 'Removing dangling images'); - await new Promise<number>((resolve, reject) => { + await new Promise<number>((resolve, reject) => { - this.log('####################################################### Stop and remove containers #######################################################\n', false, false); + this.log('####################################################### Remove dangling images #######################################################\n', false, false); - const dockerCompose = spawn(`${ dockerComposeCommand } down --volumes`, { - cwd : this.executionFolder, - shell: true - }); + const dockerCompose = spawn(`docker image prune --force`, { + cwd : this.executionFolder, + shell: true + }); - this.registerChildProcess(dockerCompose, resolve, reject, false, true); - }); - } catch ( error ) { - this.endStep(`Error while stopping and removing containers`, true); - this.finished(false, ExerciseCheckerError.DOCKER_COMPOSE_DOWN_ERROR); - return; - } - this.endStep(`Containers stopped and removed`, false); - } + this.registerChildProcess(dockerCompose, resolve, reject, false, true); + }); + } catch ( error ) { + this.endStep(`Error while removing dangling images`, true); + this.finished(false, ExerciseCheckerError.DOCKER_COMPOSE_REMOVE_DANGLING_ERROR); + throw error; } + this.endStep(`Dangling images removed`, false); + } + } - // Remove images if asked - { - if ( doDown ) { - try { - this.newStep('COMPOSE_REMOVE_DANGLING', 'Removing dangling images'); - - await new Promise<number>((resolve, reject) => { - - this.log('####################################################### Remove dangling images #######################################################\n', false, false); - - const dockerCompose = spawn(`docker image prune --force`, { - cwd : this.executionFolder, - shell: true - }); + run() { + (async () => { - this.registerChildProcess(dockerCompose, resolve, reject, false, true); - }); - } catch ( error ) { - this.endStep(`Error while removing dangling images`, true); - this.finished(false, ExerciseCheckerError.DOCKER_COMPOSE_REMOVE_DANGLING_ERROR); - return; - } - this.endStep(`Dangling images removed`, false); - } + try { + await this.serviceRun(); + await this.getLogs(); + await this.removeContainers(); + await this.removeImages(); + } catch ( error ) { + return; } - this.finished(true, containerExitCode); + this.finished(true, this.containerExitCode); })(); } }