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

Sonar => Resolve issues

parent 9a20c1a9
Branches
Tags
No related merge requests found
Pipeline #30056 passed
...@@ -5,6 +5,9 @@ Wiki/.idea ...@@ -5,6 +5,9 @@ Wiki/.idea
ExerciseChecker/src/config/Version.ts ExerciseChecker/src/config/Version.ts
sonarlint.xml
sonarlint/
############################ MacOS ############################ MacOS
# General # General
.DS_Store .DS_Store
......
...@@ -15,4 +15,7 @@ ...@@ -15,4 +15,7 @@
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="SonarLintModuleSettings">
<option name="uniqueId" value="39cd055e-87b7-4af4-82da-6d4c7c75ce23" />
</component>
</module> </module>
\ No newline at end of file
This diff is collapsed.
...@@ -29,17 +29,20 @@ ...@@ -29,17 +29,20 @@
"test" : "echo \"Error: no test specified\" && exit 1" "test" : "echo \"Error: no test specified\" && exit 1"
}, },
"dependencies" : { "dependencies" : {
"@gitbeaker/rest" : "^39.34.3",
"axios" : "^1.6.5", "axios" : "^1.6.5",
"boxen" : "^5.1.2", "boxen" : "^5.1.2",
"chalk" : "^4.1.2", "chalk" : "^4.1.2",
"dotenv" : "^16.3.1", "dotenv" : "^16.3.1",
"dotenv-expand" : "^10.0.0", "dotenv-expand" : "^10.0.0",
"form-data" : "^4.0.0",
"fs-extra" : "^11.2.0", "fs-extra" : "^11.2.0",
"http-status-codes" : "^2.3.0", "http-status-codes" : "^2.3.0",
"json5" : "^2.2.3", "json5" : "^2.2.3",
"ora" : "^5.4.1", "ora" : "^5.4.1",
"tar-stream" : "^3.1.6", "tar-stream" : "^3.1.6",
"winston" : "^3.11.0", "winston" : "^3.11.0",
"winston-transport" : "^4.7.0",
"yaml" : "^2.3.4", "yaml" : "^2.3.4",
"zod" : "^3.22.4", "zod" : "^3.22.4",
"zod-validation-error": "^3.0.0" "zod-validation-error": "^3.0.0"
......
...@@ -30,150 +30,161 @@ import ClientsSharedExerciseHelper from './sharedByClients/helpers/Dojo ...@@ -30,150 +30,161 @@ import ClientsSharedExerciseHelper from './sharedByClients/helpers/Dojo
import Icon from './shared/types/Icon'; import Icon from './shared/types/Icon';
(async () => { let exerciseAssignment: ExerciseAssignment | undefined;
HttpManager.registerAxiosInterceptor(); let exerciseDockerCompose: ExerciseDockerCompose;
let exerciseResultsValidation: ExerciseResultsSanitizerAndValidator;
console.log(Styles.APP_NAME(`${ Config.appName } (version {{VERSION}})`));
let haveResultsVolume: boolean;
let exerciseAssignment: ExerciseAssignment | undefined;
let exerciseDockerCompose: ExerciseDockerCompose; /**
let exerciseResultsValidation: ExerciseResultsSanitizerAndValidator; * Step 1:
* - Read the dojo assignment file from the assignment repository
let haveResultsVolume: boolean; * - Download immutables files (maybe throw or show an error if the files have been modified ?)
*/
async function downloadImmutablesFiles() {
/* console.log(Styles.INFO(`${ Icon.INFO }️ Checking the exercise's assignment and his immutable files`));
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 1: exerciseAssignment = await DojoBackendManager.getExerciseAssignment();
- Read the dojo assignment file from the assignment repository if ( !exerciseAssignment ) {
- Download immutables files (maybe throw or show an error if the files have been modified ?) console.error(Styles.ERROR(`${ Icon.ERROR } Error while getting the exercise's assignment`));
*/ process.exit(ExerciseCheckerError.EXERCISE_ASSIGNMENT_GET_ERROR);
{ }
console.log(Styles.INFO(`${ Icon.INFO }️Checking the exercise's assignment and his immutable files`));
exerciseAssignment = await DojoBackendManager.getExerciseAssignment();
if ( !exerciseAssignment ) {
console.error(Styles.ERROR(`${ Icon.ERROR } Error while getting the exercise's assignment`));
process.exit(ExerciseCheckerError.EXERCISE_ASSIGNMENT_GET_ERROR);
}
exerciseAssignment.immutable.forEach(immutableFile => {
const filePath = path.join(Config.folders.project, immutableFile.file_path);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, immutableFile.content, { encoding: 'base64' });
});
haveResultsVolume = exerciseAssignment.assignmentFile.result.volume !== undefined; exerciseAssignment.immutable.forEach(immutableFile => {
const filePath = path.join(Config.folders.project, immutableFile.file_path);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, immutableFile.content, { encoding: 'base64' });
});
haveResultsVolume = exerciseAssignment.assignmentFile.result.volume !== undefined;
}
/**
* Step 2:
* - 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
*/
async function runDockerCompose() {
let composeFileOverride: string[] = [];
const composeOverridePath: string = path.join(Config.folders.project, 'docker-compose-override.yml');
if ( haveResultsVolume ) {
const composeOverride = fs.readFileSync(path.join(__dirname, '../assets/docker-compose-override.yml'), 'utf8').replace('{{VOLUME_NAME}}', exerciseAssignment!.assignmentFile.result.volume!).replace('{{MOUNT_PATH}}', Config.folders.resultsExercise);
fs.writeFileSync(composeOverridePath, composeOverride);
composeFileOverride = [ composeOverridePath ];
} }
exerciseDockerCompose = new ExerciseDockerCompose(ClientsSharedConfig.dockerCompose.projectName, exerciseAssignment!.assignmentFile, Config.folders.project, composeFileOverride);
/* try {
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 2: await new Promise<void>((resolve, reject) => {
- Get override of docker-compose file (for override the volume by a bind mount to the results folder shared between dind and the host) exerciseDockerCompose.events.on('step', (_name: string, message: string) => {
- Run docker-compose file console.log(Styles.INFO(`${ Icon.INFO } ${ message }`));
- Get logs from linked services
*/
{
let composeFileOverride: string[] = [];
const composeOverridePath: string = path.join(Config.folders.project, 'docker-compose-override.yml');
if ( haveResultsVolume ) {
const composeOverride = fs.readFileSync(path.join(__dirname, '../assets/docker-compose-override.yml'), 'utf8').replace('{{VOLUME_NAME}}', exerciseAssignment.assignmentFile.result.volume!).replace('{{MOUNT_PATH}}', Config.folders.resultsExercise);
fs.writeFileSync(composeOverridePath, composeOverride);
composeFileOverride = [ composeOverridePath ];
}
exerciseDockerCompose = new ExerciseDockerCompose(ClientsSharedConfig.dockerCompose.projectName, exerciseAssignment.assignmentFile, Config.folders.project, composeFileOverride);
try {
await new Promise<void>((resolve, reject) => {
exerciseDockerCompose.events.on('step', (_name: string, message: string) => {
console.log(Styles.INFO(`${ Icon.INFO } ${ message }`));
});
exerciseDockerCompose.events.on('endStep', (_stepName: string, message: string, error: boolean) => {
if ( error ) {
console.error(Styles.ERROR(`${ Icon.ERROR } ${ message }`));
}
});
exerciseDockerCompose.events.on('finished', (success: boolean) => {
success ? resolve() : reject();
});
exerciseDockerCompose.run();
}); });
} catch ( error ) { /* empty */ }
fs.rmSync(composeOverridePath, { force: true }); exerciseDockerCompose.events.on('endStep', (_stepName: string, message: string, error: boolean) => {
fs.writeFileSync(path.join(Config.folders.resultsDojo, 'dockerComposeLogs.txt'), exerciseDockerCompose.allLogs); if ( error ) {
console.error(Styles.ERROR(`${ Icon.ERROR } ${ message }`));
}
});
if ( !exerciseDockerCompose.success ) { exerciseDockerCompose.events.on('finished', (success: boolean) => {
console.error(Styles.ERROR(`${ Icon.ERROR } Execution logs are available in artifacts`)); success ? resolve() : reject();
process.exit(exerciseDockerCompose.exitCode); });
}
}
exerciseDockerCompose.run();
});
} catch ( error ) { /* empty */ }
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 3: Check content requirements and content size fs.rmSync(composeOverridePath, { force: true });
{ fs.writeFileSync(path.join(Config.folders.resultsDojo, 'dockerComposeLogs.txt'), exerciseDockerCompose.allLogs);
exerciseResultsValidation = new ExerciseResultsSanitizerAndValidator(Config.folders.resultsDojo, Config.folders.resultsExercise, exerciseDockerCompose.exitCode);
try { if ( !exerciseDockerCompose.success ) {
await new Promise<void>((resolve) => { console.error(Styles.ERROR(`${ Icon.ERROR } Execution logs are available in artifacts`));
exerciseResultsValidation.events.on('step', (_name: string, message: string) => { process.exit(exerciseDockerCompose.exitCode);
console.log(Styles.INFO(`${ Icon.INFO } ${ message }`)); }
}); }
exerciseResultsValidation.events.on('endStep', (_stepName: string, message: string, error: boolean) => {
if ( error ) {
console.error(Styles.ERROR(`${ Icon.ERROR } ${ message }`));
}
});
exerciseResultsValidation.events.on('finished', (success: boolean, exitCode: number) => { /**
if ( !success ) { * Step 3:
process.exit(exitCode); * - Check content requirements and content size
} */
async function checkExecutionContent() {
exerciseResultsValidation = new ExerciseResultsSanitizerAndValidator(Config.folders.resultsDojo, Config.folders.resultsExercise, exerciseDockerCompose.exitCode);
resolve(); try {
}); await new Promise<void>(resolve => {
exerciseResultsValidation.events.on('step', (_name: string, message: string) => {
console.log(Styles.INFO(`${ Icon.INFO } ${ message }`));
});
exerciseResultsValidation.run(); exerciseResultsValidation.events.on('endStep', (_stepName: string, message: string, error: boolean) => {
if ( error ) {
console.error(Styles.ERROR(`${ Icon.ERROR } ${ message }`));
}
}); });
} catch ( error ) { /* empty */ }
}
exerciseResultsValidation.events.on('finished', (success: boolean, exitCode: number) => {
if ( !success ) {
process.exit(exitCode);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 4: Upload results resolve();
{
try {
console.log(Styles.INFO(`${ Icon.INFO } Uploading results to the dojo server`));
const commit: Record<string, string> = {};
Toolbox.getKeysWithPrefix(process.env, 'CI_COMMIT_').forEach(key => {
commit[Toolbox.snakeToCamel(key.replace('CI_COMMIT_', ''))] = process.env[key] as string;
}); });
const files = await RecursiveFilesStats.explore(Config.folders.resultsVolume, { exerciseResultsValidation.run();
replacePathByRelativeOne: true, });
liteStats : true } catch ( error ) { /* empty */ }
}); }
/**
* Step 4:
* - Upload results
*/
async function uploadResults() {
try {
console.log(Styles.INFO(`${ Icon.INFO } Uploading results to the dojo server`));
const commit: Record<string, string> = {};
Toolbox.getKeysWithPrefix(process.env, 'CI_COMMIT_').forEach(key => {
commit[Toolbox.snakeToCamel(key.replace('CI_COMMIT_', ''))] = process.env[key] as string;
});
await DojoBackendManager.sendResults(exerciseDockerCompose.exitCode, commit, exerciseResultsValidation.exerciseResults, files, await ArchiveHelper.getBase64(Config.folders.resultsVolume)); const files = await RecursiveFilesStats.explore(Config.folders.resultsVolume, {
} catch ( error ) { replacePathByRelativeOne: true,
console.error(Styles.ERROR(`${ Icon.ERROR } Error while uploading the results`)); liteStats : true
console.error(JSON.stringify(error)); });
process.exit(ExerciseCheckerError.UPLOAD);
} await DojoBackendManager.sendResults(exerciseDockerCompose.exitCode, commit, exerciseResultsValidation.exerciseResults, files, await ArchiveHelper.getBase64(Config.folders.resultsVolume));
} catch ( error ) {
console.error(Styles.ERROR(`${ Icon.ERROR } Error while uploading the results`));
console.error(JSON.stringify(error));
process.exit(ExerciseCheckerError.UPLOAD);
} }
}
/* /**
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 5: * Step 5:
- Display results * - Display results
- Exit with container exit code * - Exit with container exit code
*/ */
{ async function displayResults() {
ClientsSharedExerciseHelper.displayExecutionResults(exerciseResultsValidation.exerciseResults, exerciseDockerCompose.exitCode, Styles, `\n\n${ Icon.INFO }️ More detailed logs and resources may be available in artifacts`); ClientsSharedExerciseHelper.displayExecutionResults(exerciseResultsValidation.exerciseResults, exerciseDockerCompose.exitCode, Styles, `\n\n${ Icon.INFO }️ More detailed logs and resources may be available in artifacts`);
process.exit(exerciseDockerCompose.exitCode); process.exit(exerciseDockerCompose.exitCode);
} }
(async () => {
HttpManager.registerAxiosInterceptor();
console.log(Styles.APP_NAME(`${ Config.appName } (version {{VERSION}})`));
await downloadImmutablesFiles();
await runDockerCompose();
await checkExecutionContent();
await uploadResults();
await displayResults();
})(); })();
\ No newline at end of file
...@@ -20,7 +20,7 @@ class Config { ...@@ -20,7 +20,7 @@ class Config {
}; };
constructor() { constructor() {
this.appName = process.env.APP_NAME || ''; this.appName = process.env.APP_NAME ?? '';
this.folders = { this.folders = {
project : process.env.PROJECT_FOLDER?.convertWithEnvVars() ?? './', project : process.env.PROJECT_FOLDER?.convertWithEnvVars() ?? './',
...@@ -31,13 +31,13 @@ class Config { ...@@ -31,13 +31,13 @@ class Config {
this.resetResultsVolume(); this.resetResultsVolume();
this.exercise = { this.exercise = {
id : process.env.DOJO_EXERCISE_ID || '', id : process.env.DOJO_EXERCISE_ID ?? '',
secret: process.env.DOJO_SECRET || '' secret: process.env.DOJO_SECRET ?? ''
}; };
this.dockerhub = { this.dockerhub = {
repositories: { repositories: {
exerciseChecker: process.env.DOCKERHUB_EXERCISE_CHECKER_REPOSITORY || '' exerciseChecker: process.env.DOCKERHUB_EXERCISE_CHECKER_REPOSITORY ?? ''
} }
}; };
} }
......
import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig';
import axios from 'axios'; import axios from 'axios';
import DojoBackendResponse from '../shared/types/Dojo/DojoBackendResponse'; import DojoBackendResponse from '../shared/types/Dojo/DojoBackendResponse';
import ExerciseAssignment from '../sharedByClients/models/ExerciseAssignment'; import ExerciseAssignment from '../sharedByClients/models/ExerciseAssignment';
...@@ -6,23 +5,20 @@ import Config from '../config/Config'; ...@@ -6,23 +5,20 @@ import Config from '../config/Config';
import ExerciseResultsFile from '../shared/types/Dojo/ExerciseResultsFile'; import ExerciseResultsFile from '../shared/types/Dojo/ExerciseResultsFile';
import ApiRoute from '../sharedByClients/types/Dojo/ApiRoute'; import ApiRoute from '../sharedByClients/types/Dojo/ApiRoute';
import { IFileDirStat } from '../shared/helpers/recursiveFilesStats/RecursiveFilesStats'; import { IFileDirStat } from '../shared/helpers/recursiveFilesStats/RecursiveFilesStats';
import DojoBackendHelper from '../sharedByClients/helpers/Dojo/DojoBackendHelper';
class DojoBackendManager { class DojoBackendManager {
public getApiUrl(route: ApiRoute): string {
return `${ ClientsSharedConfig.apiURL }${ route }`;
}
public async getExerciseAssignment(): Promise<ExerciseAssignment | undefined> { public async getExerciseAssignment(): Promise<ExerciseAssignment | undefined> {
try { try {
return (await axios.get<DojoBackendResponse<ExerciseAssignment>>(this.getApiUrl(ApiRoute.EXERCISE_ASSIGNMENT).replace('{{id}}', Config.exercise.id))).data.data; return (await axios.get<DojoBackendResponse<ExerciseAssignment>>(DojoBackendHelper.getApiUrl(ApiRoute.EXERCISE_ASSIGNMENT, { exerciseIdOrUrl: Config.exercise.id }))).data.data;
} catch ( error ) { } catch ( error ) {
return undefined; return undefined;
} }
} }
public async sendResults(exitCode: number, commit: Record<string, string>, results: ExerciseResultsFile, files: Array<IFileDirStat>, archiveBase64: string): Promise<void> { public async sendResults(exitCode: number, commit: Record<string, string>, results: ExerciseResultsFile, files: Array<IFileDirStat>, archiveBase64: string): Promise<void> {
await axios.post(this.getApiUrl(ApiRoute.EXERCISE_RESULTS).replace('{{id}}', Config.exercise.id), { await axios.post(DojoBackendHelper.getApiUrl(ApiRoute.EXERCISE_RESULTS, { exerciseIdOrUrl: Config.exercise.id }), {
exitCode : exitCode, exitCode : exitCode,
commit : JSON.stringify(commit), commit : JSON.stringify(commit),
results : JSON.stringify(results), results : JSON.stringify(results),
......
...@@ -31,9 +31,9 @@ class HttpManager { ...@@ -31,9 +31,9 @@ class HttpManager {
} }
private registerRequestInterceptor() { private registerRequestInterceptor() {
axios.interceptors.request.use((config) => { axios.interceptors.request.use(config => {
if ( config.data instanceof FormData ) { if ( config.data instanceof FormData ) {
config.headers = { ...config.headers, ...(config.data as FormData).getHeaders() } as AxiosRequestHeaders; config.headers = { ...config.headers, ...config.data.getHeaders() } as AxiosRequestHeaders;
} }
if ( config.url && (config.url.indexOf(ClientsSharedConfig.apiURL) !== -1) ) { if ( config.url && (config.url.indexOf(ClientsSharedConfig.apiURL) !== -1) ) {
...@@ -54,9 +54,7 @@ class HttpManager { ...@@ -54,9 +54,7 @@ class HttpManager {
} }
private registerResponseInterceptor() { private registerResponseInterceptor() {
axios.interceptors.response.use((response) => { axios.interceptors.response.use(response => response, error => {
return response;
}, (error) => {
if ( error.response ) { if ( error.response ) {
if ( error.response.status === StatusCodes.METHOD_NOT_ALLOWED && error.response.data ) { if ( error.response.status === StatusCodes.METHOD_NOT_ALLOWED && error.response.data ) {
const data: DojoBackendResponse<void> = error.response.data; const data: DojoBackendResponse<void> = error.response.data;
......
Subproject commit 89f3579ca9009f793742170928d808ab4c35d931 Subproject commit fca59c4d155603b53d48a30401aabab82d91fc59
Subproject commit 098c6d20f6ed84240c086b979b56afd598fdfea4 Subproject commit 9e75273d4600f5f42c8be3c24bdced6844c32449
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment