From 8ece6bfc167da89976865a08a47e76d954fc3775 Mon Sep 17 00:00:00 2001
From: Joel von der Weid <joel.von-der-weid@hesge.ch>
Date: Mon, 10 Jun 2024 15:53:12 +0200
Subject: [PATCH] Implement sonar scan for assignments

---
 helpers/Dojo/AssignmentValidator.ts           | 65 +++++++++++++++++--
 helpers/Dojo/ClientsSharedAssignmentHelper.ts | 10 ++-
 2 files changed, 66 insertions(+), 9 deletions(-)

diff --git a/helpers/Dojo/AssignmentValidator.ts b/helpers/Dojo/AssignmentValidator.ts
index 1d2743b..f6e6ed2 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 b7672f1..ab107a4 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);
     }
 }
-- 
GitLab