diff --git a/Dockerfile_ExerciseChecker b/Dockerfile_ExerciseChecker index 7a54b91feb0874af3243f42cc14c35c63c656d6e..cb234927fe7054e98c1334fe9335d30bb6762c84 100644 --- a/Dockerfile_ExerciseChecker +++ b/Dockerfile_ExerciseChecker @@ -36,4 +36,6 @@ RUN apk add git ARG BUILD_WORKDIR -COPY --from=builder ${BUILD_WORKDIR}/bin/app /usr/local/bin/dojo_exercise_checker \ No newline at end of file +COPY --from=builder ${BUILD_WORKDIR}/bin/app /usr/local/bin/dojo_exercise_checker + +ADD sonar/ /sonar/ diff --git a/ExerciseChecker/.env.vault b/ExerciseChecker/.env.vault index 46939e0f3b226849349e4c2295ee5a91ddbb9396..252d92f508793d0ed469ad98cb09a797484b8b11 100644 --- a/ExerciseChecker/.env.vault +++ b/ExerciseChecker/.env.vault @@ -4,11 +4,11 @@ #/--------------------------------------------------/ # development -DOTENV_VAULT_DEVELOPMENT="fB7+PVy4fAW1WbIyBdMBiflwZysV3nJ1rot78zLLltQvwZK+0egbckRkKKymt75xb8KUlA03F+lkJn6004SbCBA9oz76AOOp83qcml2yQLqTOd4Z9iQq8aunENCYzCrRiuMbbDqGYAtxqVuE1NPtBn8qbkBh+bmB3KYlRChbui1W6h+GfPx1tmF7aRixKn4Rvky2mwW8YL4J3Ikt/rNVbcAAywPSoSWy3B6KKZxNESz0Ed7qynQUaRg5WnOT0bFRmpN4CbyiR9MaspH+LS/9MjlWCpM1g0OZO4BxZpnf4eZKm7ZENR1LjOzzqEBRM8AcU5iha4Civ4OBvhGdEJ9GbjDJ348Q6YK5CfscY2qRts6MimTlNlCfYYfu4ztuQuHm/rbxSDWePADFo6xbCK+ZLEhRg70Fdf2PFWxUMAKvEeZa6EDXfYfj1WRMNL9coHL2ahf7ZTg57G/LyfE4/hGw9lZT5VsyNgnm8eg4QXzRePLxqU1XvBKQQYmNJ3p+19wAcGKi3ud25K2E/F4qzXjO9siwPX4sfzR/8cRFfRDjbKz9rEsQgHZ2DZDVBbdekjyW2MQgMPzKN7Q0xN7rV6eHxBkM9GvQHWmcpbhjttVd0AcxiPdkS6Zbc1mMiA5qiXyB+DxXAxdaJgKbd5zxFKRaAb9WHjEe1FIEXZ6p1cNQBqb/oQgofmMS0LMPG2LYUjyjgM9dWXTKvWrEe7gNkU29qp0DW5aewud6Ladil02N5oXSsRxnTIMkyYN4HDei2HDMvKUOezVOkhE9aeU6" +DOTENV_VAULT_DEVELOPMENT="XzhpQfLtnDqz6aHzmNbmBclKx2JptArrxT3P6XJdZQn6H3eQvS9Kq8c3y+1DLyd45PoQGL3nbltkj8rzFiCGaXyrxQg/bDlj/MYXlnDAolJB2fiYAF7t9RmVqDZFSLQdYZI2hmw8hChRMUpopecKb/bsSBrhjqaDHWeQ2gigXjqgOUWs75mXiBY7SmkSExqoFKmys2AS2X6mDVssjnRoGsOiyrYPCZMTNH18dUSQNdbe3BZt1WKSnOxkbwp5RF2My66Hr8ZUCMgc8VpFeINDnCqSzRizlRcDDH/OVJj/9c9Mez1Zd1hLEuyITIKJBW73uqxq0OcTAyChvNLchua93r4J1Nyu4EAMQGr8lfT3zj0GYZPXR1ZFs0cL3bfFO61FxkvRv/20SgCgIUrSTpyaE/OAey2DGwIBrx9Z91P7D2plBvBJCxb7G2SaxMzaTAuE19ciEIMZ4cfltTsUU0S1ntmnoWBIFMsijHaaIroL/mC4nFcxlqv7eWPMXv0koeED2bf0vfC76BQWYEm1KEJ96nFlMIZ/t+lCsm5I/Pj/ML6UkOnQH3U0NxoSgfAbfhWHvHPKPRJn5VSt+CGirGJd30sGnWNEClByasRyoYdAn58jV96XVoVPLVP65ZB6pcVPeiQpg3seHkuW1ZkdSVgGb2su0wwooYB1gdLoBtyADFzqSWsjXjkEKT1Cj+mlHjiVY3ifbYb9jR0/Ib6D/Homxxpn0aPtWJS3VImT/rYsCdCF5YeKajFxKGfPilFiKWXfFqD9uu13TC0RBpT1vM6+HoFMIpabapNSgPbeRXSGUFcu1n1RL5bNkwHmr0nAwYMXRFm1byKCDBooX/qVD5s4p4MS/Pto6KK8ERJeRC5F+V0E6bhUNEfrSSK7OVxGrFynH02z53zUKHx9c+QSjDrUxp+j7DLhiEK3x71NkQhjgrmPsTpKx4ZG0b/a0p6L4hyve9OhhOxOppiZF2hnLhccxnUZ+h8grjD9UdcXeoCz" # production -DOTENV_VAULT_PRODUCTION="Ib1rqxyJOTdSBQoYjytpBBrkas6SBkd3WSwZTlWKyUbiZa+2xxVLE+yFYQ4OLIJ2pBuE6s+eEmFgsAtpn1x+W2dA4bdypHihSUL4fcrvMA0r4mJw30pekw8bVThbevNn8+TH1TTOcZHX7V98BM38EIf7hOsL7hwBW4qOI4g4dkSMWGtHZUqAr3DpdFUip3Tt9QOQUukJ3kfSbZNpS3VFFFKO46RxXffjfHmqCOQqEo4uYUoeSnCdMXcezp1BpUsCVeRtUQ9/dQrjrM2DawktOfCtIUtRoK/LiUJ2WBfk7NQaI5THPSLDp4zSbRQQZykI" +DOTENV_VAULT_PRODUCTION="okx53TD1qwOlbHszl84ac427GAEc4bhOKT5uh9DOYuPdfvZMce1M+ZErFPjKKZbXAuQ6zaeOQI1ZIBYXO6Vl8rF92rRFqAo9T93vXrqT1Y6HPumVWiN4G0Ch+TJs7Q9Rzt02icLAQOkqt8C8ARf3hMVu6R/PZ5Cj5Dgq0BL8GIZCCG65gQE4Riq3XCHWJ977Hk871hPVsOLCxIWwE24edeF4ebQbORfwAyo4m/GZ8Am6nISOLNbA8lZu1jVaeDJVlZoo671MmwyTiINME2BA3vuN2zxmXdYVL3zgdVPnxJKQaLDKmKDBbBLTjwy6a8Zyf9tmFxECWQ8WKwVQ7lbQ7hm8cGcniGZFLVRhG0u6SC02eGxpnYpAbjU+bCupBJwcRKM9f5azHw6vP27nRHD2wk6cLvmXPWmwHOaKwkxnb88Eep/FuGp8QgqTLHjRXxMN4feRIrOmxRGCWhPBGd03B+0FVubdxdcCIpTdPADgcFcjmOndOjxTp/8yV5fclWXM297u6Q7XUVbnZH1JyjD3jDAltA==" # test -DOTENV_VAULT_TEST="fpM5Fw6ZoPRaxZkJbbmWR6/Z7/fVW6WE/+sO6L/wYEx4XaOc3iGeCvRQr1s7vNCbj4h+ftaOIn2vd/JEPGcmhq6o+EtuQ0KzzCNaUiwLfeT5YfeDMDnmfo8gvFLQH0L7CvbT+5m/sDTXko0nBW+gca3bmCqljLTyC5Oembbnfl4sxfuDWLdWdSDqAIV3+gvgNTX9YPYe/Q9Mhg0w6fO/wdRBa3qSUgzdXy8+rOPArPI/zywEWgvuMWyoOSCIwOIhlRQYG7M2VTkTp2o4qVDTtUBJN8JS93js4XMVri3Xlk3wCC/E+uP7gmCeVaRZKT2O+qQ2" +DOTENV_VAULT_TEST="E5CN0oMh9kSb9vzJW9hjlBn+NBKoaIs6RBXA7nLw7RWy8ddazHSqq/S1BYRPz1tyRC5cW+JuCYNo6uJHwdTJFJIjxSsoK6gIvGKoDKc3Fo0d8aKSe4vWcBNrYpL8k8/rkoDHe62I8Ec4dGXnsNPKzIxgkaWCFZwygwGEm9Ar2i27intpHnX9izUKJ+3+j9dy+2kIUpm9rclm0FDF/30ZTehh6WN3Fu1+HBgU8ypyQW3Js2Qrtc2o6YDFoFT22MqWZCikChwm+GKMUA69NK2Dzl/Ju8VWKorBWD1bf8ej5LgIRm0pQ7PUuC4hdGHm8PYXwR+Xq0wRQT6V6+scSIhmQnYVh38KRR4ogepVOsWztyMmtMUFegkeqk580s+M/RVS2KxyZXqZfgpetTBSKYnne2/MI7tVnAJ/8mK6zv/tw8FhvO7duV6v89jkTf0p/U4WgUezO2/39xKcFrMJQF3mVD2p9H2OXqrK9L9tfWozEeoNZFnSv3NeA5kt0ovf2uu7qYjFrAt+ygiRr+eS7wJll12pOpC2" diff --git a/ExerciseChecker/.idea/vcs.xml b/ExerciseChecker/.idea/vcs.xml index d86e73b516141a07b5ba1f16f6f315749512bedc..8bd3f8944d87804b8c8764a48898c52d5ff89c64 100644 --- a/ExerciseChecker/.idea/vcs.xml +++ b/ExerciseChecker/.idea/vcs.xml @@ -2,6 +2,7 @@ <project version="4"> <component name="VcsDirectoryMappings"> <mapping directory="$PROJECT_DIR$/.." vcs="Git" /> + <mapping directory="$PROJECT_DIR$/.idea/jetbrainsConfiguration" vcs="Git" /> <mapping directory="$PROJECT_DIR$/src/shared" vcs="Git" /> <mapping directory="$PROJECT_DIR$/src/sharedByClients" vcs="Git" /> </component> diff --git a/ExerciseChecker/package-lock.json b/ExerciseChecker/package-lock.json index 23773092df94a83c106a1c869a157f895b70fce2..e92113de85a4c570b76fffbbd3241c6ba2ee0cfc 100644 --- a/ExerciseChecker/package-lock.json +++ b/ExerciseChecker/package-lock.json @@ -769,6 +769,45 @@ "node": ">=18.20.0" } }, + "node_modules/@gitbeaker/core": { + "version": "40.0.3", + "resolved": "https://registry.npmjs.org/@gitbeaker/core/-/core-40.0.3.tgz", + "integrity": "sha512-MzeY4oCtoa9zmPIkQIdC2KU8cGmHIXwnAi0L6jjjouqjy6kcA4BydZf8W5Xsj27Rw5iiyhfj8YC1/O3CgrzvCQ==", + "dependencies": { + "@gitbeaker/requester-utils": "^40.0.3", + "qs": "^6.11.2", + "xcase": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@gitbeaker/requester-utils": { + "version": "40.0.3", + "resolved": "https://registry.npmjs.org/@gitbeaker/requester-utils/-/requester-utils-40.0.3.tgz", + "integrity": "sha512-L8JpuMIsvXTHfu/2wXzkc5QyfQJSWg4XyEPStHq1ig5SAcbxxqbBoe8ed27eUXLah+PcGrPInMK4cCMxhQm41g==", + "dependencies": { + "picomatch-browser": "^2.2.6", + "qs": "^6.11.2", + "rate-limiter-flexible": "^4.0.0", + "xcase": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@gitbeaker/rest": { + "version": "40.0.3", + "resolved": "https://registry.npmjs.org/@gitbeaker/rest/-/rest-40.0.3.tgz", + "integrity": "sha512-ihaA0GX3yCo4oUWbISkcjFMIw+WxDAC9L+bEYq2irz4wpv/0EpAU/0jKjggPzY4cGWL9VAyPhew77VeACv4YWw==", + "dependencies": { + "@gitbeaker/core": "^40.0.3", + "@gitbeaker/requester-utils": "^40.0.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -4726,6 +4765,11 @@ "integrity": "sha512-2/dGHpDFpeA0+755oUkW+EKyklqLS9lu0go9pDsbhqQjZcxfRyJ6LA4JI0+HAdZ2bemD/oOjUeZQB2lCZqXQfQ==", "license": "ISC" }, + "node_modules/rate-limiter-flexible": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-4.0.1.tgz", + "integrity": "sha512-2/dGHpDFpeA0+755oUkW+EKyklqLS9lu0go9pDsbhqQjZcxfRyJ6LA4JI0+HAdZ2bemD/oOjUeZQB2lCZqXQfQ==" + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -4988,6 +5032,22 @@ "node": ">=10" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/ExerciseChecker/package.json b/ExerciseChecker/package.json index e29516a4f21b5ec1d7d6aaabe1baba31615f1359..6778b408365be469a35fae89212983accaab73fe 100644 --- a/ExerciseChecker/package.json +++ b/ExerciseChecker/package.json @@ -8,9 +8,9 @@ "bin" : { "dirmanager": "./dist/app.js" }, - "pkg" : { + "pkg": { "scripts": [], - "assets" : [ + "assets": [ "node_modules/axios/dist/node/axios.cjs", ".env", "config.env", diff --git a/ExerciseChecker/src/app.ts b/ExerciseChecker/src/app.ts index d813a4d496335954aa8055b57dc10f6b006efdfb..c7975111d58c4fbf245d435db9c83ec5d225f67e 100644 --- a/ExerciseChecker/src/app.ts +++ b/ExerciseChecker/src/app.ts @@ -17,8 +17,12 @@ import ClientsSharedExerciseHelper from './sharedByClients/helpers/Dojo import Icon from './shared/types/Icon.js'; import path from 'node:path'; import SharedAssignmentHelper from './shared/helpers/Dojo/SharedAssignmentHelper'; +import Exercise from './sharedByClients/models/Exercise'; +import SonarAnalyzer from './sharedByClients/helpers/Dojo/SonarAnalyzer'; +import SharedConfig from './shared/config/SharedConfig'; +let exercise: Exercise | undefined; let exerciseAssignment: ExerciseAssignment | undefined; let exerciseDockerCompose: ExerciseDockerCompose; let exerciseResultsValidation: ExerciseResultsSanitizerAndValidator; @@ -32,9 +36,10 @@ let haveResultsVolume: boolean; */ async function downloadImmutablesFiles() { console.log(Styles.INFO(`${ Icon.INFO }️ Checking the exercise's assignment and his immutable files`)); + exercise = await DojoBackendManager.getExercise(); exerciseAssignment = await DojoBackendManager.getExerciseAssignment(); - if ( !exerciseAssignment ) { - console.error(Styles.ERROR(`${ Icon.ERROR } Error while getting the exercise's assignment`)); + if ( !exerciseAssignment || !exercise ) { + console.error(Styles.ERROR(`${ Icon.ERROR } Error while getting the exercise or exercise's assignment`)); process.exit(ExerciseCheckerError.EXERCISE_ASSIGNMENT_GET_ERROR); } @@ -49,9 +54,39 @@ async function downloadImmutablesFiles() { haveResultsVolume = exerciseAssignment.assignmentFile.result.volume !== undefined; } - /** * Step 2: + * - Run sonar analysis + */ +async function runSonarAnalysis() { + if ( SharedConfig.sonar.enabled && exerciseAssignment!.assignment.useSonar ) { + console.log(Styles.INFO(`${ Icon.INFO } Running Sonar analysis on the exercise`)); + + const buildSuccess = SonarAnalyzer.buildDocker(); + if ( !buildSuccess ) { + console.error(Styles.ERROR(`${ Icon.ERROR } Error while building the Docker image`)); + process.exit(ExerciseCheckerError.SONAR_DOCKER_ERROR); + } + + if ( SonarAnalyzer.mustRunBuild(exerciseAssignment!.assignment.language, exerciseAssignment!.assignmentFile.buildLine) ) { + const buildSuccess = SonarAnalyzer.runBuildStep(exerciseAssignment!.assignmentFile.buildLine!); + if ( !buildSuccess ) { + console.error(Styles.ERROR(`${ Icon.ERROR } Error while compiling exercise files`)); + process.exit(ExerciseCheckerError.SONAR_BUILD_ERROR); + } + } + + const runSuccess = SonarAnalyzer.runAnalysis(exercise!.sonarKey, exerciseAssignment!.assignment.language, exerciseAssignment!.assignmentFile.buildLine); + if ( !runSuccess ) { + console.error(Styles.ERROR(`${ Icon.ERROR } Sonar gate failed`)); + process.exit(ExerciseCheckerError.SONAR_GATE_FAILED); + } + } +} + + +/** + * Step 3: * - Get override of docker-compose file (for override the volume by a bind mount to the results folder shared between dind and the host) * - Run docker-compose file * - Get logs from linked services @@ -99,7 +134,7 @@ async function runDockerCompose() { /** - * Step 3: + * Step 4: * - Check content requirements and content size */ async function checkExecutionContent() { @@ -130,8 +165,9 @@ async function checkExecutionContent() { } catch ( error ) { /* empty */ } } + /** - * Step 4: + * Step 5: * - Upload results */ async function uploadResults() { @@ -157,7 +193,7 @@ async function uploadResults() { /** - * Step 5: + * Step 6: * - Display results * - Exit with container exit code */ @@ -178,6 +214,7 @@ async function displayResults() { console.log(Styles.APP_NAME(`${ Config.appName } (version {{VERSION}})`)); await downloadImmutablesFiles(); + await runSonarAnalysis(); await runDockerCompose(); await checkExecutionContent(); await uploadResults(); diff --git a/ExerciseChecker/src/managers/DojoBackendManager.ts b/ExerciseChecker/src/managers/DojoBackendManager.ts index 162d8f8372cd27fa6ff43157415cf55c8e6073ea..750757148e16aabc3f64476fb013f665c2165584 100644 --- a/ExerciseChecker/src/managers/DojoBackendManager.ts +++ b/ExerciseChecker/src/managers/DojoBackendManager.ts @@ -6,9 +6,18 @@ import ExerciseResultsFile from '../shared/types/Dojo/ExerciseResultsFile.js'; import ApiRoute from '../sharedByClients/types/Dojo/ApiRoute.js'; import { IFileDirStat } from '../shared/helpers/recursiveFilesStats/RecursiveFilesStats.js'; import DojoBackendHelper from '../sharedByClients/helpers/Dojo/DojoBackendHelper.js'; +import Exercise from '../sharedByClients/models/Exercise'; class DojoBackendManager { + public async getExercise(): Promise<Exercise | undefined> { + try { + return (await axios.get<DojoBackendResponse<Exercise>>(DojoBackendHelper.getApiUrl(ApiRoute.EXERCISE_GET_DELETE).replace('{{id}}', Config.exercise.id))).data.data; + } catch ( error ) { + return undefined; + } + } + public async getExerciseAssignment(): Promise<ExerciseAssignment | undefined> { try { return (await axios.get<DojoBackendResponse<ExerciseAssignment>>(DojoBackendHelper.getApiUrl(ApiRoute.EXERCISE_ASSIGNMENT, { exerciseIdOrUrl: Config.exercise.id }))).data.data; diff --git a/ExerciseChecker/src/shared b/ExerciseChecker/src/shared index 92008b25753c96bef9917cd39baab3a4d160cd9c..937081e68f6127b669daca30e57c43e73b9c96c9 160000 --- a/ExerciseChecker/src/shared +++ b/ExerciseChecker/src/shared @@ -1 +1 @@ -Subproject commit 92008b25753c96bef9917cd39baab3a4d160cd9c +Subproject commit 937081e68f6127b669daca30e57c43e73b9c96c9 diff --git a/ExerciseChecker/src/sharedByClients b/ExerciseChecker/src/sharedByClients index e549c7f03955a2ac295a8b8486f921154af751ee..59308a719fdee1a2025e90a18774d56fc6d11d9b 160000 --- a/ExerciseChecker/src/sharedByClients +++ b/ExerciseChecker/src/sharedByClients @@ -1 +1 @@ -Subproject commit e549c7f03955a2ac295a8b8486f921154af751ee +Subproject commit 59308a719fdee1a2025e90a18774d56fc6d11d9b diff --git a/sonar/Dockerfile b/sonar/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..cc163bca27eb2fff8d20660d501ccae283facbf3 --- /dev/null +++ b/sonar/Dockerfile @@ -0,0 +1,28 @@ +FROM gcc:14 + +ARG SONAR_HOST_URL=https://isc-sonar.edu.hesge.ch + +RUN apt update && apt install -y curl unzip build-essential make g++ clang && apt clean + +# Download sonar tools +RUN mkdir -p /sonar && \ + curl -sSLo sonar-scanner.zip https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip && \ + unzip -o sonar-scanner.zip -d /sonar && \ + mv /sonar/sonar-scanner-5.0.1.3006-linux/* /sonar/ && \ + ln -s /sonar/bin/sonar-scanner /usr/local/bin/sonar-scanner && \ + curl --insecure -sSLo build-wrapper-linux-x86.zip "$SONAR_HOST_URL/static/cpp/build-wrapper-linux-x86.zip" && \ + unzip -o build-wrapper-linux-x86.zip -d /tmp && \ + mv /tmp/build-wrapper-linux-x86/* /usr/local/bin/ && \ + rm build-wrapper-linux-x86.zip sonar-scanner.zip + + +COPY ./cacerts /tmp/cacerts +ENV SONAR_SCANNER_OPTS="-Djavax.net.ssl.trustStore=/tmp/cacerts" +RUN mkdir -p /usr/src && \ + useradd -m sonar && \ + chown sonar:sonar /usr/src && \ + chmod 744 /tmp/cacerts + +USER sonar +WORKDIR /usr/src + diff --git a/sonar/cacerts b/sonar/cacerts new file mode 100644 index 0000000000000000000000000000000000000000..3259ae13ad7391f25b7f093d12f71fb5da333272 Binary files /dev/null and b/sonar/cacerts differ