@theia/dev-container 1.48.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.
Files changed (67) hide show
  1. package/README.md +43 -0
  2. package/lib/dev-container-server/dev-container-server.d.ts +2 -0
  3. package/lib/dev-container-server/dev-container-server.d.ts.map +1 -0
  4. package/lib/dev-container-server/dev-container-server.js +48 -0
  5. package/lib/dev-container-server/dev-container-server.js.map +1 -0
  6. package/lib/electron-browser/container-connection-contribution.d.ts +22 -0
  7. package/lib/electron-browser/container-connection-contribution.d.ts.map +1 -0
  8. package/lib/electron-browser/container-connection-contribution.js +106 -0
  9. package/lib/electron-browser/container-connection-contribution.js.map +1 -0
  10. package/lib/electron-browser/container-output-provider.d.ts +8 -0
  11. package/lib/electron-browser/container-output-provider.d.ts.map +1 -0
  12. package/lib/electron-browser/container-output-provider.js +41 -0
  13. package/lib/electron-browser/container-output-provider.js.map +1 -0
  14. package/lib/electron-browser/dev-container-frontend-module.d.ts +4 -0
  15. package/lib/electron-browser/dev-container-frontend-module.d.ts.map +1 -0
  16. package/lib/electron-browser/dev-container-frontend-module.js +33 -0
  17. package/lib/electron-browser/dev-container-frontend-module.js.map +1 -0
  18. package/lib/electron-common/container-output-provider.d.ts +4 -0
  19. package/lib/electron-common/container-output-provider.d.ts.map +1 -0
  20. package/lib/electron-common/container-output-provider.js +18 -0
  21. package/lib/electron-common/container-output-provider.js.map +1 -0
  22. package/lib/electron-common/remote-container-connection-provider.d.ts +27 -0
  23. package/lib/electron-common/remote-container-connection-provider.d.ts.map +1 -0
  24. package/lib/electron-common/remote-container-connection-provider.js +21 -0
  25. package/lib/electron-common/remote-container-connection-provider.js.map +1 -0
  26. package/lib/electron-node/dev-container-backend-module.d.ts +5 -0
  27. package/lib/electron-node/dev-container-backend-module.d.ts.map +1 -0
  28. package/lib/electron-node/dev-container-backend-module.js +44 -0
  29. package/lib/electron-node/dev-container-backend-module.js.map +1 -0
  30. package/lib/electron-node/dev-container-file-service.d.ts +10 -0
  31. package/lib/electron-node/dev-container-file-service.d.ts.map +1 -0
  32. package/lib/electron-node/dev-container-file-service.js +73 -0
  33. package/lib/electron-node/dev-container-file-service.js.map +1 -0
  34. package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.d.ts +29 -0
  35. package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.d.ts.map +1 -0
  36. package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.js +133 -0
  37. package/lib/electron-node/devcontainer-contributions/main-container-creation-contributions.js.map +1 -0
  38. package/lib/electron-node/devcontainer-file.d.ts +323 -0
  39. package/lib/electron-node/devcontainer-file.d.ts.map +1 -0
  40. package/lib/electron-node/devcontainer-file.js +18 -0
  41. package/lib/electron-node/devcontainer-file.js.map +1 -0
  42. package/lib/electron-node/docker-container-service.d.ts +23 -0
  43. package/lib/electron-node/docker-container-service.d.ts.map +1 -0
  44. package/lib/electron-node/docker-container-service.js +114 -0
  45. package/lib/electron-node/docker-container-service.js.map +1 -0
  46. package/lib/electron-node/remote-container-connection-provider.d.ts +74 -0
  47. package/lib/electron-node/remote-container-connection-provider.d.ts.map +1 -0
  48. package/lib/electron-node/remote-container-connection-provider.js +241 -0
  49. package/lib/electron-node/remote-container-connection-provider.js.map +1 -0
  50. package/lib/package.spec.d.ts +1 -0
  51. package/lib/package.spec.d.ts.map +1 -0
  52. package/lib/package.spec.js +26 -0
  53. package/lib/package.spec.js.map +1 -0
  54. package/package.json +55 -0
  55. package/src/dev-container-server/dev-container-server.ts +53 -0
  56. package/src/electron-browser/container-connection-contribution.ts +103 -0
  57. package/src/electron-browser/container-output-provider.ts +36 -0
  58. package/src/electron-browser/dev-container-frontend-module.ts +33 -0
  59. package/src/electron-common/container-output-provider.ts +19 -0
  60. package/src/electron-common/remote-container-connection-provider.ts +49 -0
  61. package/src/electron-node/dev-container-backend-module.ts +47 -0
  62. package/src/electron-node/dev-container-file-service.ts +72 -0
  63. package/src/electron-node/devcontainer-contributions/main-container-creation-contributions.ts +140 -0
  64. package/src/electron-node/devcontainer-file.ts +380 -0
  65. package/src/electron-node/docker-container-service.ts +124 -0
  66. package/src/electron-node/remote-container-connection-provider.ts +294 -0
  67. package/src/package.spec.ts +28 -0
