diff --git a/helpers/Dojo/AssignmentValidator.ts b/helpers/Dojo/AssignmentValidator.ts index 1d2743b66b4e4b3ee52dd6567f311fb2a1fe84bd..f6e6ed2df5a9c411d6838ebf2a4600c1dc575499 100644 --- a/helpers/Dojo/AssignmentValidator.ts +++ b/helpers/Dojo/AssignmentValidator.ts @@ -13,6 +13,8 @@ import ExerciseDockerCompose from './ExerciseDockerCompose'; import util from 'util'; import Assignment, { Language } from '../../models/Assignment'; import ClientsSharedAssignmentHelper from './ClientsSharedAssignmentHelper'; +import { spawnSync } from 'node:child_process'; +import SharedConfig from '../../../shared/config/SharedConfig'; const execAsync = util.promisify(exec); @@ -21,6 +23,7 @@ const execAsync = util.promisify(exec); class AssignmentValidator { private readonly folderAssignment: string; private readonly doDown: boolean; + private readonly runSonar: boolean; readonly events: TypedEmitter<AssignmentValidatorEvents> = new TypedEmitter<AssignmentValidatorEvents>(); @@ -39,9 +42,10 @@ class AssignmentValidator { private assignmentFile!: AssignmentFile; private assignment!: Assignment; - constructor(folderAssignment: string, doDown: boolean = false) { + constructor(folderAssignment: string, doDown: boolean = false, runSonar: boolean = false) { this.folderAssignment = folderAssignment; this.doDown = doDown; + this.runSonar = runSonar; this.events.on('logs', (log: string, _error: boolean, displayable: boolean) => { this.allLogs += log; @@ -133,17 +137,17 @@ class AssignmentValidator { private async checkAssignment() { this.newStep('ASSIGNMENT_CHECKING', 'Please wait while we are checking the assignment...'); - this.newSubStep('ASSIGNMENT_EXISTS', 'Checking if the assignment exists'); - const resp = await ClientsSharedAssignmentHelper.getAssignmentFromPath(this.folderAssignment); + + const resp = await ClientsSharedAssignmentHelper.getAssignmentByName(this.folderAssignment); if ( resp == undefined ) { this.emitError(`The assignment doesn't exist. An assignment must be created with "assignment create" before checking it.`, `Assignment doesn't exists`, AssignmentCheckerError.ASSIGNMENT_MISSING); return; } else { this.assignment = resp; } - this.endSubStep('Assignment exists', false); - this.endStep('Assignment exists and is valid', false); + + this.endStep('Assignment exists', false); } /** @@ -281,7 +285,54 @@ class AssignmentValidator { } /** - * Step 6: Run + * Step 6: Sonar analysis + * - Analyse the project with SonarCube + * @private + */ + private async sonarAnalysis() { + if ( this.assignment.useSonar && this.runSonar ) { + this.newStep('ASSIGNMENT_SONAR', 'Please wait while we are running Sonar analysis on the assignment...'); + + let additionalParams: string[] = []; + + this.newSubStep('SONAR_BUILD', 'Build files'); + + const buildProcess = spawnSync('docker', [ 'build', '--tag', 'dojo-sonar-scanner', '/sonar' ]); + if ( buildProcess.status !== 0 ) { + this.emitError(`Build sonar image failed`, 'Sonar analysis failure', AssignmentCheckerError.SONAR_ANALYSIS_FAILED); + console.log(buildProcess.stdout.toString()); + console.log(buildProcess.stderr.toString()); + return; + } + + if ( [ Language.c, Language.cpp, Language.objc ].includes(this.assignment.language) && this.assignmentFile.buildLine != undefined ) { + const process = spawnSync('docker run -v ./:/usr/src dojo-sonar-scanner /usr/local/bin/build-wrapper-linux-x86-64 --out-dir bw-output ' + this.assignmentFile.buildLine, [], { shell: true }); + if ( process.status !== 0 ) { + this.emitError(`Failed to build files using buildLine`, 'Sonar analysis failure', AssignmentCheckerError.SONAR_ANALYSIS_FAILED); + console.log(process.stdout.toString()); + console.log(process.stderr.toString()); + return; + } + additionalParams = [ '-Dsonar.cfamily.build-wrapper-output=/usr/src/bw-output' ]; + } + this.endSubStep('Sonar files build success', false); + + this.newSubStep('SONAR_RUN', 'Run sonar analysis'); + + + const process = spawnSync('docker', [ 'run', '-v', './:/usr/src', 'dojo-sonar-scanner', 'sonar-scanner', '-Dsonar.qualitygate.wait=true', '-Dsonar.projectKey=' + this.assignment.sonarKey, '-Dsonar.sources=.', '-Dsonar.host.url=' + SharedConfig.sonar.url, '-Dsonar.login=' + SharedConfig.sonar.token, ...additionalParams ]); + if ( process.status !== 0 ) { + this.emitError(`Sonar gate failed`, 'Sonar analysis failure', AssignmentCheckerError.SONAR_ANALYSIS_FAILED); + return; + } + this.endSubStep('Sonar gate passed', false); + + this.endStep('Sonar analysis success', false); + } + } + + /** + * Step 7: 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 */ @@ -335,6 +386,8 @@ class AssignmentValidator { this.dockerfilesValidation(); + await this.sonarAnalysis(); + await this.runAssignment(); this.finished(true, 0); diff --git a/helpers/Dojo/ClientsSharedAssignmentHelper.ts b/helpers/Dojo/ClientsSharedAssignmentHelper.ts index b7672f15297a7489529ffd45234e0cea9146daf8..ab107a4f410f9f88461f690a40cab9d79d7e5afa 100644 --- a/helpers/Dojo/ClientsSharedAssignmentHelper.ts +++ b/helpers/Dojo/ClientsSharedAssignmentHelper.ts @@ -30,10 +30,11 @@ class ClientsSharedAssignmentHelper { })); } - private async getAssignment(url: string): Promise<Assignment | undefined> { + private async getAssignment(nameOrUrl: string): Promise<Assignment | undefined> { try { - return (await axios.get<DojoBackendResponse<Assignment>>(`${ ClientsSharedConfig.apiURL }${ ApiRoute.ASSIGNMENT_GET }`.replace('{{nameOrUrl}}', encodeURIComponent(url)))).data.data; + return (await axios.get<DojoBackendResponse<Assignment>>(`${ ClientsSharedConfig.apiURL }${ ApiRoute.ASSIGNMENT_GET }`.replace('{{nameOrUrl}}', encodeURIComponent(nameOrUrl)))).data.data; } catch ( error ) { + console.log(error); return undefined; } } @@ -43,6 +44,10 @@ class ClientsSharedAssignmentHelper { return Array.from(content.matchAll(regexp), m => m[1])[0]; } + async getAssignmentByName(name: string): Promise<Assignment | undefined> { + return await this.getAssignment(name); + } + async getAssignmentFromPath(path: string): Promise<Assignment | undefined> { const fullPath = join(path, "./.git/config"); if (!existsSync(fullPath)) { @@ -50,7 +55,6 @@ class ClientsSharedAssignmentHelper { } const content = readFileSync(fullPath, 'utf-8'); const url = await this.extractOriginUrl(content); - return await this.getAssignment(url); } }