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
ExerciseChecker/src/config/Version.ts
sonarlint.xml
sonarlint/
############################ MacOS
# General
.DS_Store
......
......@@ -15,4 +15,7 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="SonarLintModuleSettings">
<option name="uniqueId" value="39cd055e-87b7-4af4-82da-6d4c7c75ce23" />
</component>
</module>
\ No newline at end of file
This diff is collapsed.
......@@ -29,17 +29,20 @@
"test" : "echo \"Error: no test specified\" && exit 1"
},
"dependencies" : {
"@gitbeaker/rest" : "^39.34.3",
"axios" : "^1.6.5",
"boxen" : "^5.1.2",
"chalk" : "^4.1.2",
"dotenv" : "^16.3.1",
"dotenv-expand" : "^10.0.0",
"form-data" : "^4.0.0",
"fs-extra" : "^11.2.0",
"http-status-codes" : "^2.3.0",
"json5" : "^2.2.3",
"ora" : "^5.4.1",
"tar-stream" : "^3.1.6",
"winston" : "^3.11.0",
"winston-transport" : "^4.7.0",
"yaml" : "^2.3.4",
"zod" : "^3.22.4",
"zod-validation-error": "^3.0.0"
......
......@@ -30,150 +30,161 @@ import ClientsSharedExerciseHelper from './sharedByClients/helpers/Dojo
import Icon from './shared/types/Icon';
(async () => {
HttpManager.registerAxiosInterceptor();
console.log(Styles.APP_NAME(`${ Config.appName } (version {{VERSION}})`));
let exerciseAssignment: ExerciseAssignment | undefined;
let exerciseDockerCompose: ExerciseDockerCompose;
let exerciseResultsValidation: ExerciseResultsSanitizerAndValidator;
let haveResultsVolume: boolean;
/*
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 1:
- Read the dojo assignment file from the assignment repository
- Download immutables files (maybe throw or show an error if the files have been modified ?)
*/
{
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' });
});
let exerciseAssignment: ExerciseAssignment | undefined;
let exerciseDockerCompose: ExerciseDockerCompose;
let exerciseResultsValidation: ExerciseResultsSanitizerAndValidator;
let haveResultsVolume: boolean;
/**
* Step 1:
* - Read the dojo assignment file from the assignment repository
* - 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`));
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);
}
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);
/*
//////////////////////////////////////////////////////////////////////////////////////////////////////////// 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
*/
{
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();
try {
await new Promise<void>((resolve, reject) => {
exerciseDockerCompose.events.on('step', (_name: string, message: string) => {
console.log(Styles.INFO(`${ Icon.INFO } ${ message }`));
});
} catch ( error ) { /* empty */ }
fs.rmSync(composeOverridePath, { force: true });
fs.writeFileSync(path.join(Config.folders.resultsDojo, 'dockerComposeLogs.txt'), exerciseDockerCompose.allLogs);
exerciseDockerCompose.events.on('endStep', (_stepName: string, message: string, error: boolean) => {
if ( error ) {
console.error(Styles.ERROR(`${ Icon.ERROR } ${ message }`));
}
});
if ( !exerciseDockerCompose.success ) {
console.error(Styles.ERROR(`${ Icon.ERROR } Execution logs are available in artifacts`));
process.exit(exerciseDockerCompose.exitCode);
}
}
exerciseDockerCompose.events.on('finished', (success: boolean) => {
success ? resolve() : reject();
});
exerciseDockerCompose.run();
});
} catch ( error ) { /* empty */ }
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 3: Check content requirements and content size
{
exerciseResultsValidation = new ExerciseResultsSanitizerAndValidator(Config.folders.resultsDojo, Config.folders.resultsExercise, exerciseDockerCompose.exitCode);
fs.rmSync(composeOverridePath, { force: true });
fs.writeFileSync(path.join(Config.folders.resultsDojo, 'dockerComposeLogs.txt'), exerciseDockerCompose.allLogs);
try {
await new Promise<void>((resolve) => {
exerciseResultsValidation.events.on('step', (_name: string, message: string) => {
console.log(Styles.INFO(`${ Icon.INFO } ${ message }`));
});
if ( !exerciseDockerCompose.success ) {
console.error(Styles.ERROR(`${ Icon.ERROR } Execution logs are available in artifacts`));
process.exit(exerciseDockerCompose.exitCode);
}
}
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 ) {
process.exit(exitCode);
}
/**
* Step 3:
* - 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
{
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;
resolve();
});
const files = await RecursiveFilesStats.explore(Config.folders.resultsVolume, {
replacePathByRelativeOne: true,
liteStats : true
});
exerciseResultsValidation.run();
});
} 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));
} catch ( error ) {
console.error(Styles.ERROR(`${ Icon.ERROR } Error while uploading the results`));
console.error(JSON.stringify(error));
process.exit(ExerciseCheckerError.UPLOAD);
}
const files = await RecursiveFilesStats.explore(Config.folders.resultsVolume, {
replacePathByRelativeOne: true,
liteStats : true
});
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:
- Display results
- Exit with container exit code
*/
{
ClientsSharedExerciseHelper.displayExecutionResults(exerciseResultsValidation.exerciseResults, exerciseDockerCompose.exitCode, Styles, `\n\n${ Icon.INFO }️ More detailed logs and resources may be available in artifacts`);
/**
* Step 5:
* - Display results
* - 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`);
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 {
};
constructor() {
this.appName = process.env.APP_NAME || '';
this.appName = process.env.APP_NAME ?? '';
this.folders = {
project : process.env.PROJECT_FOLDER?.convertWithEnvVars() ?? './',
......@@ -31,13 +31,13 @@ class Config {
this.resetResultsVolume();
this.exercise = {
id : process.env.DOJO_EXERCISE_ID || '',
secret: process.env.DOJO_SECRET || ''
id : process.env.DOJO_EXERCISE_ID ?? '',
secret: process.env.DOJO_SECRET ?? ''
};
this.dockerhub = {
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 DojoBackendResponse from '../shared/types/Dojo/DojoBackendResponse';
import ExerciseAssignment from '../sharedByClients/models/ExerciseAssignment';
......@@ -6,23 +5,20 @@ import Config from '../config/Config';
import ExerciseResultsFile from '../shared/types/Dojo/ExerciseResultsFile';
import ApiRoute from '../sharedByClients/types/Dojo/ApiRoute';
import { IFileDirStat } from '../shared/helpers/recursiveFilesStats/RecursiveFilesStats';
import DojoBackendHelper from '../sharedByClients/helpers/Dojo/DojoBackendHelper';
class DojoBackendManager {
public getApiUrl(route: ApiRoute): string {
return `${ ClientsSharedConfig.apiURL }${ route }`;
}
public async getExerciseAssignment(): Promise<ExerciseAssignment | undefined> {
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 ) {
return undefined;
}
}
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,
commit : JSON.stringify(commit),
results : JSON.stringify(results),
......
......@@ -31,9 +31,9 @@ class HttpManager {
}
private registerRequestInterceptor() {
axios.interceptors.request.use((config) => {
axios.interceptors.request.use(config => {
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) ) {
......@@ -54,9 +54,7 @@ class HttpManager {
}
private registerResponseInterceptor() {
axios.interceptors.response.use((response) => {
return response;
}, (error) => {
axios.interceptors.response.use(response => response, error => {
if ( error.response ) {
if ( error.response.status === StatusCodes.METHOD_NOT_ALLOWED && 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