Skip to content
Snippets Groups Projects
Select Git revision
  • 4ff3846e9415a6122b0b966be089eec3f0117f4f
  • main default protected
  • jw_sonar
  • v6.0
  • ask-user-to-delete-exercises-on-duplicates
  • v5.0
  • open_tool_for_self_hosting
  • jw_sonar_backup
  • move-to-esm-only
  • v4.2
  • v4.1
11 results

ExerciseDockerCompose.ts

Blame
  • ExerciseDockerCompose.ts 6.85 KiB
    import AssignmentFile        from '../../../shared/types/Dojo/AssignmentFile';
    import { TypedEmitter }      from 'tiny-typed-emitter';
    import ExerciseRunningEvents from '../../types/Dojo/ExerciseRunningEvents';
    import { spawn }             from 'child_process';
    import ExerciseCheckerError  from '../../../shared/types/Dojo/ExerciseCheckerError';
    
    
    class ExerciseDockerCompose {
        readonly events: TypedEmitter<ExerciseRunningEvents> = new TypedEmitter<ExerciseRunningEvents>();
    
        public displayableLogs: string = '';
        public allLogs: string = '';
    
        public isFinished: boolean = false;
        public success: boolean = false;
        public exitCode: number = -1;
    
        constructor(private projectName: string, private assignmentFile: AssignmentFile, private executionFolder: string, private composeFileOverride: Array<string> = []) {
            this.events.on('logs', (log: string, _error: boolean, displayable: boolean) => {
                this.allLogs += log;
                this.displayableLogs += displayable ? log : '';
            });
    
            this.events.on('finished', (success: boolean, exitCode: number) => {
                this.isFinished = true;
                this.success = success;
                this.exitCode = exitCode;
            });
        }
    
        run(doDown: boolean = false) {
            (async () => {
                let containerExitCode: number = -1;
    
                const dockerComposeCommand = `docker compose --project-name ${ this.projectName } --progress plain --file docker-compose.yml ${ this.composeFileOverride.map((file) => `--file ${ file }`).join(' ') }`;
    
                // Run the service
                {
                    try {
                        this.events.emit('step', 'COMPOSE_RUN', 'Running Docker Compose file');
    
                        containerExitCode = await new Promise<number>((resolve, reject) => {
    
                            this.events.emit('logs', '####################################################### Docker Compose & Main Container Logs #######################################################\n', false, false);
    
                            const dockerCompose = spawn(`${ dockerComposeCommand } run --build --rm ${ this.assignmentFile.result.container }`, {
                                cwd  : this.executionFolder,
                                shell: true,
                                env  : {
                                    'DOCKER_BUILDKIT'  : '1',
                                    'BUILDKIT_PROGRESS': 'plain', ...process.env
                                }
                            });
    
                            dockerCompose.stdout.on('data', (data) => {
                                this.events.emit('logs', data.toString(), false, true);
                            });
    
                            dockerCompose.stderr.on('data', (data) => {
                                this.events.emit('logs', data.toString(), true, true);
                            });
    
                            dockerCompose.on('exit', (code) => {
                                code !== null ? resolve(code) : reject();
                            });
                        });
                    } catch ( error ) {
                        this.events.emit('endStep', 'COMPOSE_RUN', `Error while running the docker compose file`, true);
                        this.events.emit('finished', false, ExerciseCheckerError.DOCKER_COMPOSE_RUN_ERROR);
                        return;
                    }
                    this.events.emit('endStep', 'COMPOSE_RUN', `Docker Compose file run successfully`, false);
                }
    
                // Get linked services logs
                {
                    try {
                        this.events.emit('step', 'COMPOSE_LOGS', 'Linked services logs acquisition');
    
                        await new Promise<void>((resolve, reject) => {
    
                            this.events.emit('logs', '####################################################### Other Services Logs #######################################################\n', false, false);
    
                            const dockerCompose = spawn(`${ dockerComposeCommand } logs --timestamps`, {
                                cwd  : this.executionFolder,
                                shell: true
                            });
    
                            dockerCompose.stdout.on('data', (data) => {
                                this.events.emit('logs', data.toString(), false, false);
                            });
    
                            dockerCompose.stderr.on('data', (data) => {
                                this.events.emit('logs', data.toString(), true, false);
                            });
    
                            dockerCompose.on('exit', (code) => {
                                code !== null ? resolve() : reject();
                            });
                        });
                    } catch ( error ) {
                        this.events.emit('endStep', 'COMPOSE_LOGS', `Error while getting the linked services logs`, true);
                        this.events.emit('finished', false, ExerciseCheckerError.DOCKER_COMPOSE_LOGS_ERROR);
                        return;
                    }
                    this.events.emit('endStep', 'COMPOSE_LOGS', `Linked services logs acquired`, false);
                }
    
                // Remove containers if asked
                {
                    if ( doDown ) {
                        try {
                            this.events.emit('step', 'COMPOSE_DOWN', 'Stopping and removing containers');
    
                            await new Promise<void>((resolve, reject) => {
    
                                this.events.emit('logs', '####################################################### Stop and remove containers #######################################################\n', false, false);
    
                                const dockerCompose = spawn(`${ dockerComposeCommand } down --volumes`, {
                                    cwd  : this.executionFolder,
                                    shell: true
                                });
    
                                dockerCompose.stdout.on('data', (data) => {
                                    this.events.emit('logs', data.toString(), false, false);
                                });
    
                                dockerCompose.stderr.on('data', (data) => {
                                    this.events.emit('logs', data.toString(), true, false);
                                });
    
                                dockerCompose.on('exit', (code) => {
                                    code !== null ? resolve() : reject();
                                });
                            });
                        } catch ( error ) {
                            this.events.emit('endStep', 'COMPOSE_DOWN', `Error stop and remove containers`, true);
                            this.events.emit('finished', false, ExerciseCheckerError.DOCKER_COMPOSE_DOWN_ERROR);
                            return;
                        }
                        this.events.emit('endStep', 'COMPOSE_DOWN', `Containers stopped and removed`, false);
                    }
                }
    
                this.events.emit('finished', true, containerExitCode);
            })();
        }
    }
    
    
    export default ExerciseDockerCompose;