diff --git a/helpers/Dojo/AssignmentValidator.ts b/helpers/Dojo/AssignmentValidator.ts index 357e25770084f038951d5b6f47cd6d4bd40708ce..5371665eb43108dbd73cac911c617394d06bd196 100644 --- a/helpers/Dojo/AssignmentValidator.ts +++ b/helpers/Dojo/AssignmentValidator.ts @@ -28,6 +28,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 +44,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 +90,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(`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 +126,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${ JSON5.stringify(validationResults.errors) }`, '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); + 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'); + this.newSubStep('ASSIGNMENT_FILE_IMMUTABLES_VALIDATION', 'Validating immutable files'); for ( const immutable of validationResults.results!.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 +171,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 +195,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 +224,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 +246,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 +254,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 +273,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); })(); } } diff --git a/types/Dojo/AssignmentValidatorEvents.ts b/types/Dojo/AssignmentValidatorEvents.ts index 78c22e5d0718fdc378fa1036eecdaa79db6a07cf..b4b3364725c3aac0505616620101f3d08c0767cc 100644 --- a/types/Dojo/AssignmentValidatorEvents.ts +++ b/types/Dojo/AssignmentValidatorEvents.ts @@ -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; }