Skip to content

Commit

Permalink
working port forwarding via exec instance
Browse files Browse the repository at this point in the history
Signed-off-by: Jonah Iden <jonah.iden@typefox.io>
  • Loading branch information
jonah-iden committed Feb 20, 2024
1 parent b4882b1 commit 54c5a43
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,8 @@ const config = {
${this.ifPackage('@theia/git', () => `// Ensure the git locator process can the started
'git-locator-host': require.resolve('@theia/git/lib/node/git-locator/git-locator-host'),`)}
${this.ifElectron("'electron-main': require.resolve('./src-gen/backend/electron-main'),")}
${this.ifPackage('@theia/dev-container', () => `// VS Code Dev-Container communication:
'dev-container-server': require.resolve('@theia/dev-container/lib/dev-container-server/dev-container-server'),`)}
...commonJsLibraries
},
module: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// *****************************************************************************
// Copyright (C) 2024 Typefox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { createConnection } from 'net';
import { stdin, argv, stdout } from 'process';

/**
* this node.js Program is supposed to be executed by an docker exec session inside a docker container.
* It uses a tty session to listen on stdin and send on stdout all communication with the theia backend running inside the container.
*/

let backendPort: number | undefined = undefined;
argv.slice(2).forEach(arg => {
if (arg.startsWith('-target-port')) {
backendPort = parseInt(arg.split('=')[1]);
}
});

if (!backendPort) {
throw new Error('please start with -target-port={port number}');
}
if (stdin.isTTY) {
stdin.setRawMode(true);
}
const connection = createConnection(backendPort, '0.0.0.0');

connection.pipe(stdout);
stdin.pipe(connection);

connection.on('error', error => {
console.error('connection error', error);
});

connection.on('close', () => {
console.log('connection closed');
process.exit(0);
});

// keep the process running
setInterval(() => { }, 1 << 30);
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,11 @@ export class DockerContainerService {
const containerCreateOptions: Docker.ContainerCreateOptions = {
Tty: true,
ExposedPorts: {
[`${port}/tcp`]: {},
// [`${port}/tcp`]: {},
},
HostConfig: {
PortBindings: {
[`${port}/tcp`]: [{ HostPort: '0' }],
// [`${port}/tcp`]: [{ HostPort: '0' }],
},
Mounts: [{
Source: workspace.path.toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import * as net from 'net';
import { ContainerConnectionOptions, ContainerConnectionResult, RemoteContainerConnectionProvider } from '../electron-common/remote-container-connection-provider';
import { RemoteConnection, RemoteExecOptions, RemoteExecResult, RemoteExecTester, RemoteStatusReport } from '@theia/remote/lib/electron-node/remote-types';
import { RemoteSetupService } from '@theia/remote/lib/electron-node/setup/remote-setup-service';
import { RemoteSetupResult, RemoteSetupService } from '@theia/remote/lib/electron-node/setup/remote-setup-service';
import { RemoteConnectionService } from '@theia/remote/lib/electron-node/remote-connection-service';
import { RemoteProxyServerProvider } from '@theia/remote/lib/electron-node/remote-proxy-server-provider';
import { Emitter, Event, MessageService } from '@theia/core';
Expand Down Expand Up @@ -70,11 +70,13 @@ export class DevContainerConnectionProvider implements RemoteContainerConnection
report('Connecting to remote system...');

const remote = await this.createContainerConnection(container, dockerConnection, port);
await this.remoteSetup.setup({
const result = await this.remoteSetup.setup({
connection: remote,
report,
nodeDownloadTemplate: options.nodeDownloadTemplate
});
remote.remoteSetupResult = result;

const registration = this.remoteConnectionService.register(remote);
const server = await this.serverProvider.getProxyServer(socket => {
remote.forwardOut(socket);
Expand Down Expand Up @@ -107,7 +109,7 @@ export class DevContainerConnectionProvider implements RemoteContainerConnection
type: 'container',
docker,
container,
port
port,
}));
}

Expand Down Expand Up @@ -142,6 +144,8 @@ export class RemoteDockerContainerConnection implements RemoteConnection {

containerInfo: Docker.ContainerInspectInfo | undefined;

remoteSetupResult: RemoteSetupResult;

protected activeTerminalSession: ContainerTerminalSession | undefined;

protected readonly onDidDisconnectEmitter = new Emitter<void>();
Expand All @@ -159,13 +163,20 @@ export class RemoteDockerContainerConnection implements RemoteConnection {
}

async forwardOut(socket: Socket): Promise<void> {
if (!this.containerInfo) {
this.containerInfo = await this.container.inspect();
const node = `${this.remoteSetupResult.nodeDirectory}/bin/node`;
const devContainerServer = `${this.remoteSetupResult.applicationDirectory}/backend/dev-container-server.js`;
try {
const ttySession = await this.container.exec({
Cmd: ['sh', '-c', `${node} ${devContainerServer} -target-port=${this.remotePort}`],
AttachStdin: true, AttachStdout: true, AttachStderr: true
});
const stream = await ttySession.start({ hijack: true, stdin: true });

socket.pipe(stream);
ttySession.modem.demuxStream(stream, socket, socket);
} catch (e) {
console.error(e);
}
const portMapping = this.containerInfo.NetworkSettings.Ports[`${this.remotePort}/tcp`][0];
const connectSocket = new Socket({ readable: true, writable: true }).connect(parseInt(portMapping.HostPort), portMapping.HostIp);
socket.pipe(connectSocket);
connectSocket.pipe(socket);
}

async exec(cmd: string, args?: string[], options?: RemoteExecOptions): Promise<RemoteExecResult> {
Expand Down
11 changes: 10 additions & 1 deletion packages/remote/src/electron-node/setup/remote-setup-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export interface RemoteSetupOptions {
nodeDownloadTemplate?: string;
}

export interface RemoteSetupResult {
applicationDirectory: string;
nodeDirectory: string;
}

@injectable()
export class RemoteSetupService {

Expand All @@ -47,7 +52,7 @@ export class RemoteSetupService {
@inject(ApplicationPackage)
protected readonly applicationPackage: ApplicationPackage;

async setup(options: RemoteSetupOptions): Promise<void> {
async setup(options: RemoteSetupOptions): Promise<RemoteSetupResult> {
const {
connection,
report,
Expand Down Expand Up @@ -86,6 +91,10 @@ export class RemoteSetupService {
report('Starting application on remote...');
const port = await this.startApplication(connection, platform, applicationDirectory, remoteNodeDirectory);
connection.remotePort = port;
return {
applicationDirectory: libDir,
nodeDirectory: remoteNodeDirectory
};
}

protected async startApplication(connection: RemoteConnection, platform: RemotePlatform, remotePath: string, nodeDir: string): Promise<number> {
Expand Down

0 comments on commit 54c5a43

Please sign in to comment.