@theia/dev-container 1.72.0-next.59 → 1.73.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/electron-browser/container-connection-contribution.d.ts +5 -2
- package/lib/electron-browser/container-connection-contribution.d.ts.map +1 -1
- package/lib/electron-browser/container-connection-contribution.js +130 -30
- package/lib/electron-browser/container-connection-contribution.js.map +1 -1
- package/lib/electron-browser/container-output-provider.d.ts.map +1 -1
- package/lib/electron-browser/container-output-provider.js +3 -1
- package/lib/electron-browser/container-output-provider.js.map +1 -1
- package/lib/electron-browser/dev-container-frontend-module.d.ts.map +1 -1
- package/lib/electron-browser/dev-container-frontend-module.js +5 -0
- package/lib/electron-browser/dev-container-frontend-module.js.map +1 -1
- package/lib/electron-browser/dev-container-startup-contribution.d.ts +15 -0
- package/lib/electron-browser/dev-container-startup-contribution.d.ts.map +1 -0
- package/lib/electron-browser/dev-container-startup-contribution.js +94 -0
- package/lib/electron-browser/dev-container-startup-contribution.js.map +1 -0
- package/lib/electron-common/dev-container-preferences.d.ts +12 -0
- package/lib/electron-common/dev-container-preferences.d.ts.map +1 -0
- package/lib/electron-common/dev-container-preferences.js +44 -0
- package/lib/electron-common/dev-container-preferences.js.map +1 -0
- package/lib/electron-common/remote-container-connection-provider.d.ts +20 -1
- package/lib/electron-common/remote-container-connection-provider.d.ts.map +1 -1
- package/lib/electron-node/dev-container-backend-module.d.ts.map +1 -1
- package/lib/electron-node/dev-container-backend-module.js +4 -0
- package/lib/electron-node/dev-container-backend-module.js.map +1 -1
- package/lib/electron-node/dev-container-cli-contribution.d.ts +19 -0
- package/lib/electron-node/dev-container-cli-contribution.d.ts.map +1 -0
- package/lib/electron-node/dev-container-cli-contribution.js +66 -0
- package/lib/electron-node/dev-container-cli-contribution.js.map +1 -0
- package/lib/electron-node/dev-container-cli-contribution.spec.d.ts +2 -0
- package/lib/electron-node/dev-container-cli-contribution.spec.d.ts.map +1 -0
- package/lib/electron-node/dev-container-cli-contribution.spec.js +91 -0
- package/lib/electron-node/dev-container-cli-contribution.spec.js.map +1 -0
- package/lib/electron-node/dev-container-file-service.d.ts +4 -4
- package/lib/electron-node/dev-container-file-service.d.ts.map +1 -1
- package/lib/electron-node/dev-container-file-service.js +9 -9
- package/lib/electron-node/dev-container-file-service.js.map +1 -1
- package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.d.ts +6 -2
- package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.d.ts.map +1 -1
- package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.js +24 -4
- package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.js.map +1 -1
- package/lib/electron-node/devcontainer-contributions/variable-resolver-contribution.d.ts +7 -6
- package/lib/electron-node/devcontainer-contributions/variable-resolver-contribution.d.ts.map +1 -1
- package/lib/electron-node/devcontainer-contributions/variable-resolver-contribution.js +4 -9
- package/lib/electron-node/devcontainer-contributions/variable-resolver-contribution.js.map +1 -1
- package/lib/electron-node/devcontainer-util.d.ts +19 -0
- package/lib/electron-node/devcontainer-util.d.ts.map +1 -0
- package/lib/electron-node/devcontainer-util.js +48 -0
- package/lib/electron-node/devcontainer-util.js.map +1 -0
- package/lib/electron-node/devcontainer-util.spec.d.ts +2 -0
- package/lib/electron-node/devcontainer-util.spec.d.ts.map +1 -0
- package/lib/electron-node/devcontainer-util.spec.js +128 -0
- package/lib/electron-node/devcontainer-util.spec.js.map +1 -0
- package/lib/electron-node/docker-container-service.d.ts +3 -3
- package/lib/electron-node/docker-container-service.d.ts.map +1 -1
- package/lib/electron-node/docker-container-service.js +3 -4
- package/lib/electron-node/docker-container-service.js.map +1 -1
- package/lib/electron-node/remote-container-connection-provider.d.ts +27 -66
- package/lib/electron-node/remote-container-connection-provider.d.ts.map +1 -1
- package/lib/electron-node/remote-container-connection-provider.js +269 -311
- package/lib/electron-node/remote-container-connection-provider.js.map +1 -1
- package/lib/electron-node/remote-docker-container-connection.d.ts +50 -0
- package/lib/electron-node/remote-docker-container-connection.d.ts.map +1 -0
- package/lib/electron-node/remote-docker-container-connection.js +239 -0
- package/lib/electron-node/remote-docker-container-connection.js.map +1 -0
- package/lib/electron-node/remote-docker-container-connection.spec.d.ts +2 -0
- package/lib/electron-node/remote-docker-container-connection.spec.d.ts.map +1 -0
- package/lib/electron-node/remote-docker-container-connection.spec.js +217 -0
- package/lib/electron-node/remote-docker-container-connection.spec.js.map +1 -0
- package/package.json +7 -7
- package/src/electron-browser/container-connection-contribution.ts +155 -38
- package/src/electron-browser/container-output-provider.ts +3 -1
- package/src/electron-browser/dev-container-frontend-module.ts +6 -0
- package/src/electron-browser/dev-container-startup-contribution.ts +99 -0
- package/src/electron-common/dev-container-preferences.ts +53 -0
- package/src/electron-common/remote-container-connection-provider.ts +23 -1
- package/src/electron-node/dev-container-backend-module.ts +5 -0
- package/src/electron-node/dev-container-cli-contribution.spec.ts +106 -0
- package/src/electron-node/dev-container-cli-contribution.ts +68 -0
- package/src/electron-node/dev-container-file-service.ts +10 -10
- package/src/electron-node/devcontainer-contributions/main-container-creation-contributions.ts +29 -5
- package/src/electron-node/devcontainer-contributions/variable-resolver-contribution.ts +11 -11
- package/src/electron-node/devcontainer-util.spec.ts +154 -0
- package/src/electron-node/devcontainer-util.ts +49 -0
- package/src/electron-node/docker-container-service.ts +6 -7
- package/src/electron-node/remote-container-connection-provider.ts +274 -366
- package/src/electron-node/{remote-container-connection-provider.spec.ts → remote-docker-container-connection.spec.ts} +105 -4
- package/src/electron-node/remote-docker-container-connection.ts +290 -0
- package/lib/electron-node/remote-container-connection-provider.spec.d.ts +0 -2
- package/lib/electron-node/remote-container-connection-provider.spec.d.ts.map +0 -1
- package/lib/electron-node/remote-container-connection-provider.spec.js +0 -131
- package/lib/electron-node/remote-container-connection-provider.spec.js.map +0 -1
|
@@ -15,26 +15,28 @@
|
|
|
15
15
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
16
16
|
// *****************************************************************************
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.
|
|
18
|
+
exports.DevContainerConnectionProvider = exports.RemoteDockerContainerConnection = void 0;
|
|
19
19
|
const tslib_1 = require("tslib");
|
|
20
|
-
const
|
|
20
|
+
const stream_1 = require("stream");
|
|
21
21
|
const remote_connection_service_1 = require("@theia/remote/lib/electron-node/remote-connection-service");
|
|
22
|
+
const remote_setup_service_1 = require("@theia/remote/lib/electron-node/setup/remote-setup-service");
|
|
22
23
|
const remote_proxy_server_provider_1 = require("@theia/remote/lib/electron-node/remote-proxy-server-provider");
|
|
23
24
|
const core_1 = require("@theia/core");
|
|
24
25
|
const inversify_1 = require("@theia/core/shared/inversify");
|
|
25
26
|
const Docker = require("dockerode");
|
|
26
|
-
const docker_container_service_1 = require("./docker-container-service");
|
|
27
|
-
const promise_util_1 = require("@theia/core/lib/common/promise-util");
|
|
28
|
-
const stream_1 = require("stream");
|
|
29
|
-
const child_process_1 = require("child_process");
|
|
30
27
|
const dev_container_file_service_1 = require("./dev-container-file-service");
|
|
31
|
-
const
|
|
32
|
-
const
|
|
28
|
+
const docker_container_service_1 = require("./docker-container-service");
|
|
29
|
+
const dev_container_cli_contribution_1 = require("./dev-container-cli-contribution");
|
|
30
|
+
const remote_docker_container_connection_1 = require("./remote-docker-container-connection");
|
|
31
|
+
// Re-export for backward compatibility — these types were moved to remote-docker-container-connection.ts
|
|
32
|
+
var remote_docker_container_connection_2 = require("./remote-docker-container-connection");
|
|
33
|
+
Object.defineProperty(exports, "RemoteDockerContainerConnection", { enumerable: true, get: function () { return remote_docker_container_connection_2.RemoteDockerContainerConnection; } });
|
|
34
|
+
const devcontainer_util_1 = require("./devcontainer-util");
|
|
33
35
|
let DevContainerConnectionProvider = class DevContainerConnectionProvider {
|
|
34
36
|
setClient(client) {
|
|
35
37
|
this.outputProvider = client;
|
|
36
38
|
}
|
|
37
|
-
async
|
|
39
|
+
async createDockerConnection() {
|
|
38
40
|
const dockerOptions = {};
|
|
39
41
|
const dockerHost = process.env.DOCKER_HOST;
|
|
40
42
|
try {
|
|
@@ -63,55 +65,162 @@ let DevContainerConnectionProvider = class DevContainerConnectionProvider {
|
|
|
63
65
|
}
|
|
64
66
|
catch (_) {
|
|
65
67
|
this.logger.warn(`Ignoring invalid DOCKER_HOST=${dockerHost}`);
|
|
66
|
-
this.messageService.warn(`Ignoring invalid DOCKER_HOST=${dockerHost}`);
|
|
67
68
|
}
|
|
68
69
|
const dockerConnection = new Docker(dockerOptions);
|
|
69
|
-
|
|
70
|
+
await dockerConnection.version()
|
|
70
71
|
.catch(e => {
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
this.logger.error('Docker Error:', e);
|
|
73
|
+
throw new Error(`Docker is not available: ${e.message ?? e}`);
|
|
73
74
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
return dockerConnection;
|
|
76
|
+
}
|
|
77
|
+
async listRunningContainers(docker) {
|
|
78
|
+
docker ??= await this.createDockerConnection();
|
|
79
|
+
const containers = await docker.listContainers({ all: false });
|
|
80
|
+
return containers.map(container => ({
|
|
81
|
+
id: container.Id.substring(0, 12),
|
|
82
|
+
name: (container.Names[0] || '').replace(/^\//, ''),
|
|
83
|
+
image: container.Image,
|
|
84
|
+
status: container.Status,
|
|
85
|
+
created: container.Created
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
async getWorkspaceCandidates(containerId, docker) {
|
|
89
|
+
docker ??= await this.createDockerConnection();
|
|
90
|
+
const container = docker.getContainer(containerId);
|
|
91
|
+
const info = await container.inspect();
|
|
92
|
+
const candidates = [];
|
|
93
|
+
const seen = new Set();
|
|
94
|
+
// Check for devcontainer metadata label (set by tools that created the container)
|
|
95
|
+
const metadataLabel = info.Config.Labels?.['devcontainer.metadata'];
|
|
96
|
+
if (metadataLabel) {
|
|
97
|
+
try {
|
|
98
|
+
const metadata = JSON.parse(metadataLabel);
|
|
99
|
+
if (Array.isArray(metadata)) {
|
|
100
|
+
for (const entry of metadata) {
|
|
101
|
+
if (entry.remoteWorkspaceFolder && !seen.has(entry.remoteWorkspaceFolder)) {
|
|
102
|
+
seen.add(entry.remoteWorkspaceFolder);
|
|
103
|
+
candidates.push({ path: entry.remoteWorkspaceFolder, source: 'devcontainer-label' });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// ignore malformed metadata
|
|
110
|
+
}
|
|
77
111
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
112
|
+
const localFolderLabel = info.Config.Labels?.['devcontainer.local_folder'];
|
|
113
|
+
if (localFolderLabel) {
|
|
114
|
+
const basename = core_1.URI.fromFilePath(localFolderLabel).path.base;
|
|
115
|
+
if (basename) {
|
|
116
|
+
const workspacePath = `/workspaces/${basename}`;
|
|
117
|
+
if (!seen.has(workspacePath)) {
|
|
118
|
+
seen.add(workspacePath);
|
|
119
|
+
candidates.push({ path: workspacePath, source: 'devcontainer-label' });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (info.Config.WorkingDir && info.Config.WorkingDir !== '/' && !seen.has(info.Config.WorkingDir)) {
|
|
124
|
+
seen.add(info.Config.WorkingDir);
|
|
125
|
+
candidates.push({ path: info.Config.WorkingDir, source: 'working-dir' });
|
|
126
|
+
}
|
|
127
|
+
for (const mount of (0, devcontainer_util_1.getWorkspaceMounts)(info.Mounts ?? [])) {
|
|
128
|
+
if (!seen.has(mount.Destination)) {
|
|
129
|
+
seen.add(mount.Destination);
|
|
130
|
+
candidates.push({ path: mount.Destination, source: 'bind-mount' });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!seen.has('/')) {
|
|
134
|
+
candidates.push({ path: '/', source: 'fallback' });
|
|
135
|
+
}
|
|
136
|
+
return candidates;
|
|
137
|
+
}
|
|
138
|
+
async scanForDevContainerConfig(containerId, workspacePath, docker) {
|
|
139
|
+
docker ??= await this.createDockerConnection();
|
|
140
|
+
const container = docker.getContainer(containerId);
|
|
141
|
+
// Search all three standard devcontainer.json locations:
|
|
142
|
+
// <workspace>/.devcontainer/devcontainer.json
|
|
143
|
+
// <workspace>/.devcontainer.json
|
|
144
|
+
// <workspace>/.devcontainer/<subfolder>/devcontainer.json
|
|
145
|
+
// Uses a single find command to avoid multiple exec round-trips.
|
|
82
146
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
147
|
+
// Use Cmd array form instead of sh -c to avoid shell injection.
|
|
148
|
+
// stderr (e.g. "No such file or directory" when .devcontainer doesn't exist)
|
|
149
|
+
// is demuxed to a separate stream and discarded.
|
|
150
|
+
const execution = await container.exec({
|
|
151
|
+
Cmd: [
|
|
152
|
+
'find',
|
|
153
|
+
`${workspacePath}/.devcontainer`, `${workspacePath}/.devcontainer.json`,
|
|
154
|
+
'-maxdepth', '2',
|
|
155
|
+
'(', '-name', 'devcontainer.json', '-o', '-name', '.devcontainer.json', ')',
|
|
156
|
+
'-type', 'f'
|
|
157
|
+
],
|
|
158
|
+
AttachStdout: true,
|
|
159
|
+
AttachStderr: true
|
|
93
160
|
});
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
161
|
+
let stdout = '';
|
|
162
|
+
const stream = await execution.start({});
|
|
163
|
+
const stdoutPassthrough = new stream_1.PassThrough();
|
|
164
|
+
const stderrPassthrough = new stream_1.PassThrough();
|
|
165
|
+
stdoutPassthrough.on('data', (chunk) => {
|
|
166
|
+
stdout += chunk.toString();
|
|
98
167
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
168
|
+
execution.modem.demuxStream(stream, stdoutPassthrough, stderrPassthrough);
|
|
169
|
+
await new Promise((resolve, reject) => {
|
|
170
|
+
stream.on('end', () => resolve());
|
|
171
|
+
stream.on('error', reject);
|
|
102
172
|
});
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
173
|
+
stdoutPassthrough.destroy();
|
|
174
|
+
stderrPassthrough.destroy();
|
|
175
|
+
const found = stdout.trim().split('\n').filter(line => line.length > 0);
|
|
176
|
+
if (found.length === 0) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
// Prefer the standard location, then root-level, then subfolder configs
|
|
180
|
+
const standardPath = `${workspacePath}/.devcontainer/devcontainer.json`;
|
|
181
|
+
const rootPath = `${workspacePath}/.devcontainer.json`;
|
|
182
|
+
for (const preferred of [standardPath, rootPath]) {
|
|
183
|
+
if (found.includes(preferred)) {
|
|
184
|
+
return preferred;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Return the first subfolder config found
|
|
188
|
+
return found[0];
|
|
189
|
+
}
|
|
190
|
+
catch (e) {
|
|
191
|
+
// find/sh might not be available in minimal containers
|
|
192
|
+
this.logger.debug('Failed to scan for devcontainer.json in container:', e);
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async connectToContainer(options) {
|
|
197
|
+
const progress = await this.messageService.showProgress({ text: 'Creating container' });
|
|
198
|
+
try {
|
|
199
|
+
const report = message => progress.report({ message });
|
|
200
|
+
const docker = await this.createDockerConnection();
|
|
201
|
+
let remote;
|
|
202
|
+
try {
|
|
203
|
+
const container = await this.containerService.getOrCreateContainer(docker, options, this.outputProvider);
|
|
204
|
+
const context = { containerId: container.id };
|
|
205
|
+
const devContainerConfig = await this.devContainerFileService.getConfiguration(options.devcontainerFile, context);
|
|
206
|
+
report('Connecting to remote system...');
|
|
207
|
+
const result = await this.setupRemoteConnection(container, docker, devContainerConfig, options.nodeDownloadTemplate, report);
|
|
208
|
+
remote = result.remote;
|
|
209
|
+
await this.containerService.postConnect(options.devcontainerFile, remote, this.outputProvider, context);
|
|
210
|
+
return {
|
|
211
|
+
containerId: container.id,
|
|
212
|
+
workspacePath: devContainerConfig.workspaceFolder ?? (0, devcontainer_util_1.inferWorkspacePath)(await container.inspect()),
|
|
213
|
+
port: result.localPort.toString(),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
remote?.dispose();
|
|
218
|
+
this.logger.error(e);
|
|
219
|
+
throw e;
|
|
220
|
+
}
|
|
111
221
|
}
|
|
112
222
|
catch (e) {
|
|
113
223
|
this.messageService.error(e.message);
|
|
114
|
-
console.error(e);
|
|
115
224
|
throw e;
|
|
116
225
|
}
|
|
117
226
|
finally {
|
|
@@ -121,102 +230,77 @@ let DevContainerConnectionProvider = class DevContainerConnectionProvider {
|
|
|
121
230
|
getDevContainerFiles(workspacePath) {
|
|
122
231
|
return this.devContainerFileService.getAvailableFiles(workspacePath);
|
|
123
232
|
}
|
|
124
|
-
async
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
name: config.name ?? 'dev-container',
|
|
128
|
-
type: 'Dev Container',
|
|
129
|
-
docker,
|
|
130
|
-
container,
|
|
131
|
-
config,
|
|
132
|
-
logger: this.logger
|
|
133
|
-
}));
|
|
134
|
-
}
|
|
135
|
-
async getCurrentContainerInfo(port) {
|
|
136
|
-
const connection = this.remoteConnectionService.getConnectionFromPort(port);
|
|
137
|
-
if (!connection || !(connection instanceof RemoteDockerContainerConnection)) {
|
|
233
|
+
async getAttachContainerArgs() {
|
|
234
|
+
const containerId = this.cliContribution.consumeAttachContainerId();
|
|
235
|
+
if (!containerId) {
|
|
138
236
|
return undefined;
|
|
139
237
|
}
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const docker = new Docker();
|
|
145
|
-
const containers = await docker.listContainers({ all: false });
|
|
146
|
-
return containers.map(container => ({
|
|
147
|
-
id: container.Id,
|
|
148
|
-
name: (container.Names[0] ?? '').replace(/^\//, ''),
|
|
149
|
-
image: container.Image,
|
|
150
|
-
status: container.Status
|
|
151
|
-
}));
|
|
152
|
-
}
|
|
153
|
-
catch (e) {
|
|
154
|
-
console.error('Failed to list running containers:', e);
|
|
155
|
-
return [];
|
|
156
|
-
}
|
|
238
|
+
return {
|
|
239
|
+
containerId,
|
|
240
|
+
scanForDevJson: this.cliContribution.shouldScanForDevJson()
|
|
241
|
+
};
|
|
157
242
|
}
|
|
158
|
-
async attachToContainer(
|
|
159
|
-
const
|
|
160
|
-
const container = docker.getContainer(containerId);
|
|
161
|
-
const containerInfo = await container.inspect();
|
|
162
|
-
const progress = await this.messageService.showProgress({
|
|
163
|
-
text: 'Attaching to container',
|
|
164
|
-
});
|
|
243
|
+
async attachToContainer(options) {
|
|
244
|
+
const progress = await this.messageService.showProgress({ text: 'Attaching to container' });
|
|
165
245
|
try {
|
|
166
246
|
const report = message => progress.report({ message });
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
remote
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
247
|
+
const docker = await this.createDockerConnection();
|
|
248
|
+
const container = docker.getContainer(options.containerId);
|
|
249
|
+
const containerInfo = await container.inspect();
|
|
250
|
+
if (!containerInfo.State.Running) {
|
|
251
|
+
throw new Error(`Container ${options.containerId} is not running`);
|
|
252
|
+
}
|
|
253
|
+
const context = { containerId: options.containerId };
|
|
254
|
+
let config;
|
|
255
|
+
if (options.devcontainerFile) {
|
|
256
|
+
config = await this.devContainerFileService.getConfiguration(options.devcontainerFile, context);
|
|
257
|
+
config = { ...config, shutdownAction: 'none' };
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
config = { name: containerInfo.Name.replace(/^\//, ''), shutdownAction: 'none' };
|
|
261
|
+
}
|
|
262
|
+
let remote;
|
|
263
|
+
try {
|
|
264
|
+
report('Connecting to remote system...');
|
|
265
|
+
const result = await this.setupRemoteConnection(container, docker, config, options.nodeDownloadTemplate, report);
|
|
266
|
+
remote = result.remote;
|
|
267
|
+
if (options.devcontainerFile) {
|
|
268
|
+
await this.containerService.postConnect(options.devcontainerFile, remote, this.outputProvider, context);
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
containerId: container.id,
|
|
272
|
+
workspacePath: options.workspacePath,
|
|
273
|
+
port: result.localPort.toString(),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
remote?.dispose();
|
|
278
|
+
this.logger.error(e);
|
|
279
|
+
throw e;
|
|
280
|
+
}
|
|
198
281
|
}
|
|
199
282
|
catch (e) {
|
|
200
283
|
this.messageService.error(e.message);
|
|
201
|
-
console.error(e);
|
|
202
284
|
throw e;
|
|
203
285
|
}
|
|
204
286
|
finally {
|
|
205
287
|
progress.cancel();
|
|
206
288
|
}
|
|
207
289
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
return (workspaceMount?.Destination ?? containerInfo.Config.WorkingDir) || '/';
|
|
290
|
+
async getCurrentContainerInfo(port) {
|
|
291
|
+
const connection = this.remoteConnectionService.getConnectionFromPort(port);
|
|
292
|
+
if (!connection || !(connection instanceof remote_docker_container_connection_1.RemoteDockerContainerConnection)) {
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
return connection.container.inspect();
|
|
215
296
|
}
|
|
216
297
|
async removeContainer(containerId) {
|
|
298
|
+
return this.doRemoveContainer(containerId);
|
|
299
|
+
}
|
|
300
|
+
async doRemoveContainer(containerId, docker) {
|
|
301
|
+
docker ??= await this.createDockerConnection();
|
|
302
|
+
const container = docker.getContainer(containerId);
|
|
217
303
|
try {
|
|
218
|
-
const docker = new Docker();
|
|
219
|
-
const container = docker.getContainer(containerId);
|
|
220
304
|
const info = await container.inspect();
|
|
221
305
|
if (info.State.Running) {
|
|
222
306
|
await container.stop();
|
|
@@ -224,11 +308,63 @@ let DevContainerConnectionProvider = class DevContainerConnectionProvider {
|
|
|
224
308
|
await container.remove();
|
|
225
309
|
}
|
|
226
310
|
catch (e) {
|
|
227
|
-
|
|
311
|
+
this.logger.error('Failed to remove container:', e);
|
|
228
312
|
throw e;
|
|
229
313
|
}
|
|
230
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Creates a remote connection, runs setup (injecting the Theia backend into the container),
|
|
317
|
+
* registers the connection, and starts a local proxy server.
|
|
318
|
+
*
|
|
319
|
+
* @returns the local proxy port and the remote connection
|
|
320
|
+
*/
|
|
321
|
+
async setupRemoteConnection(container, docker, config, nodeDownloadTemplate, report) {
|
|
322
|
+
const remote = new remote_docker_container_connection_1.RemoteDockerContainerConnection({
|
|
323
|
+
id: (0, core_1.generateUuid)(),
|
|
324
|
+
name: config.name ?? 'dev-container',
|
|
325
|
+
type: 'Dev Container',
|
|
326
|
+
docker,
|
|
327
|
+
container,
|
|
328
|
+
config,
|
|
329
|
+
logger: this.logger
|
|
330
|
+
});
|
|
331
|
+
let result;
|
|
332
|
+
try {
|
|
333
|
+
result = await this.remoteSetup.setup({
|
|
334
|
+
connection: remote,
|
|
335
|
+
report,
|
|
336
|
+
nodeDownloadTemplate
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
catch (e) {
|
|
340
|
+
remote.dispose();
|
|
341
|
+
throw e;
|
|
342
|
+
}
|
|
343
|
+
remote.remoteSetupResult = result;
|
|
344
|
+
let registration;
|
|
345
|
+
let server;
|
|
346
|
+
try {
|
|
347
|
+
registration = this.remoteConnectionService.register(remote);
|
|
348
|
+
server = await this.serverProvider.getProxyServer(socket => {
|
|
349
|
+
remote.forwardOut(socket);
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
catch (e) {
|
|
353
|
+
server?.close();
|
|
354
|
+
registration?.dispose();
|
|
355
|
+
remote.dispose();
|
|
356
|
+
throw e;
|
|
357
|
+
}
|
|
358
|
+
remote.onDidDisconnect(() => {
|
|
359
|
+
server.close();
|
|
360
|
+
registration.dispose();
|
|
361
|
+
});
|
|
362
|
+
const localPort = server.address().port;
|
|
363
|
+
remote.localPort = localPort;
|
|
364
|
+
return { localPort, remote };
|
|
365
|
+
}
|
|
231
366
|
dispose() {
|
|
367
|
+
this.outputProvider = undefined;
|
|
232
368
|
}
|
|
233
369
|
};
|
|
234
370
|
exports.DevContainerConnectionProvider = DevContainerConnectionProvider;
|
|
@@ -240,10 +376,6 @@ tslib_1.__decorate([
|
|
|
240
376
|
(0, inversify_1.inject)(remote_setup_service_1.RemoteSetupService),
|
|
241
377
|
tslib_1.__metadata("design:type", remote_setup_service_1.RemoteSetupService)
|
|
242
378
|
], DevContainerConnectionProvider.prototype, "remoteSetup", void 0);
|
|
243
|
-
tslib_1.__decorate([
|
|
244
|
-
(0, inversify_1.inject)(core_1.MessageService),
|
|
245
|
-
tslib_1.__metadata("design:type", core_1.MessageService)
|
|
246
|
-
], DevContainerConnectionProvider.prototype, "messageService", void 0);
|
|
247
379
|
tslib_1.__decorate([
|
|
248
380
|
(0, inversify_1.inject)(remote_proxy_server_provider_1.RemoteProxyServerProvider),
|
|
249
381
|
tslib_1.__metadata("design:type", remote_proxy_server_provider_1.RemoteProxyServerProvider)
|
|
@@ -257,192 +389,18 @@ tslib_1.__decorate([
|
|
|
257
389
|
tslib_1.__metadata("design:type", dev_container_file_service_1.DevContainerFileService)
|
|
258
390
|
], DevContainerConnectionProvider.prototype, "devContainerFileService", void 0);
|
|
259
391
|
tslib_1.__decorate([
|
|
260
|
-
(0, inversify_1.inject)(
|
|
261
|
-
tslib_1.__metadata("design:type",
|
|
262
|
-
], DevContainerConnectionProvider.prototype, "
|
|
392
|
+
(0, inversify_1.inject)(dev_container_cli_contribution_1.DevContainerCliContribution),
|
|
393
|
+
tslib_1.__metadata("design:type", dev_container_cli_contribution_1.DevContainerCliContribution)
|
|
394
|
+
], DevContainerConnectionProvider.prototype, "cliContribution", void 0);
|
|
263
395
|
tslib_1.__decorate([
|
|
264
396
|
(0, inversify_1.inject)(core_1.ILogger),
|
|
265
397
|
tslib_1.__metadata("design:type", Object)
|
|
266
398
|
], DevContainerConnectionProvider.prototype, "logger", void 0);
|
|
399
|
+
tslib_1.__decorate([
|
|
400
|
+
(0, inversify_1.inject)(core_1.MessageService),
|
|
401
|
+
tslib_1.__metadata("design:type", core_1.MessageService)
|
|
402
|
+
], DevContainerConnectionProvider.prototype, "messageService", void 0);
|
|
267
403
|
exports.DevContainerConnectionProvider = DevContainerConnectionProvider = tslib_1.__decorate([
|
|
268
404
|
(0, inversify_1.injectable)()
|
|
269
405
|
], DevContainerConnectionProvider);
|
|
270
|
-
class RemoteDockerContainerConnection {
|
|
271
|
-
constructor(options) {
|
|
272
|
-
this.onDidDisconnectEmitter = new core_1.Emitter();
|
|
273
|
-
this.onDidDisconnect = this.onDidDisconnectEmitter.event;
|
|
274
|
-
this.id = options.id;
|
|
275
|
-
this.type = options.type;
|
|
276
|
-
this.name = options.name;
|
|
277
|
-
this.docker = options.docker;
|
|
278
|
-
this.container = options.container;
|
|
279
|
-
this.config = options.config;
|
|
280
|
-
this.docker.getEvents({ filters: { container: [this.container.id], event: ['stop'] } }).then(stream => {
|
|
281
|
-
stream.on('data', () => this.onDidDisconnectEmitter.fire());
|
|
282
|
-
});
|
|
283
|
-
this.logger = options.logger;
|
|
284
|
-
}
|
|
285
|
-
getRemoteEnv() {
|
|
286
|
-
const remoteEnv = this.config.remoteEnv;
|
|
287
|
-
if (!remoteEnv || Object.keys(remoteEnv).length === 0) {
|
|
288
|
-
return undefined;
|
|
289
|
-
}
|
|
290
|
-
return Object.entries(remoteEnv)
|
|
291
|
-
.filter(([, value]) => value !== undefined)
|
|
292
|
-
.map(([key, value]) => `${key}=${value}`);
|
|
293
|
-
}
|
|
294
|
-
async forwardOut(socket, port) {
|
|
295
|
-
const node = `${this.remoteSetupResult.nodeDirectory}/bin/node`;
|
|
296
|
-
const devContainerServer = `${this.remoteSetupResult.applicationDirectory}/backend/dev-container-server.js`;
|
|
297
|
-
try {
|
|
298
|
-
const ttySession = await this.container.exec({
|
|
299
|
-
Cmd: ['sh', '-c', `${node} ${devContainerServer} -target-port=${port ?? this.remotePort}`],
|
|
300
|
-
Env: this.getRemoteEnv(),
|
|
301
|
-
AttachStdin: true, AttachStdout: true, AttachStderr: true
|
|
302
|
-
});
|
|
303
|
-
const stream = await ttySession.start({ hijack: true, stdin: true });
|
|
304
|
-
socket.pipe(stream);
|
|
305
|
-
ttySession.modem.demuxStream(stream, socket, socket);
|
|
306
|
-
}
|
|
307
|
-
catch (e) {
|
|
308
|
-
console.error(e);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
async exec(cmd, args, options) {
|
|
312
|
-
// return (await this.getOrCreateTerminalSession()).executeCommand(cmd, args);
|
|
313
|
-
const deferred = new promise_util_1.Deferred();
|
|
314
|
-
try {
|
|
315
|
-
// TODO add windows container support
|
|
316
|
-
const execution = await this.container.exec({
|
|
317
|
-
Cmd: ['sh', '-c', `${cmd} ${args?.join(' ') ?? ''}`], Env: this.getRemoteEnv(), AttachStdout: true, AttachStderr: true
|
|
318
|
-
});
|
|
319
|
-
let stdoutBuffer = '';
|
|
320
|
-
let stderrBuffer = '';
|
|
321
|
-
const stream = await execution?.start({});
|
|
322
|
-
const stdout = new stream_1.PassThrough();
|
|
323
|
-
stdout.on('data', (chunk) => {
|
|
324
|
-
stdoutBuffer += chunk.toString();
|
|
325
|
-
});
|
|
326
|
-
const stderr = new stream_1.PassThrough();
|
|
327
|
-
stderr.on('data', (chunk) => {
|
|
328
|
-
stderrBuffer += chunk.toString();
|
|
329
|
-
});
|
|
330
|
-
execution.modem.demuxStream(stream, stdout, stderr);
|
|
331
|
-
stream?.addListener('close', () => deferred.resolve({ stdout: stdoutBuffer, stderr: stderrBuffer }));
|
|
332
|
-
}
|
|
333
|
-
catch (e) {
|
|
334
|
-
deferred.reject(e);
|
|
335
|
-
}
|
|
336
|
-
return deferred.promise;
|
|
337
|
-
}
|
|
338
|
-
async execPartial(cmd, tester, args, options) {
|
|
339
|
-
const deferred = new promise_util_1.Deferred();
|
|
340
|
-
try {
|
|
341
|
-
// TODO add windows container support
|
|
342
|
-
const execution = await this.container.exec({
|
|
343
|
-
Cmd: ['sh', '-c', `${cmd} ${args?.join(' ') ?? ''}`], Env: this.getRemoteEnv(), AttachStdout: true, AttachStderr: true
|
|
344
|
-
});
|
|
345
|
-
let stdoutBuffer = '';
|
|
346
|
-
let stderrBuffer = '';
|
|
347
|
-
const stream = await execution?.start({});
|
|
348
|
-
stream.on('close', () => {
|
|
349
|
-
if (deferred.state === 'unresolved') {
|
|
350
|
-
deferred.resolve({ stdout: stdoutBuffer, stderr: stderrBuffer });
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
const stdout = new stream_1.PassThrough();
|
|
354
|
-
stdout.on('data', (data) => {
|
|
355
|
-
this.logger.debug('REMOTE STDOUT:', data.toString());
|
|
356
|
-
if (deferred.state === 'unresolved') {
|
|
357
|
-
stdoutBuffer += data.toString();
|
|
358
|
-
if (tester(stdoutBuffer, stderrBuffer)) {
|
|
359
|
-
deferred.resolve({ stdout: stdoutBuffer, stderr: stderrBuffer });
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
const stderr = new stream_1.PassThrough();
|
|
364
|
-
stderr.on('data', (data) => {
|
|
365
|
-
this.logger.debug('REMOTE STDERR:', data.toString());
|
|
366
|
-
if (deferred.state === 'unresolved') {
|
|
367
|
-
stderrBuffer += data.toString();
|
|
368
|
-
if (tester(stdoutBuffer, stderrBuffer)) {
|
|
369
|
-
deferred.resolve({ stdout: stdoutBuffer, stderr: stderrBuffer });
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
});
|
|
373
|
-
execution.modem.demuxStream(stream, stdout, stderr);
|
|
374
|
-
}
|
|
375
|
-
catch (e) {
|
|
376
|
-
deferred.reject(e);
|
|
377
|
-
}
|
|
378
|
-
return deferred.promise;
|
|
379
|
-
}
|
|
380
|
-
getDockerHost() {
|
|
381
|
-
const dockerHost = process.env.DOCKER_HOST;
|
|
382
|
-
let remoteHost = '';
|
|
383
|
-
try {
|
|
384
|
-
if (dockerHost) {
|
|
385
|
-
const dockerHostURL = new URL(dockerHost);
|
|
386
|
-
if (dockerHostURL.protocol === 'http:' || dockerHostURL.protocol === 'https:') {
|
|
387
|
-
dockerHostURL.protocol = 'tcp:';
|
|
388
|
-
}
|
|
389
|
-
remoteHost = `-H ${dockerHostURL.href} `;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
catch (e) {
|
|
393
|
-
console.error(e);
|
|
394
|
-
}
|
|
395
|
-
return remoteHost;
|
|
396
|
-
}
|
|
397
|
-
async copy(localPath, remotePath) {
|
|
398
|
-
const deferred = new promise_util_1.Deferred();
|
|
399
|
-
const remoteHost = this.getDockerHost();
|
|
400
|
-
const subprocess = (0, child_process_1.exec)(`docker ${remoteHost}cp -a ${localPath.toString()} ${this.container.id}:${remotePath}`);
|
|
401
|
-
let stderr = '';
|
|
402
|
-
subprocess.stderr?.on('data', data => {
|
|
403
|
-
stderr += data.toString();
|
|
404
|
-
});
|
|
405
|
-
subprocess.on('close', code => {
|
|
406
|
-
if (code === 0) {
|
|
407
|
-
deferred.resolve();
|
|
408
|
-
}
|
|
409
|
-
else {
|
|
410
|
-
deferred.reject(stderr);
|
|
411
|
-
}
|
|
412
|
-
});
|
|
413
|
-
return deferred.promise;
|
|
414
|
-
}
|
|
415
|
-
disposeSync() {
|
|
416
|
-
// cant use dockerrode here since this needs to happen on one tick
|
|
417
|
-
this.shutdownContainer(true);
|
|
418
|
-
}
|
|
419
|
-
async dispose() {
|
|
420
|
-
await this.shutdownContainer(false);
|
|
421
|
-
}
|
|
422
|
-
async shutdownContainer(sync) {
|
|
423
|
-
const remoteHost = this.getDockerHost();
|
|
424
|
-
const shutdownAction = this.config.shutdownAction ?? (this.config.dockerComposeFile ? 'stopCompose' : 'stopContainer');
|
|
425
|
-
if (shutdownAction === 'stopContainer') {
|
|
426
|
-
return sync ? (0, child_process_1.execSync)(`docker ${remoteHost}stop ${this.container.id}`) : this.container.stop();
|
|
427
|
-
}
|
|
428
|
-
else if (shutdownAction === 'stopCompose') {
|
|
429
|
-
if (!this.config.dockerComposeFile) {
|
|
430
|
-
console.warn('shutdownAction is stopCompose but dockerComposeFile is not defined, falling back to stopContainer');
|
|
431
|
-
return sync ? (0, child_process_1.execSync)(`docker ${remoteHost}stop ${this.container.id}`) : this.container.stop();
|
|
432
|
-
}
|
|
433
|
-
const composeFilePath = (0, compose_service_1.resolveComposeFilePath)(this.config);
|
|
434
|
-
return sync ? (0, child_process_1.execSync)(`docker ${remoteHost}compose -f ${composeFilePath} stop`) :
|
|
435
|
-
new Promise((res, rej) => (0, child_process_1.exec)(`docker ${remoteHost}compose -f ${composeFilePath} stop`, err => {
|
|
436
|
-
if (err) {
|
|
437
|
-
console.error(err);
|
|
438
|
-
rej(err);
|
|
439
|
-
}
|
|
440
|
-
else {
|
|
441
|
-
res();
|
|
442
|
-
}
|
|
443
|
-
}));
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
exports.RemoteDockerContainerConnection = RemoteDockerContainerConnection;
|
|
448
406
|
//# sourceMappingURL=remote-container-connection-provider.js.map
|