Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • ask-user-to-delete-exercises-on-duplicates
  • jw_sonar
  • jw_sonar_backup
  • main
  • move-to-esm-only
  • open_tool_for_self_hosting
  • v5.0
  • v6.0
  • v4.1
  • v4.2
10 results

Target

Select target project
  • dojo_project/projects/shared/nodeclientsharedcode
1 result
Select Git revision
  • ask-user-to-delete-exercises-on-duplicates
  • jw_sonar
  • jw_sonar_backup
  • main
  • move-to-esm-only
  • open_tool_for_self_hosting
  • v5.0
  • v6.0
  • v4.1
  • v4.2
10 results
Show changes
......@@ -4,7 +4,6 @@ import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssign
import path from 'node:path';
import AssignmentCheckerError from '../../../shared/types/Dojo/AssignmentCheckerError';
import fs from 'fs-extra';
import JSON5 from 'json5';
import ClientsSharedConfig from '../../config/ClientsSharedConfig';
import YAML from 'yaml';
import DojoDockerCompose from '../../types/Dojo/DojoDockerCompose';
......@@ -28,6 +27,9 @@ class AssignmentValidator {
public exitCode: number = -1;
public fatalErrorMessage: string = '';
private currentStep: string = 'NOT_RUNNING';
private currentSubStep: string = 'NOT_RUNNING';
constructor(private folderAssignment: string) {
this.events.on('logs', (log: string, _error: boolean, displayable: boolean) => {
this.allLogs += log;
......@@ -41,19 +43,45 @@ class AssignmentValidator {
});
}
private newStep(name: string, message: string) {
this.currentStep = name;
this.events.emit('step', name, message);
}
private newSubStep(name: string, message: string) {
this.currentSubStep = name;
this.events.emit('subStep', name, message);
}
private endStep(message: string, error: boolean) {
this.events.emit('endStep', this.currentStep, message, error);
}
private endSubStep(message: string, error: boolean) {
this.events.emit('endSubStep', this.currentStep, message, error);
}
private log(message: string, error: boolean, displayable: boolean) {
this.events.emit('logs', message, error, displayable, this.currentStep, this.currentSubStep);
}
private finished(success: boolean, code: number) {
this.events.emit('finished', success, code);
}
private emitError(subStepMessage: string, stepMessage: string, code: AssignmentCheckerError) {
this.fatalErrorMessage = stepMessage;
this.endSubStep(subStepMessage, true);
this.endStep(stepMessage, true);
this.finished(false, code);
}
run(doDown: boolean = false) {
(async () => {
let dockerComposeFile: DojoDockerCompose;
let assignmentFile: AssignmentFile;
const emitError = (subStepName: string, subStepMessage: string, stepName: string, stepMessage: string, code: AssignmentCheckerError) => {
this.fatalErrorMessage = stepMessage;
this.events.emit('endSubStep', subStepName, subStepMessage, true);
this.events.emit('endStep', stepName, stepMessage, true);
this.events.emit('finished', false, code);
};
/*
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 1: Check requirements
......@@ -61,33 +89,33 @@ class AssignmentValidator {
- Check if required files exists
*/
{
this.events.emit('step', 'REQUIREMENTS_CHECKING', 'Please wait while we are checking requirements...');
this.newStep('REQUIREMENTS_CHECKING', 'Please wait while we are checking requirements...');
// Check requirements
this.events.emit('subStep', 'DOCKER_RUNNING', 'Checking if Docker daemon is running');
this.newSubStep('DOCKER_RUNNING', 'Checking if Docker daemon is running');
try {
await execAsync(`cd "${ this.folderAssignment }";docker ps`);
await execAsync(`docker ps`);
} catch ( error ) {
emitError('DOCKER_RUNNING', `Docker daemon isn't running`, 'REQUIREMENTS_CHECKING', `Some requirements are not satisfied.`, AssignmentCheckerError.DOCKER_DAEMON_NOT_RUNNING);
this.emitError(`Docker daemon isn't running`, `Some requirements are not satisfied.`, AssignmentCheckerError.DOCKER_DAEMON_NOT_RUNNING);
return;
}
this.events.emit('endSubStep', 'DOCKER_RUNNING', 'Docker daemon is running', false);
this.endSubStep('Docker daemon is running', false);
// Check if required files exists
this.events.emit('subStep', 'REQUIRED_FILES_EXISTS', 'Checking if required files exists');
this.newSubStep('REQUIRED_FILES_EXISTS', 'Checking if required files exists');
const files = fs.readdirSync(this.folderAssignment);
const missingFiles = ClientsSharedConfig.assignment.neededFiles.map((file: string): [ string, boolean ] => [ file, files.includes(file) ]).filter((file: [ string, boolean ]) => !file[1]);
if ( missingFiles.length > 0 ) {
emitError('REQUIRED_FILES_EXISTS', `The exercise folder is missing the following files: ${ missingFiles.map((file: [ string, boolean ]) => file[0]).join(', ') }`, 'REQUIREMENTS_CHECKING', 'Some requirements are not satisfied', AssignmentCheckerError.REQUIRED_FILES_MISSING);
this.emitError(`The exercise folder is missing the following files: ${ missingFiles.map((file: [ string, boolean ]) => file[0]).join(', ') }`, 'Some requirements are not satisfied', AssignmentCheckerError.REQUIRED_FILES_MISSING);
return;
}
this.events.emit('endSubStep', 'REQUIRED_FILES_EXISTS', 'All required files exists', false);
this.endSubStep('All required files exists', false);
this.events.emit('endStep', 'REQUIREMENTS_CHECKING', 'All requirements are satisfied', false);
this.endStep('All requirements are satisfied', false);
}
......@@ -97,42 +125,42 @@ class AssignmentValidator {
- Immutable files validation (Check if exists and if the given type is correct)
*/
{
this.events.emit('step', 'ASSIGNMENT_FILE_VALIDATION', 'Please wait while we are validating dojo_assignment.json file...');
this.newStep('ASSIGNMENT_FILE_VALIDATION', 'Please wait while we are validating dojo_assignment.json file...');
// Structure validation
this.events.emit('subStep', 'ASSIGNMENT_FILE_SCHEMA_VALIDATION', 'Validating dojo_assignment.json file schema');
this.newSubStep('ASSIGNMENT_FILE_SCHEMA_VALIDATION', 'Validating dojo_assignment.json file schema');
const validationResults = SharedAssignmentHelper.validateDescriptionFile(path.join(this.folderAssignment, ClientsSharedConfig.assignment.filename));
if ( !validationResults.isValid ) {
emitError('ASSIGNMENT_FILE_SCHEMA_VALIDATION', `dojo_assignment.json file schema is invalid.\nHere are the errors:\n${ JSON5.stringify(validationResults.errors) }`, 'ASSIGNMENT_FILE_VALIDATION', 'dojo_assignment.json file is invalid', AssignmentCheckerError.ASSIGNMENT_FILE_SCHEMA_ERROR);
this.emitError(`dojo_assignment.json file schema is invalid.\nHere are the errors:\n${ validationResults.error }`, 'dojo_assignment.json file is invalid', AssignmentCheckerError.ASSIGNMENT_FILE_SCHEMA_ERROR);
return;
}
assignmentFile = validationResults.results!;
this.events.emit('endSubStep', 'ASSIGNMENT_FILE_SCHEMA_VALIDATION', 'dojo_assignment.json file schema is valid', false);
assignmentFile = validationResults.content!;
this.endSubStep('dojo_assignment.json file schema is valid', false);
// Immutable files validation (Check if exists and if the given type is correct)
this.events.emit('subStep', 'ASSIGNMENT_FILE_IMMUTABLES_VALIDATION', 'Validating immutable files');
for ( const immutable of validationResults.results!.immutable ) {
this.newSubStep('ASSIGNMENT_FILE_IMMUTABLES_VALIDATION', 'Validating immutable files');
for ( const immutable of validationResults.content!.immutable ) {
const immutablePath = path.join(this.folderAssignment, immutable.path);
if ( !fs.existsSync(immutablePath) ) {
emitError('ASSIGNMENT_FILE_IMMUTABLES_VALIDATION', `Immutable path not found: ${ immutable.path }`, 'ASSIGNMENT_FILE_VALIDATION', 'dojo_assignment.json file is invalid', AssignmentCheckerError.IMMUTABLE_PATH_NOT_FOUND);
this.emitError(`Immutable path not found: ${ immutable.path }`, 'dojo_assignment.json file is invalid', AssignmentCheckerError.IMMUTABLE_PATH_NOT_FOUND);
return;
}
const isDirectory = fs.lstatSync(immutablePath).isDirectory();
if ( isDirectory && !immutable.isDirectory ) {
emitError('ASSIGNMENT_FILE_IMMUTABLES_VALIDATION', `Immutable (${ immutable.path }) is declared as a file but is a directory.`, 'ASSIGNMENT_FILE_VALIDATION', 'dojo_assignment.json file is invalid', AssignmentCheckerError.IMMUTABLE_PATH_IS_NOT_DIRECTORY);
this.emitError(`Immutable (${ immutable.path }) is declared as a file but is a directory.`, 'dojo_assignment.json file is invalid', AssignmentCheckerError.IMMUTABLE_PATH_IS_NOT_DIRECTORY);
return;
} else if ( !isDirectory && immutable.isDirectory === true ) {
emitError('ASSIGNMENT_FILE_IMMUTABLES_VALIDATION', `Immutable (${ immutable.path }) is declared as a directory but is a file.`, 'ASSIGNMENT_FILE_VALIDATION', 'dojo_assignment.json file is invalid', AssignmentCheckerError.IMMUTABLE_PATH_IS_DIRECTORY);
this.emitError(`Immutable (${ immutable.path }) is declared as a directory but is a file.`, 'dojo_assignment.json file is invalid', AssignmentCheckerError.IMMUTABLE_PATH_IS_DIRECTORY);
return;
}
}
this.events.emit('endSubStep', 'ASSIGNMENT_FILE_IMMUTABLES_VALIDATION', 'Immutable files are valid', false);
this.endSubStep('Immutable files are valid', false);
this.events.emit('endStep', 'ASSIGNMENT_FILE_VALIDATION', 'dojo_assignment.json file is valid', false);
this.endStep('dojo_assignment.json file is valid', false);
}
......@@ -142,15 +170,15 @@ class AssignmentValidator {
- Validation of the containers and volumes named in dojo_assignment.json
*/
{
this.events.emit('step', 'DOCKER_COMPOSE_VALIDATION', 'Please wait while we are validating docker compose file...');
this.newStep('DOCKER_COMPOSE_VALIDATION', 'Please wait while we are validating docker compose file...');
// Global validation
this.events.emit('subStep', 'DOCKER_COMPOSE_STRUCTURE_VALIDATION', 'Docker compose file structure validation');
this.newSubStep('DOCKER_COMPOSE_STRUCTURE_VALIDATION', 'Docker compose file structure validation');
try {
dockerComposeFile = YAML.parse(fs.readFileSync(path.join(this.folderAssignment, 'docker-compose.yml'), 'utf8')) as DojoDockerCompose;
} catch ( error ) {
emitError('DOCKER_COMPOSE_STRUCTURE_VALIDATION', `Docker compose file yaml structure is invalid.`, 'DOCKER_COMPOSE_VALIDATION', 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_YAML_ERROR);
this.emitError(`Docker compose file yaml structure is invalid.`, 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_YAML_ERROR);
return;
}
......@@ -166,26 +194,26 @@ class AssignmentValidator {
});
});
} catch ( error ) {
emitError('DOCKER_COMPOSE_STRUCTURE_VALIDATION', `Docker compose file structure is invalid.`, 'DOCKER_COMPOSE_VALIDATION', 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_SCHEMA_ERROR);
this.emitError(`Docker compose file structure is invalid.`, 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_SCHEMA_ERROR);
return;
}
this.events.emit('endSubStep', 'DOCKER_COMPOSE_STRUCTURE_VALIDATION', 'Docker compose file structure is valid', false);
this.endSubStep('Docker compose file structure is valid', false);
// Validation of the containers and volumes named in dojo_assignment.json
this.events.emit('subStep', 'DOCKER_COMPOSE_CONTENT_VALIDATION', 'Docker compose file content validation');
this.newSubStep('DOCKER_COMPOSE_CONTENT_VALIDATION', 'Docker compose file content validation');
if ( !(assignmentFile.result.container in dockerComposeFile!.services) ) {
emitError('DOCKER_COMPOSE_CONTENT_VALIDATION', `Container specified in ${ ClientsSharedConfig.assignment.filename } is missing from compose file.`, 'DOCKER_COMPOSE_VALIDATION', 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_CONTAINER_MISSING);
this.emitError(`Container specified in ${ ClientsSharedConfig.assignment.filename } is missing from compose file.`, 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_CONTAINER_MISSING);
return;
}
if ( assignmentFile.result.volume && (!dockerComposeFile!.volumes || !(assignmentFile.result.volume in dockerComposeFile!.volumes)) ) {
emitError('DOCKER_COMPOSE_CONTENT_VALIDATION', `Volume specified in ${ ClientsSharedConfig.assignment.filename } is missing from compose file.`, 'DOCKER_COMPOSE_VALIDATION', 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_VOLUME_MISSING);
this.emitError(`Volume specified in ${ ClientsSharedConfig.assignment.filename } is missing from compose file.`, 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_VOLUME_MISSING);
return;
}
this.events.emit('endSubStep', 'DOCKER_COMPOSE_CONTENT_VALIDATION', 'Docker compose file content is valid', false);
this.endSubStep('Docker compose file content is valid', false);
this.events.emit('endStep', 'DOCKER_COMPOSE_VALIDATION', 'Docker compose file is valid', false);
this.endStep('Docker compose file is valid', false);
}
......@@ -195,20 +223,20 @@ class AssignmentValidator {
- TODO - Dockerfile structure linter - Issue #51 - https://github.com/hadolint/hadolint
*/
{
this.events.emit('step', 'DOCKERFILE_VALIDATION', 'Please wait while we are validating dockerfiles...');
this.newStep('DOCKERFILE_VALIDATION', 'Please wait while we are validating dockerfiles...');
this.events.emit('subStep', 'DOCKERFILE_EXIST', 'Docker compose file content validation');
this.newSubStep('DOCKERFILE_EXIST', 'Docker compose file content validation');
const dockerfilesPaths = Object.values(dockerComposeFile!.services).filter((service) => service.build).map((service) => path.join(this.folderAssignment, service.build!.context ?? '', service.build!.dockerfile!));
const filesNotFound = dockerfilesPaths.filter((dockerfilePath) => !fs.existsSync(dockerfilePath));
if ( filesNotFound.length > 0 ) {
emitError('DOCKERFILE_VALIDATION', `Dockerfiles not found: ${ filesNotFound.join(', ') }`, 'DOCKERFILE_VALIDATION', 'Dockerfiles are invalid', AssignmentCheckerError.DOCKERFILE_NOT_FOUND);
this.emitError(`Dockerfiles not found: ${ filesNotFound.join(', ') }`, 'Dockerfiles are invalid', AssignmentCheckerError.DOCKERFILE_NOT_FOUND);
return;
}
this.events.emit('endSubStep', 'DOCKER_COMPOSE_CONTENT_VALIDATION', 'Docker compose file content is valid', false);
this.endSubStep('Docker compose file content is valid', false);
this.events.emit('endStep', 'DOCKERFILE_VALIDATION', 'Dockerfiles are valid', false);
this.endStep('Dockerfiles are valid', false);
}
......@@ -217,7 +245,7 @@ class AssignmentValidator {
- Make a run of the assignment (If the return code is 0, the assignment is not valid because it means that there no need of modification for succeed the exercise)
*/
{
this.events.emit('step', 'ASSIGNMENT_RUN', 'Please wait while we are running the assignment...');
this.newStep('ASSIGNMENT_RUN', 'Please wait while we are running the assignment...');
const exerciseDockerCompose = new ExerciseDockerCompose(ClientsSharedConfig.dockerCompose.projectName, assignmentFile, this.folderAssignment);
......@@ -225,15 +253,15 @@ class AssignmentValidator {
try {
await new Promise<void>((resolve, reject) => {
exerciseDockerCompose.events.on('logs', (log: string, error: boolean, displayable: boolean) => {
this.events.emit('logs', log, error, displayable);
this.log(log, error, displayable);
});
exerciseDockerCompose.events.on('step', (name: string, message: string) => {
this.events.emit('subStep', name, message);
this.newSubStep(name, message);
});
exerciseDockerCompose.events.on('endStep', (stepName: string, message: string, error: boolean) => {
this.events.emit('endSubStep', stepName, message, error);
this.endSubStep(message, error);
});
exerciseDockerCompose.events.on('finished', (success: boolean, exitCode: number) => {
......@@ -244,17 +272,17 @@ class AssignmentValidator {
});
} catch ( error ) {
this.fatalErrorMessage = 'Assignment is already solved';
this.events.emit('endStep', 'ASSIGNMENT_RUN', this.fatalErrorMessage, true);
this.events.emit('finished', false, AssignmentCheckerError.COMPOSE_RUN_SUCCESSFULLY);
this.endStep(this.fatalErrorMessage, true);
this.finished(false, AssignmentCheckerError.COMPOSE_RUN_SUCCESSFULLY);
return;
}
this.events.emit('endStep', 'ASSIGNMENT_RUN', 'Assignment run successfully', false);
this.endStep('Assignment run successfully', false);
}
this.events.emit('finished', true, 0);
this.finished(true, 0);
})();
}
}
......
import chalk from 'chalk';
import boxen from 'boxen';
import Icon from '../../types/Icon';
import Icon from '../../../shared/types/Icon';
import AssignmentValidator from './AssignmentValidator';
......
import ExerciseResultsFile from '../../../shared/types/Dojo/ExerciseResultsFile';
import chalk from 'chalk';
import boxen from 'boxen';
import Icon from '../../types/Icon';
import Icon from '../../../shared/types/Icon';
class ClientsSharedExerciseHelper {
displayExecutionResults(exerciseResults: ExerciseResultsFile, containerExitCode: number, Style: { INFO: chalk.Chalk, SUCCESS: chalk.Chalk, FAILURE: chalk.Chalk }, additionalText: string = '') {
const finalLogGlobalResult = `${ Style.INFO('Global result') } : ${ exerciseResults.success ? Style.SUCCESS(`${ Icon.SUCCESS } Success`) : Style.FAILURE(`${ Icon.FAILURE } Failure`) }`;
const finalLogGlobalResult = `${ Style.INFO('Global result: ') }${ exerciseResults.success ? Style.SUCCESS(`${ Icon.SUCCESS } Success`) : Style.FAILURE(`${ Icon.FAILURE } Failure`) }`;
const finalLogExecutionExitCode = `${ Style.INFO('Execution exit code') } : ${ (containerExitCode == 0 ? Style.SUCCESS : Style.FAILURE)(containerExitCode) }`;
const finalLogExecutionExitCode = `${ Style.INFO('Execution exit code: ') }${ (containerExitCode == 0 ? Style.SUCCESS : Style.FAILURE)(containerExitCode) }`;
const finalLogResultNumbers = exerciseResults.successfulTests || exerciseResults.failedTests ? `\n\n${ Style.SUCCESS('Tests passed') } : ${ exerciseResults.successfulTests ?? '--' }\n${ Style.FAILURE('Tests failed') } : ${ exerciseResults.failedTests ?? '--' }` : '';
const finalLogResultNumbers = exerciseResults.successfulTests || exerciseResults.failedTests ? `\n\n${ Style.INFO(Style.SUCCESS('Tests passed: ')) }${ exerciseResults.successfulTests ?? '--' }\n${ Style.INFO(Style.FAILURE('Tests failed: ')) }${ exerciseResults.failedTests ?? '--' }` : '';
const finalLogSuccessResultDetails = (exerciseResults.successfulTestsList ?? []).map(testName => `- ${ Icon.SUCCESS } ${ testName }`).join('\n');
const finalLogFailedResultDetails = (exerciseResults.failedTestsList ?? []).map(testName => `- ${ Icon.FAILURE } ${ testName }`).join('\n');
const finalLogResultDetails = exerciseResults.successfulTestsList || exerciseResults.failedTestsList ? `\n\n${ Style.INFO('Tests') } :${ finalLogSuccessResultDetails != '' ? '\n' + finalLogSuccessResultDetails : '' }${ finalLogFailedResultDetails != '' ? '\n' + finalLogFailedResultDetails : '' }` : '';
const finalLogResultDetails = exerciseResults.successfulTestsList || exerciseResults.failedTestsList ? `\n\n${ Style.INFO('Tests: ') }${ finalLogSuccessResultDetails != '' ? '\n' + finalLogSuccessResultDetails : '' }${ finalLogFailedResultDetails != '' ? '\n' + finalLogFailedResultDetails : '' }` : '';
console.log(boxen(`${ finalLogGlobalResult }\n\n${ finalLogExecutionExitCode }${ finalLogResultNumbers }${ finalLogResultDetails }${ additionalText }`, {
let finalLogInformations = '';
if ( exerciseResults.otherInformations ) {
finalLogInformations = [ '', ...exerciseResults.otherInformations.map(information => {
const informationTitle = Style.INFO(`${ information.icon && information.icon != '' ? Icon[information.icon] + ' ' : '' }${ information.name }: `);
const informationItems = typeof information.itemsOrInformations == 'string' ? information.itemsOrInformations : information.itemsOrInformations.map(item => `- ${ item }`).join('\n');
return `${ informationTitle }\n${ informationItems }`;
}) ].join('\n\n');
}
console.log(boxen(`${ finalLogGlobalResult }\n\n${ finalLogExecutionExitCode }${ finalLogResultNumbers }${ finalLogResultDetails }${ finalLogInformations }${ additionalText }`, {
title : 'Results',
titleAlignment: 'center',
borderColor : 'yellow',
......
import AssignmentFile from '../../../shared/types/Dojo/AssignmentFile';
import { TypedEmitter } from 'tiny-typed-emitter';
import ExerciseRunningEvents from '../../types/Dojo/ExerciseRunningEvents';
import { spawn } from 'child_process';
import ExerciseCheckerError from '../../../shared/types/Dojo/ExerciseCheckerError';
import AssignmentFile from '../../../shared/types/Dojo/AssignmentFile';
import { TypedEmitter } from 'tiny-typed-emitter';
import ExerciseRunningEvents from '../../types/Dojo/ExerciseRunningEvents';
import { spawn } from 'child_process';
import ExerciseCheckerError from '../../../shared/types/Dojo/ExerciseCheckerError';
import { ChildProcessWithoutNullStreams } from 'node:child_process';
class ExerciseDockerCompose {
......@@ -15,6 +16,8 @@ class ExerciseDockerCompose {
public success: boolean = false;
public exitCode: number = -1;
private currentStep: string = 'NOT_RUNNING';
constructor(private projectName: string, private assignmentFile: AssignmentFile, private executionFolder: string, private composeFileOverride: Array<string> = []) {
this.events.on('logs', (log: string, _error: boolean, displayable: boolean) => {
this.allLogs += log;
......@@ -28,6 +31,37 @@ class ExerciseDockerCompose {
});
}
private newStep(name: string, message: string) {
this.currentStep = name;
this.events.emit('step', name, message);
}
private endStep(message: string, error: boolean) {
this.events.emit('endStep', this.currentStep, message, error);
}
private log(message: string, error: boolean, displayable: boolean) {
this.events.emit('logs', message, error, displayable, this.currentStep);
}
private finished(success: boolean, code: number) {
this.events.emit('finished', success, code);
}
private registerChildProcess(childProcess: ChildProcessWithoutNullStreams, resolve: (value: (number | PromiseLike<number>)) => void, reject: (reason?: unknown) => void, displayable: boolean, rejectIfCodeIsNotZero: boolean) {
childProcess.stdout.on('data', (data) => {
this.log(data.toString(), false, displayable);
});
childProcess.stderr.on('data', (data) => {
this.log(data.toString(), true, displayable);
});
childProcess.on('exit', (code) => {
code === null || (rejectIfCodeIsNotZero && code !== 0) ? reject(code) : resolve(code);
});
}
run(doDown: boolean = false) {
(async () => {
let containerExitCode: number = -1;
......@@ -37,11 +71,11 @@ class ExerciseDockerCompose {
// Run the service
{
try {
this.events.emit('step', 'COMPOSE_RUN', 'Running Docker Compose file');
this.newStep('COMPOSE_RUN', 'Running Docker Compose file');
containerExitCode = await new Promise<number>((resolve, reject) => {
this.events.emit('logs', '####################################################### Docker Compose & Main Container Logs #######################################################\n', false, false);
this.log('####################################################### Docker Compose & Main Container Logs #######################################################\n', false, false);
const dockerCompose = spawn(`${ dockerComposeCommand } run --build --rm ${ this.assignmentFile.result.container }`, {
cwd : this.executionFolder,
......@@ -52,97 +86,93 @@ class ExerciseDockerCompose {
}
});
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();
});
this.registerChildProcess(dockerCompose, resolve, reject, true, false);
});
} catch ( error ) {
this.events.emit('endStep', 'COMPOSE_RUN', `Error while running the docker compose file`, true);
this.events.emit('finished', false, ExerciseCheckerError.DOCKER_COMPOSE_RUN_ERROR);
this.endStep(`Error while running the docker compose file`, true);
this.finished(false, ExerciseCheckerError.DOCKER_COMPOSE_RUN_ERROR);
return;
}
this.events.emit('endStep', 'COMPOSE_RUN', `Docker Compose file run successfully`, false);
this.endStep(`Docker Compose file run successfully`, false);
}
// Get linked services logs
{
try {
this.events.emit('step', 'COMPOSE_LOGS', 'Linked services logs acquisition');
this.newStep('COMPOSE_LOGS', 'Linked services logs acquisition');
await new Promise<void>((resolve, reject) => {
await new Promise<number>((resolve, reject) => {
this.events.emit('logs', '####################################################### Other Services Logs #######################################################\n', false, false);
this.log('####################################################### 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();
});
this.registerChildProcess(dockerCompose, resolve, reject, false, true);
});
} catch ( error ) {
this.events.emit('endStep', 'COMPOSE_LOGS', `Error while getting the linked services logs`, true);
this.events.emit('finished', false, ExerciseCheckerError.DOCKER_COMPOSE_LOGS_ERROR);
this.endStep(`Error while getting the linked services logs`, true);
this.finished(false, ExerciseCheckerError.DOCKER_COMPOSE_LOGS_ERROR);
return;
}
this.events.emit('endStep', 'COMPOSE_LOGS', `Linked services logs acquired`, false);
this.endStep(`Linked services logs acquired`, false);
}
// Remove containers if asked
{
if ( doDown ) {
try {
this.events.emit('step', 'COMPOSE_DOWN', 'Stopping and removing containers');
this.newStep('COMPOSE_DOWN', 'Stopping and removing containers');
await new Promise<void>((resolve, reject) => {
await new Promise<number>((resolve, reject) => {
this.events.emit('logs', '####################################################### Stop and remove containers #######################################################\n', false, false);
this.log('####################################################### 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);
});
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);
}
}
dockerCompose.stderr.on('data', (data) => {
this.events.emit('logs', data.toString(), true, false);
});
// Remove images if asked
{
if ( doDown ) {
try {
this.newStep('COMPOSE_REMOVE_DANGLING', 'Removing dangling images');
dockerCompose.on('exit', (code) => {
code !== null ? resolve() : reject();
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
});
this.registerChildProcess(dockerCompose, resolve, reject, false, true);
});
} catch ( error ) {
this.events.emit('endStep', 'COMPOSE_DOWN', `Error stop and remove containers`, true);
this.events.emit('finished', false, ExerciseCheckerError.DOCKER_COMPOSE_DOWN_ERROR);
this.endStep(`Error while removing dangling images`, true);
this.finished(false, ExerciseCheckerError.DOCKER_COMPOSE_REMOVE_DANGLING_ERROR);
return;
}
this.events.emit('endStep', 'COMPOSE_DOWN', `Containers stopped and removed`, false);
this.endStep(`Dangling images removed`, false);
}
}
this.events.emit('finished', true, containerExitCode);
this.finished(true, containerExitCode);
})();
}
}
......
......@@ -2,12 +2,12 @@ import { TypedEmitter } from 'tiny-typed-emitter';
import ExerciseRunningEvents from '../../types/Dojo/ExerciseRunningEvents';
import ExerciseCheckerError from '../../../shared/types/Dojo/ExerciseCheckerError';
import path from 'node:path';
import SharedExerciseHelper from '../../../shared/helpers/Dojo/SharedExerciseHelper';
import ClientsSharedConfig from '../../config/ClientsSharedConfig';
import Toolbox from '../../../shared/helpers/Toolbox';
import * as fs from 'fs-extra';
import ExerciseResultsFile from '../../../shared/types/Dojo/ExerciseResultsFile';
import JSON5 from 'json5';
import Json5FileValidator from '../../../shared/helpers/Json5FileValidator';
class ExerciseResultsSanitizerAndValidator {
......@@ -37,13 +37,13 @@ class ExerciseResultsSanitizerAndValidator {
// Results file schema validation
{
this.events.emit('step', 'VALIDATE_RESULTS_FILE', 'Validating results file schema');
const validationResults = SharedExerciseHelper.validateResultFile(path);
const validationResults = Json5FileValidator.validateFile(ExerciseResultsFile, path);
if ( !validationResults.isValid ) {
this.events.emit('endStep', 'VALIDATE_RESULTS_FILE', `Results file is not valid. Here are the errors :\n${ JSON.stringify(validationResults.errors) }`, true);
this.events.emit('endStep', 'VALIDATE_RESULTS_FILE', `Results file is not valid. Here are the errors :\n${ validationResults.error }`, true);
this.events.emit('finished', false, ExerciseCheckerError.EXERCISE_RESULTS_FILE_SCHEMA_NOT_VALID);
return false;
}
this.exerciseResults = validationResults.results ?? {};
this.exerciseResults = validationResults.content ?? {};
this.events.emit('endStep', 'VALIDATE_RESULTS_FILE', 'Results file is valid', false);
}
......
......@@ -3,7 +3,7 @@ interface AssignmentValidatorEvents {
subStep: (name: string, message: string) => void;
endStep: (stepName: string, message: string, error: boolean) => void;
endSubStep: (subStepName: string, message: string, error: boolean) => void;
logs: (log: string, error: boolean, displayable: boolean) => void;
logs: (log: string, error: boolean, displayable: boolean, currentStep: string, currentSubStep: string) => void;
finished: (success: boolean, exitCode: number) => void;
}
......
interface ExerciseRunningEvents {
step: (name: string, message: string) => void;
endStep: (stepName: string, message: string, error: boolean) => void;
logs: (log: string, error: boolean, displayable: boolean) => void;
logs: (log: string, error: boolean, displayable: boolean, currentStep: string) => void;
finished: (success: boolean, exitCode: number) => void;
}
......
enum Icon {
CAT_INFO = '▶️',
INFO = 'ℹ️',
ERROR = '⛔️',
SUCCESS = '',
FAILURE = ''
}
export default Icon;
\ No newline at end of file