diff --git a/deadlock-plugins/deadlock-extension/src/core/api.service.ts b/deadlock-plugins/deadlock-extension/src/core/api.service.ts index b7321240493e82d6aef594a04a102d062dfdce38..9583bdc3a45b0e4e88a46b21da7a73970490c68d 100644 --- a/deadlock-plugins/deadlock-extension/src/core/api.service.ts +++ b/deadlock-plugins/deadlock-extension/src/core/api.service.ts @@ -92,6 +92,8 @@ export default class ApiService { } catch (_error: any) { if (_error.response && _error.response.data) { return Promise.reject(_error.response.data); + } else { + return await this.onInvalidRefreshToken(originalConfig); } return Promise.reject(_error); } diff --git a/deadlock-plugins/deadlock-extension/src/core/controller.ts b/deadlock-plugins/deadlock-extension/src/core/controller.ts index 7697f39772277c24768dfdcc0bfbb1efebcd6e54..9ae124a6b08f87b48e0c1a2c4bbe2b44f985fa45 100644 --- a/deadlock-plugins/deadlock-extension/src/core/controller.ts +++ b/deadlock-plugins/deadlock-extension/src/core/controller.ts @@ -1,6 +1,5 @@ import * as vscode from 'vscode'; import { KEYCLOAK_DEVICE_AUTH_URL, KEYCLOAK_TOKEN_CREATE_URL, KEYCLOAK_USER_INFO_URL } from '../config'; - import { log } from '../recorder/utils'; import BriefingView from '../view/briefingView'; import QuickSetupView from '../view/quickSetupView'; @@ -11,8 +10,8 @@ import ApiService from './api.service'; import SshKeyManager from './sshKeyManager'; import { GiteaPublicProperties } from '../model/giteaPublicProperties.model'; import { User } from '../model/user.model'; -import { openEditorInFolder } from './mission/missionOpenInEditor'; import { Mission } from './mission/mission'; +import { MissionDevContainer } from './mission/missionDevContainer'; export default class Controller { public connection: KeycloakOAuth2DeviceFlowConnection; @@ -39,6 +38,7 @@ export default class Controller { this.init(); } + private async init() { const that = this; vscode.window.registerUriHandler({ @@ -68,6 +68,7 @@ export default class Controller { this.quickSetupView.isAlreadyConnected = (await this.extensionStore.getAccessToken()) !== undefined; } + async chooseMissionWorkdir() { const actualMissionWorkDir = this.extensionStore.getMissionWorkdir(); @@ -107,6 +108,7 @@ export default class Controller { await this.createSshKeyPairIfNotExist(); this.quickSetupView.isAlreadyConnected = true; } + public static openBrowserWithUrl(url: string) { vscode.commands.executeCommand(OPEN_URL_IN_BROWSER_COMMAND.cmd, vscode.Uri.parse(url)); } @@ -125,22 +127,21 @@ export default class Controller { vscode.window.showInformationMessage('Connexion validée'); } - const user: User = await this.callApiService.getUser(); - const giteaPublicProperties: GiteaPublicProperties = await this.callApiService.getGiteaPublicProperties(); - const mission = new Mission( `${ExtensionStore.getInstance().getMissionWorkdir()}/${missionId}`, missionId, missionVersion, - giteaPublicProperties, ); + const user: User = await this.callApiService.getUser(); + const giteaPublicProperties: GiteaPublicProperties = await this.callApiService.getGiteaPublicProperties(); + + const missionDevcontainer = new MissionDevContainer(user, mission, giteaPublicProperties); + vscode.window.showInformationMessage( 'opening inside folder ' + this.extensionStore.getMissionWorkdir()! + '/' + missionId, ); - await mission.setup(user); - - await openEditorInFolder(mission.directory); + await missionDevcontainer.open(); } } diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/mission.ts b/deadlock-plugins/deadlock-extension/src/core/mission/mission.ts index bd236bec81753342bc7b594fa82239fd9b5783af..641b8112e3ab1f9be44ca2fa593568ae72b5f831 100644 --- a/deadlock-plugins/deadlock-extension/src/core/mission/mission.ts +++ b/deadlock-plugins/deadlock-extension/src/core/mission/mission.ts @@ -1,34 +1,11 @@ -import * as fs from 'fs'; -import { GiteaPublicProperties } from '../../model/giteaPublicProperties.model'; -import { User } from '../../model/user.model'; -import { DockerfileSpecific, Base, VSCodespecific, LifecycleScripts } from './devContainer'; -import { createDevContainerFile } from './missionDevContainer'; -import { createUserChallengeJsonFile } from './missionUserChallenge'; - export class Mission { readonly directory: string; readonly id: string; readonly version: string; - readonly giteaProperties: GiteaPublicProperties; - constructor(directory: string, id: string, version: string, giteaProperties: GiteaPublicProperties) { + constructor(directory: string, id: string, version: string) { this.directory = directory; this.id = id; this.version = version; - this.giteaProperties = giteaProperties; - } - - public async setup(user: User, options?: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts>) { - await this.createDirectories([ - `${this.directory}/.config`, - `${this.directory}/.devcontainer`, - `${this.directory}/mounted`, - ]); - await createDevContainerFile(this, options); - await createUserChallengeJsonFile(user, this, this.giteaProperties); - } - - private createDirectories(directoryPaths: string[]) { - return Promise.all(directoryPaths.map((directoryPath) => fs.promises.mkdir(directoryPath, { recursive: true }))); } } diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/missionDevContainer.ts b/deadlock-plugins/deadlock-extension/src/core/mission/missionDevContainer.ts index aff814655d451ca5e1b61e74d943706d38f06b33..363c60ca47c183f6b150bac48c8f954c42072d0e 100644 --- a/deadlock-plugins/deadlock-extension/src/core/mission/missionDevContainer.ts +++ b/deadlock-plugins/deadlock-extension/src/core/mission/missionDevContainer.ts @@ -1,57 +1,111 @@ import { userSshKeyFolderPath } from '../config'; -import ExtensionStore from '../extensionStore'; import { Base, DockerfileSpecific, LifecycleScripts, VSCodespecific } from './devContainer'; -import * as fs from 'fs'; +import { existsSync } from 'fs'; +import { mkdir, writeFile } from 'fs/promises'; import { Mission } from './mission'; +import { User, UserChallengeJson } from '../../model/user.model'; +import { GiteaPublicProperties } from '../../model/giteaPublicProperties.model'; +import { commands, Uri } from 'vscode'; +import assert = require('assert'); const DOCKER_IMAGE_URL = 'registry.takima.io/deadlock/deadlock-challenges'; -export function createDevContainerFile( - mission: Mission, - options?: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts>, -) { - const hostBaseWorkDir = ExtensionStore.getInstance().getMissionWorkdir() ?? ''; - const hostMissionDir = `${hostBaseWorkDir}/${mission.id}`; - const userMissionConfigPath = `${hostMissionDir}/.config`; - const hostMissionDevcontainerDir = `${hostMissionDir}/.devcontainer`; - const hostMissionDevcontainerFileDir = `${hostMissionDevcontainerDir}/devcontainer.json`; - const hostMissionMountDir = `${hostMissionDir}/mounted`; - const dockerImageURL = `${DOCKER_IMAGE_URL}/${mission.id}:${mission.version}`; - const remoteUserHomeDir = '/home/deadlock'; - const remoteMissionDir = `${remoteUserHomeDir}/mission/`; - const remoteGiteaWorkDir = `/src`; - - return fs.promises.writeFile( - hostMissionDevcontainerFileDir, - (() => { - const devcontainer: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts> = { - name: `deadlock-${mission.id}`, - image: dockerImageURL, - extensions: ['Deadlock.deadlock-coding'], - remoteUser: 'deadlock', - mounts: [ - `source=${userSshKeyFolderPath},target=/tmp/.ssh,type=bind,consistency=cached,readonly`, - `source=${userMissionConfigPath},target=/home/config/,type=bind,consistency=cached,readonly`, - 'source=/etc/hosts,target=/etc/hosts,type=bind,consistency=cached,readonly', - ], - userEnvProbe: 'interactiveShell', - settings: { - 'terminal.integrated.defaultProfile.linux': 'bash', - 'terminal.integrated.profiles.linux': { - bash: { - path: '/bin/bash', +export class MissionDevContainer { + private isInit = false; + + private readonly dirs = { + config: `${this.mission.directory}/.config`, + devcontainer: `${this.mission.directory}/.devcontainer`, + mounted: `${this.mission.directory}/mounted`, + }; + + constructor( + private readonly user: User, + private readonly mission: Mission, + private readonly giteaProperties: GiteaPublicProperties, + ) {} + + async open() { + if (!this.isInit) { + await this.init(); + } + assert(existsSync(this.mission.directory)); + await commands.executeCommand('remote-containers.openFolder', Uri.file(this.mission.directory)); + } + private async init() { + if (this.isInit) { + throw new Error(`${MissionDevContainer.constructor.name} Already initialized`); + } + + this.isInit = true; + await this.createDirectories(...Object.values(this.dirs)); + await this.createDevContainerFile(); + await this.createUserChallengeJsonFile(this.user, this.giteaProperties); + } + + private createUserChallengeJsonFile(user: User, giteaPublicProperties: GiteaPublicProperties) { + return writeFile( + `${this.dirs.config}/user-challenge.json`, + (() => { + const userChallengeJson: UserChallengeJson = { + giteaHost: giteaPublicProperties.sshHost, + giteaSshPort: giteaPublicProperties.sshPort, + username: user.id.split('-').join(''), + email: `${user.id.split('-').join('')}@deadlock.io`, + missionId: this.mission.id, + remoteGitUsername: user.id.split('-').join(''), + currentUserDetails: user.details, + remoteUserDetails: user.details, + }; + return JSON.stringify(userChallengeJson, null, 2); + })(), + ); + } + + private createDevContainerFile(options?: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts>) { + const remoteUserHomeDir = '/home/deadlock'; + const remoteMissionDir = `${remoteUserHomeDir}/mission/`; + const remoteGiteaWorkDir = `/src`; + const hostMissionDevcontainerFileDir = `${this.dirs.devcontainer}/devcontainer.json`; + + const image = `${DOCKER_IMAGE_URL}/${this.mission.id}:${this.mission.version}`; + + return writeFile( + hostMissionDevcontainerFileDir, + (() => { + const devcontainer: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts> = { + name: `deadlock-${this.mission.id}`, + image, + extensions: ['Deadlock.deadlock-coding'], + remoteUser: 'deadlock', + mounts: [ + `source=${userSshKeyFolderPath},target=/tmp/.ssh,type=bind,consistency=cached,readonly`, + `source=${this.dirs.config},target=/home/config/,type=bind,consistency=cached,readonly`, + 'source=/etc/hosts,target=/etc/hosts,type=bind,consistency=cached,readonly', + ], + userEnvProbe: 'interactiveShell', + settings: { + 'terminal.integrated.defaultProfile.linux': 'bash', + 'terminal.integrated.profiles.linux': { + bash: { + path: '/bin/bash', + }, }, }, - }, - overrideCommand: false, - shutdownAction: 'stopContainer', - workspaceMount: `source=${hostMissionMountDir},target=${remoteMissionDir},type=bind`, - workspaceFolder: `${remoteMissionDir}`, - onCreateCommand: `cp -R ${remoteGiteaWorkDir}/. ${remoteMissionDir}`, - runArgs: ['--privileged'], - ...options, - }; - return JSON.stringify(devcontainer, null, 2); - })(), - ); + overrideCommand: false, + shutdownAction: 'stopContainer', + workspaceMount: `source=${this.dirs.mounted},target=${remoteMissionDir},type=bind`, + workspaceFolder: `${remoteMissionDir}`, + onCreateCommand: `cp -R ${remoteGiteaWorkDir}/. ${remoteMissionDir}`, + runArgs: ['--privileged'], + ...options, + }; + return JSON.stringify(devcontainer, null, 2); + })(), + ); + } + + private createDirectories(...directoryPaths: string[]) { + return Promise.all(directoryPaths.map((directoryPath) => mkdir(directoryPath, { recursive: true }))); + } } diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/missionOpenInEditor.ts b/deadlock-plugins/deadlock-extension/src/core/mission/missionOpenInEditor.ts deleted file mode 100644 index 4c3f8367bc3f0fd3cf886830815ff52a43a77d03..0000000000000000000000000000000000000000 --- a/deadlock-plugins/deadlock-extension/src/core/mission/missionOpenInEditor.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as fs from 'fs'; -import { error as err, log } from '../../recorder/utils'; -import * as vscode from 'vscode'; - -export async function openEditorInFolder(folderPath: string) { - if (!fs.existsSync(folderPath)) { - log('WARN missing path ', folderPath); - await fs.promises.mkdir(folderPath, { recursive: true }); - } - await vscode.commands.executeCommand('remote-containers.openFolder', vscode.Uri.file(folderPath)); -} diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/missionUserChallenge.ts b/deadlock-plugins/deadlock-extension/src/core/mission/missionUserChallenge.ts deleted file mode 100644 index aa98cfbbec7d13447978c9b9230c6e2d11bbc7da..0000000000000000000000000000000000000000 --- a/deadlock-plugins/deadlock-extension/src/core/mission/missionUserChallenge.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { User, UserChallengeJson } from '../../model/user.model'; -import * as fs from 'fs'; -import { GiteaPublicProperties } from '../../model/giteaPublicProperties.model'; -import { Mission } from './mission'; - -export function createUserChallengeJsonFile( - user: User, - mission: Mission, - giteaPublicProperties: GiteaPublicProperties, -) { - return fs.promises.writeFile( - `${mission.directory}/.config/user-challenge.json`, - (() => { - const userChallengeJson: UserChallengeJson = { - giteaHost: giteaPublicProperties.sshHost, - giteaSshPort: giteaPublicProperties.sshPort, - username: user.id.split('-').join(''), - email: `${user.id.split('-').join('')}@deadlock.io`, - missionId: mission.id, - remoteGitUsername: user.id.split('-').join(''), - currentUserDetails: user.details, - remoteUserDetails: user.details, - }; - return JSON.stringify(userChallengeJson, null, 2); - })(), - ); -} diff --git a/deadlock-plugins/deadlock-extension/src/model/user.model.ts b/deadlock-plugins/deadlock-extension/src/model/user.model.ts index f3e1b03e91b32d58e85f07df898e8f50b554020e..7744bdc8f081d3c950926eb76932c3dc6078225b 100644 --- a/deadlock-plugins/deadlock-extension/src/model/user.model.ts +++ b/deadlock-plugins/deadlock-extension/src/model/user.model.ts @@ -1,3 +1,5 @@ +import { GiteaPublicProperties } from './giteaPublicProperties.model'; + export interface User { id: string; details: UserDetails; @@ -23,3 +25,13 @@ export interface UserChallengeJson { currentUserDetails: UserDetails; remoteUserDetails: UserDetails; } + +export interface UserChallengeJson2 { + gitea: Pick<GiteaPublicProperties, 'sshPort' | 'sshHost'>; + username: string; + email: string; + missionId: string; + remoteGitUsername: string; + currentUserDetails: UserDetails; + remoteUserDetails: UserDetails; +}