Select Git revision
MainPage,module.scss
missionDevContainer.ts 5.40 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.utils';
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 extentionPath: 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,
) {
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.extentionPath = '/injected-extension/extension.vsix';
} else {
this.extentionPath = '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);
} 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.extentionPath},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.extentionPath];
if (this.revieweeId) {
extensions.push('eamodio.gitlens');
}
return writeFile(
`${this.dirs.devcontainer}/devcontainer.json`,
(() => {
const devcontainer: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts> = {
name: `deadlock-${this.missionId}`,
image: `${REGISTRY_MISSION_URL}/${this.missionId}:${this.missionVersion}`,
containerEnv: {
WORKDIR: `${remoteMissionDir}`,
},
extensions,
remoteUser: 'deadlock',
mounts: this.mounts,
userEnvProbe: 'interactiveShell',
settings: defaultSettings,
overrideCommand: true,
shutdownAction: 'stopContainer',
workspaceMount: `source=${this.dirs.mounted},target=${remoteMissionDir},type=bind`,
workspaceFolder: `${remoteMissionDir}`,
onCreateCommand: `cp -R ${remoteGiteaWorkDir}/* ${remoteMissionDir} && sudo bash /start.desktop.sh`,
runArgs: ['--privileged'],
...options,
};
return JSON.stringify(devcontainer, null, 2);
})(),
);
}
}