From fedf5bad32b85934833b660d3a69c6e386c205e8 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Tue, 25 Jul 2023 12:39:28 -0400 Subject: [PATCH] Use new container client package (#3982) --- package-lock.json | 11 +- package.json | 2 +- .../containers/attachShellContainer.ts | 2 +- src/commands/containers/browseContainer.ts | 2 +- src/commands/containers/composeGroup.ts | 2 +- src/commands/images/runAzureCliImage.ts | 2 +- .../registries/azure/deployImageToAca.ts | 2 +- src/commands/selectCommandTemplate.ts | 2 +- src/debugging/netcore/NetCoreDebugHelper.ts | 2 +- src/runtimes/ContainerRuntimeManager.ts | 2 +- src/runtimes/ContextManager.ts | 2 +- src/runtimes/OrchestratorRuntimeManager.ts | 4 +- src/runtimes/RuntimeManager.ts | 2 +- .../clients/AutoConfigurableDockerClient.ts | 2 +- .../AutoConfigurableDockerComposeClient.ts | 2 +- .../docker/clients/ConfigurableClient.ts | 46 - .../clients/DockerClient/DockerClient.ts | 217 -- .../DockerClient/DockerContextRecord.ts | 29 - .../DockerInspectContextRecord.ts | 16 - .../DockerClientBase/DockerClientBase.ts | 1727 --------------- .../DockerClientBase/DockerEventRecord.ts | 50 - .../DockerClientBase/DockerInfoRecord.ts | 21 - .../DockerInspectContainerRecord.ts | 177 -- .../DockerInspectImageRecord.ts | 171 -- .../DockerInspectNetworkRecord.ts | 109 - .../DockerInspectVolumeRecord.ts | 38 - .../DockerListContainerRecord.ts | 120 - .../DockerClientBase/DockerListImageRecord.ts | 62 - .../DockerListNetworkRecord.ts | 79 - .../DockerClientBase/DockerVersionRecord.ts | 23 - .../DockerClientBase/DockerVolumeRecord.ts | 44 - .../parseDockerLikeEnvironmentVariables.ts | 26 - .../parseDockerLikeImageName.ts | 55 - .../DockerClientBase/parseDockerLikeLabels.ts | 19 - .../parseDockerRawPortString.ts | 36 - .../parseListFilesCommandOutput.ts | 133 -- .../clients/DockerClientBase/tryParseSize.ts | 43 - .../DockerClientBase/withContainerPathArg.ts | 11 - .../DockerClientBase/withDockerAddHostArg.ts | 15 - .../DockerClientBase/withDockerBuildArg.ts | 10 - .../DockerClientBase/withDockerEnvArg.ts | 10 - .../withDockerExposePortsArg.ts | 10 - .../DockerClientBase/withDockerFilterArg.ts | 15 - .../withDockerIgnoreSizeArg.ts | 8 - .../withDockerJsonFormatArg.ts | 8 - .../withDockerLabelFilterArgs.ts | 24 - .../DockerClientBase/withDockerLabelsArg.ts | 14 - .../DockerClientBase/withDockerMountsArg.ts | 22 - .../DockerClientBase/withDockerNoTruncArg.ts | 8 - .../DockerClientBase/withDockerPlatformArg.ts | 36 - .../DockerClientBase/withDockerPortsArg.ts | 22 - .../DockerComposeClient.ts | 280 --- .../docker/commandRunners/shellStream.ts | 118 - .../docker/commandRunners/wslStream.ts | 36 - .../docker/contracts/CommandRunner.ts | 76 - .../docker/contracts/ContainerClient.ts | 1925 ----------------- .../contracts/ContainerOrchestratorClient.ts | 192 -- src/runtimes/docker/contracts/Shell.ts | 13 - src/runtimes/docker/index.ts | 23 - src/runtimes/docker/test/DockerClient.test.ts | 135 -- .../docker/test/DockerComposeClient.test.ts | 132 -- .../docker/test/commandLineBuilder.test.ts | 56 - .../test/normalizeContainerState.test.ts | 35 - .../test/parseDockerLikeImageName.test.ts | 197 -- .../test/parseDockerRawPortString.test.ts | 47 - src/runtimes/docker/test/tryParseSize.test.ts | 54 - .../docker/test/tsconfig-paths-bootstrap.js | 16 - .../docker/typings/CancellationTokenLike.ts | 26 - src/runtimes/docker/typings/DisposableLike.ts | 26 - src/runtimes/docker/typings/EventLike.ts | 25 - .../docker/utils/AccumulatorStream.ts | 63 - .../docker/utils/CancellationError.ts | 18 - .../docker/utils/ChildProcessError.ts | 16 - .../docker/utils/CommandNotSupportedError.ts | 16 - src/runtimes/docker/utils/asIds.ts | 13 - .../docker/utils/commandLineBuilder.ts | 193 -- src/runtimes/docker/utils/conditional.ts | 25 - src/runtimes/docker/utils/dayjs.ts | 54 - .../docker/utils/getNativeArchitecture.ts | 39 - .../docker/utils/normalizeContainerOS.ts | 24 - src/runtimes/docker/utils/range.ts | 37 - src/runtimes/docker/utils/spawnStreamAsync.ts | 294 --- .../docker/utils/streamToGenerator.ts | 28 - src/runtimes/docker/utils/toArray.ts | 13 - src/runtimes/files/ContainerFilesProvider.ts | 2 +- src/runtimes/files/DockerUri.ts | 2 +- .../runners/TaskCommandRunnerFactory.ts | 2 +- src/runtimes/runners/runWithDefaults.ts | 4 +- src/tasks/DockerBuildTaskDefinitionBase.ts | 2 +- src/tasks/DockerComposeTaskProvider.ts | 2 +- src/tasks/DockerPseudoterminal.ts | 2 +- src/tasks/DockerRunTaskProvider.ts | 2 +- src/tasks/TaskHelper.ts | 2 +- src/tasks/netSdk/netSdkTaskUtils.ts | 4 +- src/tasks/netcore/NetCoreTaskHelper.ts | 2 +- .../images/NormalizedImageNameInfo.test.ts | 2 +- src/tree/LocalRootTreeItemBase.ts | 2 +- src/tree/RefreshManager.ts | 2 +- src/tree/containers/ContainerProperties.ts | 4 +- src/tree/containers/ContainerTreeItem.ts | 8 +- src/tree/containers/ContainersTreeItem.ts | 8 +- .../containers/files/DirectoryTreeItem.ts | 2 +- src/tree/containers/files/FilesTreeItem.ts | 2 +- src/tree/contexts/ContextGroupTreeItem.ts | 2 +- src/tree/contexts/ContextTreeItem.ts | 4 +- src/tree/contexts/ContextsTreeItem.ts | 8 +- src/tree/images/ImageProperties.ts | 4 +- src/tree/images/ImagesTreeItem.ts | 8 +- src/tree/images/NormalizedImageNameInfo.ts | 2 +- .../imageChecker/OutdatedImageChecker.ts | 4 +- src/tree/images/imageChecker/registries.ts | 2 +- src/tree/networks/NetworkGroupTreeItem.ts | 2 +- src/tree/networks/NetworkTreeItem.ts | 4 +- src/tree/networks/NetworksTreeItem.ts | 6 +- src/tree/volumes/VolumeGroupTreeItem.ts | 2 +- src/tree/volumes/VolumeTreeItem.ts | 4 +- src/tree/volumes/VolumesTreeItem.ts | 6 +- src/utils/execAsync.ts | 2 +- src/utils/osUtils.ts | 2 +- .../registerDockerContextStatusBarItems.ts | 2 +- 120 files changed, 85 insertions(+), 7775 deletions(-) delete mode 100644 src/runtimes/docker/clients/ConfigurableClient.ts delete mode 100644 src/runtimes/docker/clients/DockerClient/DockerClient.ts delete mode 100644 src/runtimes/docker/clients/DockerClient/DockerContextRecord.ts delete mode 100644 src/runtimes/docker/clients/DockerClient/DockerInspectContextRecord.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/DockerClientBase.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/DockerEventRecord.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/DockerInfoRecord.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/DockerInspectContainerRecord.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/DockerInspectImageRecord.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/DockerInspectNetworkRecord.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/DockerInspectVolumeRecord.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/DockerListContainerRecord.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/DockerListImageRecord.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/DockerListNetworkRecord.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/DockerVersionRecord.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/DockerVolumeRecord.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/parseDockerLikeEnvironmentVariables.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/parseDockerLikeImageName.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/parseDockerLikeLabels.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/parseDockerRawPortString.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/parseListFilesCommandOutput.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/tryParseSize.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/withContainerPathArg.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/withDockerAddHostArg.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/withDockerBuildArg.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/withDockerEnvArg.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/withDockerExposePortsArg.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/withDockerFilterArg.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/withDockerIgnoreSizeArg.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/withDockerJsonFormatArg.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/withDockerLabelFilterArgs.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/withDockerLabelsArg.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/withDockerMountsArg.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/withDockerNoTruncArg.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/withDockerPlatformArg.ts delete mode 100644 src/runtimes/docker/clients/DockerClientBase/withDockerPortsArg.ts delete mode 100644 src/runtimes/docker/clients/DockerComposeClient/DockerComposeClient.ts delete mode 100644 src/runtimes/docker/commandRunners/shellStream.ts delete mode 100644 src/runtimes/docker/commandRunners/wslStream.ts delete mode 100644 src/runtimes/docker/contracts/CommandRunner.ts delete mode 100644 src/runtimes/docker/contracts/ContainerClient.ts delete mode 100644 src/runtimes/docker/contracts/ContainerOrchestratorClient.ts delete mode 100644 src/runtimes/docker/contracts/Shell.ts delete mode 100644 src/runtimes/docker/index.ts delete mode 100644 src/runtimes/docker/test/DockerClient.test.ts delete mode 100644 src/runtimes/docker/test/DockerComposeClient.test.ts delete mode 100644 src/runtimes/docker/test/commandLineBuilder.test.ts delete mode 100644 src/runtimes/docker/test/normalizeContainerState.test.ts delete mode 100644 src/runtimes/docker/test/parseDockerLikeImageName.test.ts delete mode 100644 src/runtimes/docker/test/parseDockerRawPortString.test.ts delete mode 100644 src/runtimes/docker/test/tryParseSize.test.ts delete mode 100644 src/runtimes/docker/test/tsconfig-paths-bootstrap.js delete mode 100644 src/runtimes/docker/typings/CancellationTokenLike.ts delete mode 100644 src/runtimes/docker/typings/DisposableLike.ts delete mode 100644 src/runtimes/docker/typings/EventLike.ts delete mode 100644 src/runtimes/docker/utils/AccumulatorStream.ts delete mode 100644 src/runtimes/docker/utils/CancellationError.ts delete mode 100644 src/runtimes/docker/utils/ChildProcessError.ts delete mode 100644 src/runtimes/docker/utils/CommandNotSupportedError.ts delete mode 100644 src/runtimes/docker/utils/asIds.ts delete mode 100644 src/runtimes/docker/utils/commandLineBuilder.ts delete mode 100644 src/runtimes/docker/utils/conditional.ts delete mode 100644 src/runtimes/docker/utils/dayjs.ts delete mode 100644 src/runtimes/docker/utils/getNativeArchitecture.ts delete mode 100644 src/runtimes/docker/utils/normalizeContainerOS.ts delete mode 100644 src/runtimes/docker/utils/range.ts delete mode 100644 src/runtimes/docker/utils/spawnStreamAsync.ts delete mode 100644 src/runtimes/docker/utils/streamToGenerator.ts delete mode 100644 src/runtimes/docker/utils/toArray.ts diff --git a/package-lock.json b/package-lock.json index ec00edf306..a95cfdef1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@microsoft/vscode-azext-azureappservice": "^1.0.2", "@microsoft/vscode-azext-azureutils": "^1.1.5", "@microsoft/vscode-azext-utils": "^1.2.2", + "@microsoft/vscode-container-client": "^0.1.0", "dayjs": "^1.11.7", "dockerfile-language-server-nodejs": "^0.10.2", "fs-extra": "^11.1.1", @@ -24,7 +25,6 @@ "node-fetch": "^2.6.9", "semver": "^7.5.2", "tar": "^6.1.13", - "tree-kill": "^1.2.2", "vscode-languageclient": "^8.1.0", "vscode-tas-client": "^0.1.63", "xml2js": "^0.5.0" @@ -775,6 +775,15 @@ "resolved": "https://registry.npmjs.org/@microsoft/vscode-azureresources-api/-/vscode-azureresources-api-2.0.4.tgz", "integrity": "sha512-LridV1h2rCydrBzEpwy+pUIUx61GpZNwrK04G7LdlhoxHrzuM/WAoy8jXaSC/FSKSsXD1QXuE6u/YofEfsuKeg==" }, + "node_modules/@microsoft/vscode-container-client": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-container-client/-/vscode-container-client-0.1.0.tgz", + "integrity": "sha512-qA9xMZWH6JvhovJLlbxgyPRgtEj8cA7ktJtJPykJbycC+F/vwt1j2/GyWhrgwlkTlmmM1aGOnF9nSD84DGhABw==", + "dependencies": { + "dayjs": "^1.11.2", + "tree-kill": "^1.2.2" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/package.json b/package.json index b996edd486..d93f461433 100644 --- a/package.json +++ b/package.json @@ -3033,6 +3033,7 @@ "@microsoft/vscode-azext-azureappservice": "^1.0.2", "@microsoft/vscode-azext-azureutils": "^1.1.5", "@microsoft/vscode-azext-utils": "^1.2.2", + "@microsoft/vscode-container-client": "^0.1.0", "dayjs": "^1.11.7", "dockerfile-language-server-nodejs": "^0.10.2", "fs-extra": "^11.1.1", @@ -3041,7 +3042,6 @@ "node-fetch": "^2.6.9", "semver": "^7.5.2", "tar": "^6.1.13", - "tree-kill": "^1.2.2", "vscode-languageclient": "^8.1.0", "vscode-tas-client": "^0.1.63", "xml2js": "^0.5.0" diff --git a/src/commands/containers/attachShellContainer.ts b/src/commands/containers/attachShellContainer.ts index 240018cbe9..f7fdcb04bd 100644 --- a/src/commands/containers/attachShellContainer.ts +++ b/src/commands/containers/attachShellContainer.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext } from '@microsoft/vscode-azext-utils'; +import { ContainerOS, VoidCommandResponse } from '@microsoft/vscode-container-client'; import { l10n } from 'vscode'; import { ext } from '../../extensionVariables'; -import { ContainerOS, VoidCommandResponse } from '../../runtimes/docker'; import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem'; import { getDockerOSType } from '../../utils/osUtils'; diff --git a/src/commands/containers/browseContainer.ts b/src/commands/containers/browseContainer.ts index 84b3d5595e..8c144091d9 100644 --- a/src/commands/containers/browseContainer.ts +++ b/src/commands/containers/browseContainer.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { PortBinding } from '../../runtimes/docker'; import { IActionContext, IAzureQuickPickItem, TelemetryProperties } from '@microsoft/vscode-azext-utils'; +import { PortBinding } from '@microsoft/vscode-container-client'; import * as vscode from 'vscode'; import { ext } from "../../extensionVariables"; import { ContainerTreeItem } from "../../tree/containers/ContainerTreeItem"; diff --git a/src/commands/containers/composeGroup.ts b/src/commands/containers/composeGroup.ts index 3b6d048f01..c5177570c8 100644 --- a/src/commands/containers/composeGroup.ts +++ b/src/commands/containers/composeGroup.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext } from '@microsoft/vscode-azext-utils'; +import { CommonOrchestratorCommandOptions, IContainerOrchestratorClient, LogsCommandOptions, VoidCommandResponse } from '@microsoft/vscode-container-client'; import * as path from 'path'; import { l10n } from 'vscode'; import { ext } from '../../extensionVariables'; -import { CommonOrchestratorCommandOptions, IContainerOrchestratorClient, LogsCommandOptions, VoidCommandResponse } from '../../runtimes/docker'; import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; import { ContainerGroupTreeItem } from '../../tree/containers/ContainerGroupTreeItem'; import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem'; diff --git a/src/commands/images/runAzureCliImage.ts b/src/commands/images/runAzureCliImage.ts index f7f5634b34..f0289ae732 100644 --- a/src/commands/images/runAzureCliImage.ts +++ b/src/commands/images/runAzureCliImage.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { DialogResponses, IActionContext } from '@microsoft/vscode-azext-utils'; +import { RunContainerBindMount } from '@microsoft/vscode-container-client'; import * as fse from 'fs-extra'; import * as os from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; import { l10n } from 'vscode'; import { ext } from '../../extensionVariables'; -import { RunContainerBindMount } from '../../runtimes/docker'; import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; import { getDockerOSType } from '../../utils/osUtils'; diff --git a/src/commands/registries/azure/deployImageToAca.ts b/src/commands/registries/azure/deployImageToAca.ts index a539b729ac..f31a57c7cd 100644 --- a/src/commands/registries/azure/deployImageToAca.ts +++ b/src/commands/registries/azure/deployImageToAca.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext, nonNullProp, UserCancelledError } from '@microsoft/vscode-azext-utils'; +import { parseDockerLikeImageName } from '@microsoft/vscode-container-client'; import * as semver from 'semver'; import * as vscode from 'vscode'; import { ext } from '../../../extensionVariables'; -import { parseDockerLikeImageName } from '../../../runtimes/docker/clients/DockerClientBase/parseDockerLikeImageName'; import { AzureRegistryTreeItem } from '../../../tree/registries/azure/AzureRegistryTreeItem'; import { DockerHubNamespaceTreeItem } from '../../../tree/registries/dockerHub/DockerHubNamespaceTreeItem'; import { registryExpectedContextValues } from '../../../tree/registries/registryContextValues'; diff --git a/src/commands/selectCommandTemplate.ts b/src/commands/selectCommandTemplate.ts index 4a9a993a4d..a01d139ecd 100644 --- a/src/commands/selectCommandTemplate.ts +++ b/src/commands/selectCommandTemplate.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext, IAzureQuickPickItem, IAzureQuickPickOptions, UserCancelledError } from '@microsoft/vscode-azext-utils'; +import { PortBinding, VoidCommandResponse } from '@microsoft/vscode-container-client'; import * as vscode from 'vscode'; import { ext } from '../extensionVariables'; -import { PortBinding, VoidCommandResponse } from '../runtimes/docker'; import { isDockerComposeClient } from '../runtimes/OrchestratorRuntimeManager'; import { resolveVariables } from '../utils/resolveVariables'; diff --git a/src/debugging/netcore/NetCoreDebugHelper.ts b/src/debugging/netcore/NetCoreDebugHelper.ts index e97ccf96c4..d06456f7f9 100644 --- a/src/debugging/netcore/NetCoreDebugHelper.ts +++ b/src/debugging/netcore/NetCoreDebugHelper.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { DialogResponses, IActionContext, UserCancelledError } from '@microsoft/vscode-azext-utils'; +import { CommandLineArgs, ContainerOS, VoidCommandResponse, composeArgs, withArg, withQuotedArg } from '@microsoft/vscode-container-client'; import * as fse from 'fs-extra'; import * as path from 'path'; import { DebugConfiguration, MessageItem, ProgressLocation, l10n, window } from 'vscode'; import { ext } from '../../extensionVariables'; -import { CommandLineArgs, ContainerOS, VoidCommandResponse, composeArgs, withArg, withQuotedArg } from '../../runtimes/docker'; import { NetCoreTaskHelper, NetCoreTaskOptions } from '../../tasks/netcore/NetCoreTaskHelper'; import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem'; import { getNetCoreProjectInfo } from '../../utils/netCoreUtils'; diff --git a/src/runtimes/ContainerRuntimeManager.ts b/src/runtimes/ContainerRuntimeManager.ts index 17f9669878..fdf5d21e8b 100644 --- a/src/runtimes/ContainerRuntimeManager.ts +++ b/src/runtimes/ContainerRuntimeManager.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DockerClient, IContainersClient } from './docker'; +import { DockerClient, IContainersClient } from '@microsoft/vscode-container-client'; import { ContextManager, IContextManager } from './ContextManager'; import { RuntimeManager } from './RuntimeManager'; diff --git a/src/runtimes/ContextManager.ts b/src/runtimes/ContextManager.ts index 1d270e66bb..2437a194b8 100644 --- a/src/runtimes/ContextManager.ts +++ b/src/runtimes/ContextManager.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { InspectContextsItem, ListContextItem } from './docker'; +import { InspectContextsItem, ListContextItem } from '@microsoft/vscode-container-client'; import * as vscode from 'vscode'; import { ext } from '../extensionVariables'; diff --git a/src/runtimes/OrchestratorRuntimeManager.ts b/src/runtimes/OrchestratorRuntimeManager.ts index ae025b0f79..e62df9189a 100644 --- a/src/runtimes/OrchestratorRuntimeManager.ts +++ b/src/runtimes/OrchestratorRuntimeManager.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isAutoConfigurableDockerComposeClient } from './clients/AutoConfigurableDockerComposeClient'; -import { DockerComposeClient, IContainerOrchestratorClient } from './docker'; +import { DockerComposeClient, IContainerOrchestratorClient } from '@microsoft/vscode-container-client'; import { RuntimeManager } from './RuntimeManager'; +import { isAutoConfigurableDockerComposeClient } from './clients/AutoConfigurableDockerComposeClient'; export class OrchestratorRuntimeManager extends RuntimeManager { public readonly onOrchestratorRuntimeClientRegistered = this.runtimeClientRegisteredEmitter.event; diff --git a/src/runtimes/RuntimeManager.ts b/src/runtimes/RuntimeManager.ts index d8f0e33ff1..7313556744 100644 --- a/src/runtimes/RuntimeManager.ts +++ b/src/runtimes/RuntimeManager.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ClientIdentity } from '@microsoft/vscode-container-client'; import * as vscode from 'vscode'; -import { ClientIdentity } from './docker'; export abstract class RuntimeManager extends vscode.Disposable { private readonly _runtimeClients = new Map(); diff --git a/src/runtimes/clients/AutoConfigurableDockerClient.ts b/src/runtimes/clients/AutoConfigurableDockerClient.ts index 2ca4789e1b..bb280dadfa 100644 --- a/src/runtimes/clients/AutoConfigurableDockerClient.ts +++ b/src/runtimes/clients/AutoConfigurableDockerClient.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DockerClient } from '@microsoft/vscode-container-client'; import * as vscode from 'vscode'; import { ext } from '../../extensionVariables'; -import { DockerClient } from '../docker'; import { AutoConfigurableClient } from './AutoConfigurableClient'; export class AutoConfigurableDockerClient extends DockerClient implements AutoConfigurableClient { diff --git a/src/runtimes/clients/AutoConfigurableDockerComposeClient.ts b/src/runtimes/clients/AutoConfigurableDockerComposeClient.ts index 10070c0c67..d9a7dd54c2 100644 --- a/src/runtimes/clients/AutoConfigurableDockerComposeClient.ts +++ b/src/runtimes/clients/AutoConfigurableDockerComposeClient.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DockerComposeClient, IContainerOrchestratorClient } from '@microsoft/vscode-container-client'; import * as vscode from 'vscode'; import { ext } from '../../extensionVariables'; import { execAsync } from '../../utils/execAsync'; import { AsyncLazy } from '../../utils/lazy'; -import { DockerComposeClient, IContainerOrchestratorClient } from '../docker'; import { isDockerComposeClient } from '../OrchestratorRuntimeManager'; import { AutoConfigurableClient } from './AutoConfigurableClient'; diff --git a/src/runtimes/docker/clients/ConfigurableClient.ts b/src/runtimes/docker/clients/ConfigurableClient.ts deleted file mode 100644 index d1db5d3cb5..0000000000 --- a/src/runtimes/docker/clients/ConfigurableClient.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ClientIdentity } from '../contracts/ContainerClient'; - -export abstract class ConfigurableClient implements ClientIdentity { - public constructor( - public readonly id: string, - commandName: string, - displayName: string, - description: string - ) { - this.#commandName = commandName; - this.#displayName = displayName; - this.#description = description; - } - - #commandName: string; - public get commandName(): string { - return this.#commandName; - } - - public set commandName(value: string) { - this.#commandName = value; - } - - #displayName: string; - public get displayName(): string { - return this.#displayName; - } - - public set displayName(value: string) { - this.#displayName = value; - } - - #description: string; - public get description(): string { - return this.#description; - } - - public set description(value: string) { - this.#description = value; - } -} diff --git a/src/runtimes/docker/clients/DockerClient/DockerClient.ts b/src/runtimes/docker/clients/DockerClient/DockerClient.ts deleted file mode 100644 index a124d3d840..0000000000 --- a/src/runtimes/docker/clients/DockerClient/DockerClient.ts +++ /dev/null @@ -1,217 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PromiseCommandResponse, VoidCommandResponse } from "../../contracts/CommandRunner"; -import { IContainersClient, InspectContextsCommandOptions, InspectContextsItem, ListContextItem, ListContextsCommandOptions, RemoveContextsCommandOptions, UseContextCommandOptions } from "../../contracts/ContainerClient"; -import { asIds } from "../../utils/asIds"; -import { CommandLineArgs, composeArgs, withArg } from "../../utils/commandLineBuilder"; -import { DockerClientBase } from "../DockerClientBase/DockerClientBase"; -import { withDockerJsonFormatArg } from "../DockerClientBase/withDockerJsonFormatArg"; -import { isDockerContextRecord } from "./DockerContextRecord"; -import { isDockerInspectContextRecord } from "./DockerInspectContextRecord"; - -export class DockerClient extends DockerClientBase implements IContainersClient { - /** - * The ID of the Docker client - */ - public static ClientId = 'com.microsoft.visualstudio.containers.docker'; - - /** - * Constructs a new {@link DockerClient} - * @param commandName (Optional, default `docker`) The command that will be run - * as the base command. If quoting is necessary, it is the responsibility of the - * caller to add. - * @param displayName (Optional, default 'Docker') The human-friendly display - * name of the client - * @param description (Optional, with default) The human-friendly description of - * the client - */ - public constructor( - commandName: string = 'docker', - displayName: string = 'Docker', - description: string = 'Runs container commands using the Docker CLI' - ) { - super( - DockerClient.ClientId, - commandName, - displayName, - description - ); - } - - //#region Context Commands - - //#region ListContexts Command - - private getListContextsCommandArgs(options: ListContextsCommandOptions): CommandLineArgs { - return composeArgs( - withArg('context', 'ls'), - withDockerJsonFormatArg, - )(); - } - - private async parseListContextsCommandOutput( - output: string, - strict: boolean, - ): Promise { - const contexts = new Array(); - try { - // Docker returns JSON per-line output, so we need to split each line - // and parse as independent JSON objects - output.split('\n').forEach((contextJson) => { - try { - // Ignore empty lines when parsing - if (!contextJson) { - return; - } - - const rawContext = JSON.parse(contextJson); - - // Validate that the image object matches the expected output - // for the list contexts command - if (!isDockerContextRecord(rawContext)) { - throw new Error('Invalid context JSON'); - } - - contexts.push({ - name: rawContext.Name, - current: rawContext.Current, - description: rawContext.Description, - containerEndpoint: rawContext.DockerEndpoint, - }); - } catch (err) { - if (strict) { - throw err; - } - } - }); - } catch (err) { - if (strict) { - throw err; - } - } - - return contexts; - } - - override async listContexts(options: ListContextsCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getListContextsCommandArgs(options), - parse: this.parseListContextsCommandOutput, - }; - } - - //#endregion - - //#region RemoveContexts Command - - private getRemoveContextsCommandArgs(options: RemoveContextsCommandOptions): CommandLineArgs { - return composeArgs( - withArg('context', 'rm'), - withArg(...options.contexts), - withArg('--force'), - )(); - } - - private async parseRemoveContextsCommandOutput( - output: string, - strict: boolean, - ): Promise { - return asIds(output); - } - - override async removeContexts(options: RemoveContextsCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getRemoveContextsCommandArgs(options), - parse: this.parseRemoveContextsCommandOutput, - }; - } - - //#endregion - - //#region UseContext Command - - private getUseContextCommandArgs(options: UseContextCommandOptions): CommandLineArgs { - return composeArgs( - withArg('context', 'use'), - withArg(options.context), - )(); - } - - override async useContext(options: UseContextCommandOptions): Promise { - return { - command: this.commandName, - args: this.getUseContextCommandArgs(options), - }; - } - - //#endregion - - //#region InspectContexts Command - - private getInspectContextsCommandArgs(options: InspectContextsCommandOptions): CommandLineArgs { - return composeArgs( - withArg('context', 'inspect'), - withDockerJsonFormatArg, - withArg(...options.contexts), - )(); - } - - private async parseInspectContextsCommandOutput( - output: string, - strict: boolean, - ): Promise { - try { - return output.split('\n').reduce>((volumes, inspectString) => { - if (!inspectString) { - return volumes; - } - - try { - const inspect = JSON.parse(inspectString); - - if (!isDockerInspectContextRecord(inspect)) { - throw new Error('Invalid context inspect json'); - } - - // Return the normalized InspectVolumesItem record - const volume: InspectContextsItem = { - name: inspect.Name, - description: inspect.Metadata?.Description, - raw: inspectString, - }; - - return [...volumes, volume]; - } catch (err) { - if (strict) { - throw err; - } - } - - return volumes; - }, new Array()); - } catch (err) { - if (strict) { - throw err; - } - } - - return new Array(); - } - - override async inspectContexts(options: InspectContextsCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getInspectContextsCommandArgs(options), - parse: this.parseInspectContextsCommandOutput, - }; - } - - //#endregion - - //#endregion -} diff --git a/src/runtimes/docker/clients/DockerClient/DockerContextRecord.ts b/src/runtimes/docker/clients/DockerClient/DockerContextRecord.ts deleted file mode 100644 index c5999827f0..0000000000 --- a/src/runtimes/docker/clients/DockerClient/DockerContextRecord.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export type DockerContextRecord = { - Name: string; - Current: boolean; - Description?: string; - DockerEndpoint?: string; -}; - -export function isDockerContextRecord(maybeContext: unknown): maybeContext is DockerContextRecord { - const context = maybeContext as DockerContextRecord; - - if (!context || typeof context !== 'object') { - return false; - } - - if (typeof context.Name !== 'string') { - return false; - } - - if (typeof context.Current !== 'boolean') { - return false; - } - - return true; -} diff --git a/src/runtimes/docker/clients/DockerClient/DockerInspectContextRecord.ts b/src/runtimes/docker/clients/DockerClient/DockerInspectContextRecord.ts deleted file mode 100644 index 799404aa10..0000000000 --- a/src/runtimes/docker/clients/DockerClient/DockerInspectContextRecord.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export type DockerInspectContextRecord = { - Name: string; - Metadata?: { - Description?: string; - } -}; - -// TODO: Actually test properties -export function isDockerInspectContextRecord(maybeContext: unknown): maybeContext is DockerInspectContextRecord { - return true; -} diff --git a/src/runtimes/docker/clients/DockerClientBase/DockerClientBase.ts b/src/runtimes/docker/clients/DockerClientBase/DockerClientBase.ts deleted file mode 100644 index 65379de30e..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/DockerClientBase.ts +++ /dev/null @@ -1,1727 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as readline from 'readline'; -import { ShellQuotedString, ShellQuoting } from 'vscode'; -import { GeneratorCommandResponse, PromiseCommandResponse, VoidCommandResponse } from '../../contracts/CommandRunner'; -import { - BuildImageCommandOptions, - CheckInstallCommandOptions, - ContainersStatsCommandOptions, - CreateNetworkCommandOptions, - CreateVolumeCommandOptions, - EventItem, - EventStreamCommandOptions, - ExecContainerCommandOptions, - IContainersClient, - InfoCommandOptions, - InfoItem, - InspectContainersCommandOptions, - InspectContainersItem, - InspectContextsCommandOptions, - InspectContextsItem, - InspectImagesCommandOptions, - InspectImagesItem, - InspectNetworksCommandOptions, - InspectNetworksItem, - InspectVolumesCommandOptions, - InspectVolumesItem, - ListContainersCommandOptions, - ListContainersItem, - ListContextItem, - ListContextsCommandOptions, - ListFilesCommandOptions, - ListFilesItem, - ListImagesCommandOptions, - ListImagesItem, - ListNetworkItem, - ListNetworksCommandOptions, - ListVolumeItem, - ListVolumesCommandOptions, - LoginCommandOptions, - LogoutCommandOptions, - LogsForContainerCommandOptions, - PruneContainersCommandOptions, - PruneContainersItem, - PruneImagesCommandOptions, - PruneImagesItem, - PruneNetworksCommandOptions, - PruneNetworksItem, - PruneVolumesCommandOptions, - PruneVolumesItem, - PullImageCommandOptions, - PushImageCommandOptions, - ReadFileCommandOptions, - RemoveContainersCommandOptions, - RemoveContextsCommandOptions, - RemoveImagesCommandOptions, - RemoveNetworksCommandOptions, - RemoveVolumesCommandOptions, - RestartContainersCommandOptions, - RunContainerCommandOptions, - StartContainersCommandOptions, - StatPathCommandOptions, - StatPathItem, - StopContainersCommandOptions, - TagImageCommandOptions, - UseContextCommandOptions, - VersionCommandOptions, - VersionItem, - WriteFileCommandOptions -} from "../../contracts/ContainerClient"; -import { CancellationTokenLike } from '../../typings/CancellationTokenLike'; -import { CancellationError } from '../../utils/CancellationError'; -import { CommandNotSupportedError } from '../../utils/CommandNotSupportedError'; -import { asIds } from '../../utils/asIds'; -import { - CommandLineArgs, - composeArgs, - withArg, - withFlagArg, - withNamedArg, - withQuotedArg, - withVerbatimArg, -} from "../../utils/commandLineBuilder"; -import { dayjs } from '../../utils/dayjs'; -import { byteStreamToGenerator, stringStreamToGenerator } from '../../utils/streamToGenerator'; -import { toArray } from '../../utils/toArray'; -import { ConfigurableClient } from '../ConfigurableClient'; -import { DockerEventRecord, isDockerEventRecord } from './DockerEventRecord'; -import { isDockerInfoRecord } from './DockerInfoRecord'; -import { isDockerInspectContainerRecord, normalizeDockerInspectContainerRecord } from './DockerInspectContainerRecord'; -import { isDockerInspectImageRecord, normalizeDockerInspectImageRecord } from './DockerInspectImageRecord'; -import { isDockerInspectNetworkRecord, normalizeDockerInspectNetworkRecord } from './DockerInspectNetworkRecord'; -import { isDockerInspectVolumeRecord, normalizeDockerInspectVolumeRecord } from './DockerInspectVolumeRecord'; -import { isDockerListContainerRecord, normalizeDockerListContainerRecord } from './DockerListContainerRecord'; -import { isDockerListImageRecord, normalizeDockerListImageRecord } from "./DockerListImageRecord"; -import { isDockerListNetworkRecord, normalizeDockerListNetworkRecord } from './DockerListNetworkRecord'; -import { isDockerVersionRecord } from "./DockerVersionRecord"; -import { isDockerVolumeRecord } from './DockerVolumeRecord'; -import { parseDockerLikeLabels } from './parseDockerLikeLabels'; -import { parseListFilesCommandLinuxOutput, parseListFilesCommandWindowsOutput } from './parseListFilesCommandOutput'; -import { tryParseSize } from './tryParseSize'; -import { withContainerPathArg } from './withContainerPathArg'; -import { withDockerAddHostArg } from './withDockerAddHostArg'; -import { withDockerBuildArg } from './withDockerBuildArg'; -import { withDockerEnvArg } from './withDockerEnvArg'; -import { withDockerExposePortsArg } from './withDockerExposePortsArg'; -import { withDockerBooleanFilterArg, withDockerFilterArg } from './withDockerFilterArg'; -import { withDockerIgnoreSizeArg } from './withDockerIgnoreSizeArg'; -import { withDockerJsonFormatArg } from "./withDockerJsonFormatArg"; -import { withDockerLabelFilterArgs } from "./withDockerLabelFilterArgs"; -import { withDockerLabelsArg } from "./withDockerLabelsArg"; -import { withDockerMountsArg } from './withDockerMountsArg'; -import { withDockerNoTruncArg } from "./withDockerNoTruncArg"; -import { withDockerPlatformArg } from './withDockerPlatformArg'; -import { withDockerPortsArg } from './withDockerPortsArg'; - -const LinuxStatArguments = '%f %h %g %u %s %X %Y %Z %n'; -const WindowsStatArguments = '/A-S /-C /TW'; - -export abstract class DockerClientBase extends ConfigurableClient implements IContainersClient { - /** - * The default registry for Docker-like clients is docker.io AKA Docker Hub - */ - public readonly defaultRegistry: string = 'docker.io'; - - /** - * The default tag for Docker-like clients is 'latest' - */ - public readonly defaultTag: string = 'latest'; - - //#region Information Commands - - protected getInfoCommandArgs( - options: InfoCommandOptions, - ): CommandLineArgs { - return composeArgs( - withArg('info'), - withDockerJsonFormatArg, - )(); - } - - protected async parseInfoCommandOutput(output: string, strict: boolean): Promise { - const info = JSON.parse(output); - - if (!isDockerInfoRecord(info)) { - throw new Error('Invalid info JSON'); - } - - return { - operatingSystem: info.OperatingSystem, - osType: info.OSType, - raw: output, - }; - } - - async info(options: InfoCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getInfoCommandArgs(options), - parse: this.parseInfoCommandOutput, - }; - } - - /** - * Get the command line arguments for a Docker-like client version command - * @param options Standard version command options - * @returns Command line args for getting version information from a Docker-like client - */ - protected getVersionCommandArgs(options: VersionCommandOptions): CommandLineArgs { - return composeArgs( - withArg('version'), - withDockerJsonFormatArg, - )(); - } - - /** - * Parse/normalize the output from running a Docker-like client version command - * @param output The standard out from invoking the version command - * @param strict Use strict parsing to validate the command? - * @returns - */ - protected async parseVersionCommandOutput(output: string, strict: boolean): Promise { - const version = JSON.parse(output); - if (!isDockerVersionRecord(version)) { - throw new Error('Invalid version JSON'); - } - - return { - client: version.Client.ApiVersion, - server: version.Server.ApiVersion, - }; - } - - /** - * Version command implementation for Docker-like clients - * @param options Standard version command options - * @returns A CommandResponse object indicating how to run and parse a version command for this runtime - */ - async version(options: VersionCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getVersionCommandArgs(options), - parse: this.parseVersionCommandOutput, - }; - } - - /** - * Get the command line arguments for a Docker-like client install check command - * @param options Standard install check command options - * @returns Command line args for doing install check for a Docker-like client - */ - protected getCheckInstallCommandArgs(options: CheckInstallCommandOptions): CommandLineArgs { - return composeArgs( - withArg('-v') - )(); - } - - /** - * Install check command implementation for Docker-like clients - * @param options Standard install check command options - * @returns A CommandResponse object indicating how to run and parse an install check - * command for this runtime - */ - async checkInstall(options: CheckInstallCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getCheckInstallCommandArgs(options), - parse: (output) => Promise.resolve(output), - }; - } - - protected getEventStreamCommandArgs( - options: EventStreamCommandOptions, - ): CommandLineArgs { - return composeArgs( - withArg('events'), - withNamedArg('--since', options.since?.toString(), { shouldQuote: !(typeof options.since === 'number') }), // If it's numeric it should not be quoted - withNamedArg('--until', options.until?.toString(), { shouldQuote: !(typeof options.until === 'number') }), // If it's numeric it should not be quoted - withDockerLabelFilterArgs(options.labels), - withDockerFilterArg(options.types?.map((type) => `type=${type}`)), - withDockerFilterArg(options.events?.map((event) => `event=${event}`)), - withDockerJsonFormatArg, - )(); - } - - protected async *parseEventStreamCommandOutput( - options: EventStreamCommandOptions, - output: NodeJS.ReadableStream, - strict: boolean, - cancellationToken?: CancellationTokenLike - ): AsyncGenerator { - cancellationToken ||= CancellationTokenLike.None; - - const lineReader = readline.createInterface({ - input: output, - crlfDelay: Infinity, - }); - - for await (const line of lineReader) { - if (cancellationToken.isCancellationRequested) { - throw new CancellationError('Event stream cancelled', cancellationToken); - } - - try { - // Parse a line at a time - const item: DockerEventRecord = JSON.parse(line); - if (!isDockerEventRecord(item)) { - throw new Error('Invalid event JSON'); - } - - // Yield the parsed data - yield { - type: item.Type, - action: item.Action, - actor: { id: item.Actor.ID, attributes: item.Actor.Attributes }, - timestamp: new Date(item.time), - raw: JSON.stringify(line), - }; - } catch (err) { - if (strict) { - throw err; - } - } - } - } - - async getEventStream(options: EventStreamCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getEventStreamCommandArgs(options), - parseStream: (output, strict) => this.parseEventStreamCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region Auth Commands - - protected getLoginCommandArgs(options: LoginCommandOptions): CommandLineArgs { - return composeArgs( - withArg('login'), - withNamedArg('--username', options.username), - withArg('--password-stdin'), - withArg(options.registry), - )(); - } - - async login(options: LoginCommandOptions): Promise { - return { - command: this.commandName, - args: this.getLoginCommandArgs(options), - }; - } - - protected getLogoutCommandArgs(options: LogoutCommandOptions): CommandLineArgs { - return composeArgs( - withArg('logout'), - withArg(options.registry), - )(); - } - - async logout(options: LogoutCommandOptions): Promise { - return { - command: this.commandName, - args: this.getLogoutCommandArgs(options), - }; - } - - //#endregion - - //#region Image Commands - - //#region BuildImage Command - - /** - * Get build image command arguments for the current runtime - * @param options Standard build image command options - * @returns Command line args for running a build image command on the current runtime - */ - protected getBuildImageCommandArgs(options: BuildImageCommandOptions): CommandLineArgs { - return composeArgs( - withArg('image', 'build'), - withFlagArg('--pull', options.pull), - withNamedArg('--file', options.file), - withNamedArg('--target', options.stage), - withNamedArg('--tag', options.tags), - withNamedArg( - '--disable-content-trust', - typeof options.disableContentTrust === 'boolean' - ? options.disableContentTrust.toString() - : options.disableContentTrust), - withDockerLabelsArg(options.labels), - withNamedArg('--iidfile', options.imageIdFile), - withDockerPlatformArg(options.platform), - withDockerBuildArg(options.args), - withVerbatimArg(options.customOptions), - withQuotedArg(options.path), - )(); - } - - /** - * Implements the build image command for a Docker-like runtime - * @param options Standard build image command options - * @returns A CommandResponse object that can be used to invoke and parse the build image command for the current runtime - */ - async buildImage(options: BuildImageCommandOptions): Promise { - return { - command: this.commandName, - args: this.getBuildImageCommandArgs(options), - }; - } - - //#endregion - - //#region ListImages Command - - /** - * Get list images command arguments for the current runtime - * @param options The process that runs the list images command for a given runtime - * @returns Command line args for running a list image command on the current runtime - */ - protected getListImagesCommandArgs(options: ListImagesCommandOptions): CommandLineArgs { - return composeArgs( - withArg('image', 'ls'), - withFlagArg('--all', options.all), - withDockerBooleanFilterArg('dangling', options.dangling), - withDockerFilterArg(options.references?.map((reference) => `reference=${reference}`)), - withDockerLabelFilterArgs(options.labels), - withDockerNoTruncArg, - withDockerJsonFormatArg, - )(); - } - - /** - * Parse and normalize the standard out from a Docker-like list images command - * @param options List images command options - * @param output The standard out from the list images command - * @param strict Should the output be strictly parsed? - * @returns A normalized array of ListImagesItem records - */ - protected async parseListImagesCommandOutput( - options: ListImagesCommandOptions, - output: string, - strict: boolean, - ): Promise> { - const images = new Array(); - try { - // Docker returns JSON per-line output, so we need to split each line - // and parse as independent JSON objects - output.split('\n').forEach((imageJson) => { - try { - // Ignore empty lines when parsing - if (!imageJson) { - return; - } - - const rawImage = JSON.parse(imageJson); - - // Validate that the image object matches the expected output - // for the list images command - if (!isDockerListImageRecord(rawImage)) { - throw new Error('Invalid image JSON'); - } - - images.push(normalizeDockerListImageRecord(rawImage)); - } catch (err) { - if (strict) { - throw err; - } - } - }); - } catch (err) { - if (strict) { - throw err; - } - } - - return images; - } - - /** - * Generates the necessary information for running and parsing the results - * of a list image command for a Docker-like client - * @param options Standard list images command options - * @returns A CommandResponse indicating how to run and parse/normalize a list image command for a Docker-like client - */ - async listImages(options: ListImagesCommandOptions): Promise>> { - return { - command: this.commandName, - args: this.getListImagesCommandArgs(options), - parse: (output, strict) => this.parseListImagesCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region RemoveImages Command - - protected getRemoveImagesCommandArgs(options: RemoveImagesCommandOptions): CommandLineArgs { - return composeArgs( - withArg('image', 'remove'), - withFlagArg('--force', options.force), - withArg(...options.imageRefs), - )(); - } - - protected async parseRemoveImagesCommandOutput( - options: RemoveImagesCommandOptions, - output: string, - strict: boolean, - ): Promise> { - return asIds(output); - } - - async removeImages(options: RemoveImagesCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getRemoveImagesCommandArgs(options), - parse: (output, strict) => this.parseRemoveImagesCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region PushImage Command - - protected getPushImageCommandArgs(options: PushImageCommandOptions): CommandLineArgs { - return composeArgs( - withArg('image', 'push'), - withArg(options.imageRef), - )(); - } - - async pushImage(options: PushImageCommandOptions): Promise { - return { - command: this.commandName, - args: this.getPushImageCommandArgs(options), - }; - } - - //#endregion - - //#region PruneImages Command - - protected getPruneImagesCommandArgs(options: PruneImagesCommandOptions): CommandLineArgs { - return composeArgs( - withArg('image', 'prune'), - withArg('--force'), - withFlagArg('--all', options.all), - )(); - } - - protected parsePruneImagesCommandOutput( - options: PruneImagesCommandOptions, - output: string, - strict: boolean, - ): Promise { - // TODO: Parse output for prune info - return Promise.resolve({}); - } - - async pruneImages(options: PruneImagesCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getPruneImagesCommandArgs(options), - parse: (output, strict) => this.parsePruneImagesCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region PullImage Command - - /** - * Generate the command line arguments for invoking a pull image command on - * a Docker-like client - * @param options Pull image command options - * @returns Command line arguments for pulling a container image - */ - protected getPullImageCommandArgs(options: PullImageCommandOptions): CommandLineArgs { - return composeArgs( - withArg('image', 'pull'), - withFlagArg('--all-tags', options.allTags), - withNamedArg( - '--disable-content-trust', - typeof options.disableContentTrust === 'boolean' - ? options.disableContentTrust.toString() - : undefined), - withArg(options.imageRef), - )(); - } - - async pullImage(options: PullImageCommandOptions): Promise { - return { - command: this.commandName, - args: this.getPullImageCommandArgs(options), - }; - } - - //#endregion - - //#region TagImage Command - - protected getTagImageCommandArgs(options: TagImageCommandOptions): CommandLineArgs { - return composeArgs( - withArg('image', 'tag'), - withArg(options.fromImageRef, options.toImageRef), - )(); - } - - async tagImage(options: TagImageCommandOptions): Promise { - return { - command: this.commandName, - args: this.getTagImageCommandArgs(options), - }; - } - - //#endregion - - //#region InspectImages Command - - /** - * Generate the command line arguments to run an inspect images command on a - * Docker-like client - * @param options Standard inspect images options - * @returns Command line args to run an inspect images command on a given Docker-like client - */ - protected getInspectImagesCommandArgs( - options: InspectImagesCommandOptions, - ): CommandLineArgs { - return composeArgs( - withArg('image', 'inspect'), - withDockerJsonFormatArg, - withArg(...options.imageRefs), - )(); - } - - /** - * Parse the standard output from a Docker-like inspect images command and - * normalize the result - * @param options Inspect images command options - * @param output The standard out from a Docker-like runtime inspect images command - * @param strict Should strict parsing be enforced? - * @returns Normalized array of InspectImagesItem records - */ - protected async parseInspectImagesCommandOutput( - options: InspectImagesCommandOptions, - output: string, - strict: boolean, - ): Promise> { - try { - return output.split('\n').reduce>((images, inspectString) => { - if (!inspectString) { - return images; - } - - try { - const inspect = JSON.parse(inspectString); - - if (!isDockerInspectImageRecord(inspect)) { - throw new Error('Invalid image inspect json'); - } - - return [...images, normalizeDockerInspectImageRecord(inspect)]; - } catch (err) { - if (strict) { - throw err; - } - } - - return images; - }, new Array()); - } catch (err) { - if (strict) { - throw err; - } - } - - // If there were no image records or there was a parsing error but - // strict parsing was disabled, return an empty array - return new Array(); - } - - async inspectImages(options: InspectImagesCommandOptions): Promise>> { - return { - command: this.commandName, - args: this.getInspectImagesCommandArgs(options), - parse: (output, strict) => this.parseInspectImagesCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#endregion - - //#region Container Commands - - //#region RunContainer Command - - /** - * Generate the command line arguments for a Docker-like run container - * command - * @param options Standard run container options - * @returns Command line arguments for a Docker-like run container command - */ - protected getRunContainerCommandArgs(options: RunContainerCommandOptions): CommandLineArgs { - return composeArgs( - withArg('container', 'run'), - withFlagArg('--detach', options.detached), - withFlagArg('--interactive', options.interactive), - withFlagArg('--tty', options.detached || options.interactive), - withFlagArg('--rm', options.removeOnExit), - withNamedArg('--name', options.name), - withDockerPortsArg(options.ports), - withFlagArg('--publish-all', options.publishAllPorts), - withNamedArg('--network', options.network), - withNamedArg('--network-alias', options.networkAlias), - withDockerAddHostArg(options.addHost), - withDockerMountsArg(options.mounts), - withDockerLabelsArg(options.labels), - withDockerEnvArg(options.environmentVariables), - withNamedArg('--env-file', options.environmentFiles), - withNamedArg('--entrypoint', options.entrypoint), - withDockerExposePortsArg(options.exposePorts), - withVerbatimArg(options.customOptions), - withArg(options.imageRef), - typeof options.command === 'string' ? withVerbatimArg(options.command) : withArg(...(toArray(options.command || []))), - )(); - } - - /** - * Parse standard output for a run container command - * @param options The standard run container command options - * @param output Standard output for a run container command - * @param strict Should strict parsing be enforced - * @returns The container ID if running detached or standard out if running attached - */ - protected async parseRunContainerCommandOutput( - options: RunContainerCommandOptions, - output: string, - strict: boolean, - ): Promise { - return options.detached ? output.split('\n', 1)[0] : output; - } - - /** - * Generate a CommandResponse for a Docker-like run container command that includes how to run and parse command output - * @param options Standard run container command options - * @returns A CommandResponse object for a Docker-like run container command - */ - async runContainer(options: RunContainerCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getRunContainerCommandArgs(options), - parse: (output, strict) => this.parseRunContainerCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region ExecContainer Command - - protected getExecContainerCommandArgs(options: ExecContainerCommandOptions): CommandLineArgs { - return composeArgs( - withArg('container', 'exec'), - withFlagArg('--interactive', options.interactive), - withFlagArg('--detached', options.detached), - withFlagArg('--tty', options.tty), - withDockerEnvArg(options.environmentVariables), - withArg(options.container), - typeof options.command === 'string' ? withVerbatimArg(options.command) : withArg(...toArray(options.command)), - )(); - } - - async execContainer(options: ExecContainerCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getExecContainerCommandArgs(options), - parseStream: (output, strict) => stringStreamToGenerator(output), - }; - } - - //#endregion - - //#region ListContainers Command - - protected getListContainersCommandArgs( - options: ListContainersCommandOptions, - ): CommandLineArgs { - return composeArgs( - withArg('container', 'ls'), - withFlagArg('--all', options.all), - withDockerLabelFilterArgs(options.labels), - withDockerFilterArg(options.running ? 'status=running' : undefined), - withDockerFilterArg(options.exited ? 'status=exited' : undefined), - withDockerFilterArg(options.names?.map((name) => `name=${name}`)), - withDockerFilterArg(options.imageAncestors?.map((id) => `ancestor=${id}`)), - withDockerFilterArg(options.volumes?.map((volume) => `volume=${volume}`)), - withDockerFilterArg(options.networks?.map((network) => `network=${network}`)), - withDockerNoTruncArg, - withDockerJsonFormatArg, - withDockerIgnoreSizeArg, - )(); - } - - protected async parseListContainersCommandOutput( - options: ListContainersCommandOptions, - output: string, - strict: boolean, - ): Promise> { - const containers = new Array(); - try { - output.split('\n').forEach((containerJson) => { - try { - if (!containerJson) { - return; - } - - const rawContainer = JSON.parse(containerJson); - - if (!isDockerListContainerRecord(rawContainer)) { - throw new Error('Invalid container JSON'); - } - - containers.push(normalizeDockerListContainerRecord(rawContainer, strict)); - } catch (err) { - if (strict) { - throw err; - } - } - }); - } catch (err) { - if (strict) { - throw err; - } - } - - return containers; - } - - async listContainers(options: ListContainersCommandOptions): Promise>> { - return { - command: this.commandName, - args: this.getListContainersCommandArgs(options), - parse: (output, strict) => this.parseListContainersCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region StartContainers Command - - protected getStartContainersCommandArgs(options: StartContainersCommandOptions): CommandLineArgs { - return composeArgs( - withArg('container', 'start'), - withArg(...toArray(options.container)), - )(); - } - - protected async parseStartContainersCommandOutput( - options: StartContainersCommandOptions, - output: string, - strict: boolean, - ): Promise> { - return asIds(output); - } - - async startContainers(options: StartContainersCommandOptions): Promise>> { - return { - command: this.commandName, - args: this.getStartContainersCommandArgs(options), - parse: (output, strict) => this.parseStartContainersCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region RestartContainers Command - - protected getRestartContainersCommandArgs(options: RestartContainersCommandOptions): CommandLineArgs { - return composeArgs( - withArg('container', 'restart'), - withArg(...toArray(options.container)), - )(); - } - - protected async parseRestartContainersCommandOutput( - options: RestartContainersCommandOptions, - output: string, - strict: boolean, - ): Promise> { - return asIds(output); - } - - async restartContainers(options: RestartContainersCommandOptions): Promise>> { - return { - command: this.commandName, - args: this.getRestartContainersCommandArgs(options), - parse: (output, strict) => this.parseRestartContainersCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region StopContainers Command - - /** - * Generate command line arguments for running a stop container command - * @param options Standard stop container command options - * @returns The command line arguments required to run the stop container command on a Docker-like runtime - */ - protected getStopContainersCommandArgs(options: StopContainersCommandOptions): CommandLineArgs { - return composeArgs( - withArg('container', 'stop'), - withNamedArg('--time', typeof options.time === 'number' ? options.time.toString() : undefined), - withArg(...toArray(options.container)), - )(); - } - - /** - * Parse the standard output from running a stop container command on a Docker-like runtime - * @param options Stop container command options - * @param output The standard out from the stop containers command - * @param strict Should strict parsing be enforced - * @returns A list of IDs for containers that were stopped - */ - protected async parseStopContainersCommandOutput( - options: StopContainersCommandOptions, - output: string, - strict: boolean, - ): Promise> { - return asIds(output); - } - - async stopContainers(options: StopContainersCommandOptions): Promise>> { - return { - command: this.commandName, - args: this.getStopContainersCommandArgs(options), - parse: (output, strict) => this.parseStopContainersCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region RemoveContainers Command - - protected getRemoveContainersCommandArgs(options: RemoveContainersCommandOptions): CommandLineArgs { - return composeArgs( - withArg('container', 'rm'), - withFlagArg('--force', options.force), - withArg(...options.containers), - )(); - } - - protected async parseRemoveContainersCommandOutput( - options: RemoveContainersCommandOptions, - output: string, - strict: boolean, - ): Promise> { - return asIds(output); - } - - async removeContainers(options: RemoveContainersCommandOptions): Promise>> { - return { - command: this.commandName, - args: this.getRemoveContainersCommandArgs(options), - parse: (output, strict) => this.parseRemoveContainersCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region PruneContainers Command - - protected getPruneContainersCommandArgs(options: PruneContainersCommandOptions): CommandLineArgs { - return composeArgs( - withArg('container', 'prune'), - withArg('--force'), - )(); - } - - protected async parsePruneContainersCommandOutput( - options: PruneContainersCommandOptions, - output: string, - strict: boolean, - ): Promise { - // TODO: Parse output for prune info - return {}; - } - - async pruneContainers(options: PruneContainersCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getPruneContainersCommandArgs(options), - parse: (output, strict) => this.parsePruneContainersCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region StatsContainers Command - - protected getStatsContainersCommandArgs(options: ContainersStatsCommandOptions): CommandLineArgs { - return composeArgs( - withArg('container', 'stats'), - withFlagArg('--all', options.all), - )(); - } - - protected async parseStatsContainersCommandArgs( - options: ContainersStatsCommandOptions, - output: string, - strict: boolean, - ): Promise { - return output; - } - - async statsContainers(options: ContainersStatsCommandOptions): Promise> { - throw new CommandNotSupportedError('statsContainers is not supported for this runtime'); - } - - //#endregion - - //#region LogsForContainer Command - - /** - * Generate the command line arguments for the log container command on a - * Docker-like client - * @param options Options for log container command - * @returns Command line arguments to invoke a log container command on a Docker-like client - */ - protected getLogsForContainerCommandArgs(options: LogsForContainerCommandOptions): CommandLineArgs { - return composeArgs( - withArg('container', 'logs'), - withFlagArg('--follow', options.follow), - withFlagArg('--timestamps', options.timestamps), - withNamedArg('--tail', options.tail?.toString()), - withNamedArg('--since', options.since), - withNamedArg('--until', options.until), - withArg(options.container), - )(); - } - - /** - * Generate a CommandResponse object for a Docker-like log container command - * @param options Options for the log container command - * @returns The CommandResponse object for the log container command - */ - async logsForContainer(options: LogsForContainerCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getLogsForContainerCommandArgs(options), - parseStream: (output, strict) => stringStreamToGenerator(output), - }; - } - - //#endregion - - //#region InspectContainers Command - - /** - * Override this method if the default inspect containers args need to be changed for a given runtime - * @param options Inspect containers command options - * @returns Command line args for invoking inspect containers on a Docker-like client - */ - protected getInspectContainersCommandArgs( - options: InspectContainersCommandOptions, - ): CommandLineArgs { - return composeArgs( - withArg('container', 'inspect'), - withDockerJsonFormatArg, - withArg(...options.containers) - )(); - } - - /** - * Parse the output from running an inspect containers command on a Docker-like client - * @param options Inspect containers command options - * @param output Standard out from running a Docker-like inspect containers command - * @param strict Should strict parsing be used to parse the output? - * @returns An array of InspectContainersItem records - */ - protected async parseInspectContainersCommandOutput( - options: InspectContainersCommandOptions, - output: string, - strict: boolean, - ): Promise> { - try { - return output.split('\n').reduce>((containers, inspectString) => { - if (!inspectString) { - return containers; - } - - try { - const inspect = JSON.parse(inspectString); - - if (!isDockerInspectContainerRecord(inspect)) { - throw new Error('Invalid container inspect json'); - } - - return [...containers, normalizeDockerInspectContainerRecord(inspect)]; - } catch (err) { - if (strict) { - throw err; - } - } - - return containers; - }, new Array()); - } catch (err) { - if (strict) { - throw err; - } - } - - return new Array(); - } - - async inspectContainers( - options: InspectContainersCommandOptions, - ): Promise> { - return { - command: this.commandName, - args: this.getInspectContainersCommandArgs(options), - parse: (output, strict) => this.parseInspectContainersCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#endregion - - //#region Volume Commands - - //#region CreateVolume Command - - protected getCreateVolumeCommandArgs(options: CreateVolumeCommandOptions): CommandLineArgs { - return composeArgs( - withArg('volume', 'create'), - withNamedArg('--driver', options.driver), - withArg(options.name), - withDockerJsonFormatArg, - )(); - } - - async createVolume(options: CreateVolumeCommandOptions): Promise { - return { - command: this.commandName, - args: this.getCreateVolumeCommandArgs(options), - }; - } - - //#endregion - - //#region ListVolumes Command - - protected getListVolumesCommandArgs(options: ListVolumesCommandOptions): CommandLineArgs { - return composeArgs( - withArg('volume', 'ls'), - withDockerBooleanFilterArg('dangling', options.dangling), - withDockerFilterArg(options.driver ? `driver=${options.driver}` : undefined), - withDockerLabelFilterArgs(options.labels), - withDockerJsonFormatArg, - )(); - } - - protected async parseListVolumesCommandOputput( - options: ListVolumesCommandOptions, - output: string, - strict: boolean, - ): Promise { - const volumes = new Array(); - try { - output.split("\n").forEach((volumeJson) => { - try { - if (!volumeJson) { - return; - } - - const rawVolume = JSON.parse(volumeJson); - - if (!isDockerVolumeRecord(rawVolume)) { - throw new Error('Invalid volume JSON'); - } - - // Parse the labels assigned to the volumes and normalize to key value pairs - const labels = parseDockerLikeLabels(rawVolume.Labels); - - const createdAt = rawVolume.CreatedAt - ? dayjs.utc(rawVolume.CreatedAt) - : undefined; - - const size = tryParseSize(rawVolume.Size); - - volumes.push({ - name: rawVolume.Name, - driver: rawVolume.Driver, - labels, - mountpoint: rawVolume.Mountpoint, - scope: rawVolume.Scope, - createdAt: createdAt?.toDate(), - size - }); - } catch (err) { - if (strict) { - throw err; - } - } - }); - } catch (err) { - if (strict) { - throw err; - } - } - - return volumes; - } - - async listVolumes(options: ListVolumesCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getListVolumesCommandArgs(options), - parse: (output, strict) => this.parseListVolumesCommandOputput(options, output, strict), - }; - } - - //#endregion - - //#region RemoveVolumes Command - - /** - * Generate the command line arguments for a Docker-like remove volumes - * command - * @param options Remove volumes command options - * @returns Command line arguments for invoking a remove volumes command - */ - protected getRemoveVolumesCommandArgs(options: RemoveVolumesCommandOptions): CommandLineArgs { - return composeArgs( - withArg('volume', 'rm'), - withFlagArg('--force', options.force), - withArg(...options.volumes), - )(); - } - - /** - * Parse the output from running a Docker-like remove volumes command - * @param options Options for the remove volumes command - * @param output Standard out from running the remove volumes command - * @param strict Should strict parsing be enforced? - * @returns A list of IDs for the volumes removed - */ - protected async parseRemoveVolumesCommandOutput( - options: RemoveVolumesCommandOptions, - output: string, - strict: boolean, - ): Promise { - return asIds(output); - } - - /** - * Generate a CommandResponse instance for a Docker-like remove volumes - * command - * @param options Options for remove volumes command - * @returns CommandResponse for the remove volumes command - */ - async removeVolumes(options: RemoveVolumesCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getRemoveVolumesCommandArgs(options), - parse: (output, strict) => this.parseRemoveVolumesCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region PruneVolumes Command - - protected getPruneVolumesCommandArgs(options: PruneVolumesCommandOptions): CommandLineArgs { - return composeArgs( - withArg('volume', 'prune'), - withArg('--force'), - )(); - } - - protected async parsePruneVolumesCommandOutput( - options: PruneVolumesCommandOptions, - output: string, - strict: boolean, - ): Promise { - // TODO: Parse output for prune info - return {}; - } - - async pruneVolumes(options: PruneVolumesCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getPruneVolumesCommandArgs(options), - parse: (output, strict) => this.parsePruneVolumesCommandOutput(options, output, strict), - - }; - } - - //#endregion - - //#region InspectVolumes Command - - protected getInspectVolumesCommandArgs( - options: InspectVolumesCommandOptions, - ): CommandLineArgs { - return composeArgs( - withArg('volume', 'inspect'), - withDockerJsonFormatArg, - withArg(...options.volumes), - )(); - } - - protected async parseInspectVolumesCommandOutput( - options: InspectVolumesCommandOptions, - output: string, - strict: boolean, - ): Promise> { - try { - return output.split('\n').reduce>((volumes, inspectString) => { - if (!inspectString) { - return volumes; - } - - try { - const inspect = JSON.parse(inspectString); - - if (!isDockerInspectVolumeRecord(inspect)) { - throw new Error('Invalid volume inspect json'); - } - - return [...volumes, normalizeDockerInspectVolumeRecord(inspect)]; - } catch (err) { - if (strict) { - throw err; - } - } - - return volumes; - }, new Array()); - } catch (err) { - if (strict) { - throw err; - } - } - - return new Array(); - } - - async inspectVolumes(options: InspectVolumesCommandOptions): Promise>> { - return { - command: this.commandName, - args: this.getInspectVolumesCommandArgs(options), - parse: (output, strict) => this.parseInspectVolumesCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#endregion - - //#region Network Commands - - //#region CreateNetwork Command - - protected getCreateNetworkCommandArgs(options: CreateNetworkCommandOptions): CommandLineArgs { - return composeArgs( - withArg('network', 'create'), - withNamedArg('--driver', options.driver), - withArg(options.name), - )(); - } - - async createNetwork(options: CreateNetworkCommandOptions): Promise { - return { - command: this.commandName, - args: this.getCreateNetworkCommandArgs(options), - }; - } - - //#endregion - - //#region ListNetworks Command - - protected getListNetworksCommandArgs( - options: ListNetworksCommandOptions, - ): CommandLineArgs { - return composeArgs( - withArg('network', 'ls'), - withDockerLabelFilterArgs(options.labels), - withDockerNoTruncArg, - withDockerJsonFormatArg, - )(); - } - - protected async parseListNetworksCommandOutput( - options: ListNetworksCommandOptions, - output: string, - strict: boolean, - ): Promise> { - const networks = new Array(); - try { - output.split("\n").forEach((networkJson) => { - try { - if (!networkJson) { - return; - } - - const rawNetwork = JSON.parse(networkJson); - - if (!isDockerListNetworkRecord(rawNetwork)) { - throw new Error('Invalid volume JSON'); - } - - networks.push(normalizeDockerListNetworkRecord(rawNetwork)); - } catch (err) { - if (strict) { - throw err; - } - } - }); - } catch (err) { - if (strict) { - throw err; - } - } - - return networks; - } - - async listNetworks(options: ListNetworksCommandOptions): Promise>> { - return { - command: this.commandName, - args: this.getListNetworksCommandArgs(options), - parse: (output, strict) => this.parseListNetworksCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region RemoveNetworks Command - - protected getRemoveNetworksCommandArgs(options: RemoveNetworksCommandOptions): CommandLineArgs { - return composeArgs( - withArg('network', 'remove'), - withFlagArg('--force', options.force), - withArg(...options.networks), - )(); - } - - protected async parseRemoveNetworksCommandOutput( - options: RemoveNetworksCommandOptions, - output: string, - strict: boolean, - ): Promise> { - return output.split('\n').map((id) => id); - } - - async removeNetworks(options: RemoveNetworksCommandOptions): Promise>> { - return { - command: this.commandName, - args: this.getRemoveNetworksCommandArgs(options), - parse: (output, strict) => this.parseRemoveNetworksCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region PruneNetworks Command - - protected getPruneNetworksCommandArgs(options: PruneNetworksCommandOptions): CommandLineArgs { - return composeArgs( - withArg('network', 'prune'), - withArg('--force'), - )(); - } - - protected async parsePruneNetworksCommandOutput( - options: PruneNetworksCommandOptions, - output: string, - strict: boolean, - ): Promise { - // TODO: Parse output for prune info - return {}; - } - - async pruneNetworks(options: PruneNetworksCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getPruneNetworksCommandArgs(options), - parse: (output, strict) => this.parsePruneNetworksCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region InspectNetworks Command - - protected getInspectNetworksCommandArgs( - options: InspectNetworksCommandOptions, - ): CommandLineArgs { - return composeArgs( - withArg('network', 'inspect'), - withDockerJsonFormatArg, - withArg(...options.networks), - )(); - } - - protected async parseInspectNetworksCommandOutput( - options: InspectNetworksCommandOptions, - output: string, - strict: boolean, - ): Promise> { - try { - return output.split('\n').reduce>((networks, inspectString) => { - if (!inspectString) { - return networks; - } - - try { - const inspect = JSON.parse(inspectString); - - if (!isDockerInspectNetworkRecord(inspect)) { - throw new Error('Invalid network inspect json'); - } - - return [...networks, normalizeDockerInspectNetworkRecord(inspect)]; - } catch (err) { - if (strict) { - throw err; - } - } - - return networks; - }, new Array()); - } catch (err) { - if (strict) { - throw err; - } - } - - return new Array(); - } - - async inspectNetworks(options: InspectNetworksCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getInspectNetworksCommandArgs(options), - parse: (output, strict) => this.parseInspectNetworksCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#endregion - - //#region Context Commands - - //#region ListContexts Command - - async listContexts(options: ListContextsCommandOptions): Promise> { - throw new CommandNotSupportedError('listContexts is not supported for this runtime'); - } - - //#endregion - - //#region RemoveContexts Command - - async removeContexts(options: RemoveContextsCommandOptions): Promise> { - throw new CommandNotSupportedError('removeContexts is not supported for this runtime'); - } - - //#endregion - - //#region UseContext Command - - async useContext(options: UseContextCommandOptions): Promise { - throw new CommandNotSupportedError('useContext is not supported for this runtime'); - } - - //#endregion - - //#region InspectContexts Command - - async inspectContexts(options: InspectContextsCommandOptions): Promise> { - throw new CommandNotSupportedError('inspectContexts is not supported for this runtime'); - } - - //#endregion - - //#endregion - - //#region File Commands - - //#region ListFiles Command - - protected getListFilesCommandArgs(options: ListFilesCommandOptions): CommandLineArgs { - let command: (string | ShellQuotedString)[]; - if (options.operatingSystem === 'windows') { - command = [ - 'cmd', - '/D', - '/S', - '/C', - `dir ${WindowsStatArguments} "${options.path}"`, - ]; - } else { - const dirPath = options.path.endsWith('/') ? options.path : options.path + '/'; - // Calling stat /* on an empty directory returns an error code, while stat /.* for hidden files - // should still succeed due to the implicit . and .. relative folders. Therefore we are calling stat twice - // and uppressing any errors from the first call with || true. If there are any legitimate issues invoking - // stat in a given contaienr, the second call will still fail and should surface the actual error, allowing - // us to suppress a false error without suppressing legitimate issues. - command = [ - '/bin/sh', - '-c', - { value: `stat -c '${LinuxStatArguments}' "${dirPath}"* || true && stat -c '${LinuxStatArguments}' "${dirPath}".*`, quoting: ShellQuoting.Strong }, - ]; - } - - return this.getExecContainerCommandArgs( - { - container: options.container, - interactive: true, - command, - } - ); - } - - protected async parseListFilesCommandOutput( - options: ListFilesCommandOptions, - output: string, - strict: boolean, - ): Promise { - if (options.operatingSystem === 'windows') { - return parseListFilesCommandWindowsOutput(options, output); - } else { - return parseListFilesCommandLinuxOutput(options, output); - } - } - - async listFiles(options: ListFilesCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getListFilesCommandArgs(options), - parse: (output, strict) => this.parseListFilesCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region StatPath Command - - protected getStatPathCommandArgs(options: StatPathCommandOptions): CommandLineArgs { - let command: (string | ShellQuotedString)[]; - if (options.operatingSystem === 'windows') { - command = [ - 'cmd', - '/D', - '/S', - '/C', - `dir ${WindowsStatArguments} "${options.path}"`, - ]; - } else { - command = [ - '/bin/sh', - '-c', - { value: `stat -c '${LinuxStatArguments}' "${options.path}"`, quoting: ShellQuoting.Strong }, - ]; - } - - return this.getExecContainerCommandArgs( - { - container: options.container, - interactive: true, - command, - } - ); - } - - protected async parseStatPathCommandOutput( - options: StatPathCommandOptions, - output: string, - strict: boolean, - ): Promise { - if (options.operatingSystem === 'windows') { - return parseListFilesCommandWindowsOutput(options, output).shift(); - } else { - return parseListFilesCommandLinuxOutput(options, output).shift(); - } - } - - async statPath(options: StatPathCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getStatPathCommandArgs(options), - parse: (output, strict) => this.parseStatPathCommandOutput(options, output, strict), - }; - } - - //#endregion - - //#region ReadFile Command - - protected getReadFileCommandArgs(options: ReadFileCommandOptions): CommandLineArgs { - if (options.operatingSystem === 'windows') { - const command = [ - 'cmd', - '/D', - '/S', - '/C', - `type "${options.path}"`, - ]; - - return this.getExecContainerCommandArgs( - { - container: options.container, - interactive: true, - command, - } - ); - } else { - return composeArgs( - withArg('cp'), - withContainerPathArg(options), - withArg('-'), - )(); - } - } - - async readFile(options: ReadFileCommandOptions): Promise> { - return { - command: this.commandName, - args: this.getReadFileCommandArgs(options), - parseStream: (output, strict) => byteStreamToGenerator(output), - }; - } - - //#endregion - - //#region WriteFile Command - - protected getWriteFileCommandArgs(options: WriteFileCommandOptions): CommandLineArgs { - return composeArgs( - withArg('cp'), - withArg(options.inputFile || '-'), - withContainerPathArg(options), - )(); - } - - async writeFile(options: WriteFileCommandOptions): Promise { - return { - command: this.commandName, - args: this.getWriteFileCommandArgs(options), - }; - } - - //#endregion - - //#endregion -} diff --git a/src/runtimes/docker/clients/DockerClientBase/DockerEventRecord.ts b/src/runtimes/docker/clients/DockerClientBase/DockerEventRecord.ts deleted file mode 100644 index dacdde64d4..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/DockerEventRecord.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { EventAction, EventType } from "../../contracts/ContainerClient"; - -export type DockerEventRecord = { - Type: EventType; - Action: EventAction; - Actor: { - ID: string; - Attributes: Record; - }; - time: number; -}; - -export function isDockerEventRecord(maybeEvent: unknown): maybeEvent is DockerEventRecord { - const event = maybeEvent as DockerEventRecord; - - if (!event || typeof event !== 'object') { - return false; - } - - if (typeof event.Type !== 'string') { - return false; - } - - if (typeof event.Action !== 'string') { - return false; - } - - if (typeof event.Actor !== 'object') { - return false; - } - - if (typeof event.Actor.ID !== 'string') { - return false; - } - - if (typeof event.Actor.Attributes !== 'object') { - return false; - } - - if (typeof event.time !== 'number') { - return false; - } - - return true; -} diff --git a/src/runtimes/docker/clients/DockerClientBase/DockerInfoRecord.ts b/src/runtimes/docker/clients/DockerClientBase/DockerInfoRecord.ts deleted file mode 100644 index c04a81eff2..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/DockerInfoRecord.ts +++ /dev/null @@ -1,21 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ContainerOS, isContainerOS } from "../../contracts/ContainerClient"; - -export type DockerInfoRecord = { - OperatingSystem?: string; - OSType?: ContainerOS; -}; - -export function isDockerInfoRecord(maybeInfo: unknown): maybeInfo is DockerInfoRecord { - const info = maybeInfo as DockerInfoRecord; - - if (typeof info.OSType === 'string' && !isContainerOS(info.OSType)) { - return false; - } - - return true; -} diff --git a/src/runtimes/docker/clients/DockerClientBase/DockerInspectContainerRecord.ts b/src/runtimes/docker/clients/DockerClientBase/DockerInspectContainerRecord.ts deleted file mode 100644 index 74d6392127..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/DockerInspectContainerRecord.ts +++ /dev/null @@ -1,177 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { InspectContainersItem, InspectContainersItemMount, InspectContainersItemNetwork, PortBinding } from '../../contracts/ContainerClient'; -import { dayjs } from '../../utils/dayjs'; -import { toArray } from '../../utils/toArray'; -import { parseDockerLikeEnvironmentVariables } from './parseDockerLikeEnvironmentVariables'; -import { parseDockerLikeImageName } from './parseDockerLikeImageName'; - -export type DockerInspectContainerPortHost = { - HostIp?: string; - HostPort?: number; -}; - -export type DockerInspectContainerBindMount = { - Type: 'bind'; - Source: string; - Destination: string; - RW: boolean; -}; - -export type DockerInspectContainerVolumeMount = { - Type: 'volume'; - Name: string; - Source: string; - Destination: string; - Driver: string; - RW: boolean; -}; - -export type DockerInspectContainerMount = - | DockerInspectContainerBindMount - | DockerInspectContainerVolumeMount; - -export type DockerInspectNetwork = { - Gateway: string; - IPAddress: string; - MacAddress: string; -}; - -export type DockerInspectContainerConfig = { - Image: string; - Status: string; - Entrypoint: Array | string | null; - Cmd: Array | string | null; - Env?: Array | null; - Labels?: Record | null; - WorkingDir?: string | null; -}; - -export type DockerInspectContainerHostConfig = { - PublishAllPorts?: boolean | null; - Isolation?: string | null; -}; - -export type DockerInspectContainerNetworkSettings = { - Networks?: Record | null; - IPAddress?: string; - Ports?: Record> | null; -}; - -export type DockerInspectContainerState = { - Status?: string; - StartedAt?: string; - FinishedAt?: string; -}; - -export type DockerInspectContainerRecord = { - Id: string; - Name: string; - Image: string; - Platform: string; - Created: string; - Mounts: Array; - State: DockerInspectContainerState; - Config: DockerInspectContainerConfig; - HostConfig: DockerInspectContainerHostConfig; - NetworkSettings: DockerInspectContainerNetworkSettings; -}; - -// TODO: Actually test properties -export function isDockerInspectContainerRecord(maybeContainer: unknown): maybeContainer is DockerInspectContainerRecord { - return true; -} - -export function normalizeDockerInspectContainerRecord(container: DockerInspectContainerRecord): InspectContainersItem { - // Parse the environment variables assigned to the container at runtime - const environmentVariables = parseDockerLikeEnvironmentVariables(container.Config?.Env || []); - - // Parse the networks assigned to the container and normalize to InspectContainersItemNetwork - // records - const networks = Object.entries(container.NetworkSettings?.Networks || {}).map(([name, dockerNetwork]) => { - return { - name, - gateway: dockerNetwork.Gateway || undefined, - ipAddress: dockerNetwork.IPAddress || undefined, - macAddress: dockerNetwork.MacAddress || undefined, - }; - }); - - // Parse the exposed ports for the container and normalize to a PortBinding record - const ports = Object.entries(container.NetworkSettings?.Ports || {}).map(([rawPort, hostBinding]) => { - const [port, protocol] = rawPort.split('/'); - return { - hostIp: hostBinding?.[0]?.HostIp, - hostPort: hostBinding?.[0]?.HostPort, - containerPort: parseInt(port), - protocol: protocol.toLowerCase() === 'tcp' - ? 'tcp' - : protocol.toLowerCase() === 'udp' - ? 'udp' - : undefined, - }; - }); - - // Parse the volume and bind mounts associated with the given runtime and normalize to - // InspectContainersItemMount records - const mounts = (container.Mounts || []).reduce>((curMounts, mount) => { - switch (mount?.Type) { - case 'bind': - return [...curMounts, { - type: 'bind', - source: mount.Source, - destination: mount.Destination, - readOnly: !mount.RW, - }]; - case 'volume': - return [...curMounts, { - type: 'volume', - name: mount.Name, - source: mount.Source, - destination: mount.Destination, - driver: mount.Driver, - readOnly: !mount.RW, - }]; - } - - }, new Array()); - const labels = container.Config?.Labels ?? {}; - - const createdAt = dayjs.utc(container.Created); - const startedAt = container.State?.StartedAt - ? dayjs.utc(container.State?.StartedAt) - : undefined; - const finishedAt = container.State?.FinishedAt - ? dayjs.utc(container.State?.FinishedAt) - : undefined; - - // Return the normalized InspectContainersItem record - return { - id: container.Id, - name: container.Name, - imageId: container.Image, - image: parseDockerLikeImageName(container.Config.Image), - isolation: container.HostConfig?.Isolation, - status: container.State?.Status, - environmentVariables, - networks, - ipAddress: container.NetworkSettings?.IPAddress ? container.NetworkSettings?.IPAddress : undefined, - ports, - mounts, - labels, - entrypoint: toArray(container.Config?.Entrypoint ?? []), - command: toArray(container.Config?.Cmd ?? []), - currentDirectory: container.Config?.WorkingDir || undefined, - createdAt: createdAt.toDate(), - startedAt: startedAt && (startedAt.isSame(createdAt) || startedAt.isAfter(createdAt)) - ? startedAt.toDate() - : undefined, - finishedAt: finishedAt && (finishedAt.isSame(createdAt) || finishedAt.isAfter(createdAt)) - ? finishedAt.toDate() - : undefined, - raw: JSON.stringify(container), - }; -} diff --git a/src/runtimes/docker/clients/DockerClientBase/DockerInspectImageRecord.ts b/src/runtimes/docker/clients/DockerClientBase/DockerInspectImageRecord.ts deleted file mode 100644 index e738cbe2f7..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/DockerInspectImageRecord.ts +++ /dev/null @@ -1,171 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ImageNameInfo, InspectImagesItem, PortBinding } from "../../contracts/ContainerClient"; -import { dayjs } from '../../utils/dayjs'; -import { toArray } from "../../utils/toArray"; -import { parseDockerLikeEnvironmentVariables } from "./parseDockerLikeEnvironmentVariables"; -import { parseDockerLikeImageName } from "./parseDockerLikeImageName"; - -export type DockerInspectImageConfig = { - Entrypoint?: Array | string | null; - Cmd?: Array | string | null; - Env?: Array, - Labels?: Record | null, - ExposedPorts?: Record | null; - Volumes?: Record | null; - WorkingDir?: string | null; - User?: string | null; -}; - -export type DockerInspectImageRecord = { - Id: string; - RepoTags: Array; - Config: DockerInspectImageConfig, - RepoDigests: Array; - Architecture: string; - Os: string; - Created: string; - User?: string; -}; - -function isDockerInspectImageConfig(maybeImageConfig: unknown): maybeImageConfig is DockerInspectImageConfig { - const imageConfig = maybeImageConfig as DockerInspectImageConfig; - - if (!imageConfig || typeof imageConfig !== 'object') { - return false; - } - - if (imageConfig.Env && !Array.isArray(imageConfig.Env)) { - return false; - } - - if (imageConfig.Labels && typeof imageConfig.Labels !== 'object') { - return false; - } - - if (imageConfig.ExposedPorts && typeof imageConfig.ExposedPorts !== 'object') { - return false; - } - - if (imageConfig.Volumes && typeof imageConfig.Volumes !== 'object') { - return false; - } - - if (imageConfig.WorkingDir && typeof imageConfig.WorkingDir !== 'string') { - return false; - } - - if (imageConfig.User && typeof imageConfig.User !== 'string') { - return false; - } - - if (imageConfig.Entrypoint && !Array.isArray(imageConfig.Entrypoint) && typeof imageConfig.Entrypoint !== 'string') { - return false; - } - - if (imageConfig.Cmd && !Array.isArray(imageConfig.Cmd) && typeof imageConfig.Cmd !== 'string') { - return false; - } - - return true; -} - -export function isDockerInspectImageRecord(maybeImage: unknown): maybeImage is DockerInspectImageRecord { - const image = maybeImage as DockerInspectImageRecord; - - if (!image || typeof image !== 'object') { - return false; - } - - if (typeof image.Id !== 'string') { - return false; - } - - if (!Array.isArray(image.RepoTags)) { - return false; - } - - if (!isDockerInspectImageConfig(image.Config)) { - return false; - } - - if (!Array.isArray(image.RepoDigests)) { - return false; - } - - if (typeof image.Architecture !== 'string') { - return false; - } - - if (typeof image.Os !== 'string') { - return false; - } - - if (typeof image.Created !== 'string') { - return false; - } - - return true; -} - -export function normalizeDockerInspectImageRecord(image: DockerInspectImageRecord): InspectImagesItem { - // This is effectively doing firstOrDefault on the RepoTags for the image. If there are any values - // in RepoTags, the first one will be parsed and returned as the tag name for the image. - const imageNameInfo: ImageNameInfo = parseDockerLikeImageName(image.RepoTags?.[0]); - - // Parse any environment variables defined for the image - const environmentVariables = parseDockerLikeEnvironmentVariables(image.Config?.Env || []); - - // Parse any default ports exposed by the image - const ports = Object.entries(image.Config?.ExposedPorts || {}).map(([rawPort]) => { - const [port, protocol] = rawPort.split('/'); - return { - containerPort: parseInt(port), - protocol: protocol.toLowerCase() === 'tcp' ? 'tcp' : protocol.toLowerCase() === 'udp' ? 'udp' : undefined, - }; - }); - - // Parse any default volumes specified by the image - const volumes = Object.entries(image.Config?.Volumes || {}).map(([rawVolume]) => rawVolume); - - // Parse any labels assigned to the image - const labels = image.Config?.Labels ?? {}; - - // Parse and normalize the image architecture - const architecture = image.Architecture?.toLowerCase() === 'amd64' - ? 'amd64' - : image.Architecture?.toLowerCase() === 'arm64' ? 'arm64' : undefined; - - // Parse and normalize the image OS - const os = image.Os?.toLowerCase() === 'linux' - ? 'linux' - : image.Architecture?.toLowerCase() === 'windows' - ? 'windows' - : undefined; - - // Determine if the image has been pushed to a remote repo - // (no repo digests or only localhost/ repo digests) - const isLocalImage = !(image.RepoDigests || []).some((digest) => !digest.toLowerCase().startsWith('localhost/')); - - return { - id: image.Id, - image: imageNameInfo, - repoDigests: image.RepoDigests, - isLocalImage, - environmentVariables, - ports, - volumes, - labels, - entrypoint: toArray(image.Config?.Entrypoint || []), - command: toArray(image.Config?.Cmd || []), - currentDirectory: image.Config?.WorkingDir || undefined, - architecture, - operatingSystem: os, - createdAt: dayjs(image.Created).toDate(), - user: image.Config?.User || undefined, - raw: JSON.stringify(image), - }; -} diff --git a/src/runtimes/docker/clients/DockerClientBase/DockerInspectNetworkRecord.ts b/src/runtimes/docker/clients/DockerClientBase/DockerInspectNetworkRecord.ts deleted file mode 100644 index 95a161330a..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/DockerInspectNetworkRecord.ts +++ /dev/null @@ -1,109 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { InspectNetworksItem, NetworkIpamConfig } from '../../contracts/ContainerClient'; -import { dayjs } from '../../utils/dayjs'; - -export type DockerIpamConfig = { - Subnet: string; - Gateway: string; -}; - -export type DockerIpam = { - Driver: string; - Config: Array; -}; - -export type DockerInspectNetworkRecord = { - Id: string; - Name: string; - Driver: string; - Scope: string; - Labels: Record; - IPAM: DockerIpam; - EnableIPv6: boolean; - Internal: boolean; - Attachable: boolean; - Ingress: boolean; - Created: string; -}; - -export function isDockerInspectNetworkRecord(maybeNetwork: unknown): maybeNetwork is DockerInspectNetworkRecord { - const network = maybeNetwork as DockerInspectNetworkRecord; - - if (!network || typeof network !== 'object') { - return false; - } - - if (typeof network.Id !== 'string') { - return false; - } - - if (typeof network.Name !== 'string') { - return false; - } - - if (typeof network.Scope !== 'string') { - return false; - } - - if (typeof network.Labels !== 'object') { - return false; - } - - if (network.IPAM === null || typeof network.IPAM !== 'object' || typeof network.IPAM.Driver !== 'string') { - return false; - } - - if (typeof network.EnableIPv6 !== 'boolean') { - return false; - } - - if (typeof network.Internal !== 'boolean') { - return false; - } - - if (typeof network.Attachable !== 'boolean') { - return false; - } - - if (typeof network.Ingress !== 'boolean') { - return false; - } - - if (typeof network.Created !== 'string') { - return false; - } - - return true; -} - -export function normalizeDockerInspectNetworkRecord(network: DockerInspectNetworkRecord): InspectNetworksItem { - const ipam: NetworkIpamConfig = { - driver: network.IPAM.Driver, - config: network.IPAM.Config.map(({ Subnet, Gateway }) => ({ - subnet: Subnet, - gateway: Gateway, - })), - }; - - const createdAt = dayjs.utc(network.Created).toDate(); - - // Return the normalized InspectNetworksItem record - return { - id: network.Id, - name: network.Name, - driver: network.Driver, - scope: network.Scope, - labels: network.Labels || {}, - ipam, - ipv6: network.EnableIPv6, - internal: network.Internal, - attachable: network.Attachable, - ingress: network.Ingress, - createdAt, - raw: JSON.stringify(network), - }; -} diff --git a/src/runtimes/docker/clients/DockerClientBase/DockerInspectVolumeRecord.ts b/src/runtimes/docker/clients/DockerClientBase/DockerInspectVolumeRecord.ts deleted file mode 100644 index 1d42f03bd8..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/DockerInspectVolumeRecord.ts +++ /dev/null @@ -1,38 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { InspectVolumesItem } from '../../contracts/ContainerClient'; -import { dayjs } from '../../utils/dayjs'; - -export type DockerInspectVolumeRecord = { - Name: string; - Driver: string; - Mountpoint: string; - Scope: string; - Labels: Record; - Options: Record; - CreatedAt: string; -}; - -// TODO: Actually test properties -export function isDockerInspectVolumeRecord(maybeVolume: unknown): maybeVolume is DockerInspectVolumeRecord { - return true; -} - -export function normalizeDockerInspectVolumeRecord(volume: DockerInspectVolumeRecord): InspectVolumesItem { - const createdAt = dayjs.utc(volume.CreatedAt); - - // Return the normalized InspectVolumesItem record - return { - name: volume.Name, - driver: volume.Driver, - mountpoint: volume.Mountpoint, - scope: volume.Scope, - labels: volume.Labels, - options: volume.Options, - createdAt: createdAt.toDate(), - raw: JSON.stringify(volume), - }; -} diff --git a/src/runtimes/docker/clients/DockerClientBase/DockerListContainerRecord.ts b/src/runtimes/docker/clients/DockerClientBase/DockerListContainerRecord.ts deleted file mode 100644 index 1a667e8b36..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/DockerListContainerRecord.ts +++ /dev/null @@ -1,120 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ListContainersItem, PortBinding } from "../../contracts/ContainerClient"; -import { dayjs } from '../../utils/dayjs'; -import { parseDockerLikeImageName } from "./parseDockerLikeImageName"; -import { parseDockerLikeLabels } from "./parseDockerLikeLabels"; -import { parseDockerRawPortString } from "./parseDockerRawPortString"; - -export type DockerListContainerRecord = { - ID: string; - Names: string; - Image: string; - Ports: string; - Networks: string; - Labels: string; - CreatedAt: string; - State?: string; - Status: string; -}; - -export function isDockerListContainerRecord(maybeContainer: unknown): maybeContainer is DockerListContainerRecord { - const container = maybeContainer as DockerListContainerRecord; - - if (!container || typeof container !== 'object') { - return false; - } - - if (typeof container.ID !== 'string') { - return false; - } - - if (typeof container.Names !== 'string') { - return false; - } - - if (typeof container.Image !== 'string') { - return false; - } - - if (typeof container.Ports !== 'string') { - return false; - } - - if (typeof container.Networks !== 'string') { - return false; - } - - if (typeof container.Labels !== 'string') { - return false; - } - - if (typeof container.CreatedAt !== 'string') { - return false; - } - - if (typeof container.Status !== 'string') { - return false; - } - - return true; -} - -export function normalizeDockerListContainerRecord(container: DockerListContainerRecord, strict: boolean): ListContainersItem { - const labels = parseDockerLikeLabels(container.Labels); - - const ports = container.Ports - .split(',') - .map((port) => port.trim()) - .filter((port) => !!port) - .reduce>((portBindings, rawPort) => { - const parsedPort = parseDockerRawPortString(rawPort); - if (parsedPort) { - return portBindings.concat(parsedPort); - } else if (strict) { - throw new Error('Invalid container JSON'); - } else { - return portBindings; - } - }, []); - - const networks = container.Networks - .split(','); - - const name = container.Names.split(',')[0].trim(); - const createdAt = dayjs.utc(container.CreatedAt).toDate(); - - return { - id: container.ID, - name, - labels, - image: parseDockerLikeImageName(container.Image), - ports, - networks, - createdAt, - state: normalizeContainerState(container), - status: container.Status, - }; -} - -// Exported just for tests, also why the typing is just a subset of the full record -export function normalizeContainerState(container: Pick): string { - if (container.State) { - return container.State; - } - - if (/paused/i.test(container.Status)) { - return 'paused'; - } else if (/exit|terminate|dead/i.test(container.Status)) { - return 'exited'; - } else if (/created/i.test(container.Status)) { - return 'created'; - } else if (/up/i.test(container.Status)) { - return 'running'; - } - - return 'unknown'; -} diff --git a/src/runtimes/docker/clients/DockerClientBase/DockerListImageRecord.ts b/src/runtimes/docker/clients/DockerClientBase/DockerListImageRecord.ts deleted file mode 100644 index 7ea0a8ed85..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/DockerListImageRecord.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ListImagesItem } from '../../contracts/ContainerClient'; -import { dayjs } from '../../utils/dayjs'; -import { parseDockerLikeImageName } from './parseDockerLikeImageName'; -import { tryParseSize } from './tryParseSize'; - -export type DockerListImageRecord = { - ID: string; - Repository: string; - Tag: string; - CreatedAt: string; - Size: string | number; -}; - -export function isDockerListImageRecord(maybeImage: unknown): maybeImage is DockerListImageRecord { - const image = maybeImage as DockerListImageRecord; - - if (!image || typeof image !== 'object') { - return false; - } - - if (typeof image.ID !== 'string') { - return false; - } - - if (typeof image.Repository !== 'string') { - return false; - } - - if (typeof image.Tag !== 'string') { - return false; - } - - if (typeof image.CreatedAt !== 'string') { - return false; - } - - if (typeof image.Size !== 'string' && typeof image.Size !== 'number') { - return false; - } - - return true; -} - -export function normalizeDockerListImageRecord(image: DockerListImageRecord): ListImagesItem { - const createdAt = dayjs.utc(image.CreatedAt).toDate(); - const size = tryParseSize(image.Size); - - const repositoryAndTag = `${image.Repository}${image.Tag ? `:${image.Tag}` : ''}`; - - return { - id: image.ID, - image: parseDockerLikeImageName(repositoryAndTag), - // labels: {}, // TODO: image labels are conspicuously absent from Docker image listing output - createdAt, - size, - }; -} diff --git a/src/runtimes/docker/clients/DockerClientBase/DockerListNetworkRecord.ts b/src/runtimes/docker/clients/DockerClientBase/DockerListNetworkRecord.ts deleted file mode 100644 index de1d95635f..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/DockerListNetworkRecord.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ListNetworkItem } from '../../contracts/ContainerClient'; -import { dayjs } from '../../utils/dayjs'; -import { parseDockerLikeLabels } from './parseDockerLikeLabels'; - -export type DockerListNetworkRecord = { - ID: string; - Name: string; - Driver: string; - Labels: string; - Scope: string; - IPv6: string; - CreatedAt: string; - Internal: string; -}; - -export function isDockerListNetworkRecord(maybeNetwork: unknown): maybeNetwork is DockerListNetworkRecord { - const network = maybeNetwork as DockerListNetworkRecord; - - if (!network || typeof network !== 'object') { - return false; - } - - if (typeof network.ID !== 'string') { - return false; - } - - if (typeof network.Name !== 'string') { - return false; - } - - if (typeof network.Driver !== 'string') { - return false; - } - - if (typeof network.Labels !== 'string') { - return false; - } - - if (typeof network.Scope !== 'string') { - return false; - } - - if (typeof network.IPv6 !== 'string') { - return false; - } - - if (typeof network.CreatedAt !== 'string') { - return false; - } - - if (typeof network.Internal !== 'string') { - return false; - } - - return true; -} - -export function normalizeDockerListNetworkRecord(network: DockerListNetworkRecord): ListNetworkItem { - // Parse the labels assigned to the networks and normalize to key value pairs - const labels = parseDockerLikeLabels(network.Labels); - - const createdAt = dayjs.utc(network.CreatedAt).toDate(); - - return { - id: network.ID, - name: network.Name, - driver: network.Driver, - labels, - scope: network.Scope, - ipv6: network.IPv6.toLowerCase() === 'true', - internal: network.Internal.toLowerCase() === 'true', - createdAt, - }; -} diff --git a/src/runtimes/docker/clients/DockerClientBase/DockerVersionRecord.ts b/src/runtimes/docker/clients/DockerClientBase/DockerVersionRecord.ts deleted file mode 100644 index dda67fb403..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/DockerVersionRecord.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export type DockerVersionRecord = { - Client: { ApiVersion: string }; - Server: { ApiVersion: string }; -}; - -export function isDockerVersionRecord(maybeVersion: unknown): maybeVersion is DockerVersionRecord { - const version = maybeVersion as DockerVersionRecord; - - if (typeof version?.Client?.ApiVersion !== 'string') { - return false; - } - - if (typeof version?.Server?.ApiVersion !== 'string') { - return false; - } - - return true; -} diff --git a/src/runtimes/docker/clients/DockerClientBase/DockerVolumeRecord.ts b/src/runtimes/docker/clients/DockerClientBase/DockerVolumeRecord.ts deleted file mode 100644 index 86754dda82..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/DockerVolumeRecord.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export type DockerVolumeRecord = { - Name: string; - Driver: string; - Labels: string; - Mountpoint: string; - Scope: string; - CreatedAt?: string; - Size?: string; -}; - -export function isDockerVolumeRecord(maybeVolume: unknown): maybeVolume is DockerVolumeRecord { - const volume = maybeVolume as DockerVolumeRecord; - - if (!volume || typeof volume !== 'object') { - return false; - } - - if (typeof volume.Name !== 'string') { - return false; - } - - if (typeof volume.Driver !== 'string') { - return false; - } - - if (typeof volume.Labels !== 'string') { - return false; - } - - if (typeof volume.Mountpoint !== 'string') { - return false; - } - - if (typeof volume.Scope !== 'string') { - return false; - } - - return true; -} diff --git a/src/runtimes/docker/clients/DockerClientBase/parseDockerLikeEnvironmentVariables.ts b/src/runtimes/docker/clients/DockerClientBase/parseDockerLikeEnvironmentVariables.ts deleted file mode 100644 index 7eebdb8366..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/parseDockerLikeEnvironmentVariables.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * Parse the standard Docker environment variable format into a key value record object - * @param environmentVariables A Docker like list of key=value environment variables - * @returns An object of key value pairs for the environment variables - */ -export function parseDockerLikeEnvironmentVariables(environmentVariables: Array): Record { - return environmentVariables.reduce>((evs, ev) => { - const index = ev.indexOf('='); - if (index > -1) { - const name = ev.slice(0, index); - const value = ev.slice(index + 1); - - return { - ...evs, - [name]: value, - }; - } - - return evs; - }, {}); -} diff --git a/src/runtimes/docker/clients/DockerClientBase/parseDockerLikeImageName.ts b/src/runtimes/docker/clients/DockerClientBase/parseDockerLikeImageName.ts deleted file mode 100644 index 9bc53570da..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/parseDockerLikeImageName.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ImageNameInfo } from '../../contracts/ContainerClient'; - -/** - * A regex for parsing image names. Because this is only used to parse CLI output, we can assume - * the image names are valid. - * - * Registry: Everything before the first slash--must be either exactly "localhost", or contain a DNS - * separator "." or port separator ":". Port, if present, will also be included. If it does not meet - * these rules, it is not the registry but instead part of the image name. See - * https://stackoverflow.com/questions/37861791/how-are-docker-image-names-parsed. - * - * Image name: Everything after the registry (if the registry is valid) until the tag. Otherwise, - * everything until the tag. - * - * Tag: Everything after the ":", if it is present. - */ -const imageNameRegex = /^((?((localhost|([\w-]+(\.[\w-]+)+))(:\d+)?)|([\w-]+:\d+))\/)?(?[\w-./<>]+)(:(?[\w-.<>]+))?(@(?.+))?$/; - -// In certain cases, Docker makes image/tag names "", which is not really valid. We will reinterpret those as `undefined`. -const noneImageName = /[<>]/i; - -/** - * Parse an image name and return its components. - * @param originalName The original image name - * @returns The separated registry, image, and tag, along with the input original name - * and a verbose name composed of as much information as possible. - */ -export function parseDockerLikeImageName(originalName: string | undefined): ImageNameInfo { - if (!originalName) { - return { - originalName, - }; - } - - const match = imageNameRegex.exec(originalName); - - if (!match || !match.groups) { - throw new Error('Invalid image name'); - } - - const { registry, image, tag, digest } = match.groups; - - return { - originalName, - image: noneImageName.test(image) ? undefined : image, - tag: noneImageName.test(tag) ? undefined : tag, - digest, - registry, - }; -} diff --git a/src/runtimes/docker/clients/DockerClientBase/parseDockerLikeLabels.ts b/src/runtimes/docker/clients/DockerClientBase/parseDockerLikeLabels.ts deleted file mode 100644 index a4e7699ba5..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/parseDockerLikeLabels.ts +++ /dev/null @@ -1,19 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Labels } from "../../contracts/ContainerClient"; - -/** - * Parse Docker-like label string - * @param rawLabels Comma seperated string of labels - * @returns A {@link Labels} record - */ -export function parseDockerLikeLabels(rawLabels: string): Labels { - return rawLabels.split(',').reduce((labels, labelPair) => { - const index = labelPair.indexOf('='); - labels[labelPair.substring(0, index)] = labelPair.substring(index + 1); - return labels; - }, {} as Labels); -} diff --git a/src/runtimes/docker/clients/DockerClientBase/parseDockerRawPortString.ts b/src/runtimes/docker/clients/DockerClientBase/parseDockerRawPortString.ts deleted file mode 100644 index 282bf7a25e..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/parseDockerRawPortString.ts +++ /dev/null @@ -1,36 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { PortBinding } from "../../contracts/ContainerClient"; - -/** - * Attempt to parse a Docker-like raw port binding string - * @param portString the raw port string to parse, e.g. "1234/tcp" or "0.0.0.0:1234->1234/udp" - * @returns Parsed raw port string as a PortBinding record or undefined if invalid - */ -export function parseDockerRawPortString(portString: string): PortBinding | undefined { - const portRegex = /((?[\da-f.:[\]]+)(:(?\d+)))?(\s*->\s*)?((?\d+)\/(?tcp|udp))/i; - const result = portRegex.exec(portString); - - if (!result || !result.groups) { - return undefined; - } - - const hostIp = result.groups['hostIp'] || undefined; - const hostPort = result.groups['hostPort'] ? Number.parseInt(result.groups['hostPort']) : undefined; - const containerPort = result.groups['containerPort'] ? Number.parseInt(result.groups['containerPort']) : undefined; - const protocol = result.groups['protocol'] || undefined; - - if (containerPort === undefined || (protocol !== 'tcp' && protocol !== 'udp')) { - return undefined; - } - - return { - hostIp, - hostPort, - containerPort, - protocol, - }; -} diff --git a/src/runtimes/docker/clients/DockerClientBase/parseListFilesCommandOutput.ts b/src/runtimes/docker/clients/DockerClientBase/parseListFilesCommandOutput.ts deleted file mode 100644 index 90fdbd11d0..0000000000 --- a/src/runtimes/docker/clients/DockerClientBase/parseListFilesCommandOutput.ts +++ /dev/null @@ -1,133 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as path from 'path'; -import * as vscode from 'vscode'; -import { ListFilesCommandOptions, ListFilesItem } from '../../contracts/ContainerClient'; -import { dayjs } from '../../utils/dayjs'; - -const DateFormats = [ - 'MMM D HH:mm', // Linux format - 'MMM D YYYY', // Linux format for last year - 'MM/DD/YYYY hh:mm A', // Windows format -]; - -const SupportedFileTypes = [vscode.FileType.Directory, vscode.FileType.File]; - -type FileMode = { - mode?: number; - fileType: vscode.FileType; -}; - -export function parseListFilesCommandLinuxOutput( - options: ListFilesCommandOptions, - output: string -): ListFilesItem[] { - const regex = /^(?[0-9a-fA-F]+)\s+(?\d+)\s+(?\d+)\s+(?\d+)\s+(?\d+)\s+(?\d+)\s+(?\d+)\s+(?\d+)\s+(?.*)$/gm; - - const items = new Array(); - for (const match of output.matchAll(regex)) { - /* eslint-disable @typescript-eslint/no-non-null-assertion */ - const name = path.basename(match.groups!.name); - const { mode, fileType } = parseLinuxType(match.groups!.type); - const size = Number.parseInt(match.groups!.size, 10); - const mtime = dayjs.unix(Number.parseInt(match.groups!.mtime, 10)).valueOf(); - const ctime = dayjs.unix(Number.parseInt(match.groups!.ctime, 10)).valueOf(); - const atime = dayjs.unix(Number.parseInt(match.groups!.atime, 10)).valueOf(); - /* eslint-enable @typescript-eslint/no-non-null-assertion */ - - // Ignore relative directory items... - if (fileType === vscode.FileType.Directory && (name === '.' || name === '..')) { - continue; - } - - // Ignore everything other than directories and plain files - if (!SupportedFileTypes.includes(fileType)) { - continue; - } - - items.push({ - name, - path: path.posix.join(options.path, name), - type: fileType, - mode, - ctime, - mtime, - atime, - size, - }); - } - - return items; -} - -export function parseListFilesCommandWindowsOutput( - options: ListFilesCommandOptions, - output: string -): ListFilesItem[] { - const regex = /^(?(?\d{1,2}(\/|\.)\d{1,2}(\/|\.)\d{4})\s+(?