diff --git a/deadlock-plugins/deadlock-extension/src/config.ts b/deadlock-plugins/deadlock-extension/src/config.ts index 21960df6fea4d8e530bc89caaf217a4219f82ab8..fcbe08addba35d99f5fd64632720c89235514ed1 100644 --- a/deadlock-plugins/deadlock-extension/src/config.ts +++ b/deadlock-plugins/deadlock-extension/src/config.ts @@ -4,7 +4,6 @@ export const KEYCLOAK_TOKEN_CREATE_URL = 'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/token'; export const KEYCLOAK_USER_INFO_URL = 'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo'; -export const REGISTRY_MISSION_URL = 'registry.takima.io/deadlock/deadlock-challenges'; export const REJECT_UNAUTHORIZED = false; export const ENABLE_AUTOMATIC_SAVE = true; export const ENABLE_RECORDER_HTTP_SERVER = false; diff --git a/deadlock-plugins/deadlock-extension/src/core/controller.ts b/deadlock-plugins/deadlock-extension/src/core/controller.ts index 7d3d362dff3265d01acf55d5ea49180c11d4453f..8a876b338555b9d2e6f0a0bebcc5060b6aaad5d3 100644 --- a/deadlock-plugins/deadlock-extension/src/core/controller.ts +++ b/deadlock-plugins/deadlock-extension/src/core/controller.ts @@ -1,10 +1,5 @@ import * as vscode from 'vscode'; -import { - KEYCLOAK_DEVICE_AUTH_URL, - KEYCLOAK_TOKEN_CREATE_URL, - KEYCLOAK_USER_INFO_URL, - REGISTRY_MISSION_URL, -} from '../config'; +import { KEYCLOAK_DEVICE_AUTH_URL, KEYCLOAK_TOKEN_CREATE_URL, KEYCLOAK_USER_INFO_URL } from '../config'; import { log } from '../recorder/utils'; import { OPEN_QUICK_SETUP_COMMAND } from '../theia/command'; @@ -13,7 +8,7 @@ import QuickSetupView from '../view/quickSetupView'; import { CHOOSE_MISSION_WORKDIR_COMMAND, CommandHandler, OPEN_URL_IN_BROWSER_COMMAND } from './commandHandler'; import ExtensionStore from './extensionStore'; import KeycloakOAuth2DeviceFlowConnection from './keycloakOAuth2DeviceFlowConnection'; -import Mission from './mission'; +import Mission from './mission/mission'; import ApiService from './api.service'; import SshKeyManager from './sshKeyManager'; import { GiteaPublicProperties } from '../model/giteaPublicProperties.model'; @@ -131,19 +126,19 @@ export default class Controller { const user: User = await this.callApiService.getUser(); const giteaPublicProperties: GiteaPublicProperties = await this.callApiService.getGiteaPublicProperties(); - const mission = new Mission( - { - registryBaseURL: REGISTRY_MISSION_URL, - missionId: missionId, - missionVersion: missionVersion, - }, - user, - giteaPublicProperties, - ); - vscode.window.showInformationMessage( - 'opening inside folder ' + this.extensionStore.getMissionWorkdir()! + '/' + missionId, - ); - await mission.setup(); + + const mission = new Mission( + { + missionId: missionId, + missionVersion: missionVersion, + }, + user, + giteaPublicProperties, + ); + vscode.window.showInformationMessage( + 'opening inside folder ' + this.extensionStore.getMissionWorkdir()! + '/' + missionId, + ); + await mission.setup(); await mission.openEditorInFolder(); diff --git a/deadlock-plugins/deadlock-extension/src/core/mission.ts b/deadlock-plugins/deadlock-extension/src/core/mission.ts deleted file mode 100644 index da765ccb2724f4dd0f1f04f3e7c6401fdbb22783..0000000000000000000000000000000000000000 --- a/deadlock-plugins/deadlock-extension/src/core/mission.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { exec as _exec } from 'child_process'; -import * as fs from 'fs'; -import * as util from 'util'; -import * as vscode from 'vscode'; -import { error as err, log } from '../recorder/utils'; -import ExtensionStore from './extensionStore'; -import { userSshKeyFolderPath } from './config'; -import { GiteaPublicProperties } from '../model/giteaPublicProperties.model'; -import { User, UserChallengeJson } from '../model/user.model'; - -/** - * {@link https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback} - */ -const exec = util.promisify(_exec); - -export default class Mission { - private readonly hostBaseWorkDir: string; - private readonly dockerImageURL: string; - private readonly hostMissionDir: string; - private readonly hostMissionDevcontainerDir: string; - private readonly hostMissionDevcontainerFileDir: string; - private readonly hostMissionMountDir: string; - private readonly remoteUserHomeDir: string; - private readonly remoteMissionDir: any; - private readonly remoteGiteaWorkDir: string; - private readonly userMissionConfigPath: string; - private readonly user: User; - private readonly giteaPublicProperties: GiteaPublicProperties; - - constructor( - private params: { registryBaseURL: string; missionId: string; missionVersion: string }, - user: User, - giteaPublicProperties: GiteaPublicProperties, - ) { - const { registryBaseURL, missionId, missionVersion } = params; - this.hostBaseWorkDir = ExtensionStore.getInstance().getMissionWorkdir() ?? ''; - this.hostMissionDir = `${this.hostBaseWorkDir}/${missionId}`; - this.userMissionConfigPath = `${this.hostMissionDir}/.config`; - this.hostMissionDevcontainerDir = `${this.hostMissionDir}/.devcontainer`; - this.hostMissionDevcontainerFileDir = `${this.hostMissionDevcontainerDir}/devcontainer.json`; - this.hostMissionMountDir = `${this.hostMissionDir}/mounted`; - this.dockerImageURL = getDockerImageURL(registryBaseURL, missionId, missionVersion); - this.remoteUserHomeDir = '/home/deadlock'; - this.remoteMissionDir = `${this.remoteUserHomeDir}/mission/`; - this.remoteGiteaWorkDir = `/src`; - this.user = user; - this.giteaPublicProperties = giteaPublicProperties; - } - - static async pullImage(url) { - const { stdout, stderr } = await exec(`docker pull ${url}`); - log(stdout); - err(stderr); - } - - public async setup(options?: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts>) { - await fs.promises.mkdir(this.hostMissionDevcontainerDir, { recursive: true }); - await fs.promises.mkdir(this.hostMissionMountDir, { recursive: true }); - await fs.promises.mkdir(this.userMissionConfigPath, { recursive: true }); - await this.createDevContainerFile(options); - await this.createUserChallengeJsonFile(); - } - - private createDevContainerFile(options?: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts>) { - return fs.promises.writeFile( - this.hostMissionDevcontainerFileDir, - (() => { - const devcontainer: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts> = { - name: `deadlock-${this.params.missionId}`, - image: this.dockerImageURL, - extensions: ['Deadlock.deadlock-coding'], - remoteUser: 'deadlock', - mounts: [ - `source=${userSshKeyFolderPath},target=/tmp/.ssh,type=bind,consistency=cached,readonly`, - `source=${this.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', - }, - }, - }, - overrideCommand: false, - shutdownAction: 'stopContainer', - workspaceMount: `source=${this.hostMissionMountDir},target=${this.remoteMissionDir},type=bind`, - workspaceFolder: `${this.remoteMissionDir}`, - onCreateCommand: `cp -R ${this.remoteGiteaWorkDir}/. ${this.remoteMissionDir}`, - runArgs: ['--privileged'], - ...options, - }; - return JSON.stringify(devcontainer, null, 2); - })(), - ); - } - - private createUserChallengeJsonFile() { - return fs.promises.writeFile( - `${this.userMissionConfigPath}/user-challenge.json`, - (() => { - const userChallengeJson: UserChallengeJson = { - giteaHost: this.giteaPublicProperties.sshHost, - giteaSshPort: this.giteaPublicProperties.sshPort, - username: this.user.id.split('-').join(''), - email: `${this.user.id.split('-').join('')}@deadlock.io`, - missionId: this.params.missionId, - remoteGitUsername: this.user.id.split('-').join(''), - currentUserDetails: this.user.details, - remoteUserDetails: this.user.details, - }; - return JSON.stringify(userChallengeJson, null, 2); - })(), - ); - } - - public async openEditorInFolder(arbitraryPath?: string) { - if (arbitraryPath) { - return vscode.commands.executeCommand('remote-containers.openFolder', vscode.Uri.file(arbitraryPath)); - } - if (!fs.existsSync(this.hostMissionDir)) { - log('WARN missing path ', this.hostMissionDir); - await fs.promises.mkdir(this.hostMissionDir, { recursive: true }); - } - await vscode.commands.executeCommand('remote-containers.openFolder', vscode.Uri.file(this.hostMissionDir)); - } -} - -export function getDockerImageURL(base, missionId, missionVersion) { - return `${base}/${missionId}:${missionVersion}`; -} -interface DockerfileSpecific { - image?; - dockerFile?; - context?; - 'build.args'?; - 'build.target'?; - 'build.cacheFrom'?; - containerEnv?; - containerUser?; - mounts?; - workspaceMount?; - workspaceFolder?; - runArgs?; -} - -interface Base { - name?; - forwardPorts?; - portsAttributes?; - otherPortsAttributes?; - remoteEnv?; - remoteUser?; - updateRemoteUserUID?; - userEnvProbe?; - overrideCommand?; - features?; - shutdownAction?; -} - -interface VSCodespecific { - extensions?; - settings?; - devPort?; -} - -interface LifecycleScripts { - initializeCommand?; - onCreateCommand?; - updateContentCommand?; - postCreateCommand?; - postStartCommand?; - postAttachCommand?; - waitFor?; -} diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/devContainer.ts b/deadlock-plugins/deadlock-extension/src/core/mission/devContainer.ts new file mode 100644 index 0000000000000000000000000000000000000000..a0a4ca41c3444e16f126984ec799cf5306835b3a --- /dev/null +++ b/deadlock-plugins/deadlock-extension/src/core/mission/devContainer.ts @@ -0,0 +1,44 @@ +export interface DockerfileSpecific { + image?: string; + dockerFile?: string; + context?: string; + 'build.args'?: string[]; + 'build.target'?: string; + 'build.cacheFrom'?: string; + containerEnv?: string; + containerUser?: string; + mounts?: string[]; + workspaceMount?: string; + workspaceFolder?: string; + runArgs?: string[]; +} + +export interface Base { + name?: string; + forwardPorts?: string[]; + portsAttributes?: string[]; + otherPortsAttributes?: string[]; + remoteEnv?: string; + remoteUser?: string; + updateRemoteUserUID?: string; + userEnvProbe?: string; + overrideCommand?: boolean; + features?: string[]; + shutdownAction?: string; +} + +export interface VSCodespecific { + extensions?: string[]; + settings?: object; + devPort?: string; +} + +export interface LifecycleScripts { + initializeCommand?: string; + onCreateCommand?: string; + updateContentCommand?: string; + postCreateCommand?: string; + postStartCommand?: string; + postAttachCommand?: string; + waitFor?: string; +} diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/mission.ts b/deadlock-plugins/deadlock-extension/src/core/mission/mission.ts new file mode 100644 index 0000000000000000000000000000000000000000..dc1808af35e68af50ece12803ae0a9ba0fc1d119 --- /dev/null +++ b/deadlock-plugins/deadlock-extension/src/core/mission/mission.ts @@ -0,0 +1,64 @@ +import { exec as _exec } from 'child_process'; +import * as fs from 'fs'; +import * as util from 'util'; +import * as vscode from 'vscode'; +import { error as err, log } from '../../recorder/utils'; +import ExtensionStore from '../extensionStore'; +import { userSshKeyFolderPath } from '../config'; +import { GiteaPublicProperties } from '../../model/giteaPublicProperties.model'; +import { User, UserChallengeJson } from '../../model/user.model'; +import { createDevContainerFile } from './missionDevContainer'; +import { DockerfileSpecific, Base, VSCodespecific, LifecycleScripts } from './devContainer'; +import { MissionResource } from './missionResources'; +import { createUserChallengeJsonFile } from './missionUserChallenge'; + +/** + * {@link https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback} + */ +const exec = util.promisify(_exec); + +export default class Mission { + private readonly hostBaseWorkDir: string; + private readonly hostMissionDir: string; + private readonly hostMissionDevcontainerDir: string; + private readonly hostMissionMountDir: string; + private readonly userMissionConfigPath: string; + private readonly user: User; + private readonly giteaPublicProperties: GiteaPublicProperties; + private readonly missionRessource: MissionResource; + + constructor( + private params: { missionId: string; missionVersion: string }, + user: User, + giteaPublicProperties: GiteaPublicProperties, + ) { + const { missionId, missionVersion } = params; + this.hostBaseWorkDir = ExtensionStore.getInstance().getMissionWorkdir() ?? ''; + this.hostMissionDir = `${this.hostBaseWorkDir}/${missionId}`; + this.userMissionConfigPath = `${this.hostMissionDir}/.config`; + this.hostMissionDevcontainerDir = `${this.hostMissionDir}/.devcontainer`; + this.hostMissionMountDir = `${this.hostMissionDir}/mounted`; + this.user = user; + this.giteaPublicProperties = giteaPublicProperties; + this.missionRessource = new MissionResource(this.hostMissionDir, missionId, missionVersion); + } + + public async setup(options?: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts>) { + await fs.promises.mkdir(this.hostMissionDevcontainerDir, { recursive: true }); + await fs.promises.mkdir(this.hostMissionMountDir, { recursive: true }); + await fs.promises.mkdir(this.userMissionConfigPath, { recursive: true }); + await createDevContainerFile(this.missionRessource, options); + await createUserChallengeJsonFile(this.user, this.missionRessource, this.giteaPublicProperties); + } + + public async openEditorInFolder(arbitraryPath?: string) { + if (arbitraryPath) { + return vscode.commands.executeCommand('remote-containers.openFolder', vscode.Uri.file(arbitraryPath)); + } + if (!fs.existsSync(this.hostMissionDir)) { + log('WARN missing path ', this.hostMissionDir); + await fs.promises.mkdir(this.hostMissionDir, { recursive: true }); + } + await vscode.commands.executeCommand('remote-containers.openFolder', vscode.Uri.file(this.hostMissionDir)); + } +} diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/missionDevContainer.ts b/deadlock-plugins/deadlock-extension/src/core/mission/missionDevContainer.ts new file mode 100644 index 0000000000000000000000000000000000000000..37403ba5a04af97494906c0453e5c3818ecc4c69 --- /dev/null +++ b/deadlock-plugins/deadlock-extension/src/core/mission/missionDevContainer.ts @@ -0,0 +1,57 @@ +import { userSshKeyFolderPath } from '../config'; +import ExtensionStore from '../extensionStore'; +import { Base, DockerfileSpecific, LifecycleScripts, VSCodespecific } from './devContainer'; +import * as fs from 'fs'; +import { MissionResource } from './missionResources'; + +const DOCKER_IMAGE_URL = 'registry.takima.io/deadlock/deadlock-challenges'; + +export function createDevContainerFile( + mission: MissionResource, + 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', + }, + }, + }, + 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); + })(), + ); +} diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/missionDocker.ts b/deadlock-plugins/deadlock-extension/src/core/mission/missionDocker.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/missionResources.ts b/deadlock-plugins/deadlock-extension/src/core/mission/missionResources.ts new file mode 100644 index 0000000000000000000000000000000000000000..7196429d9554c1f32a7957e16f96b30258c983ac --- /dev/null +++ b/deadlock-plugins/deadlock-extension/src/core/mission/missionResources.ts @@ -0,0 +1,7 @@ +export class MissionResource { + constructor(public missionDir: string, public id: string, public version: string) { + this.missionDir = missionDir; + this.id = id; + this.version = version; + } +} diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/missionUserChallenge.ts b/deadlock-plugins/deadlock-extension/src/core/mission/missionUserChallenge.ts new file mode 100644 index 0000000000000000000000000000000000000000..13166798da2d7c09d2d17b7177f4d0dc6cce0616 --- /dev/null +++ b/deadlock-plugins/deadlock-extension/src/core/mission/missionUserChallenge.ts @@ -0,0 +1,27 @@ +import { User, UserChallengeJson } from '../../model/user.model'; +import * as fs from 'fs'; +import { GiteaPublicProperties } from '../../model/giteaPublicProperties.model'; +import { MissionResource } from './missionResources'; + +export function createUserChallengeJsonFile( + user: User, + mission: MissionResource, + giteaPublicProperties: GiteaPublicProperties, +) { + return fs.promises.writeFile( + `${mission.missionDir}/.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); + })(), + ); +}