// ATTENTION : This line MUST be the first of this file
import './init.js';
import ClientsSharedConfig from './sharedByClients/config/ClientsSharedConfig.js';
import Styles from './types/Style.js';
import RecursiveFilesStats from './shared/helpers/recursiveFilesStats/RecursiveFilesStats.js';
import Toolbox from './shared/helpers/Toolbox.js';
import ExerciseCheckerError from './shared/types/Dojo/ExerciseCheckerError.js';
import fs from 'fs-extra';
import HttpManager from './managers/HttpManager.js';
import DojoBackendManager from './managers/DojoBackendManager.js';
import Config from './config/Config.js';
import ArchiveHelper from './shared/helpers/ArchiveHelper.js';
import ExerciseDockerCompose from './sharedByClients/helpers/Dojo/ExerciseDockerCompose.js';
import ExerciseResultsSanitizerAndValidator from './sharedByClients/helpers/Dojo/ExerciseResultsSanitizerAndValidator.js';
import ExerciseAssignment from './sharedByClients/models/ExerciseAssignment.js';
import ClientsSharedExerciseHelper from './sharedByClients/helpers/Dojo/ClientsSharedExerciseHelper.js';
import Icon from './shared/types/Icon.js';
import path from 'node:path';
import SharedAssignmentHelper from './shared/helpers/Dojo/SharedAssignmentHelper';
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);
}
exerciseAssignment.immutable.forEach(immutableFile => {
if ( typeof immutableFile.content === 'string' ) {
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 {
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 });
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
*/
async function checkExecutionContent() {
exerciseResultsValidation = new ExerciseResultsSanitizerAndValidator(Config.folders.resultsDojo, Config.folders.resultsExercise, exerciseDockerCompose.exitCode);
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 ) { /* 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;
});
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
*/
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);
}
(async () => {
await Config.init();
SharedAssignmentHelper.init(Config.gitlabManager);
HttpManager.registerAxiosInterceptor();
console.log(Styles.APP_NAME(`${ Config.appName } (version {{VERSION}})`));
await downloadImmutablesFiles();
await runDockerCompose();
await checkExecutionContent();
await uploadResults();
await displayResults();
})();