@@ -0,0 +1,103 @@
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 { inject, injectable } from '@theia/core/shared/inversify';
18
+ import { AbstractRemoteRegistryContribution, RemoteRegistry } from '@theia/remote/lib/electron-browser/remote-registry-contribution';
19
+ import { LastContainerInfo, RemoteContainerConnectionProvider } from '../electron-common/remote-container-connection-provider';
20
+ import { RemotePreferences } from '@theia/remote/lib/electron-browser/remote-preferences';
21
+ import { WorkspaceStorageService } from '@theia/workspace/lib/browser/workspace-storage-service';
22
+ import { Command, QuickInputService } from '@theia/core';
23
+ import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
24
+ import { ContainerOutputProvider } from './container-output-provider';
25
+
26
+ export namespace RemoteContainerCommands {
27
+ export const REOPEN_IN_CONTAINER = Command.toLocalizedCommand({
28
+ id: 'dev-container:reopen-in-container',
29
+ label: 'Reopen in Container',
30
+ category: 'Dev Container'
31
+ }, 'theia/dev-container/connect');
32
+ }
33
+
34
+ const LAST_USED_CONTAINER = 'lastUsedContainer';
35
+ @injectable()
36
+ export class ContainerConnectionContribution extends AbstractRemoteRegistryContribution {
37
+
38
+ @inject(RemoteContainerConnectionProvider)
39
+ protected readonly connectionProvider: RemoteContainerConnectionProvider;
40
+
41
+ @inject(RemotePreferences)
42
+ protected readonly remotePreferences: RemotePreferences;
43
+
44
+ @inject(WorkspaceStorageService)
45
+ protected readonly workspaceStorageService: WorkspaceStorageService;
46
+
47
+ @inject(WorkspaceService)
48
+ protected readonly workspaceService: WorkspaceService;
49
+
50
+ @inject(QuickInputService)
51
+ protected readonly quickInputService: QuickInputService;
52
+
53
+ @inject(ContainerOutputProvider)
54
+ protected readonly containerOutputProvider: ContainerOutputProvider;
55
+
56
+ registerRemoteCommands(registry: RemoteRegistry): void {
57
+ registry.registerCommand(RemoteContainerCommands.REOPEN_IN_CONTAINER, {
58
+ execute: () => this.openInContainer()
59
+ });
60
+ }
61
+
62
+ async openInContainer(): Promise<void> {
63
+ const devcontainerFile = await this.getOrSelectDevcontainerFile();
64
+ if (!devcontainerFile) {
65
+ return;
66
+ }
67
+ const lastContainerInfoKey = `${LAST_USED_CONTAINER}:${devcontainerFile}`;
68
+ const lastContainerInfo = await this.workspaceStorageService.getData<LastContainerInfo | undefined>(lastContainerInfoKey);
69
+
70
+ this.containerOutputProvider.openChannel();
71
+
72
+ const connectionResult = await this.connectionProvider.connectToContainer({
73
+ nodeDownloadTemplate: this.remotePreferences['remote.nodeDownloadTemplate'],
74
+ lastContainerInfo,
75
+ devcontainerFile
76
+ });
77
+
78
+ this.workspaceStorageService.setData<LastContainerInfo>(lastContainerInfoKey, {
79
+ id: connectionResult.containerId,
80
+ lastUsed: Date.now()
81
+ });
82
+
83
+ this.openRemote(connectionResult.port, false, connectionResult.workspacePath);
84
+ }
85
+
86
+ async getOrSelectDevcontainerFile(): Promise<string | undefined> {
87
+ const devcontainerFiles = await this.connectionProvider.getDevContainerFiles();
88
+
89
+ if (devcontainerFiles.length === 1) {
90
+ return devcontainerFiles[0].path;
91
+ }
92
+
93
+ return (await this.quickInputService.pick(devcontainerFiles.map(file => ({
94
+ type: 'item',
95
+ label: file.name,
96
+ description: file.path,
97
+ file: file.path,
98
+ })), {
99
+ title: 'Select a devcontainer.json file'
100
+ }))?.file;
101
+ }
102
+
103
+ }
@@ -0,0 +1,36 @@
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 { injectable, inject } from '@theia/core/shared/inversify';
18
+ import { OutputChannel, OutputChannelManager } from '@theia/output/lib/browser/output-channel';
19
+
20
+ @injectable()
21
+ export class ContainerOutputProvider implements ContainerOutputProvider {
22
+
23
+ @inject(OutputChannelManager)
24
+ protected readonly outputChannelManager: OutputChannelManager;
25
+
26
+ protected currentChannel?: OutputChannel;
27
+
28
+ openChannel(): void {
29
+ this.currentChannel = this.outputChannelManager.getChannel('Container');
30
+ this.currentChannel.show();
31
+ };
32
+
33
+ onRemoteOutput(output: string): void {
34
+ this.currentChannel?.appendLine(output);
35
+ }
36
+ }
@@ -0,0 +1,33 @@
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
+ import { ContainerModule } from '@theia/core/shared/inversify';
17
+ import { RemoteRegistryContribution } from '@theia/remote/lib/electron-browser/remote-registry-contribution';
18
+ import { RemoteContainerConnectionProvider, RemoteContainerConnectionProviderPath } from '../electron-common/remote-container-connection-provider';
19
+ import { ContainerConnectionContribution } from './container-connection-contribution';
20
+ import { ServiceConnectionProvider } from '@theia/core/lib/browser/messaging/service-connection-provider';
21
+ import { ContainerOutputProvider } from './container-output-provider';
22
+
23
+ export default new ContainerModule(bind => {
24
+ bind(ContainerConnectionContribution).toSelf().inSingletonScope();
25
+ bind(RemoteRegistryContribution).toService(ContainerConnectionContribution);
26
+
27
+ bind(ContainerOutputProvider).toSelf().inSingletonScope();
28
+
29
+ bind(RemoteContainerConnectionProvider).toDynamicValue(ctx => {
30
+ const outputProvider = ctx.container.get(ContainerOutputProvider);
31
+ return ServiceConnectionProvider.createLocalProxy<RemoteContainerConnectionProvider>(ctx.container, RemoteContainerConnectionProviderPath, outputProvider);
32
+ }).inSingletonScope();
33
+ });
@@ -0,0 +1,19 @@
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
+ export interface ContainerOutputProvider {
18
+ onRemoteOutput(output: string): void;
19
+ }
@@ -0,0 +1,49 @@
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
+ import { RpcServer } from '@theia/core';
17
+ import { ContainerOutputProvider } from './container-output-provider';
18
+
19
+ // *****************************************************************************
20
+ export const RemoteContainerConnectionProviderPath = '/remote/container';
21
+
22
+ export const RemoteContainerConnectionProvider = Symbol('RemoteContainerConnectionProvider');
23
+
24
+ export interface ContainerConnectionOptions {
25
+ nodeDownloadTemplate?: string;
26
+ lastContainerInfo?: LastContainerInfo
27
+ devcontainerFile: string;
28
+ }
29
+
30
+ export interface LastContainerInfo {
31
+ id: string;
32
+ lastUsed: number;
33
+ }
34
+
35
+ export interface ContainerConnectionResult {
36
+ port: string;
37
+ workspacePath: string;
38
+ containerId: string;
39
+ }
40
+
41
+ export interface DevContainerFile {
42
+ name: string;
43
+ path: string;
44
+ }
45
+
46
+ export interface RemoteContainerConnectionProvider extends RpcServer<ContainerOutputProvider> {
47
+ connectToContainer(options: ContainerConnectionOptions): Promise<ContainerConnectionResult>;
48
+ getDevContainerFiles(): Promise<DevContainerFile[]>;
49
+ }
@@ -0,0 +1,47 @@
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 { ContainerModule } from '@theia/core/shared/inversify';
18
+ import { ConnectionContainerModule } from '@theia/core/lib/node/messaging/connection-container-module';
19
+ import { DevContainerConnectionProvider } from './remote-container-connection-provider';
20
+ import { RemoteContainerConnectionProvider, RemoteContainerConnectionProviderPath } from '../electron-common/remote-container-connection-provider';
21
+ import { ContainerCreationContribution, DockerContainerService } from './docker-container-service';
22
+ import { bindContributionProvider, ConnectionHandler, RpcConnectionHandler } from '@theia/core';
23
+ import { registerContainerCreationContributions } from './devcontainer-contributions/main-container-creation-contributions';
24
+ import { DevContainerFileService } from './dev-container-file-service';
25
+ import { ContainerOutputProvider } from '../electron-common/container-output-provider';
26
+
27
+ export const remoteConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService }) => {
28
+ bindContributionProvider(bind, ContainerCreationContribution);
29
+ registerContainerCreationContributions(bind);
30
+
31
+ bind(DevContainerConnectionProvider).toSelf().inSingletonScope();
32
+ bind(RemoteContainerConnectionProvider).toService(DevContainerConnectionProvider);
33
+ bind(ConnectionHandler).toDynamicValue(ctx =>
34
+ new RpcConnectionHandler<ContainerOutputProvider>(RemoteContainerConnectionProviderPath, client => {
35
+ const server = ctx.container.get<RemoteContainerConnectionProvider>(RemoteContainerConnectionProvider);
36
+ server.setClient(client);
37
+ client.onDidCloseConnection(() => server.dispose());
38
+ return server;
39
+ }));
40
+ });
41
+
42
+ export default new ContainerModule((bind, unbind, isBound, rebind) => {
43
+ bind(DockerContainerService).toSelf().inSingletonScope();
44
+ bind(ConnectionContainerModule).toConstantValue(remoteConnectionModule);
45
+
46
+ bind(DevContainerFileService).toSelf().inSingletonScope();
47
+ });
@@ -0,0 +1,72 @@
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 { inject, injectable } from '@theia/core/shared/inversify';
18
+ import { WorkspaceServer } from '@theia/workspace/lib/common';
19
+ import { DevContainerFile } from '../electron-common/remote-container-connection-provider';
20
+ import { DevContainerConfiguration } from './devcontainer-file';
21
+ import { parse } from 'jsonc-parser';
22
+ import * as fs from '@theia/core/shared/fs-extra';
23
+ import { Path, URI } from '@theia/core';
24
+
25
+ @injectable()
26
+ export class DevContainerFileService {
27
+
28
+ @inject(WorkspaceServer)
29
+ protected readonly workspaceServer: WorkspaceServer;
30
+
31
+ async getConfiguration(path: string): Promise<DevContainerConfiguration> {
32
+ const configuration: DevContainerConfiguration = parse(await fs.readFile(path, 'utf-8').catch(() => '0')) as DevContainerConfiguration;
33
+ if (!configuration) {
34
+ throw new Error(`devcontainer file ${path} could not be parsed`);
35
+ }
36
+
37
+ configuration.location = path;
38
+ return configuration;
39
+ }
40
+
41
+ async getAvailableFiles(): Promise<DevContainerFile[]> {
42
+ const workspace = await this.workspaceServer.getMostRecentlyUsedWorkspace();
43
+ if (!workspace) {
44
+ return [];
45
+ }
46
+
47
+ const devcontainerPath = new URI(workspace).path.join('.devcontainer').fsPath();
48
+
49
+ return (await this.searchForDevontainerJsonFiles(devcontainerPath, 1)).map(file => ({
50
+ name: parse(fs.readFileSync(file, 'utf-8')).name ?? 'devcontainer',
51
+ path: file
52
+ }));
53
+
54
+ }
55
+
56
+ protected async searchForDevontainerJsonFiles(directory: string, depth: number): Promise<string[]> {
57
+ if (depth < 0) {
58
+ return [];
59
+ }
60
+ const filesPaths = (await fs.readdir(directory)).map(file => new Path(directory).join(file).fsPath());
61
+
62
+ const devcontainerFiles = [];
63
+ for (const file of filesPaths) {
64
+ if (file.endsWith('devcontainer.json')) {
65
+ devcontainerFiles.push(file);
66
+ } else if ((await fs.stat(file)).isDirectory()) {
67
+ devcontainerFiles.push(...await this.searchForDevontainerJsonFiles(file, depth - 1));
68
+ }
69
+ }
70
+ return devcontainerFiles;
71
+ }
72
+ }
@@ -0,0 +1,140 @@
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
+ import * as Docker from 'dockerode';
17
+ import { injectable, interfaces } from '@theia/core/shared/inversify';
18
+ import { ContainerCreationContribution } from '../docker-container-service';
19
+ import { DevContainerConfiguration, DockerfileContainer, ImageContainer, NonComposeContainerBase } from '../devcontainer-file';
20
+ import { Path } from '@theia/core';
21
+ import { ContainerOutputProvider } from '../../electron-common/container-output-provider';
22
+
23
+ export function registerContainerCreationContributions(bind: interfaces.Bind): void {
24
+ bind(ContainerCreationContribution).to(ImageFileContribution).inSingletonScope();
25
+ bind(ContainerCreationContribution).to(DockerFileContribution).inSingletonScope();
26
+ bind(ContainerCreationContribution).to(ForwardPortsContribution).inSingletonScope();
27
+ bind(ContainerCreationContribution).to(MountsContribution).inSingletonScope();
28
+ }
29
+
30
+ @injectable()
31
+ export class ImageFileContribution implements ContainerCreationContribution {
32
+ async handleContainerCreation(createOptions: Docker.ContainerCreateOptions, containerConfig: ImageContainer,
33
+ api: Docker, outputprovider: ContainerOutputProvider): Promise<void> {
34
+ if (containerConfig.image) {
35
+ await new Promise<void>((res, rej) => api.pull(containerConfig.image, {}, (err, stream) => {
36
+ if (err) {
37
+ rej(err);
38
+ } else {
39
+ api.modem.followProgress(stream, (error, output) => error ?
40
+ rej(error) :
41
+ res(), progress => outputprovider.onRemoteOutput(OutputHelper.parseProgress(progress)));
42
+ }
43
+ }));
44
+ createOptions.Image = containerConfig.image;
45
+ }
46
+ }
47
+ }
48
+
49
+ @injectable()
50
+ export class DockerFileContribution implements ContainerCreationContribution {
51
+ async handleContainerCreation(createOptions: Docker.ContainerCreateOptions, containerConfig: DockerfileContainer,
52
+ api: Docker, outputprovider: ContainerOutputProvider): Promise<void> {
53
+ // check if dockerfile container
54
+ if (containerConfig.dockerFile || containerConfig.build?.dockerfile) {
55
+ const dockerfile = (containerConfig.dockerFile ?? containerConfig.build?.dockerfile) as string;
56
+ const buildStream = await api.buildImage({
57
+ context: containerConfig.context ?? new Path(containerConfig.location as string).dir.fsPath(),
58
+ src: [dockerfile],
59
+ } as Docker.ImageBuildContext, {
60
+ buildargs: containerConfig.build?.args
61
+ });
62
+ // TODO probably have some console windows showing the output of the build
63
+ const imageId = await new Promise<string>((res, rej) => api.modem.followProgress(buildStream, (err, outputs) => {
64
+ if (err) {
65
+ rej(err);
66
+ } else {
67
+ for (let i = outputs.length - 1; i >= 0; i--) {
68
+ if (outputs[i].aux?.ID) {
69
+ res(outputs[i].aux.ID);
70
+ return;
71
+ }
72
+ }
73
+ }
74
+ }, progress => outputprovider.onRemoteOutput(OutputHelper.parseProgress(progress))));
75
+ createOptions.Image = imageId;
76
+ }
77
+ }
78
+ }
79
+
80
+ @injectable()
81
+ export class ForwardPortsContribution implements ContainerCreationContribution {
82
+ async handleContainerCreation(createOptions: Docker.ContainerCreateOptions, containerConfig: DevContainerConfiguration, api: Docker): Promise<void> {
83
+ if (!containerConfig.forwardPorts) {
84
+ return;
85
+ }
86
+
87
+ for (const port of containerConfig.forwardPorts) {
88
+ let portKey: string;
89
+ let hostPort: string;
90
+ if (typeof port === 'string') {
91
+ const parts = port.split(':');
92
+ portKey = isNaN(+parts[0]) ? parts[0] : `${parts[0]}/tcp`;
93
+ hostPort = parts[1] ?? parts[0];
94
+ } else {
95
+ portKey = `${port}/tcp`;
96
+ hostPort = port.toString();
97
+ }
98
+ createOptions.ExposedPorts![portKey] = {};
99
+ createOptions.HostConfig!.PortBindings[portKey] = [{ HostPort: hostPort }];
100
+ }
101
+
102
+ }
103
+
104
+ }
105
+
106
+ @injectable()
107
+ export class MountsContribution implements ContainerCreationContribution {
108
+ async handleContainerCreation(createOptions: Docker.ContainerCreateOptions, containerConfig: DevContainerConfiguration, api: Docker): Promise<void> {
109
+ if (!containerConfig.mounts) {
110
+ return;
111
+ }
112
+
113
+ createOptions.HostConfig!.Mounts!.push(...(containerConfig as NonComposeContainerBase)?.mounts
114
+ ?.map(mount => typeof mount === 'string' ?
115
+ this.parseMountString(mount) :
116
+ { Source: mount.source, Target: mount.target, Type: mount.type ?? 'bind' }) ?? []);
117
+ }
118
+
119
+ parseMountString(mount: string): Docker.MountSettings {
120
+ const parts = mount.split(',');
121
+ return {
122
+ Source: parts.find(part => part.startsWith('source=') || part.startsWith('src='))?.split('=')[1]!,
123
+ Target: parts.find(part => part.startsWith('target=') || part.startsWith('dst='))?.split('=')[1]!,
124
+ Type: (parts.find(part => part.startsWith('type='))?.split('=')[1] ?? 'bind') as Docker.MountType
125
+ };
126
+ }
127
+ }
128
+
129
+ export namespace OutputHelper {
130
+ export interface Progress {
131
+ id?: string;
132
+ stream: string;
133
+ status?: string;
134
+ progress?: string;
135
+ }
136
+
137
+ export function parseProgress(progress: Progress): string {
138
+ return progress.stream ?? progress.progress ?? progress.status ?? '';
139
+ }
140
+ }