Skip to content
Snippets Groups Projects
Select Git revision
  • 8186baef38598273295fb248773bcdd97ddcc6fc
  • master default protected
  • docs-improve_user_path_description
  • fix-pipeline
  • ci-change_exercises_image_registry
  • feat-merge_recorder_in_extension
  • feat-default_folder
  • feat-change_user
  • develop protected
  • refactor-mission
  • feat-exercise_automatic_save
  • docs-improve_documentation
  • feat-create_little_container_for_developer
  • feat-local-dev
  • 0.1.12
  • 0.1.11
  • 0.1.10
  • 0.1.5
18 results

missionDevContainer.ts

Blame
  • Lansana DIOMANDE's avatar
    Lansana DIOMANDE authored and Mohamed AZIKIOU committed
    8186baef
    History
    missionDevContainer.ts 5.58 KiB
    import { missionWorkdir, userSshKeyFolderPath } from '../config';
    import { Base, DockerfileSpecific, LifecycleScripts, VSCodespecific } from './model/devContainer';
    import { writeFile } from 'fs/promises';
    import { commands, Uri, window } from 'vscode';
    import { existsSync, copyFileSync } from 'fs';
    import { join } from 'path';
    
    import UserMission from './model/userMission';
    import { REGISTRY_MISSION_URL } from '../../config';
    import { getReviewedStudentWorkdirPath } from '../utils/mission.util';
    import { homedir } from 'os';
    import { extensionError } from '../../recorder/utils/log';
    import { createDirectories } from '../../recorder/utils/workdir';
    
    const remoteUserHomeDir = '/home/deadlock';
    const remoteMissionDir = `${remoteUserHomeDir}/mission/`;
    const remoteGiteaWorkDir = '/workdir';
    const defaultSettings = {
      'terminal.integrated.defaultProfile.linux': 'bash',
      'terminal.integrated.profiles.linux': {
        bash: {
          path: '/bin/bash',
        },
      },
    };
    
    export class MissionDevContainer {
      private isInit = false;
      private readonly mounts: string[] = [];
      private readonly extensionPath: string;
    
      private readonly dirs: {
        missionWorkdir: string;
        devcontainer: string;
        mounted: string;
        solution: string;
      };
    
      constructor(
        private readonly missionId: string,
        private readonly missionVersion: string,
        private readonly revieweeId?: string,
        private readonly storyId?: string,
      ) {
        let prefix: string;
        if (revieweeId) {
          prefix = getReviewedStudentWorkdirPath(revieweeId);
        } else {
          prefix = `${missionWorkdir}`;
        }
        this.dirs = {
          missionWorkdir: `${prefix}/${missionId}`,
          devcontainer: `${prefix}/${missionId}/.devcontainer`,
          mounted: `${prefix}/${missionId}/mounted`,
          solution: `${prefix}/${missionId}/.solution`,
        };
        if (process.env.DL_MOUNT_EXTENSION === 'true') {
          this.extensionPath = '/injected-extension/extension.vsix';
        } else {
          this.extensionPath = 'Deadlock.deadlock-coding';
        }
      }
    
      async open() {
        if (!this.isInit) {
          await this.init();
        }
        await commands.executeCommand('remote-containers.openFolder', Uri.file(this.dirs.missionWorkdir));
      }
    
      private async init() {
        if (this.isInit) {
          throw new Error(`${MissionDevContainer.constructor.name} Already initialized`);
        }
        this.isInit = true;
        try {
          await createDirectories(...Object.values(this.dirs));
          await this.createDevContainerFile();
          await this.createUserChallengeJsonFile();
        } catch (e) {
          console.error(e);
          window.showErrorMessage(`Une erreur est survenue lors de la création de la mission ${this.missionId}`);
        }
      }
    
      private async createUserChallengeJsonFile() {
        try {
          await UserMission.writeFile(this.missionId, this.missionVersion, this.revieweeId, this.storyId);
        } catch (e) {
          extensionError(e);
          window.showErrorMessage(`Une erreur est survenue lors de la création du fichier userChallenge.json`);
        }
      }
    
      private addInMountRCs(tempFolder: string, ...rcs: string[]): void {
        for (const rc of rcs) {
          if (existsSync(rc)) {
            const rcPath = join(homedir(), rc);
            const tempRC = join(tempFolder, rc);
            const mountRC = join(remoteUserHomeDir, rc);
            copyFileSync(rcPath, tempRC);
            this.mounts.push(`source=${tempRC},target=${mountRC},type=bind`);
          }
        }
      }
    
      private async setupMounts() {
        this.mounts.splice(0, this.mounts.length);
        this.mounts.push(
          `source=${userSshKeyFolderPath},target=/tmp/.ssh,type=bind,consistency=cached,readonly`,
          `source=${UserMission.getMissionUserFolder(
            this.missionId,
            this.revieweeId,
          )},target=/home/config/,type=bind,consistency=cached,readonly`,
          'source=/etc/hosts,target=/etc/hosts,type=bind,consistency=cached,readonly',
        );
        if (process.env.DL_MOUNT_EXTENSION === 'true') {
          this.mounts.push(`source=${process.env.EXTENSION_PATH},target=${this.extensionPath},type=bind`);
        }
        this.addInMountRCs(this.dirs.missionWorkdir, '.bashrc', '.zshrc');
        if (this.revieweeId) {
          this.mounts.push(`source=${this.dirs.solution},target=${remoteMissionDir}/solution,type=bind`);
        }
      }
    
      private async createDevContainerFile(
        options?: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts>,
      ) {
        await this.setupMounts();
    
        const extensions = [this.extensionPath];
        if (this.revieweeId) {
          extensions.push('eamodio.gitlens');
        }
    
        return writeFile(
          `${this.dirs.devcontainer}/devcontainer.json`,
          (() => {
            const containerName =
              `deadlock-mission-${this.missionId}-${this.missionVersion}` + (this.revieweeId ? `-${this.revieweeId}` : '');
            const devcontainer: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts> = {
              name: containerName,
              image: `${REGISTRY_MISSION_URL}/${this.missionId}:${this.missionVersion}`,
              containerEnv: {
                WORKDIR: `${remoteMissionDir}`,
              },
              extensions,
              remoteUser: 'deadlock',
              mounts: this.mounts,
              userEnvProbe: 'interactiveShell',
              settings: defaultSettings,
              overrideCommand: false,
              shutdownAction: 'stopContainer',
              workspaceMount: `source=${this.dirs.mounted},target=${remoteMissionDir},type=bind`,
              workspaceFolder: `${remoteMissionDir}`,
              onCreateCommand: `cp -R ${remoteGiteaWorkDir}/* ${remoteMissionDir}`,
              runArgs: ['--name', containerName, '--privileged'],
              ...options,
            };
            return JSON.stringify(devcontainer, null, 2);
          })(),
        );
      }
    }