Skip to content
Snippets Groups Projects
Commit c4efbcfb authored by michael.minelli's avatar michael.minelli
Browse files

Merge branch 'add-sonar-integration' into v3.6

parents 4efff1c5 9e75273d
No related branches found
No related tags found
No related merge requests found
......@@ -21,28 +21,28 @@ class ClientsSharedConfig {
constructor() {
this.apiURL = process.env.API_URL || '';
this.apiURL = process.env.API_URL ?? '';
this.assignment = {
filename : process.env.ASSIGNMENT_FILENAME || '',
neededFiles: JSON.parse(process.env.EXERCISE_NEEDED_FILES || '[]')
filename : process.env.ASSIGNMENT_FILENAME ?? '',
neededFiles: JSON.parse(process.env.EXERCISE_NEEDED_FILES ?? '[]')
};
this.gitlab = {
dojoAccount: {
id : Number(process.env.GITLAB_DOJO_ACCOUNT_ID) || -1,
username: process.env.GITLAB_DOJO_ACCOUNT_USERNAME || ''
id : Number(process.env.GITLAB_DOJO_ACCOUNT_ID ?? -1),
username: process.env.GITLAB_DOJO_ACCOUNT_USERNAME ?? ''
}
};
this.dockerCompose = {
projectName: process.env.DOCKER_COMPOSE_PROJECT_NAME || ''
projectName: process.env.DOCKER_COMPOSE_PROJECT_NAME ?? ''
};
this.exerciseResultsFolderMaxSizeInBytes = Number(process.env.EXERCISE_RESULTS_FOLDER_MAX_SIZE_IN_BYTES || 0);
this.exerciseResultsFolderMaxSizeInBytes = Number(process.env.EXERCISE_RESULTS_FOLDER_MAX_SIZE_IN_BYTES ?? 0);
this.filenames = {
results: process.env.EXERCISE_RESULTS_FILENAME || ''
results: process.env.EXERCISE_RESULTS_FILENAME ?? ''
};
}
}
......
......@@ -17,6 +17,9 @@ const execAsync = util.promisify(exec);
class AssignmentValidator {
private readonly folderAssignment: string;
private readonly doDown: boolean;
readonly events: TypedEmitter<AssignmentValidatorEvents> = new TypedEmitter<AssignmentValidatorEvents>();
public displayableLogs: string = '';
......@@ -30,7 +33,13 @@ class AssignmentValidator {
private currentStep: string = 'NOT_RUNNING';
private currentSubStep: string = 'NOT_RUNNING';
constructor(private folderAssignment: string) {
private dockerComposeFile!: DojoDockerCompose;
private assignmentFile!: AssignmentFile;
constructor(folderAssignment: string, doDown: boolean = false) {
this.folderAssignment = folderAssignment;
this.doDown = doDown;
this.events.on('logs', (log: string, _error: boolean, displayable: boolean) => {
this.allLogs += log;
this.displayableLogs += displayable ? log : '';
......@@ -77,18 +86,13 @@ class AssignmentValidator {
this.finished(false, code);
}
run(doDown: boolean = false) {
(async () => {
let dockerComposeFile: DojoDockerCompose;
let assignmentFile: AssignmentFile;
/*
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 1: Check requirements
- Check if Docker daemon is running
- Check if required files exists
/**
* Step 1: Check requirements
* - Check if Docker daemon is running
* - Check if required files exists
* @private
*/
{
private async checkRequirements() {
this.newStep('REQUIREMENTS_CHECKING', 'Please wait while we are checking requirements...');
......@@ -98,7 +102,7 @@ class AssignmentValidator {
await execAsync(`docker ps`);
} catch ( error ) {
this.emitError(`Docker daemon isn't running`, `Some requirements are not satisfied.`, AssignmentCheckerError.DOCKER_DAEMON_NOT_RUNNING);
return;
throw new Error();
}
this.endSubStep('Docker daemon is running', false);
......@@ -110,7 +114,7 @@ class AssignmentValidator {
if ( missingFiles.length > 0 ) {
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;
throw new Error();
}
this.endSubStep('All required files exists', false);
......@@ -118,43 +122,44 @@ class AssignmentValidator {
this.endStep('All requirements are satisfied', false);
}
/*
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 2: dojo_assignment.json file validation
- Structure validation
- Immutable files validation (Check if exists and if the given type is correct)
/**
* Step 2: dojo_assignment.json file validation
* - Structure validation
* - Immutable files validation (Check if exists and if the given type is correct)
* @private
*/
{
private dojoAssignmentFileValidation() {
this.newStep('ASSIGNMENT_FILE_VALIDATION', 'Please wait while we are validating dojo_assignment.json file...');
const assignmentFileValidationError = `${ ClientsSharedConfig.assignment.filename } file is invalid`;
// Structure validation
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 ) {
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;
this.emitError(`dojo_assignment.json file schema is invalid.\nHere are the errors:\n${ validationResults.error }`, assignmentFileValidationError, AssignmentCheckerError.ASSIGNMENT_FILE_SCHEMA_ERROR);
throw new Error();
}
assignmentFile = validationResults.content!;
this.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.newSubStep('ASSIGNMENT_FILE_IMMUTABLES_VALIDATION', 'Validating immutable files');
for ( const immutable of validationResults.content!.immutable ) {
for ( const immutable of this.assignmentFile.immutable ) {
const immutablePath = path.join(this.folderAssignment, immutable.path);
if ( !fs.existsSync(immutablePath) ) {
this.emitError(`Immutable path not found: ${ immutable.path }`, 'dojo_assignment.json file is invalid', AssignmentCheckerError.IMMUTABLE_PATH_NOT_FOUND);
return;
this.emitError(`Immutable path not found: ${ immutable.path }`, assignmentFileValidationError, AssignmentCheckerError.IMMUTABLE_PATH_NOT_FOUND);
throw new Error();
}
const isDirectory = fs.lstatSync(immutablePath).isDirectory();
if ( isDirectory && !immutable.isDirectory ) {
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;
this.emitError(`Immutable (${ immutable.path }) is declared as a file but is a directory.`, assignmentFileValidationError, AssignmentCheckerError.IMMUTABLE_PATH_IS_NOT_DIRECTORY);
throw new Error();
} else if ( !isDirectory && immutable.isDirectory === true ) {
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.emitError(`Immutable (${ immutable.path }) is declared as a directory but is a file.`, assignmentFileValidationError, AssignmentCheckerError.IMMUTABLE_PATH_IS_DIRECTORY);
throw new Error();
}
}
this.endSubStep('Immutable files are valid', false);
......@@ -163,23 +168,24 @@ class AssignmentValidator {
this.endStep('dojo_assignment.json file is valid', false);
}
/*
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 3: Docker Compose file validation
- Global validation
- Validation of the containers and volumes named in dojo_assignment.json
/**
* Step 3: Docker Compose file validation
* - Global validation
* - Validation of the containers and volumes named in dojo_assignment.json
* @private
*/
{
private async dockerComposeFileValidation() {
this.newStep('DOCKER_COMPOSE_VALIDATION', 'Please wait while we are validating docker compose file...');
const composeFileValidationError = `Docker compose file is invalid`;
// Global 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;
this.dockerComposeFile = YAML.parse(fs.readFileSync(path.join(this.folderAssignment, 'docker-compose.yml'), 'utf8')) as DojoDockerCompose;
} catch ( error ) {
this.emitError(`Docker compose file yaml structure is invalid.`, 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_YAML_ERROR);
return;
this.emitError(`Docker compose file yaml structure is invalid.`, composeFileValidationError, AssignmentCheckerError.COMPOSE_FILE_YAML_ERROR);
throw new Error();
}
try {
......@@ -189,26 +195,26 @@ class AssignmentValidator {
shell: true
});
dockerComposeValidation.on('exit', (code) => {
code !== null && code == 0 ? resolve() : reject();
dockerComposeValidation.on('exit', code => {
code !== null && code === 0 ? resolve() : reject(code);
});
});
} catch ( error ) {
this.emitError(`Docker compose file structure is invalid.`, 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_SCHEMA_ERROR);
return;
this.emitError(`Docker compose file structure is invalid.`, composeFileValidationError, AssignmentCheckerError.COMPOSE_FILE_SCHEMA_ERROR);
throw new Error();
}
this.endSubStep('Docker compose file structure is valid', false);
// Validation of the containers and volumes named in dojo_assignment.json
this.newSubStep('DOCKER_COMPOSE_CONTENT_VALIDATION', 'Docker compose file content validation');
if ( !(assignmentFile.result.container in dockerComposeFile!.services) ) {
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 ( !(this.assignmentFile.result.container in this.dockerComposeFile.services) ) {
this.emitError(`Container specified in ${ ClientsSharedConfig.assignment.filename } is missing from compose file.`, composeFileValidationError, AssignmentCheckerError.COMPOSE_FILE_CONTAINER_MISSING);
throw new Error();
}
if ( assignmentFile.result.volume && (!dockerComposeFile!.volumes || !(assignmentFile.result.volume in dockerComposeFile!.volumes)) ) {
this.emitError(`Volume specified in ${ ClientsSharedConfig.assignment.filename } is missing from compose file.`, 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_VOLUME_MISSING);
return;
if ( this.assignmentFile.result.volume && (!this.dockerComposeFile.volumes || !(this.assignmentFile.result.volume in this.dockerComposeFile.volumes)) ) {
this.emitError(`Volume specified in ${ ClientsSharedConfig.assignment.filename } is missing from compose file.`, composeFileValidationError, AssignmentCheckerError.COMPOSE_FILE_VOLUME_MISSING);
throw new Error();
}
this.endSubStep('Docker compose file content is valid', false);
......@@ -216,22 +222,22 @@ class AssignmentValidator {
this.endStep('Docker compose file is valid', false);
}
/*
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 4: Dockerfiles validation
- Check if file exists
- TODO - Dockerfile structure linter - Issue #51 - https://github.com/hadolint/hadolint
/**
* Step 4: Dockerfiles validation
* - Check if file exists
* - TODO - Dockerfile structure linter - Issue #51 - https://github.com/hadolint/hadolint
* @private
*/
{
private dockerfilesValidation() {
this.newStep('DOCKERFILE_VALIDATION', 'Please wait while we are validating dockerfiles...');
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));
const dockerfilesPaths = Object.values(this.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 ) {
this.emitError(`Dockerfiles not found: ${ filesNotFound.join(', ') }`, 'Dockerfiles are invalid', AssignmentCheckerError.DOCKERFILE_NOT_FOUND);
return;
throw new Error();
}
this.endSubStep('Docker compose file content is valid', false);
......@@ -239,16 +245,16 @@ class AssignmentValidator {
this.endStep('Dockerfiles are valid', false);
}
/*
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 5: Run
- 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)
/**
* Step 5: Run
* - 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)
* @private
*/
{
private async runAssignment() {
this.newStep('ASSIGNMENT_RUN', 'Please wait while we are running the assignment...');
const exerciseDockerCompose = new ExerciseDockerCompose(ClientsSharedConfig.dockerCompose.projectName, assignmentFile, this.folderAssignment);
const exerciseDockerCompose = new ExerciseDockerCompose(ClientsSharedConfig.dockerCompose.projectName, this.assignmentFile, this.folderAssignment);
try {
await new Promise<void>((resolve, reject) => {
......@@ -260,29 +266,44 @@ class AssignmentValidator {
this.newSubStep(name, message);
});
exerciseDockerCompose.events.on('endStep', (stepName: string, message: string, error: boolean) => {
exerciseDockerCompose.events.on('endStep', (_stepName: string, message: string, error: boolean) => {
this.endSubStep(message, error);
});
exerciseDockerCompose.events.on('finished', (success: boolean, exitCode: number) => {
exitCode != 0 ? resolve() : reject();
exerciseDockerCompose.events.on('finished', (_success: boolean, exitCode: number) => {
exitCode !== 0 ? resolve() : reject(exitCode);
});
exerciseDockerCompose.run(doDown);
exerciseDockerCompose.run(this.doDown);
});
} catch ( error ) {
this.fatalErrorMessage = 'Assignment is already solved';
this.endStep(this.fatalErrorMessage, true);
this.finished(false, AssignmentCheckerError.COMPOSE_RUN_SUCCESSFULLY);
return;
throw new Error();
}
this.endStep('Assignment run successfully', false);
}
run() {
(async () => {
try {
await this.checkRequirements();
this.dojoAssignmentFileValidation();
await this.dockerComposeFileValidation();
this.dockerfilesValidation();
await this.runAssignment();
this.finished(true, 0);
} catch ( error ) {
return;
}
})();
}
}
......
......@@ -6,7 +6,8 @@ import AssignmentValidator from './AssignmentValidator';
class ClientsSharedAssignmentHelper {
displayExecutionResults(validator: AssignmentValidator, successMessage: string, Style: { INFO: chalk.Chalk, SUCCESS: chalk.Chalk, FAILURE: chalk.Chalk }) {
const finalLogGlobalResult = `${ Style.INFO('Global result') } : ${ validator.success ? Style.SUCCESS(`${ Icon.SUCCESS } Success`) : Style.FAILURE(`${ Icon.FAILURE } Failure`) }`;
const globalResult = validator.success ? Style.SUCCESS(`${ Icon.SUCCESS } Success`) : Style.FAILURE(`${ Icon.FAILURE } Failure`);
const finalLogGlobalResult = `${ Style.INFO('Global result') } : ${ globalResult }`;
const finalLogSuccessMessage = validator.success ? `${ successMessage }` : '';
const finalLogErrorMessage = !validator.success ? `${ Style.INFO('Error message') } :\n${ Style.FAILURE(validator.fatalErrorMessage) }` : '';
......
......@@ -5,27 +5,31 @@ 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 finalLogExecutionExitCode = `${ Style.INFO('Execution exit code: ') }${ (containerExitCode == 0 ? Style.SUCCESS : Style.FAILURE)(containerExitCode) }`;
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 : '' }` : '';
let finalLogInformations = '';
if ( exerciseResults.otherInformations ) {
finalLogInformations = [ '', ...exerciseResults.otherInformations.map(information => {
const informationTitle = Style.INFO(`${ information.icon && information.icon != '' ? Icon[information.icon] + ' ' : '' }${ information.name }: `);
private getOtherInformations(exerciseResults: ExerciseResultsFile, Style: { INFO: chalk.Chalk, SUCCESS: chalk.Chalk, FAILURE: chalk.Chalk }) {
return exerciseResults.otherInformations ? [ '', ...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');
}) ].join('\n\n') : '';
}
displayExecutionResults(exerciseResults: ExerciseResultsFile, containerExitCode: number, Style: { INFO: chalk.Chalk, SUCCESS: chalk.Chalk, FAILURE: chalk.Chalk }, additionalText: string = '') {
const globalResult = exerciseResults.success ? Style.SUCCESS(`${ Icon.SUCCESS } Success`) : Style.FAILURE(`${ Icon.FAILURE } Failure`);
const finalLogGlobalResult = `${ Style.INFO('Global result: ') }${ globalResult }`;
const finalLogExecutionExitCode = `${ Style.INFO('Execution exit code: ') }${ (containerExitCode === 0 ? Style.SUCCESS : Style.FAILURE)(containerExitCode) }`;
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 ?? '--' }` : '';
let finalLogSuccessResultDetails = (exerciseResults.successfulTestsList ?? []).map(testName => `- ${ Icon.SUCCESS } ${ testName }`).join('\n');
finalLogSuccessResultDetails = finalLogSuccessResultDetails !== '' ? '\n' + finalLogSuccessResultDetails : '';
let finalLogFailedResultDetails = (exerciseResults.failedTestsList ?? []).map(testName => `- ${ Icon.FAILURE } ${ testName }`).join('\n');
finalLogFailedResultDetails = finalLogFailedResultDetails !== '' ? '\n' + finalLogFailedResultDetails : '';
const finalLogResultDetails = exerciseResults.successfulTestsList || exerciseResults.failedTestsList ? `\n\n${ Style.INFO('Tests: ') }${ finalLogSuccessResultDetails }${ finalLogFailedResultDetails }` : '';
const finalLogInformations = this.getOtherInformations(exerciseResults, Style);
console.log(boxen(`${ finalLogGlobalResult }\n\n${ finalLogExecutionExitCode }${ finalLogResultNumbers }${ finalLogResultDetails }${ finalLogInformations }${ additionalText }`, {
title : 'Results',
......
import ApiRoute from '../../types/Dojo/ApiRoute';
import ClientsSharedConfig from '../../config/ClientsSharedConfig';
class DojoBackendHelper {
public getApiUrl(route: ApiRoute, options?: Partial<{ assignmentNameOrUrl: string, exerciseIdOrUrl: string, gitlabProjectId: string }>): string {
const url = `${ ClientsSharedConfig.apiURL }${ route }`;
if ( options ) {
if ( options.assignmentNameOrUrl ) {
return url.replace('{{assignmentNameOrUrl}}', encodeURIComponent(options.assignmentNameOrUrl));
}
if ( options.exerciseIdOrUrl ) {
return url.replace('{{exerciseIdOrUrl}}', encodeURIComponent(options.exerciseIdOrUrl));
}
if ( options.gitlabProjectId ) {
return url.replace('{{gitlabProjectId}}', encodeURIComponent(options.gitlabProjectId));
}
}
return url;
}
}
export default new DojoBackendHelper();
\ No newline at end of file
......@@ -18,7 +18,17 @@ class ExerciseDockerCompose {
private currentStep: string = 'NOT_RUNNING';
constructor(private projectName: string, private assignmentFile: AssignmentFile, private executionFolder: string, private composeFileOverride: Array<string> = []) {
private readonly projectName: string;
private readonly assignmentFile: AssignmentFile;
private readonly executionFolder: string;
private readonly composeFileOverride: Array<string> = [];
constructor(projectName: string, assignmentFile: AssignmentFile, executionFolder: string, composeFileOverride: Array<string> = []) {
this.projectName = projectName;
this.assignmentFile = assignmentFile;
this.executionFolder = executionFolder;
this.composeFileOverride = composeFileOverride;
this.events.on('logs', (log: string, _error: boolean, displayable: boolean) => {
this.allLogs += log;
this.displayableLogs += displayable ? log : '';
......@@ -49,15 +59,15 @@ class ExerciseDockerCompose {
}
private registerChildProcess(childProcess: ChildProcessWithoutNullStreams, resolve: (value: (number | PromiseLike<number>)) => void, reject: (reason?: unknown) => void, displayable: boolean, rejectIfCodeIsNotZero: boolean) {
childProcess.stdout.on('data', (data) => {
childProcess.stdout.on('data', data => {
this.log(data.toString(), false, displayable);
});
childProcess.stderr.on('data', (data) => {
childProcess.stderr.on('data', data => {
this.log(data.toString(), true, displayable);
});
childProcess.on('exit', (code) => {
childProcess.on('exit', code => {
code === null || (rejectIfCodeIsNotZero && code !== 0) ? reject(code) : resolve(code);
});
}
......@@ -66,7 +76,8 @@ class ExerciseDockerCompose {
(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(' ') }`;
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
{
......
......@@ -17,7 +17,15 @@ class ExerciseResultsSanitizerAndValidator {
private resultsFilePath: string = '';
constructor(private folderResultsDojo: string, private folderResultsExercise: string, private containerExitCode: number) { }
private readonly folderResultsDojo: string;
private readonly folderResultsExercise: string;
private readonly containerExitCode: number;
constructor(folderResultsDojo: string, folderResultsExercise: string, containerExitCode: number) {
this.folderResultsDojo = folderResultsDojo;
this.folderResultsExercise = folderResultsExercise;
this.containerExitCode = containerExitCode;
}
private async resultsFileSanitization() {
this.events.emit('step', 'RESULTS_FILE_SANITIZATION', 'Sanitizing results file');
......@@ -33,11 +41,11 @@ class ExerciseResultsSanitizerAndValidator {
this.events.emit('endStep', 'RESULTS_FILE_SANITIZATION', 'Results file sanitized', false);
}
private async resultsFileProvided(path: string): Promise<boolean> {
private async resultsFileProvided(resultFilePath: string): Promise<boolean> {
// Results file schema validation
{
this.events.emit('step', 'VALIDATE_RESULTS_FILE', 'Validating results file schema');
const validationResults = Json5FileValidator.validateFile(ExerciseResultsFile, path);
const validationResults = Json5FileValidator.validateFile(ExerciseResultsFile, resultFilePath);
if ( !validationResults.isValid ) {
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);
......
import GitlabRepository from '../../shared/types/Gitlab/GitlabRepository';
import { CommitSchema } from '@gitbeaker/rest';
import * as Gitlab from '@gitbeaker/rest';
import User from './User';
import Assignment from './Assignment';
......@@ -18,7 +18,7 @@ interface Exercise {
assignment: Assignment | undefined;
isCorrection: boolean;
correctionCommit: CommitSchema | undefined;
correctionCommit: Gitlab.CommitSchema | undefined;
}
......
......@@ -2,16 +2,16 @@ enum ApiRoute {
LOGIN = '/login',
REFRESH_TOKENS = '/refresh_tokens',
TEST_SESSION = '/test_session',
GITLAB_CHECK_TEMPLATE_ACCESS = '/gitlab/project/{{id}}/checkTemplateAccess',
ASSIGNMENT_GET = '/assignments/{{nameOrUrl}}',
GITLAB_CHECK_TEMPLATE_ACCESS = '/gitlab/project/{{gitlabProjectId}}/checkTemplateAccess',
ASSIGNMENT_GET = '/assignments/{{assignmentNameOrUrl}}',
ASSIGNMENT_CREATE = '/assignments',
ASSIGNMENT_PUBLISH = '/assignments/{{nameOrUrl}}/publish',
ASSIGNMENT_UNPUBLISH = '/assignments/{{nameOrUrl}}/unpublish',
ASSIGNMENT_PUBLISH = '/assignments/{{assignmentNameOrUrl}}/publish',
ASSIGNMENT_UNPUBLISH = '/assignments/{{assignmentNameOrUrl}}/unpublish',
ASSIGNMENT_CORRECTION_LINK = '/assignments/{{assignmentNameOrUrl}}/corrections',
ASSIGNMENT_CORRECTION_UPDATE = '/assignments/{{assignmentNameOrUrl}}/corrections/{{exerciseIdOrUrl}}',
EXERCISE_CREATE = '/assignments/{{nameOrUrl}}/exercises',
EXERCISE_ASSIGNMENT = '/exercises/{{id}}/assignment',
EXERCISE_RESULTS = '/exercises/{{id}}/results'
EXERCISE_CREATE = '/assignments/{{assignmentNameOrUrl}}/exercises',
EXERCISE_ASSIGNMENT = '/exercises/{{exerciseIdOrUrl}}/assignment',
EXERCISE_RESULTS = '/exercises/{{exerciseIdOrUrl}}/results'
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment