// Read from the .env file
// ATTENTION : This lines MUST be the first of this file (except for the path import)
const path = require('node:path');
require('dotenv').config({ path: path.join(__dirname, '../.env') });
require('./shared/helpers/TypeScriptExtensions'); // ATTENTION : This line MUST be the second of this file
import ClientsSharedConfig from './sharedByClients/config/ClientsSharedConfig';
import Styles from './types/Style';
import Icon from './sharedByClients/types/Icon';
import RecursiveFilesStats from './shared/helpers/recursiveFilesStats/RecursiveFilesStats';
import Toolbox from './shared/helpers/Toolbox';
import ExerciseCheckerError from './shared/types/Dojo/ExerciseCheckerError';
import { exec } from 'child_process';
import util from 'util';
import fs from 'fs-extra';
import HttpManager from './managers/HttpManager';
import DojoBackendManager from './managers/DojoBackendManager';
import Config from './config/Config';
import ArchiveHelper from './shared/helpers/ArchiveHelper';
import ExerciseDockerCompose from './sharedByClients/helpers/Dojo/ExerciseDockerCompose';
import ExerciseResultsValidation from './sharedByClients/helpers/Dojo/ExerciseResultsValidation';
import ExerciseAssignment from './sharedByClients/models/ExerciseAssignment';
import ClientsSharedExerciseHelper from './sharedByClients/helpers/Dojo/ClientsSharedExerciseHelper';
(async () => {
const execAsync = util.promisify(exec);
HttpManager.registerAxiosInterceptor();
console.log(Styles.APP_NAME(Config.appName));
let exerciseAssignment: ExerciseAssignment | undefined;
let exerciseDockerCompose: ExerciseDockerCompose;
let exerciseResultsValidation: ExerciseResultsValidation;
/*
//////////////////////////////////////////////////////////////////////////////////////////////////////////// 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' });
});
}
/*
//////////////////////////////////////////////////////////////////////////////////////////////////////////// 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
*/
{
const composeOverridePath: string = path.join(Config.folders.project, 'docker-compose-override.yml');
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);
exerciseDockerCompose = new ExerciseDockerCompose(ClientsSharedConfig.dockerCompose.projectName, exerciseAssignment.assignmentFile, Config.folders.project, [ composeOverridePath ]);
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, exitCode: number) => {
success ? resolve() : reject();
});
exerciseDockerCompose.run();
});
} catch ( error ) { }
fs.rmSync(composeOverridePath);
fs.writeFileSync(path.join(Config.folders.resultsDojo, 'dockerComposeLogs.txt'), exerciseDockerCompose.allLogs);
if ( !exerciseDockerCompose.success ) {
console.error(Styles.ERROR(`${ Icon.ERROR } Execution logs are available in artifacts`));
process.exit(exerciseDockerCompose.exitCode);
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 3: Check content requirements and content size
{
exerciseResultsValidation = new ExerciseResultsValidation(Config.folders.resultsDojo, Config.folders.resultsExercise);
try {
await new Promise<void>((resolve) => {
exerciseResultsValidation.events.on('step', (name: string, message: string) => {
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 ) {
process.exit(exitCode);
}
resolve();
});
exerciseResultsValidation.run();
});
} catch ( error ) { }
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 4: Upload results
{
try {
console.log(Styles.INFO(`${ Icon.INFO } Uploading results to the dojo server`));
const commit: any = {};
Toolbox.getKeysWithPrefix(process.env, 'CI_COMMIT_').forEach(key => {
commit[Toolbox.snakeToCamel(key.replace('CI_COMMIT_', ''))] = process.env[key];
});
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`);
process.exit(exerciseDockerCompose.exitCode);
}
})();