Skip to content

Commit 54c5a43

Browse files
committed
working port forwarding via exec instance
Signed-off-by: Jonah Iden <[email protected]>
1 parent b4882b1 commit 54c5a43

File tree

5 files changed

+87
-12
lines changed

5 files changed

+87
-12
lines changed

dev-packages/application-manager/src/generator/webpack-generator.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,8 @@ const config = {
437437
${this.ifPackage('@theia/git', () => `// Ensure the git locator process can the started
438438
'git-locator-host': require.resolve('@theia/git/lib/node/git-locator/git-locator-host'),`)}
439439
${this.ifElectron("'electron-main': require.resolve('./src-gen/backend/electron-main'),")}
440+
${this.ifPackage('@theia/dev-container', () => `// VS Code Dev-Container communication:
441+
'dev-container-server': require.resolve('@theia/dev-container/lib/dev-container-server/dev-container-server'),`)}
440442
...commonJsLibraries
441443
},
442444
module: {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2024 Typefox and others.
3+
//
4+
// This program and the accompanying materials are made available under the
5+
// terms of the Eclipse Public License v. 2.0 which is available at
6+
// http://www.eclipse.org/legal/epl-2.0.
7+
//
8+
// This Source Code may also be made available under the following Secondary
9+
// Licenses when the conditions for such availability set forth in the Eclipse
10+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
// with the GNU Classpath Exception which is available at
12+
// https://www.gnu.org/software/classpath/license.html.
13+
//
14+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
17+
import { createConnection } from 'net';
18+
import { stdin, argv, stdout } from 'process';
19+
20+
/**
21+
* this node.js Program is supposed to be executed by an docker exec session inside a docker container.
22+
* It uses a tty session to listen on stdin and send on stdout all communication with the theia backend running inside the container.
23+
*/
24+
25+
let backendPort: number | undefined = undefined;
26+
argv.slice(2).forEach(arg => {
27+
if (arg.startsWith('-target-port')) {
28+
backendPort = parseInt(arg.split('=')[1]);
29+
}
30+
});
31+
32+
if (!backendPort) {
33+
throw new Error('please start with -target-port={port number}');
34+
}
35+
if (stdin.isTTY) {
36+
stdin.setRawMode(true);
37+
}
38+
const connection = createConnection(backendPort, '0.0.0.0');
39+
40+
connection.pipe(stdout);
41+
stdin.pipe(connection);
42+
43+
connection.on('error', error => {
44+
console.error('connection error', error);
45+
});
46+
47+
connection.on('close', () => {
48+
console.log('connection closed');
49+
process.exit(0);
50+
});
51+
52+
// keep the process running
53+
setInterval(() => { }, 1 << 30);

packages/dev-container/src/electron-node/docker-container-service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ export class DockerContainerService {
8181
const containerCreateOptions: Docker.ContainerCreateOptions = {
8282
Tty: true,
8383
ExposedPorts: {
84-
[`${port}/tcp`]: {},
84+
// [`${port}/tcp`]: {},
8585
},
8686
HostConfig: {
8787
PortBindings: {
88-
[`${port}/tcp`]: [{ HostPort: '0' }],
88+
// [`${port}/tcp`]: [{ HostPort: '0' }],
8989
},
9090
Mounts: [{
9191
Source: workspace.path.toString(),

packages/dev-container/src/electron-node/remote-container-connection-provider.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import * as net from 'net';
1818
import { ContainerConnectionOptions, ContainerConnectionResult, RemoteContainerConnectionProvider } from '../electron-common/remote-container-connection-provider';
1919
import { RemoteConnection, RemoteExecOptions, RemoteExecResult, RemoteExecTester, RemoteStatusReport } from '@theia/remote/lib/electron-node/remote-types';
20-
import { RemoteSetupService } from '@theia/remote/lib/electron-node/setup/remote-setup-service';
20+
import { RemoteSetupResult, RemoteSetupService } from '@theia/remote/lib/electron-node/setup/remote-setup-service';
2121
import { RemoteConnectionService } from '@theia/remote/lib/electron-node/remote-connection-service';
2222
import { RemoteProxyServerProvider } from '@theia/remote/lib/electron-node/remote-proxy-server-provider';
2323
import { Emitter, Event, MessageService } from '@theia/core';
@@ -70,11 +70,13 @@ export class DevContainerConnectionProvider implements RemoteContainerConnection
7070
report('Connecting to remote system...');
7171

7272
const remote = await this.createContainerConnection(container, dockerConnection, port);
73-
await this.remoteSetup.setup({
73+
const result = await this.remoteSetup.setup({
7474
connection: remote,
7575
report,
7676
nodeDownloadTemplate: options.nodeDownloadTemplate
7777
});
78+
remote.remoteSetupResult = result;
79+
7880
const registration = this.remoteConnectionService.register(remote);
7981
const server = await this.serverProvider.getProxyServer(socket => {
8082
remote.forwardOut(socket);
@@ -107,7 +109,7 @@ export class DevContainerConnectionProvider implements RemoteContainerConnection
107109
type: 'container',
108110
docker,
109111
container,
110-
port
112+
port,
111113
}));
112114
}
113115

@@ -142,6 +144,8 @@ export class RemoteDockerContainerConnection implements RemoteConnection {
142144

143145
containerInfo: Docker.ContainerInspectInfo | undefined;
144146

147+
remoteSetupResult: RemoteSetupResult;
148+
145149
protected activeTerminalSession: ContainerTerminalSession | undefined;
146150

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

161165
async forwardOut(socket: Socket): Promise<void> {
162-
if (!this.containerInfo) {
163-
this.containerInfo = await this.container.inspect();
166+
const node = `${this.remoteSetupResult.nodeDirectory}/bin/node`;
167+
const devContainerServer = `${this.remoteSetupResult.applicationDirectory}/backend/dev-container-server.js`;
168+
try {
169+
const ttySession = await this.container.exec({
170+
Cmd: ['sh', '-c', `${node} ${devContainerServer} -target-port=${this.remotePort}`],
171+
AttachStdin: true, AttachStdout: true, AttachStderr: true
172+
});
173+
const stream = await ttySession.start({ hijack: true, stdin: true });
174+
175+
socket.pipe(stream);
176+
ttySession.modem.demuxStream(stream, socket, socket);
177+
} catch (e) {
178+
console.error(e);
164179
}
165-
const portMapping = this.containerInfo.NetworkSettings.Ports[`${this.remotePort}/tcp`][0];
166-
const connectSocket = new Socket({ readable: true, writable: true }).connect(parseInt(portMapping.HostPort), portMapping.HostIp);
167-
socket.pipe(connectSocket);
168-
connectSocket.pipe(socket);
169180
}
170181

171182
async exec(cmd: string, args?: string[], options?: RemoteExecOptions): Promise<RemoteExecResult> {

packages/remote/src/electron-node/setup/remote-setup-service.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ export interface RemoteSetupOptions {
2929
nodeDownloadTemplate?: string;
3030
}
3131

32+
export interface RemoteSetupResult {
33+
applicationDirectory: string;
34+
nodeDirectory: string;
35+
}
36+
3237
@injectable()
3338
export class RemoteSetupService {
3439

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

50-
async setup(options: RemoteSetupOptions): Promise<void> {
55+
async setup(options: RemoteSetupOptions): Promise<RemoteSetupResult> {
5156
const {
5257
connection,
5358
report,
@@ -86,6 +91,10 @@ export class RemoteSetupService {
8691
report('Starting application on remote...');
8792
const port = await this.startApplication(connection, platform, applicationDirectory, remoteNodeDirectory);
8893
connection.remotePort = port;
94+
return {
95+
applicationDirectory: libDir,
96+
nodeDirectory: remoteNodeDirectory
97+
};
8998
}
9099

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

0 commit comments

Comments
 (0)