Add common display settings for explorers (#989)

Includes "Group By", "Sort By", "Label", and "Description"
This commit is contained in:
Eric Jizba 2019-06-17 14:43:54 -07:00 коммит произвёл GitHub
Родитель 2b068e5a0d
Коммит e843749a24
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
65 изменённых файлов: 3465 добавлений и 740 удалений

Просмотреть файл

@ -18,6 +18,7 @@ export { activateInternal, deactivateInternal } from './src/extension';
// The tests should import '../extension.bundle.ts'. At design-time they live in tests/ and so will pick up this file (extension.bundle.ts).
// At runtime the tests live in dist/tests and will therefore pick up the main webpack bundle at dist/extension.bundle.js.
export { configure, ConfigureApiOptions, ConfigureTelemetryProperties } from './src/configureWorkspace/configure';
export { configPrefix } from './src/constants';
export { ProcessProvider } from './src/debugging/coreclr/ChildProcessProvider';
export { DockerBuildImageOptions, DockerClient } from './src/debugging/coreclr/CliDockerClient';
export { CommandLineBuilder } from './src/debugging/coreclr/commandLineBuilder';
@ -28,7 +29,6 @@ export { LineSplitter } from './src/debugging/coreclr/lineSplitter';
export { OSProvider } from './src/debugging/coreclr/LocalOSProvider';
export { DockerDaemonIsLinuxPrerequisite, DockerfileExistsPrerequisite, DotNetSdkInstalledPrerequisite, LinuxUserInDockerGroupPrerequisite, MacNuGetFallbackFolderSharedPrerequisite } from './src/debugging/coreclr/prereqManager';
export { ext } from './src/extensionVariables';
export { getImageLabel, trimWithElipsis } from './src/tree/images/getImageLabel';
export { globAsync } from './src/utils/globAsync';
export { httpsRequestBinary } from './src/utils/httpRequest';
export { IKeytar } from './src/utils/keytar';
@ -36,4 +36,6 @@ export { nonNullProp } from './src/utils/nonNull';
export { getDockerOSType, isWindows10RS3OrNewer, isWindows10RS4OrNewer, isWindows10RS5OrNewer } from "./src/utils/osUtils";
export { Platform, PlatformOS } from './src/utils/platform';
export { DefaultTerminalProvider } from './src/utils/TerminalProvider';
export { trimWithElipsis } from './src/utils/trimWithElipsis';
export { wrapError } from './src/utils/wrapError';
export * from 'vscode-azureextensionui';

Просмотреть файл

@ -35,6 +35,7 @@
"onCommand:vscode-docker.compose.up",
"onCommand:vscode-docker.configure",
"onCommand:vscode-docker.containers.attachShell",
"onCommand:vscode-docker.containers.configureExplorer",
"onCommand:vscode-docker.containers.inspect",
"onCommand:vscode-docker.containers.prune",
"onCommand:vscode-docker.containers.refresh",
@ -44,7 +45,7 @@
"onCommand:vscode-docker.containers.stop",
"onCommand:vscode-docker.containers.viewLogs",
"onCommand:vscode-docker.images.build",
"onCommand:vscode-docker.images.groupBy",
"onCommand:vscode-docker.images.configureExplorer",
"onCommand:vscode-docker.images.inspect",
"onCommand:vscode-docker.images.prune",
"onCommand:vscode-docker.images.push",
@ -54,6 +55,7 @@
"onCommand:vscode-docker.images.runAzureCli",
"onCommand:vscode-docker.images.runInteractive",
"onCommand:vscode-docker.images.tag",
"onCommand:vscode-docker.networks.configureExplorer",
"onCommand:vscode-docker.networks.prune",
"onCommand:vscode-docker.networks.refresh",
"onCommand:vscode-docker.networks.remove",
@ -82,6 +84,7 @@
"onCommand:vscode-docker.registries.refresh",
"onCommand:vscode-docker.registries.setAsDefault",
"onCommand:vscode-docker.system.prune",
"onCommand:vscode-docker.volumes.configureExplorer",
"onCommand:vscode-docker.volumes.inspect",
"onCommand:vscode-docker.volumes.prune",
"onCommand:vscode-docker.volumes.refresh",
@ -194,6 +197,16 @@
"when": "view == dockerContainers",
"group": "navigation@1"
},
{
"command": "vscode-docker.containers.configureExplorer",
"when": "view == dockerContainers",
"group": "navigation@8"
},
{
"command": "vscode-docker.networks.configureExplorer",
"when": "view == dockerNetworks",
"group": "navigation@8"
},
{
"command": "vscode-docker.containers.refresh",
"when": "view == dockerContainers",
@ -209,16 +222,16 @@
"when": "view == dockerNetworks",
"group": "navigation@9"
},
{
"command": "vscode-docker.images.groupBy",
"when": "view == dockerImages",
"group": "navigation@1"
},
{
"command": "vscode-docker.images.prune",
"when": "view == dockerImages",
"group": "navigation@2"
},
{
"command": "vscode-docker.images.configureExplorer",
"when": "view == dockerImages",
"group": "navigation@8"
},
{
"command": "vscode-docker.images.refresh",
"when": "view == dockerImages",
@ -234,6 +247,11 @@
"when": "view == dockerVolumes",
"group": "navigation@1"
},
{
"command": "vscode-docker.volumes.configureExplorer",
"when": "view == dockerVolumes",
"group": "navigation@8"
},
{
"command": "vscode-docker.volumes.refresh",
"when": "view == dockerVolumes",
@ -725,15 +743,231 @@
"default": 1000,
"description": "Explorer refresh interval, default is 1000ms"
},
"docker.groupImagesBy": {
"docker.containers.groupBy": {
"type": "string",
"default": "Repository",
"description": "How to group items in the treeview Images node",
"default": "None",
"description": "The property to use when grouping containers.",
"enum": [
"ContainerId",
"ContainerName",
"CreatedTime",
"FullTag",
"ImageId",
"None",
"Ports",
"Registry",
"Repository",
"RepositoryName",
"ImageId"
"RepositoryNameAndTag",
"State",
"Status",
"Tag"
]
},
"docker.containers.description": {
"type": "array",
"default": [
"ContainerName",
"Status"
],
"description": "Any secondary properties to display for a container.",
"items": {
"type": "string",
"enum": [
"ContainerId",
"ContainerName",
"CreatedTime",
"FullTag",
"ImageId",
"Ports",
"Registry",
"Repository",
"RepositoryName",
"RepositoryNameAndTag",
"State",
"Status",
"Tag"
]
}
},
"docker.containers.label": {
"type": "string",
"default": "FullTag",
"description": "The primary property to display for a container.",
"enum": [
"ContainerId",
"ContainerName",
"CreatedTime",
"FullTag",
"ImageId",
"Ports",
"Registry",
"Repository",
"RepositoryName",
"RepositoryNameAndTag",
"State",
"Status",
"Tag"
]
},
"docker.containers.sortBy": {
"type": "string",
"default": "CreatedTime",
"description": "The property to use when sorting containers.",
"enum": [
"CreatedTime",
"Label"
]
},
"docker.images.groupBy": {
"type": "string",
"default": "Repository",
"description": "The property to use when grouping images.",
"enum": [
"CreatedTime",
"FullTag",
"ImageId",
"None",
"Registry",
"Repository",
"RepositoryName",
"RepositoryNameAndTag",
"Tag"
]
},
"docker.images.description": {
"type": "array",
"default": [
"CreatedTime"
],
"description": "Any secondary properties to display for a image.",
"items": {
"type": "string",
"enum": [
"CreatedTime",
"FullTag",
"ImageId",
"Registry",
"Repository",
"RepositoryName",
"RepositoryNameAndTag",
"Tag"
]
}
},
"docker.images.label": {
"type": "string",
"default": "Tag",
"description": "The primary property to display for a image.",
"enum": [
"CreatedTime",
"FullTag",
"ImageId",
"Registry",
"Repository",
"RepositoryName",
"RepositoryNameAndTag",
"Tag"
]
},
"docker.images.sortBy": {
"type": "string",
"default": "CreatedTime",
"description": "The property to use when sorting images.",
"enum": [
"CreatedTime",
"Label"
]
},
"docker.networks.groupBy": {
"type": "string",
"default": "None",
"description": "The property to use when grouping networks.",
"enum": [
"CreatedTime",
"NetworkDriver",
"NetworkId",
"NetworkName",
"None"
]
},
"docker.networks.description": {
"type": "array",
"default": [
"NetworkDriver",
"CreatedTime"
],
"description": "Any secondary properties to display for a network.",
"items": {
"type": "string",
"enum": [
"CreatedTime",
"NetworkDriver",
"NetworkId",
"NetworkName"
]
}
},
"docker.networks.label": {
"type": "string",
"default": "NetworkName",
"description": "The primary property to display for a network.",
"enum": [
"CreatedTime",
"NetworkDriver",
"NetworkId",
"NetworkName"
]
},
"docker.networks.sortBy": {
"type": "string",
"default": "CreatedTime",
"description": "The property to use when sorting networks.",
"enum": [
"CreatedTime",
"Label"
]
},
"docker.volumes.groupBy": {
"type": "string",
"default": "None",
"description": "The property to use when grouping volumes.",
"enum": [
"CreatedTime",
"VolumeName",
"None"
]
},
"docker.volumes.description": {
"type": "array",
"default": [
"CreatedTime"
],
"description": "Any secondary properties to display for a volume.",
"items": {
"type": "string",
"enum": [
"CreatedTime",
"VolumeName"
]
}
},
"docker.volumes.label": {
"type": "string",
"default": "VolumeName",
"description": "The primary property to display for a volume.",
"enum": [
"CreatedTime",
"VolumeName"
]
},
"docker.volumes.sortBy": {
"type": "string",
"default": "CreatedTime",
"description": "The property to use when sorting volumes.",
"enum": [
"CreatedTime",
"Label"
]
},
"docker.imageBuildContextPath": {
@ -945,6 +1179,15 @@
"title": "Attach Shell",
"category": "Docker Containers"
},
{
"command": "vscode-docker.containers.configureExplorer",
"title": "Configure Explorer...",
"category": "Docker Containers",
"icon": {
"light": "resources/light/settings.svg",
"dark": "resources/dark/settings.svg"
}
},
{
"command": "vscode-docker.containers.inspect",
"title": "Inspect",
@ -999,12 +1242,12 @@
"category": "Docker Images"
},
{
"command": "vscode-docker.images.groupBy",
"title": "Group By...",
"command": "vscode-docker.images.configureExplorer",
"title": "Configure Explorer...",
"category": "Docker Images",
"icon": {
"light": "resources/light/grouping.svg",
"dark": "resources/dark/grouping.svg"
"light": "resources/light/settings.svg",
"dark": "resources/dark/settings.svg"
}
},
{
@ -1060,6 +1303,15 @@
"title": "Tag...",
"category": "Docker Images"
},
{
"command": "vscode-docker.networks.configureExplorer",
"title": "Configure Explorer...",
"category": "Docker Networks",
"icon": {
"light": "resources/light/settings.svg",
"dark": "resources/dark/settings.svg"
}
},
{
"command": "vscode-docker.networks.prune",
"title": "Prune...",
@ -1228,6 +1480,15 @@
"dark": "resources/dark/prune.svg"
}
},
{
"command": "vscode-docker.volumes.configureExplorer",
"title": "Configure Explorer...",
"category": "Docker Volumes",
"icon": {
"light": "resources/light/settings.svg",
"dark": "resources/dark/settings.svg"
}
},
{
"command": "vscode-docker.volumes.inspect",
"title": "Inspect",

Просмотреть файл

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#252526}.icon-vs-out{fill:#252526}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2a292c}.icon-vs-action-blue{fill:#75beff}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 9.402l-.852-.852L14 9.695V6h-1V3.586L9.414 0H0v16h12l1-.076v-.971l3-3.011v-2.54z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M4 6.707L2.646 5.354l.707-.707.647.646 2.646-2.646.707.707L4 6.707zM3 12h1v-1H3v1zm8.077 2H2V2h6.289l2.728 2.777L11 6h1V4L9 1H1v14h11v-.076L11.077 14zM4 8H3v1h1V8zm5 3.922L8.078 11H5v1h4v-.078zM5 8v1h4.42l.449-.449.131.13V8H5zm2-2h3V5H7v1z" id="iconBg"/><path class="icon-vs-fg" d="M9 11.922V12H5v-1h3.078l-.329-.329L9.42 9H5V8h5v.681l1 .997V4.759L8.254 2H2v12h9v-.077l-2-2.001zM10 5v1H7V5h3zm-6 7H3v-1h1v1zm0-3H3V8h1v1zM2.646 5.354l.707-.707.647.646 2.646-2.646.004-.004.707.707-.003.004L4 6.707 2.646 5.354z" id="iconFg" style="display: none;"/><path class="icon-vs-action-blue" d="M15.148 9.964l.707.707-3.34 3.352-3.352-3.352.707-.707L12 12.086V7h1v5.104l2.148-2.14z" id="colorAction"/></svg>

До

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-bg{fill:#c5c5c5;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M5.986 16l-.373-2.237-1.846 1.317-2.848-2.847 1.319-1.847-2.238-.373v-4.027l2.238-.373-1.319-1.846 2.849-2.848 1.846 1.319.372-2.238h4.028l.372 2.238 1.847-1.319 2.847 2.848-1.318 1.846 2.238.373v4.028l-2.238.372 1.318 1.847-2.847 2.847-1.847-1.318-.373 2.238h-4.027z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M9.964 3.257l-.443-.133-.354-2.124h-2.334l-.353 2.121c-.296.093-.58.21-.855.354l-1.75-1.25-1.65 1.65 1.252 1.752-.22.409-.133.443-2.124.354v2.333l2.121.354c.092.296.21.58.354.855l-1.25 1.75 1.65 1.65 1.752-1.252.408.219.444.134.354 2.124h2.333l.354-2.121c.296-.092.58-.21.854-.354l1.75 1.25 1.65-1.65-1.252-1.752.219-.408.134-.444 2.125-.354v-2.334l-2.121-.353c-.092-.296-.21-.58-.354-.854l1.25-1.75-1.65-1.65-1.752 1.252-.409-.221zm.248 4.743c0 1.222-.991 2.212-2.212 2.212-1.222 0-2.212-.991-2.212-2.212s.99-2.212 2.212-2.212c1.222 0 2.212.99 2.212 2.212z" id="iconBg"/></svg>

После

Ширина:  |  Высота:  |  Размер: 1.2 KiB

1
resources/dark/time.svg Normal file
Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#252526}.icon-vs-out{fill:#252526}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2a292c}</style><path class="icon-canvas-transparent" d="M16 0v16H0V0h16z" id="canvas"/><path class="icon-vs-out" d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M14 8c0 3.309-2.691 6-6 6s-6-2.691-6-6 2.691-6 6-6 6 2.691 6 6z" id="iconFg" style="display: none;"/><g id="iconBg"><path class="icon-vs-bg" d="M8 8h3v1H7V4h1v4zm7 0c0 3.859-3.141 7-7 7-3.86 0-7-3.141-7-7 0-3.86 3.14-7 7-7 3.859 0 7 3.14 7 7zm-1 0c0-3.309-2.691-6-6-6S2 4.691 2 8s2.691 6 6 6 6-2.691 6-6z"/></g></svg>

После

Ширина:  |  Высота:  |  Размер: 747 B

Просмотреть файл

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}.icon-vs-action-blue{fill:#00539c}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 9.402l-.852-.852L14 9.695V6h-1V3.586L9.414 0H0v16h12l1-.076v-.971l3-3.011v-2.54z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M4 6.707L2.646 5.354l.707-.707.647.646 2.646-2.646.707.707L4 6.707zM3 12h1v-1H3v1zm8.077 2H2V2h6.289l2.728 2.777L11 6h1V4L9 1H1v14h11v-.076L11.077 14zM4 8H3v1h1V8zm5 3.922L8.078 11H5v1h4v-.078zM5 8v1h4.42l.449-.449.131.13V8H5zm2-2h3V5H7v1z" id="iconBg"/><path class="icon-vs-fg" d="M9 11.922V12H5v-1h3.078l-.329-.329L9.42 9H5V8h5v.681l1 .997V4.759L8.254 2H2v12h9v-.077l-2-2.001zM10 5v1H7V5h3zm-6 7H3v-1h1v1zm0-3H3V8h1v1zM2.646 5.354l.707-.707.647.646 2.646-2.646.004-.004.707.707-.003.004L4 6.707 2.646 5.354z" id="iconFg" style="display: none;"/><path class="icon-vs-action-blue" d="M15.148 9.964l.707.707-3.34 3.352-3.352-3.352.707-.707L12 12.086V7h1v5.104l2.148-2.14z" id="colorAction"/></svg>

До

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-bg{fill:#424242;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M5.986 16l-.373-2.237-1.846 1.317-2.848-2.847 1.319-1.847-2.238-.373v-4.027l2.238-.373-1.319-1.846 2.849-2.848 1.846 1.319.372-2.238h4.028l.372 2.238 1.847-1.319 2.847 2.848-1.318 1.846 2.238.373v4.028l-2.238.372 1.318 1.847-2.847 2.847-1.847-1.318-.373 2.238h-4.027z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M9.964 3.257l-.443-.133-.354-2.124h-2.334l-.353 2.121c-.296.093-.58.21-.855.354l-1.75-1.25-1.65 1.65 1.252 1.752-.22.409-.133.443-2.124.354v2.333l2.121.354c.092.296.21.58.354.855l-1.25 1.75 1.65 1.65 1.752-1.252.408.219.444.134.354 2.124h2.333l.354-2.121c.296-.092.58-.21.854-.354l1.75 1.25 1.65-1.65-1.252-1.752.219-.408.134-.444 2.125-.354v-2.334l-2.121-.353c-.092-.296-.21-.58-.354-.854l1.25-1.75-1.65-1.65-1.752 1.252-.409-.221zm.248 4.743c0 1.222-.991 2.212-2.212 2.212-1.222 0-2.212-.991-2.212-2.212s.99-2.212 2.212-2.212c1.222 0 2.212.99 2.212 2.212z" id="iconBg"/></svg>

После

Ширина:  |  Высота:  |  Размер: 1.2 KiB

1
resources/light/time.svg Normal file
Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 0v16H0V0h16z" id="canvas"/><path class="icon-vs-out" d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M14 8c0 3.309-2.691 6-6 6s-6-2.691-6-6 2.691-6 6-6 6 2.691 6 6z" id="iconFg" style="display: none;"/><g id="iconBg"><path class="icon-vs-bg" d="M8 8h3v1H7V4h1v4zm7 0c0 3.859-3.141 7-7 7-3.86 0-7-3.141-7-7 0-3.86 3.14-7 7-7 3.859 0 7 3.14 7 7zm-1 0c0-3.309-2.691-6-6-6S2 4.691 2 8s2.691 6 6 6 6-2.691 6-6z"/></g></svg>

После

Ширина:  |  Высота:  |  Размер: 747 B

Просмотреть файл

@ -26,7 +26,7 @@ export async function attachShellContainer(context: IActionContext, node?: Conta
}
context.telemetry.properties.shellCommand = shellCommand;
const terminal = ext.terminalProvider.createTerminal(`Shell: ${node.container.Image}`);
terminal.sendText(`docker exec -it ${node.container.Id} ${shellCommand}`);
const terminal = ext.terminalProvider.createTerminal(`Shell: ${node.fullTag}`);
terminal.sendText(`docker exec -it ${node.containerId} ${shellCommand}`);
terminal.show();
}

Просмотреть файл

@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IActionContext } from "vscode-azureextensionui";
import { ext } from "../../extensionVariables";
export async function configureContainersExplorer(context: IActionContext): Promise<void> {
await ext.containersRoot.configureExplorer(context);
}

Просмотреть файл

@ -12,6 +12,6 @@ export async function inspectContainer(context: IActionContext, node?: Container
node = await ext.containersTree.showTreeItemPicker<ContainerTreeItem>(ContainerTreeItem.allContextRegExp, context);
}
const inspectInfo = await ext.dockerode.getContainer(node.container.Id).inspect();
const inspectInfo = await node.getContainer().inspect();
await openReadOnlyJson(node, inspectInfo);
}

Просмотреть файл

@ -18,7 +18,7 @@ export async function restartContainer(context: IActionContext, node: ContainerT
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: "Restarting Container(s)..." }, async () => {
await Promise.all(nodes.map(async n => {
await ext.dockerode.getContainer(n.container.Id).restart();
await n.getContainer().restart();
}));
});
}

Просмотреть файл

@ -18,7 +18,7 @@ export async function startContainer(context: IActionContext, node: ContainerTre
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: "Starting Container(s)..." }, async () => {
await Promise.all(nodes.map(async n => {
await ext.dockerode.getContainer(n.container.Id).start();
await n.getContainer().start();
}));
});
}

Просмотреть файл

@ -18,7 +18,7 @@ export async function stopContainer(context: IActionContext, node: ContainerTree
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: "Stopping Container(s)..." }, async () => {
await Promise.all(nodes.map(async n => {
await ext.dockerode.getContainer(n.container.Id).stop();
await n.getContainer().stop();
}));
});
}

Просмотреть файл

@ -12,7 +12,7 @@ export async function viewContainerLogs(context: IActionContext, node?: Containe
node = await ext.containersTree.showTreeItemPicker<ContainerTreeItem>(ContainerTreeItem.allContextRegExp, context);
}
const terminal = ext.terminalProvider.createTerminal(node.container.Image);
terminal.sendText(`docker logs -f ${node.container.Id}`);
const terminal = ext.terminalProvider.createTerminal(node.fullTag);
terminal.sendText(`docker logs -f ${node.containerId}`);
terminal.show();
}

Просмотреть файл

@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IActionContext } from "vscode-azureextensionui";
import { ext } from "../../extensionVariables";
export async function configureImagesExplorer(context: IActionContext): Promise<void> {
await ext.imagesRoot.configureExplorer(context);
}

Просмотреть файл

@ -1,26 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ConfigurationTarget, workspace, WorkspaceConfiguration } from 'vscode';
import { configPrefix, configurationKeys } from '../../constants';
import { ext, ImageGrouping } from '../../extensionVariables';
export async function groupImagesBy(): Promise<void> {
let response = await ext.ui.showQuickPick(
[
{ label: "No grouping", data: ImageGrouping.None },
{ label: "Group by repository", data: ImageGrouping.Repository },
{ label: "Group by repository name", data: ImageGrouping.RepositoryName },
{ label: "Group by image ID", data: ImageGrouping.ImageId },
],
{
placeHolder: "Select how to group the Images node entries"
});
ext.groupImagesBy = response.data;
const configOptions: WorkspaceConfiguration = workspace.getConfiguration(configPrefix);
configOptions.update(configurationKeys.groupImagesBy, ImageGrouping[ext.groupImagesBy], ConfigurationTarget.Global);
await ext.imagesTree.refresh();
}

Просмотреть файл

@ -12,6 +12,6 @@ export async function inspectImage(context: IActionContext, node?: ImageTreeItem
node = await ext.imagesTree.showTreeItemPicker<ImageTreeItem>(ImageTreeItem.contextValue, context);
}
const inspectInfo = await ext.dockerode.getImage(node.image.Id).inspect();
const inspectInfo = await node.getImage().inspect();
await openReadOnlyJson(node, inspectInfo);
}

Просмотреть файл

@ -20,7 +20,7 @@ async function runImageCore(context: IActionContext, node: ImageTreeItem | undef
node = await ext.imagesTree.showTreeItemPicker<ImageTreeItem>(ImageTreeItem.contextValue, context);
}
const inspectInfo = await ext.dockerode.getImage(node.image.Id).inspect();
const inspectInfo = await node.getImage().inspect();
const ports: string[] = inspectInfo.Config.ExposedPorts ? Object.keys(inspectInfo.Config.ExposedPorts) : [];
let options = `--rm ${interactive ? '-it' : '-d'}`;

Просмотреть файл

@ -28,12 +28,12 @@ export async function tagImage(context: IActionContext, node: ImageTreeItem | un
tag = newTaggedName.slice(newTaggedName.lastIndexOf(':') + 1);
}
const image: Image = ext.dockerode.getImage(node.image.Id);
const image: Image = node.getImage();
await image.tag({ repo: repo, tag: tag });
return newTaggedName;
}
export async function getTagFromUserInput(imageName: string, addDefaultRegistry: boolean): Promise<string> {
export async function getTagFromUserInput(fullTag: string, addDefaultRegistry: boolean): Promise<string> {
const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('docker');
const defaultRegistryPath = configOptions.get(configurationKeys.defaultRegistryPath, '');
@ -42,15 +42,15 @@ export async function getTagFromUserInput(imageName: string, addDefaultRegistry:
prompt: 'Tag image as...',
};
if (addDefaultRegistry) {
let registryLength: number = imageName.indexOf('/');
let registryLength: number = fullTag.indexOf('/');
if (defaultRegistryPath.length > 0 && registryLength < 0) {
imageName = defaultRegistryPath + '/' + imageName;
fullTag = defaultRegistryPath + '/' + fullTag;
registryLength = defaultRegistryPath.length;
}
opt.valueSelection = registryLength < 0 ? undefined : [0, registryLength + 1]; //include the '/'
}
opt.value = imageName;
opt.value = fullTag;
const nameWithTag: string = await ext.ui.showInputBox(opt);
return nameWithTag;

Просмотреть файл

@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IActionContext } from "vscode-azureextensionui";
import { ext } from "../../extensionVariables";
export async function configureNetworksExplorer(context: IActionContext): Promise<void> {
await ext.networksRoot.configureExplorer(context);
}

Просмотреть файл

@ -9,6 +9,7 @@ import { configure, configureApi } from "../configureWorkspace/configure";
import { ext } from "../extensionVariables";
import { composeDown, composeRestart, composeUp } from "./compose";
import { attachShellContainer } from "./containers/attachShellContainer";
import { configureContainersExplorer } from "./containers/configureContainersExplorer";
import { inspectContainer } from "./containers/inspectContainer";
import { pruneContainers } from "./containers/pruneContainers";
import { removeContainer } from "./containers/removeContainer";
@ -17,7 +18,7 @@ import { startContainer } from "./containers/startContainer";
import { stopContainer } from "./containers/stopContainer";
import { viewContainerLogs } from "./containers/viewContainerLogs";
import { buildImage } from "./images/buildImage";
import { groupImagesBy } from "./images/groupImagesBy";
import { configureImagesExplorer } from "./images/configureImagesExplorer";
import { inspectImage } from "./images/inspectImage";
import { pruneImages } from "./images/pruneImages";
import { pushImage } from "./images/pushImage";
@ -25,6 +26,7 @@ import { removeImage } from "./images/removeImage";
import { runAzureCliImage } from "./images/runAzureCliImage";
import { runImage, runImageInteractive } from "./images/runImage";
import { tagImage } from "./images/tagImage";
import { configureNetworksExplorer } from "./networks/configureNetworksExplorer";
import { pruneNetworks } from "./networks/pruneNetworks";
import { removeNetwork } from "./networks/removeNetwork";
import { createAzureRegistry } from "./registries/azure/createAzureRegistry";
@ -48,6 +50,7 @@ import { disconnectPrivateRegistry } from "./registries/private/disconnectPrivat
import { pullImage, pullRepository } from "./registries/pullImages";
import { setRegistryAsDefault } from "./registries/registrySettings";
import { systemPrune } from "./systemPrune";
import { configureVolumesExplorer } from "./volumes/configureVolumesExplorer";
import { inspectVolume } from "./volumes/inspectVolume";
import { pruneVolumes } from "./volumes/pruneVolumes";
import { removeVolume } from "./volumes/removeVolume";
@ -62,6 +65,7 @@ export function registerCommands(): void {
registerCommand('vscode-docker.containers.attachShell', attachShellContainer);
registerCommand('vscode-docker.containers.inspect', inspectContainer);
registerCommand('vscode-docker.containers.configureExplorer', configureContainersExplorer);
registerCommand('vscode-docker.containers.prune', pruneContainers);
registerCommand('vscode-docker.containers.remove', removeContainer);
registerCommand('vscode-docker.containers.restart', restartContainer);
@ -70,7 +74,7 @@ export function registerCommands(): void {
registerCommand('vscode-docker.containers.viewLogs', viewContainerLogs);
registerCommand('vscode-docker.images.build', buildImage);
registerCommand('vscode-docker.images.groupBy', groupImagesBy);
registerCommand('vscode-docker.images.configureExplorer', configureImagesExplorer);
registerCommand('vscode-docker.images.inspect', inspectImage);
registerCommand('vscode-docker.images.prune', pruneImages);
registerCommand('vscode-docker.images.push', pushImage);
@ -80,6 +84,7 @@ export function registerCommands(): void {
registerCommand('vscode-docker.images.runInteractive', runImageInteractive);
registerCommand('vscode-docker.images.tag', tagImage);
registerCommand('vscode-docker.networks.configureExplorer', configureNetworksExplorer);
registerCommand('vscode-docker.networks.remove', removeNetwork);
registerCommand('vscode-docker.networks.prune', pruneNetworks);
@ -111,6 +116,7 @@ export function registerCommands(): void {
registerCommand('vscode-docker.registries.private.connectRegistry', connectPrivateRegistry);
registerCommand('vscode-docker.registries.private.disconnectRegistry', disconnectPrivateRegistry);
registerCommand('vscode-docker.volumes.configureExplorer', configureVolumesExplorer);
registerCommand('vscode-docker.volumes.inspect', inspectVolume);
registerCommand('vscode-docker.volumes.prune', pruneVolumes);
registerCommand('vscode-docker.volumes.remove', removeVolume);

Просмотреть файл

@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IActionContext } from "vscode-azureextensionui";
import { ext } from "../../extensionVariables";
export async function configureVolumesExplorer(context: IActionContext): Promise<void> {
await ext.volumesRoot.configureExplorer(context);
}

Просмотреть файл

@ -12,6 +12,6 @@ export async function inspectVolume(context: IActionContext, node?: VolumeTreeIt
node = await ext.volumesTree.showTreeItemPicker<VolumeTreeItem>(VolumeTreeItem.contextValue, context);
}
const inspectInfo = await ext.dockerode.getVolume(node.volume.Name).inspect();
const inspectInfo = node.getVolume().inspect();
await openReadOnlyJson(node, inspectInfo);
}

Просмотреть файл

@ -16,14 +16,14 @@ import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelect
import { registerCommands } from './commands/registerCommands';
import { consolidateDefaultRegistrySettings } from './commands/registries/registrySettings';
import { DockerDebugConfigProvider } from './configureWorkspace/DockerDebugConfigProvider';
import { COMPOSE_FILE_GLOB_PATTERN, configPrefix, configurationKeys, ignoreBundle } from './constants';
import { COMPOSE_FILE_GLOB_PATTERN, ignoreBundle } from './constants';
import { registerDebugConfigurationProvider } from './debugging/coreclr/registerDebugConfigurationProvider';
import { DockerComposeCompletionItemProvider } from './dockerCompose/dockerComposeCompletionItemProvider';
import { DockerComposeHoverProvider } from './dockerCompose/dockerComposeHoverProvider';
import composeVersionKeys from './dockerCompose/dockerComposeKeyInfo';
import { DockerComposeParser } from './dockerCompose/dockerComposeParser';
import { DockerfileCompletionItemProvider } from './dockerfileCompletionItemProvider';
import { DefaultImageGrouping, ext, ImageGrouping } from './extensionVariables';
import { ext } from './extensionVariables';
import { registerTrees } from './tree/registerTrees';
import { addUserAgent } from './utils/addUserAgent';
import { getTrustedCertificates } from './utils/getTrustedCertificates';
@ -116,7 +116,6 @@ export async function activateInternal(ctx: vscode.ExtensionContext, perfStats:
registerDebugConfigurationProvider(ctx);
refreshDockerode();
refreshImageGrouping();
await consolidateDefaultRegistrySettings();
activateLanguageClient();
@ -199,8 +198,6 @@ namespace Configuration {
refreshDockerode();
// tslint:disable-next-line: no-floating-promises
setRequestDefaults();
refreshImageGrouping();
await ext.imagesTree.refresh();
}
}
);
@ -272,12 +269,6 @@ function activateLanguageClient(): void {
});
}
function refreshImageGrouping(): void {
const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(configPrefix);
let imageGrouping: string | undefined = configOptions.get<string>(configurationKeys.groupImagesBy);
ext.groupImagesBy = imageGrouping && imageGrouping in ImageGrouping ? <ImageGrouping>ImageGrouping[imageGrouping] : DefaultImageGrouping;
}
function refreshDockerode(): void {
const value: string = vscode.workspace.getConfiguration("docker").get("host", "");
if (value) {

Просмотреть файл

@ -9,23 +9,20 @@ import { RequestAPI, RequiredUriUrl } from 'request';
import { RequestPromise, RequestPromiseOptions } from 'request-promise-native';
import { ExtensionContext, OutputChannel, TreeView } from "vscode";
import { AzExtTreeDataProvider, AzExtTreeItem, IAzureUserInput, ITelemetryReporter } from "vscode-azureextensionui";
import { ContainersTreeItem } from './tree/containers/ContainersTreeItem';
import { ImagesTreeItem } from './tree/images/ImagesTreeItem';
import { NetworksTreeItem } from './tree/networks/NetworksTreeItem';
import { DockerHubAccountTreeItem } from './tree/registries/dockerHub/DockerHubAccountTreeItem';
import { VolumesTreeItem } from './tree/volumes/VolumesTreeItem';
import { IKeytar } from './utils/keytar';
import { ITerminalProvider } from "./utils/TerminalProvider";
type requestPromise = RequestAPI<RequestPromise, RequestPromiseOptions, RequiredUriUrl>;
export enum ImageGrouping {
None,
Repository,
RepositoryName,
ImageId
}
export const DefaultImageGrouping = ImageGrouping.Repository;
/**
* Namespace for common variables used throughout the extension. They must be initialized in the activate() method of extension.ts
*/
// tslint:disable-next-line: export-name
export namespace ext {
export let context: ExtensionContext;
export let outputChannel: OutputChannel;
@ -37,12 +34,15 @@ export namespace ext {
export let imagesTree: AzExtTreeDataProvider;
export let imagesTreeView: TreeView<AzExtTreeItem>;
export let imagesRoot: ImagesTreeItem;
export let containersTree: AzExtTreeDataProvider;
export let containersTreeView: TreeView<AzExtTreeItem>;
export let containersRoot: ContainersTreeItem;
export let networksTree: AzExtTreeDataProvider;
export let networksTreeView: TreeView<AzExtTreeItem>;
export let networksRoot: NetworksTreeItem;
export let registriesTree: AzExtTreeDataProvider;
export let registriesTreeView: TreeView<AzExtTreeItem>;
@ -50,6 +50,7 @@ export namespace ext {
export let volumesTree: AzExtTreeDataProvider;
export let volumesTreeView: TreeView<AzExtTreeItem>;
export let volumesRoot: VolumesTreeItem;
/**
* A version of 'request-promise' which should be used for all direct request calls (it has the user agent set up properly)
@ -63,6 +64,4 @@ export namespace ext {
platform: osNode.platform(),
release: osNode.release()
};
export let groupImagesBy: ImageGrouping = ImageGrouping.ImageId;
}

Просмотреть файл

@ -1,130 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TreeView, TreeViewVisibilityChangeEvent, workspace, WorkspaceConfiguration } from "vscode";
import { AzExtParentTreeItem, AzExtTreeItem, GenericTreeItem, IActionContext, InvalidTreeItem, registerEvent } from "vscode-azureextensionui";
import { isLinux } from "../utils/osUtils";
import { getThemedIconPath } from "./IconPath";
import { OpenUrlTreeItem } from "./OpenUrlTreeItem";
export abstract class AutoRefreshTreeItemBase<T> extends AzExtParentTreeItem {
private _currentItems: T[] | undefined;
private _itemsFromPolling: T[] | undefined;
private _failedToConnect: boolean = false;
public abstract noItemsMessage: string;
public abstract getItemID(item: T): string;
public abstract getItems(): Promise<T[] | undefined>;
public abstract convertToTreeItems(items: T[]): Promise<AzExtTreeItem[]>;
public initAutoRefresh(treeView: TreeView<AzExtTreeItem>): void {
let intervalId: NodeJS.Timeout;
registerEvent('treeView.onDidChangeVisibility', treeView.onDidChangeVisibility, (context: IActionContext, e: TreeViewVisibilityChangeEvent) => {
context.errorHandling.suppressDisplay = true;
context.telemetry.suppressIfSuccessful = true;
context.telemetry.properties.isActivationEvent = 'true';
if (e.visible) {
const configOptions: WorkspaceConfiguration = workspace.getConfiguration('docker');
const refreshInterval: number = configOptions.get<number>('explorerRefreshInterval', 1000);
intervalId = setInterval(
async () => {
if (await this.hasChanged()) {
await this.refresh();
}
},
refreshInterval);
} else {
clearInterval(intervalId);
}
});
}
public async loadMoreChildrenImpl(_clearCache: boolean, context: IActionContext): Promise<AzExtTreeItem[]> {
try {
this._currentItems = this._itemsFromPolling || await this.getSortedItems();
this._itemsFromPolling = undefined;
this._failedToConnect = false;
} catch (error) {
this._currentItems = undefined;
this._failedToConnect = true;
context.telemetry.properties.failedToConnect = 'true';
return this.getDockerErrorTreeItems(error);
}
if (this._currentItems.length === 0) {
context.telemetry.properties.noItems = 'true';
return [new GenericTreeItem(this, {
label: this.noItemsMessage,
iconPath: getThemedIconPath('info'),
contextValue: 'dockerNoItems'
})];
} else {
return await this.convertToTreeItems(this._currentItems);
}
}
public hasMoreChildrenImpl(): boolean {
return false;
}
public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number {
if (this._failedToConnect) {
return 0; // children are already sorted
} else {
return super.compareChildrenImpl(ti1, ti2);
}
}
private getDockerErrorTreeItems(error: unknown): AzExtTreeItem[] {
const connectionMessage = 'Failed to connect. Is Docker installed and running?';
const installDockerUrl = 'https://aka.ms/AA37qtj';
const linuxPostInstallUrl = 'https://aka.ms/AA37yk6';
const troubleshootingUrl = 'https://aka.ms/AA37qt2';
const result: AzExtTreeItem[] = [
new InvalidTreeItem(this, error, { label: connectionMessage, contextValue: 'dockerConnectionError', description: '' }),
new OpenUrlTreeItem(this, 'Install Docker...', installDockerUrl),
new OpenUrlTreeItem(this, 'Additional Troubleshooting...', troubleshootingUrl),
];
if (isLinux()) {
result.push(new OpenUrlTreeItem(this, 'Manage Docker as a non-root user on Linux...', linuxPostInstallUrl))
}
return result;
}
private async getSortedItems(): Promise<T[]> {
const items: T[] = await this.getItems() || [];
return items.sort((a, b) => this.getItemID(a).localeCompare(this.getItemID(b)));
}
private async hasChanged(): Promise<boolean> {
try {
this._itemsFromPolling = await this.getSortedItems();
} catch {
this._itemsFromPolling = undefined;
}
return !this.areArraysEqual(this._currentItems, this._itemsFromPolling);
}
private areArraysEqual(array1: T[] | undefined, array2: T[] | undefined): boolean {
if (array1 === array2) {
return true;
} else if (array1 && array2) {
if (array1.length !== array2.length) {
return false;
} else {
return !array1.some((item1, index) => {
return this.getItemID(item1) !== this.getItemID(array2[index]);
});
}
} else {
return false;
}
}
}

Просмотреть файл

@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzExtParentTreeItem, AzExtTreeItem } from "vscode-azureextensionui";
import { ILocalItem, LocalRootTreeItemBase } from "./LocalRootTreeItemBase";
import { CommonProperty } from "./settings/CommonProperties";
export abstract class LocalGroupTreeItemBase<TItem extends ILocalItem, TProperty extends string | CommonProperty> extends AzExtParentTreeItem {
public parent: LocalRootTreeItemBase<TItem, TProperty>;
public group: string;
private _items: TItem[];
public constructor(parent: LocalRootTreeItemBase<TItem, TProperty>, group: string, items: TItem[]) {
super(parent);
this.group = group;
this._items = items;
}
public get label(): string {
return this.group;
}
public get id(): string {
return this.group;
}
public get maxCreatedTime(): number {
return Math.max(...this._items.map(i => i.createdTime));
}
public async loadMoreChildrenImpl(_clearCache: boolean): Promise<AzExtTreeItem[]> {
return this._items.map(i => new this.parent.childType(this, i));
}
public hasMoreChildrenImpl(): boolean {
return false;
}
public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number {
return this.parent.compareChildrenImpl(ti1, ti2);
}
}

Просмотреть файл

@ -0,0 +1,325 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ConfigurationChangeEvent, ConfigurationTarget, TreeView, TreeViewVisibilityChangeEvent, workspace, WorkspaceConfiguration } from "vscode";
import { AzExtParentTreeItem, AzExtTreeItem, AzureWizard, GenericTreeItem, IActionContext, InvalidTreeItem, registerEvent } from "vscode-azureextensionui";
import { configPrefix } from "../constants";
import { nonNullProp } from "../utils/nonNull";
import { isLinux } from "../utils/osUtils";
import { getThemedIconPath } from "./IconPath";
import { LocalGroupTreeItemBase } from "./LocalGroupTreeItemBase";
import { OpenUrlTreeItem } from "./OpenUrlTreeItem";
import { CommonGroupBy, CommonProperty, CommonSortBy, sortByProperties } from "./settings/CommonProperties";
import { ITreeArraySettingInfo, ITreeSettingInfo } from "./settings/ITreeSettingInfo";
import { ITreeSettingsWizardContext, ITreeSettingWizardInfo } from "./settings/ITreeSettingsWizardContext";
import { TreeSettingListStep } from "./settings/TreeSettingListStep";
import { TreeSettingStep } from "./settings/TreeSettingStep";
export interface ILocalItem {
createdTime: number;
treeId: string;
data: {};
}
export type LocalChildType<T extends ILocalItem> = new (parent: AzExtParentTreeItem, item: T) => AzExtTreeItem & { createdTime: number; };
export type LocalChildGroupType<TItem extends ILocalItem, TProperty extends string | CommonProperty> = new (parent: LocalRootTreeItemBase<TItem, TProperty>, group: string, items: TItem[]) => LocalGroupTreeItemBase<TItem, TProperty>;
const groupByKey: string = 'groupBy';
const sortByKey: string = 'sortBy';
const labelKey: string = 'label';
const descriptionKey: string = 'description';
export abstract class LocalRootTreeItemBase<TItem extends ILocalItem, TProperty extends string | CommonProperty> extends AzExtParentTreeItem {
public abstract labelSettingInfo: ITreeSettingInfo<TProperty>;
public abstract descriptionSettingInfo: ITreeArraySettingInfo<TProperty>;
public abstract groupBySettingInfo: ITreeSettingInfo<TProperty | CommonGroupBy>;
public sortBySettingInfo: ITreeSettingInfo<CommonSortBy> = {
properties: sortByProperties,
defaultProperty: 'CreatedTime',
}
public abstract treePrefix: string;
public abstract configureExplorerTitle: string;
public abstract childType: LocalChildType<TItem>;
public abstract childGroupType: LocalChildGroupType<TItem, TProperty>;
public abstract getItems(): Promise<TItem[] | undefined>;
public abstract getPropertyValue(item: TItem, property: TProperty): string;
public groupBySetting: TProperty | CommonGroupBy;
public sortBySetting: CommonSortBy;
public labelSetting: TProperty;
public descriptionSetting: TProperty[];
private _currentItems: TItem[] | undefined;
private _itemsFromPolling: TItem[] | undefined;
private _failedToConnect: boolean = false;
public get contextValue(): string {
return this.treePrefix;
}
public get config(): WorkspaceConfiguration {
return workspace.getConfiguration(`${configPrefix}.${this.treePrefix}`);
}
public registerRefreshEvents(treeView: TreeView<AzExtTreeItem>): void {
let intervalId: NodeJS.Timeout;
registerEvent('treeView.onDidChangeVisibility', treeView.onDidChangeVisibility, (context: IActionContext, e: TreeViewVisibilityChangeEvent) => {
context.errorHandling.suppressDisplay = true;
context.telemetry.suppressIfSuccessful = true;
context.telemetry.properties.isActivationEvent = 'true';
if (e.visible) {
const configOptions: WorkspaceConfiguration = workspace.getConfiguration('docker');
const refreshInterval: number = configOptions.get<number>('explorerRefreshInterval', 1000);
intervalId = setInterval(
async () => {
if (await this.hasChanged()) {
await this.refresh();
}
},
refreshInterval);
} else {
clearInterval(intervalId);
}
});
registerEvent('treeView.onDidChangeConfiguration', workspace.onDidChangeConfiguration, async (context: IActionContext, e: ConfigurationChangeEvent) => {
context.errorHandling.suppressDisplay = true;
context.telemetry.suppressIfSuccessful = true;
context.telemetry.properties.isActivationEvent = 'true';
if (e.affectsConfiguration(`${configPrefix}.${this.treePrefix}`)) {
await this.refresh();
}
});
}
public async loadMoreChildrenImpl(_clearCache: boolean, context: IActionContext): Promise<AzExtTreeItem[]> {
try {
this._currentItems = this._itemsFromPolling || await this.getSortedItems();
this._itemsFromPolling = undefined;
this._failedToConnect = false;
} catch (error) {
this._currentItems = undefined;
this._failedToConnect = true;
context.telemetry.properties.failedToConnect = 'true';
return this.getDockerErrorTreeItems(error);
}
if (this._currentItems.length === 0) {
context.telemetry.properties.noItems = 'true';
return [new GenericTreeItem(this, {
label: "Successfully connected, but no items found.",
iconPath: getThemedIconPath('info'),
contextValue: 'dockerNoItems'
})];
} else {
this.groupBySetting = this.getTreeSetting(groupByKey, this.groupBySettingInfo);
context.telemetry.properties.groupBySetting = this.groupBySetting;
this.sortBySetting = this.getTreeSetting(sortByKey, this.sortBySettingInfo);
context.telemetry.properties.sortBySetting = this.sortBySetting;
this.labelSetting = this.getTreeSetting(labelKey, this.labelSettingInfo);
context.telemetry.properties.labelSetting = this.labelSetting;
this.descriptionSetting = this.getTreeArraySetting(descriptionKey, this.descriptionSettingInfo);
context.telemetry.properties.descriptionSetting = this.descriptionSetting.toString();
return this.groupItems(this._currentItems);
}
}
public hasMoreChildrenImpl(): boolean {
return false;
}
public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number {
if (this._failedToConnect) {
return 0; // children are already sorted
} else {
if (ti1 instanceof this.childGroupType && ti2 instanceof this.childGroupType) {
if (this.groupBySetting === 'CreatedTime' && ti2.maxCreatedTime !== ti1.maxCreatedTime) {
return ti2.maxCreatedTime - ti1.maxCreatedTime;
}
} else if (ti1 instanceof this.childType && ti2 instanceof this.childType) {
if (this.sortBySetting === 'CreatedTime' && ti2.createdTime !== ti1.createdTime) {
return ti2.createdTime - ti1.createdTime;
}
}
return super.compareChildrenImpl(ti1, ti2);
}
}
private async groupItems(items: TItem[]): Promise<AzExtTreeItem[]> {
let itemsWithNoGroup: TItem[] = [];
const groupMap = new Map<string, TItem[]>();
if (this.groupBySetting === 'None') {
itemsWithNoGroup = items;
} else {
for (const item of items) {
const groupName: string | undefined = this.getPropertyValue(item, this.groupBySetting);
if (!groupName) {
itemsWithNoGroup.push(item);
} else {
const groupedItems = groupMap.get(groupName);
if (groupedItems) {
groupedItems.push(item);
} else {
groupMap.set(groupName, [item]);
}
}
}
}
return await this.createTreeItemsWithErrorHandling(
[...itemsWithNoGroup, ...groupMap.entries()],
'invalidLocalItemOrGroup',
itemOrGroup => {
if (Array.isArray(itemOrGroup)) {
const [groupName, groupedItems] = itemOrGroup;
return new this.childGroupType(this, groupName, groupedItems);
} else {
return new this.childType(this, itemOrGroup);
}
},
itemOrGroup => {
if (Array.isArray(itemOrGroup)) {
const [group] = itemOrGroup;
return group;
} else {
return itemOrGroup.treeId;
}
}
);
}
public getTreeItemLabel(item: TItem): string {
return this.getPropertyValue(item, this.labelSetting);
}
public getTreeItemDescription(item: TItem): string {
const values: string[] = this.descriptionSetting.map(prop => this.getPropertyValue(item, prop));
return values.join(' - ');
}
public getTreeSetting<T extends string>(setting: string, settingInfo: ITreeSettingInfo<T>): T {
const value = this.config.get<T>(setting);
if (value && settingInfo.properties.find(propInfo => propInfo.property === value)) {
return value;
} else {
return settingInfo.defaultProperty;
}
}
public getTreeArraySetting<T extends string>(setting: string, settingInfo: ITreeArraySettingInfo<T>): T[] {
const value = this.config.get<T[]>(setting);
if (Array.isArray(value) && value.every(v1 => !!settingInfo.properties.find(v2 => v1 === v2.property))) {
return value;
} else {
return settingInfo.defaultProperty;
}
}
public getSettingWizardInfoList(): ITreeSettingWizardInfo[] {
return [
{
label: 'Label',
setting: labelKey,
currentValue: this.labelSetting,
description: 'The primary property to display.',
settingInfo: this.labelSettingInfo
},
{
label: 'Description',
setting: descriptionKey,
currentValue: this.descriptionSetting,
description: 'Any secondary properties to display.',
settingInfo: this.descriptionSettingInfo
},
{
label: 'Group By',
setting: groupByKey,
currentValue: this.groupBySetting,
description: 'The property used for grouping.',
settingInfo: this.groupBySettingInfo
},
{
label: 'Sort By',
setting: sortByKey,
currentValue: this.sortBySetting,
description: 'The property used for sorting.',
settingInfo: this.sortBySettingInfo
},
]
}
public async configureExplorer(context: IActionContext): Promise<void> {
const wizardContext: ITreeSettingsWizardContext = { infoList: this.getSettingWizardInfoList(), ...context };
const wizard = new AzureWizard(wizardContext, {
title: this.configureExplorerTitle,
promptSteps: [
new TreeSettingListStep(),
new TreeSettingStep()
],
hideStepCount: true
});
await wizard.prompt();
await wizard.execute();
const info = nonNullProp(wizardContext, 'info');
this.config.update(info.setting, wizardContext.newValue, ConfigurationTarget.Global);
}
private getDockerErrorTreeItems(error: unknown): AzExtTreeItem[] {
const connectionMessage = 'Failed to connect. Is Docker installed and running?';
const installDockerUrl = 'https://aka.ms/AA37qtj';
const linuxPostInstallUrl = 'https://aka.ms/AA37yk6';
const troubleshootingUrl = 'https://aka.ms/AA37qt2';
const result: AzExtTreeItem[] = [
new InvalidTreeItem(this, error, { label: connectionMessage, contextValue: 'dockerConnectionError', description: '' }),
new OpenUrlTreeItem(this, 'Install Docker...', installDockerUrl),
new OpenUrlTreeItem(this, 'Additional Troubleshooting...', troubleshootingUrl),
];
if (isLinux()) {
result.push(new OpenUrlTreeItem(this, 'Manage Docker as a non-root user on Linux...', linuxPostInstallUrl))
}
return result;
}
private async getSortedItems(): Promise<TItem[]> {
const items: TItem[] = await this.getItems() || [];
return items.sort((a, b) => a.treeId.localeCompare(b.treeId));
}
private async hasChanged(): Promise<boolean> {
try {
this._itemsFromPolling = await this.getSortedItems();
} catch {
this._itemsFromPolling = undefined;
}
return !this.areArraysEqual(this._currentItems, this._itemsFromPolling);
}
private areArraysEqual(array1: TItem[] | undefined, array2: TItem[] | undefined): boolean {
if (array1 === array2) {
return true;
} else if (array1 && array2) {
if (array1.length !== array2.length) {
return false;
} else {
return !array1.some((item1, index) => {
return item1.treeId !== array2[index].treeId;
});
}
} else {
return false;
}
}
}

Просмотреть файл

@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getThemedIconPath, IconPath } from "../IconPath";
import { getImageGroupIcon } from "../images/ImageProperties";
import { LocalGroupTreeItemBase } from "../LocalGroupTreeItemBase";
import { ContainerProperty, getContainerStateIcon } from "./ContainerProperties";
import { LocalContainerInfo } from "./LocalContainerInfo";
export class ContainerGroupTreeItem extends LocalGroupTreeItemBase<LocalContainerInfo, ContainerProperty> {
public static readonly contextValue: string = 'containerGroup';
public readonly contextValue: string = ContainerGroupTreeItem.contextValue;
public childTypeLabel: string = 'container';
public get iconPath(): IconPath {
let icon: string;
switch (this.parent.groupBySetting) {
case 'ContainerId':
case 'ContainerName':
case 'Ports':
case 'Status':
icon = 'applicationGroup';
break;
case 'State':
return getContainerStateIcon(this.group);
default:
return getImageGroupIcon(this.parent.groupBySetting);
}
return getThemedIconPath(icon);
}
}

Просмотреть файл

@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getThemedIconPath, IconPath } from "../IconPath";
import { imageProperties, ImageProperty } from "../images/ImageProperties";
import { ITreePropertyInfo } from "../settings/ITreeSettingInfo";
export type ContainerProperty = ImageProperty | 'ContainerId' | 'ContainerName' | 'Ports' | 'State' | 'Status';
export const containerProperties: ITreePropertyInfo<ContainerProperty>[] = [
...imageProperties,
{ property: 'ContainerId', exampleValue: 'fdeab20e859d' },
{ property: 'ContainerName', exampleValue: 'amazing_hoover' },
{ property: 'Ports', exampleValue: '8080' },
{ property: 'State', exampleValue: 'exited' },
{ property: 'Status', exampleValue: 'Exited (0) 2 hours ago' }
];
export function getContainerStateIcon(state: string): IconPath {
let icon: string;
switch (state) {
case 'created':
case 'dead':
case 'exited':
case 'removing':
icon = 'statusStop';
break;
case 'paused':
icon = 'statusPause';
break;
case 'restarting':
icon = 'restart';
break;
case 'running':
default:
icon = 'statusRun';
}
return getThemedIconPath(icon);
}

Просмотреть файл

@ -3,69 +3,63 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ContainerInfo } from "dockerode";
import { Container } from "dockerode";
import { AzExtParentTreeItem, AzExtTreeItem } from "vscode-azureextensionui";
import { ext } from "../../extensionVariables";
import { getThemedIconPath, IconPath } from '../IconPath';
import { getContainerStateIcon } from "./ContainerProperties";
import { LocalContainerInfo } from "./LocalContainerInfo";
export class ContainerTreeItem extends AzExtTreeItem {
public static allContextRegExp: RegExp = /Container$/;
public container: ContainerInfo;
private readonly _item: LocalContainerInfo;
public constructor(parent: AzExtParentTreeItem, container: ContainerInfo) {
public constructor(parent: AzExtParentTreeItem, itemInfo: LocalContainerInfo) {
super(parent);
this.container = container;
}
public get label(): string {
return this.container.Image;
}
public get description(): string {
let name = this.container.Names[0].substr(1); // Remove start '/'
let status = this.container.Status;
return [name, status].join(' - ');
this._item = itemInfo;
}
public get id(): string {
return this.container.Id;
return this._item.treeId;
}
public get createdTime(): number {
return this._item.createdTime;
}
public get containerId(): string {
return this._item.containerId;
}
public get fullTag(): string {
return this._item.fullTag;
}
public get label(): string {
return ext.containersRoot.getTreeItemLabel(this._item);
}
public get description(): string | undefined {
return ext.containersRoot.getTreeItemDescription(this._item);
}
public get contextValue(): string {
return this.container.State + 'Container';
return this._item.state + 'Container';
}
public get iconPath(): IconPath {
let icon: string;
if (this.isUnhealthy) {
icon = 'statusWarning';
if (this._item.status.includes('(unhealthy)')) {
return getThemedIconPath('statusWarning');
} else {
switch (this.container.State) {
case "created":
case "dead":
case "exited":
icon = 'statusStop';
break;
case "paused":
icon = 'statusPause';
break;
case "restarting":
icon = 'restart';
break;
case "running":
default:
icon = 'statusRun';
}
return getContainerStateIcon(this._item.state);
}
return getThemedIconPath(icon);
}
private get isUnhealthy(): boolean {
return this.container.Status.includes('(unhealthy)');
public getContainer(): Container {
return ext.dockerode.getContainer(this.containerId);
}
public async deleteTreeItemImpl(): Promise<void> {
await ext.dockerode.getContainer(this.container.Id).remove({ force: true });
await this.getContainer().remove({ force: true });
}
}

Просмотреть файл

@ -3,43 +3,68 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ContainerInfo } from "dockerode";
import { AzExtTreeItem } from "vscode-azureextensionui";
import { ext } from "../../extensionVariables";
import { AutoRefreshTreeItemBase } from "../AutoRefreshTreeItemBase";
import { getImagePropertyValue } from "../images/ImageProperties";
import { LocalChildGroupType, LocalChildType, LocalRootTreeItemBase } from "../LocalRootTreeItemBase";
import { CommonGroupBy, groupByNoneProperty } from "../settings/CommonProperties";
import { ITreeArraySettingInfo, ITreeSettingInfo } from "../settings/ITreeSettingInfo";
import { ContainerGroupTreeItem } from "./ContainerGroupTreeItem";
import { containerProperties, ContainerProperty } from "./ContainerProperties";
import { ContainerTreeItem } from "./ContainerTreeItem";
import { LocalContainerInfo } from "./LocalContainerInfo";
export class ContainersTreeItem extends AutoRefreshTreeItemBase<ContainerInfo> {
public static contextValue: string = 'containers';
public contextValue: string = ContainersTreeItem.contextValue;
export class ContainersTreeItem extends LocalRootTreeItemBase<LocalContainerInfo, ContainerProperty> {
public treePrefix: string = 'containers';
public label: string = 'Containers';
public childTypeLabel: string = 'container';
public noItemsMessage: string = "Successfully connected, but no containers found.";
public configureExplorerTitle: string = 'Configure containers explorer';
public getItemID(item: ContainerInfo): string {
return item.Id + item.State;
public childType: LocalChildType<LocalContainerInfo> = ContainerTreeItem;
public childGroupType: LocalChildGroupType<LocalContainerInfo, ContainerProperty> = ContainerGroupTreeItem;
public labelSettingInfo: ITreeSettingInfo<ContainerProperty> = {
properties: containerProperties,
defaultProperty: 'FullTag',
};
public descriptionSettingInfo: ITreeArraySettingInfo<ContainerProperty> = {
properties: containerProperties,
defaultProperty: ['ContainerName', 'Status'],
};
public groupBySettingInfo: ITreeSettingInfo<ContainerProperty | CommonGroupBy> = {
properties: [...containerProperties, groupByNoneProperty],
defaultProperty: 'None',
};
public get childTypeLabel(): string {
return this.groupBySetting === 'None' ? 'container' : 'container group';
}
public async getItems(): Promise<ContainerInfo[]> {
public async getItems(): Promise<LocalContainerInfo[]> {
const options = {
"filters": {
"status": ["created", "restarting", "running", "paused", "exited", "dead"]
}
};
return await ext.dockerode.listContainers(options) || [];
const items = await ext.dockerode.listContainers(options) || [];
return items.map(c => new LocalContainerInfo(c));
}
public async convertToTreeItems(items: ContainerInfo[]): Promise<AzExtTreeItem[]> {
return items.map(c => new ContainerTreeItem(this, c));
}
public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number {
if (ti1 instanceof ContainerTreeItem && ti2 instanceof ContainerTreeItem) {
// tslint:disable-next-line: no-unsafe-any no-any
return (<any>ti2.container).Created - (<any>ti1.container).Created;
} else {
return super.compareChildrenImpl(ti1, ti2);
public getPropertyValue(item: LocalContainerInfo, property: ContainerProperty): string {
switch (property) {
case 'ContainerId':
return item.containerId.slice(0, 12);
case 'ContainerName':
return item.containerName;
case 'Ports':
return item.ports.length > 0 ? item.ports.join(',') : '<none>';
case 'State':
return item.state;
case 'Status':
return item.status;
default:
return getImagePropertyValue(item, property);
}
}
}

Просмотреть файл

@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ContainerInfo } from "dockerode";
import { ILocalImageInfo } from "../images/LocalImageInfo";
/**
* Wrapper class for Dockerode item, which has inconsistent names/types
*/
export class LocalContainerInfo implements ILocalImageInfo {
public data: ContainerInfo;
public constructor(data: ContainerInfo) {
this.data = data;
}
public get createdTime(): number {
return this.data.Created * 1000;
}
public get containerId(): string {
return this.data.Id;
}
public get containerName(): string {
return this.data.Names[0].substr(1); // Remove start '/'
}
public get fullTag(): string {
return this.data.Image;
}
public get imageId(): string {
return this.data.ImageID;
}
public get ports(): number[] {
return this.data.Ports.map(p => p.PublicPort);
}
public get state(): string {
return this.data.State;
}
public get status(): string {
return this.data.Status;
}
public get treeId(): string {
return this.containerId;
}
}

Просмотреть файл

@ -3,54 +3,17 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzExtParentTreeItem, AzExtTreeItem } from "vscode-azureextensionui";
import { ext, ImageGrouping } from "../../extensionVariables";
import { getThemedIconPath, IconPath } from "../IconPath";
import { IImageAndTag, ImageTreeItem, sortImages } from "./ImageTreeItem";
import { IconPath } from "../IconPath";
import { LocalGroupTreeItemBase } from "../LocalGroupTreeItemBase";
import { getImageGroupIcon, ImageProperty } from "./ImageProperties";
import { ILocalImageInfo } from "./LocalImageInfo";
export class ImageGroupTreeItem extends AzExtParentTreeItem {
export class ImageGroupTreeItem extends LocalGroupTreeItemBase<ILocalImageInfo, ImageProperty> {
public static readonly contextValue: string = 'imageGroup';
public readonly contextValue: string = ImageGroupTreeItem.contextValue;
public childTypeLabel: string = 'image';
private _group: string;
private _images: IImageAndTag[];
public constructor(parent: AzExtParentTreeItem, group: string, images: IImageAndTag[]) {
super(parent);
this._group = group;
this._images = images;
}
public get label(): string {
return this._group;
}
public get iconPath(): IconPath {
let icon: string;
switch (ext.groupImagesBy) {
case ImageGrouping.Repository:
icon = 'repository';
break;
default:
icon = 'applicationGroup';
}
return getThemedIconPath(icon);
}
public async loadMoreChildrenImpl(_clearCache: boolean): Promise<AzExtTreeItem[]> {
return this._images.map(r => new ImageTreeItem(this, r));
}
public hasMoreChildrenImpl(): boolean {
return false;
}
public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number {
if (ti1 instanceof ImageTreeItem && ti2 instanceof ImageTreeItem) {
return sortImages(ti1, ti2);
} else {
return super.compareChildrenImpl(ti1, ti2);
}
return getImageGroupIcon(this.parent.groupBySetting);
}
}

Просмотреть файл

@ -0,0 +1,153 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { workspace } from "vscode";
import { configPrefix } from "../../constants";
import { trimWithElipsis } from "../../utils/trimWithElipsis";
import { getThemedIconPath, IconPath } from "../IconPath";
import { CommonGroupBy, commonProperties, CommonProperty, getCommonGroupIcon, getCommonPropertyValue } from '../settings/CommonProperties';
import { ITreePropertyInfo } from '../settings/ITreeSettingInfo';
import { ImageProperty } from "./ImageProperties";
import { ILocalImageInfo } from "./LocalImageInfo";
export type ImageProperty = CommonProperty | 'FullTag' | 'ImageId' | 'Registry' | 'Repository' | 'RepositoryName' | 'RepositoryNameAndTag' | 'Tag';
export const imageProperties: ITreePropertyInfo<ImageProperty>[] = [
...commonProperties,
{ property: 'FullTag', exampleValue: 'example.azurecr.io/hello-world:latest' },
{ property: 'ImageId', exampleValue: 'd9d09edd6115' },
{ property: 'Registry', exampleValue: 'example.azurecr.io' },
{ property: 'Repository', exampleValue: 'example.azurecr.io/hello-world' },
{ property: 'RepositoryName', exampleValue: 'hello-world' },
{ property: 'RepositoryNameAndTag', exampleValue: 'hello-world:latest' },
{ property: 'Tag', exampleValue: 'latest' },
];
export function getImageGroupIcon(property: ImageProperty | CommonGroupBy): IconPath {
let icon: string;
switch (property) {
case 'Registry':
icon = 'registry';
break;
case 'Repository':
case 'RepositoryName':
icon = 'repository';
break;
case 'FullTag':
case 'ImageId':
case 'RepositoryNameAndTag':
icon = 'applicationGroup';
break;
case 'Tag':
icon = 'tag';
break;
default:
return getCommonGroupIcon(property);
}
return getThemedIconPath(icon);
}
export function getImagePropertyValue(item: ILocalImageInfo, property: ImageProperty): string {
const parsedFullTag = parseFullTag(item.fullTag);
let registry: string | undefined;
switch (property) {
case 'FullTag':
if (parsedFullTag.registry) {
return item.fullTag.replace(parsedFullTag.registry, truncateRegistry(parsedFullTag.registry));
} else {
return item.fullTag;
}
case 'ImageId':
return item.imageId.replace('sha256:', '').slice(0, 12);
case 'Registry':
registry = parsedFullTag.registry;
if (!registry) {
registry = 'docker.io' + '/' + (parsedFullTag.namespace || 'library');
}
return truncateRegistry(registry);
case 'Repository':
registry = parsedFullTag.registry || parsedFullTag.namespace;
if (registry) {
return truncateRegistry(registry) + '/' + parsedFullTag.repositoryName;
} else {
return parsedFullTag.repositoryName;
}
case 'RepositoryName':
return parsedFullTag.repositoryName;
case 'RepositoryNameAndTag':
if (parsedFullTag.tag) {
return parsedFullTag.repositoryName + ':' + parsedFullTag.tag;
} else {
return parsedFullTag.repositoryName;
}
case 'Tag':
return parsedFullTag.tag || 'latest';
default:
return getCommonPropertyValue(item, property);
}
}
interface IParsedFullTag {
registry?: string;
namespace?: string;
repositoryName: string;
tag?: string;
}
function parseFullTag(rawTag: string): IParsedFullTag {
let registry: string | undefined;
let namespace: string | undefined;
let repositoryName: string;
let tag: string | undefined;
// Pull out registry or namespace from the beginning
let index = rawTag.indexOf('/');
if (index !== -1) {
const firstPart = rawTag.substring(0, index);
if (firstPart === 'localhost' || /[:.]/.test(firstPart)) {
// The hostname must contain a . dns separator or a : port separator before the first /
// https://stackoverflow.com/questions/37861791/how-are-docker-image-names-parsed
registry = firstPart;
} else {
// otherwise it's a part of docker.io and the first part is a namespace
namespace = firstPart;
}
rawTag = rawTag.substring(index + 1);
}
// Pull out tag from the end
index = rawTag.lastIndexOf(':');
if (index !== -1) {
tag = rawTag.substring(index + 1);
rawTag = rawTag.substring(0, index);
}
// Whatever's left is the repository name
repositoryName = rawTag;
return {
registry,
repositoryName,
namespace,
tag
};
}
function truncateRegistry(registry: string): string {
let config = workspace.getConfiguration(configPrefix);
let truncateLongRegistryPaths = config.get<boolean>('truncateLongRegistryPaths');
if (typeof truncateLongRegistryPaths === "boolean" && truncateLongRegistryPaths) {
let truncateMaxLength = config.get<number>('truncateMaxLength');
if (typeof truncateMaxLength !== 'number' || truncateMaxLength < 1) {
truncateMaxLength = 10;
}
return trimWithElipsis(registry, truncateMaxLength);
}
return registry;
}

Просмотреть файл

@ -3,60 +3,55 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ImageInfo } from 'dockerode';
import * as moment from 'moment';
import { Image } from 'dockerode';
import { AzExtParentTreeItem, AzExtTreeItem } from "vscode-azureextensionui";
import { ext, ImageGrouping } from '../../extensionVariables';
import { ext } from '../../extensionVariables';
import { getThemedIconPath, IconPath } from '../IconPath';
import { getImageLabel } from './getImageLabel';
export interface IImageAndTag {
image: ImageInfo;
fullTag: string;
}
import { ILocalImageInfo } from './LocalImageInfo';
export class ImageTreeItem extends AzExtTreeItem {
public static contextValue: string = 'image';
public contextValue: string = ImageTreeItem.contextValue;
private readonly _item: ILocalImageInfo;
public image: ImageInfo;
public fullTag: string;
public constructor(parent: AzExtParentTreeItem, item: IImageAndTag) {
public constructor(parent: AzExtParentTreeItem, itemInfo: ILocalImageInfo) {
super(parent);
this.image = item.image;
this.fullTag = item.fullTag;
this._item = itemInfo;
}
public get id(): string {
return this.image.Id + this.fullTag;
return this._item.treeId;
}
public get createdTime(): number {
return this._item.createdTime;
}
public get imageId(): string {
return this._item.imageId;
}
public get fullTag(): string {
return this._item.fullTag;
}
public get label(): string {
let template: string;
switch (ext.groupImagesBy) {
case ImageGrouping.Repository:
template = '{tag}';
break;
default:
template = '{fullTag}';
}
return getImageLabel(this.fullTag, this.image, template);
return ext.imagesRoot.getTreeItemLabel(this._item);
}
public get description(): string | undefined {
return this.image.Created ? moment(new Date(this.image.Created * 1000)).fromNow() : undefined;
return ext.imagesRoot.getTreeItemDescription(this._item);
}
public get iconPath(): IconPath {
return getThemedIconPath('application');
}
public getImage(): Image {
return ext.dockerode.getImage(this.imageId);
}
public async deleteTreeItemImpl(): Promise<void> {
await ext.dockerode.getImage(this.image.Id).remove({ force: true });
await this.getImage().remove({ force: true });
}
}
export function sortImages(ti1: ImageTreeItem, ti2: ImageTreeItem): number {
return ti2.image.Created - ti1.image.Created;
}

Просмотреть файл

@ -3,37 +3,43 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzExtTreeItem } from "vscode-azureextensionui";
import { ext, ImageGrouping } from "../../extensionVariables";
import { AutoRefreshTreeItemBase } from "../AutoRefreshTreeItemBase";
import { getImageLabel } from "./getImageLabel";
import { ext } from "../../extensionVariables";
import { LocalChildGroupType, LocalChildType, LocalRootTreeItemBase } from "../LocalRootTreeItemBase";
import { CommonGroupBy, groupByNoneProperty } from "../settings/CommonProperties";
import { ITreeArraySettingInfo, ITreeSettingInfo } from "../settings/ITreeSettingInfo";
import { ImageGroupTreeItem } from './ImageGroupTreeItem';
import { IImageAndTag, ImageTreeItem, sortImages } from "./ImageTreeItem";
import { getImagePropertyValue, imageProperties, ImageProperty } from "./ImageProperties";
import { ImageTreeItem } from "./ImageTreeItem";
import { ILocalImageInfo, LocalImageInfo } from "./LocalImageInfo";
export class ImagesTreeItem extends AutoRefreshTreeItemBase<IImageAndTag> {
public static contextValue: string = ImagesTreeItem.contextValue;
public contextValue: string = 'images';
export class ImagesTreeItem extends LocalRootTreeItemBase<ILocalImageInfo, ImageProperty> {
public treePrefix: string = 'images';
public label: string = 'Images';
public noItemsMessage: string = "Successfully connected, but no images found.";
public configureExplorerTitle: string = 'Configure images explorer';
public childType: LocalChildType<ILocalImageInfo> = ImageTreeItem;
public childGroupType: LocalChildGroupType<ILocalImageInfo, ImageProperty> = ImageGroupTreeItem;
public labelSettingInfo: ITreeSettingInfo<ImageProperty> = {
properties: imageProperties,
defaultProperty: 'Tag',
};
public descriptionSettingInfo: ITreeArraySettingInfo<ImageProperty> = {
properties: imageProperties,
defaultProperty: ['CreatedTime'],
};
public groupBySettingInfo: ITreeSettingInfo<ImageProperty | CommonGroupBy> = {
properties: [...imageProperties, groupByNoneProperty],
defaultProperty: 'Repository',
};
public get childTypeLabel(): string {
switch (ext.groupImagesBy) {
case ImageGrouping.ImageId:
return 'image id';
case ImageGrouping.Repository:
return 'repository';
case ImageGrouping.RepositoryName:
return 'repository name';
default:
return 'image';
}
return this.groupBySetting === 'None' ? 'image' : 'image group';
}
public getItemID(item: IImageAndTag): string {
return item.image.Id + item.fullTag;
}
public async getItems(): Promise<IImageAndTag[]> {
public async getItems(): Promise<ILocalImageInfo[]> {
const options = {
"filters": {
"dangling": ["false"]
@ -41,62 +47,21 @@ export class ImagesTreeItem extends AutoRefreshTreeItemBase<IImageAndTag> {
};
const images = await ext.dockerode.listImages(options) || [];
let result: IImageAndTag[] = [];
let result: ILocalImageInfo[] = [];
for (const image of images) {
if (!image.RepoTags) {
result.push({ image, fullTag: '<none>:<none>' });
result.push(new LocalImageInfo(image, '<none>:<none>'));
} else {
for (let fullTag of image.RepoTags) {
result.push({ image, fullTag });
result.push(new LocalImageInfo(image, fullTag));
}
}
}
return result;
}
public async convertToTreeItems(items: IImageAndTag[]): Promise<AzExtTreeItem[]> {
const groupMap = new Map<string | undefined, IImageAndTag[]>();
for (const item of items) {
let groupTemplate: string | undefined;
switch (ext.groupImagesBy) {
case ImageGrouping.ImageId:
groupTemplate = '{shortImageId}';
break;
case ImageGrouping.Repository:
groupTemplate = '{repository}';
break;
case ImageGrouping.RepositoryName:
groupTemplate = '{repositoryName}';
break;
default:
}
const group: string | undefined = groupTemplate ? getImageLabel(item.fullTag, item.image, groupTemplate) : undefined;
const tags = groupMap.get(group);
if (tags) {
tags.push(item);
} else {
groupMap.set(group, [item]);
}
}
const result: AzExtTreeItem[] = [];
for (const [group, groupedItems] of groupMap.entries()) {
if (!group) {
result.push(...groupedItems.map(r => new ImageTreeItem(this, r)));
} else {
result.push(new ImageGroupTreeItem(this, group, groupedItems));
}
}
return result;
}
public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number {
if (ti1 instanceof ImageTreeItem && ti2 instanceof ImageTreeItem) {
return sortImages(ti1, ti2);
} else {
return super.compareChildrenImpl(ti1, ti2);
}
public getPropertyValue(item: ILocalImageInfo, property: ImageProperty): string {
return getImagePropertyValue(item, property);
}
}

Просмотреть файл

@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ImageInfo } from "dockerode";
import { ILocalItem } from "../LocalRootTreeItemBase";
/**
* Common interface that both `LocalImageInfo` and `LocalContainerInfo` implement
*/
export interface ILocalImageInfo extends ILocalItem {
fullTag: string;
imageId: string;
}
/**
* Wrapper class for Dockerode item, which has inconsistent names/types
*/
export class LocalImageInfo implements ILocalImageInfo {
public data: ImageInfo;
public fullTag: string;
public constructor(data: ImageInfo, fullTag: string) {
this.data = data;
this.fullTag = fullTag;
}
public get createdTime(): number {
return this.data.Created * 1000;
}
public get imageId(): string {
return this.data.Id;
}
public get treeId(): string {
return this.fullTag + this.imageId;
}
}

Просмотреть файл

@ -1,107 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ImageInfo } from 'dockerode';
import * as moment from 'moment';
import * as vscode from 'vscode';
import { extractRegExGroups } from '../../utils/extractRegExGroups';
// If options not specified, retrieves them from user settings
export function getImageLabel(fullTag: string, image: ImageInfo, labelTemplate: string, options?: { truncateLongRegistryPaths: boolean, truncateMaxLength: number }): string {
let truncatedRepository = truncate(getRepository(fullTag), options);
let repositoryName = getRepositoryName(fullTag);
let tag = getTag(fullTag);
let truncatedFullTag = truncate(fullTag, options);
let createdSince = getCreatedSince(image.Created || 0);
let imageId = (image.Id || '').replace('sha256:', '');
let shortImageId = imageId.slice(0, 12);
let label = labelTemplate
.replace('{repository}', truncatedRepository)
.replace('{repositoryName}', repositoryName)
.replace('{tag}', tag)
.replace('{fullTag}', truncatedFullTag)
.replace('{createdSince}', createdSince)
.replace('{shortImageId}', shortImageId);
assert(!label.match(/{|}/), "Unreplaced token");
return label;
}
export function trimWithElipsis(str: string, max: number = 10): string {
const elipsis: string = "...";
const len: number = str.length;
if (max <= 0 || max >= 100) { return str; }
if (str.length <= max) { return str; }
if (max < 3) { return str.substr(0, max); }
const front: string = str.substr(0, (len / 2) - (-0.5 * (max - len - 3)));
const back: string = str.substr(len - (len / 2) + (-0.5 * (max - len - 3)));
return front + elipsis + back;
}
/**
* Retrieves the full repository name
* @param fullTag [hostname/][username/]repositoryname[:tag]
* @returns [hostname/][username/]repositoryname
*/
function getRepository(fullTag: string): string {
let n = fullTag.lastIndexOf(':');
return n > 0 ? fullTag.slice(0, n) : fullTag;
}
function truncate(partialTag: string, options: { truncateLongRegistryPaths: boolean, truncateMaxLength: number } | undefined): string {
// Truncate if user desires
if (!options) {
let config = vscode.workspace.getConfiguration('docker');
let truncateLongRegistryPaths = config.get<boolean>('truncateLongRegistryPaths');
let truncateMaxLength = config.get<number>('truncateMaxLength');
options = {
truncateLongRegistryPaths: typeof truncateLongRegistryPaths === "boolean" ? truncateLongRegistryPaths : false,
truncateMaxLength: typeof truncateMaxLength === 'number' ? truncateMaxLength : 10
}
}
if (!options.truncateLongRegistryPaths) {
return partialTag;
}
// Extract registry from the rest of the name
let [registry, restOfName] = extractRegExGroups(partialTag, /^([^\/]+)\/(.*)$/, ['', partialTag]);
if (registry) {
let trimmedRegistry = trimWithElipsis(registry, options.truncateMaxLength);
return `${trimmedRegistry}/${restOfName}`;
}
return partialTag;
}
/**
* Retrieves just the name of the repository
* @param fullTag [hostname/][username/]repositoryname[:tag]
* @returns repositoryname
*/
function getRepositoryName(fullTag: string): string {
return fullTag.replace(/.*\//, "")
.replace(/:.*/, "");
}
/**
* Retrieves just the tag (without colon)
* @param fullTag [hostname/][username/]repositoryname[:tag]
* @returns tag
*/
function getTag(fullTag: string): string {
let n = fullTag.lastIndexOf(':');
return n > 0 ? fullTag.slice(n + 1) : '';
}
function getCreatedSince(created: number): string {
return moment(new Date(created * 1000)).fromNow();
}

Просмотреть файл

@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NetworkInspectInfo } from "dockerode";
import { ILocalItem } from "../LocalRootTreeItemBase";
/**
* Wrapper class for Dockerode item, which has inconsistent names/types
*/
export class LocalNetworkInfo implements ILocalItem {
public data: NetworkInspectInfo;
public constructor(data: NetworkInspectInfo) {
this.data = data;
}
public get createdTime(): number {
return new Date(this.data.Created).valueOf();
}
public get networkName(): string {
return this.data.Name;
}
public get networkDriver(): string {
return this.data.Driver;
}
public get networkId(): string {
return this.data.Id;
}
public get treeId(): string {
return this.networkId;
}
}

Просмотреть файл

@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getThemedIconPath, IconPath } from "../IconPath";
import { LocalGroupTreeItemBase } from "../LocalGroupTreeItemBase";
import { getCommonGroupIcon } from "../settings/CommonProperties";
import { LocalNetworkInfo } from "./LocalNetworkInfo";
import { NetworkProperty } from "./NetworkProperties";
export class NetworkGroupTreeItem extends LocalGroupTreeItemBase<LocalNetworkInfo, NetworkProperty> {
public static readonly contextValue: string = 'networkGroup';
public readonly contextValue: string = NetworkGroupTreeItem.contextValue;
public childTypeLabel: string = 'network';
public get iconPath(): IconPath {
let icon: string;
switch (this.parent.groupBySetting) {
case 'NetworkDriver':
case 'NetworkId':
case 'NetworkName':
icon = 'network';
break;
default:
return getCommonGroupIcon(this.parent.groupBySetting);
}
return getThemedIconPath(icon);
}
}

Просмотреть файл

@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { commonProperties, CommonProperty } from "../settings/CommonProperties";
import { ITreePropertyInfo } from "../settings/ITreeSettingInfo";
export type NetworkProperty = CommonProperty | 'NetworkDriver' | 'NetworkId' | 'NetworkName';
export const networkProperties: ITreePropertyInfo<NetworkProperty>[] = [
...commonProperties,
{ property: 'NetworkDriver', exampleValue: 'bridge' },
{ property: 'NetworkId', exampleValue: 'ad0bd70488d1' },
{ property: 'NetworkName', exampleValue: 'my-network' },
];

Просмотреть файл

@ -3,43 +3,51 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NetworkInspectInfo } from "dockerode";
import { Network } from "dockerode";
import { AzExtParentTreeItem, AzExtTreeItem } from "vscode-azureextensionui";
import { ext } from "../../extensionVariables";
import { getThemedIconPath, IconPath } from '../IconPath';
import { LocalNetworkInfo } from "./LocalNetworkInfo";
export class NetworkTreeItem extends AzExtTreeItem {
public static contextValue: string = 'network';
public contextValue: string = NetworkTreeItem.contextValue;
private readonly _item: LocalNetworkInfo;
public network: NetworkInspectInfo;
public constructor(parent: AzExtParentTreeItem, network: NetworkInspectInfo) {
public constructor(parent: AzExtParentTreeItem, itemInfo: LocalNetworkInfo) {
super(parent);
this.network = network;
}
public get label(): string {
return this.network.Name;
}
public get description(): string {
return this.network.Driver;
}
public hasMoreChildrenImpl(): boolean {
return false;
this._item = itemInfo;
}
public get id(): string {
return this.network.Id;
return this._item.treeId;
}
public get networkId(): string {
return this._item.networkId;
}
public get createdTime(): number {
return this._item.createdTime;
}
public get label(): string {
return ext.networksRoot.getTreeItemLabel(this._item);
}
public get description(): string | undefined {
return ext.networksRoot.getTreeItemDescription(this._item);
}
public get iconPath(): IconPath {
return getThemedIconPath('network');
}
public getNetwork(): Network {
return ext.dockerode.getNetwork(this.networkId);
}
public async deleteTreeItemImpl(): Promise<void> {
await ext.dockerode.getNetwork(this.network.Id).remove();
await this.getNetwork().remove({ force: true });
}
}

Просмотреть файл

@ -4,27 +4,56 @@
*--------------------------------------------------------------------------------------------*/
import { NetworkInspectInfo } from "dockerode";
import { AzExtTreeItem } from "vscode-azureextensionui";
import { ext } from "../../extensionVariables";
import { AutoRefreshTreeItemBase } from "../AutoRefreshTreeItemBase";
import { LocalChildGroupType, LocalChildType, LocalRootTreeItemBase } from "../LocalRootTreeItemBase";
import { CommonGroupBy, getCommonPropertyValue, groupByNoneProperty } from "../settings/CommonProperties";
import { ITreeArraySettingInfo, ITreeSettingInfo } from "../settings/ITreeSettingInfo";
import { LocalNetworkInfo } from "./LocalNetworkInfo";
import { NetworkGroupTreeItem } from "./NetworkGroupTreeItem";
import { networkProperties, NetworkProperty } from "./NetworkProperties";
import { NetworkTreeItem } from "./NetworkTreeItem";
export class NetworksTreeItem extends AutoRefreshTreeItemBase<NetworkInspectInfo> {
public static contextValue: string = 'networks';
public contextValue: string = NetworksTreeItem.contextValue;
export class NetworksTreeItem extends LocalRootTreeItemBase<LocalNetworkInfo, NetworkProperty> {
public treePrefix: string = 'networks';
public label: string = 'Networks';
public childTypeLabel: string = 'network';
public noItemsMessage: string = "Successfully connected, but no networks found.";
public configureExplorerTitle: string = 'Configure networks explorer';
public childType: LocalChildType<LocalNetworkInfo> = NetworkTreeItem;
public childGroupType: LocalChildGroupType<LocalNetworkInfo, NetworkProperty> = NetworkGroupTreeItem;
public getItemID(item: NetworkInspectInfo): string {
return item.Id;
public labelSettingInfo: ITreeSettingInfo<NetworkProperty> = {
properties: networkProperties,
defaultProperty: 'NetworkName',
};
public descriptionSettingInfo: ITreeArraySettingInfo<NetworkProperty> = {
properties: networkProperties,
defaultProperty: ['NetworkDriver', 'CreatedTime'],
};
public groupBySettingInfo: ITreeSettingInfo<NetworkProperty | CommonGroupBy> = {
properties: [...networkProperties, groupByNoneProperty],
defaultProperty: 'None',
};
public get childTypeLabel(): string {
return this.groupBySetting === 'None' ? 'network' : 'network group';
}
public async getItems(): Promise<NetworkInspectInfo[]> {
return await ext.dockerode.listNetworks() || [];
public async getItems(): Promise<LocalNetworkInfo[]> {
const networks = <NetworkInspectInfo[]>await ext.dockerode.listNetworks() || [];
return networks.map(n => new LocalNetworkInfo(n));
}
public async convertToTreeItems(items: NetworkInspectInfo[]): Promise<AzExtTreeItem[]> {
return items.map(n => new NetworkTreeItem(this, n));
public getPropertyValue(item: LocalNetworkInfo, property: NetworkProperty): string {
switch (property) {
case 'NetworkDriver':
return item.networkDriver;
case 'NetworkId':
return item.networkId.slice(0, 12);
case 'NetworkName':
return item.networkName;
default:
return getCommonPropertyValue(item, property);
}
}
}

Просмотреть файл

@ -14,30 +14,30 @@ import { RegistriesTreeItem } from "./registries/RegistriesTreeItem";
import { VolumesTreeItem } from "./volumes/VolumesTreeItem";
export function registerTrees(): void {
const containersTreeItem = new ContainersTreeItem(undefined);
ext.containersRoot = new ContainersTreeItem(undefined);
const containersLoadMore = 'vscode-docker.containers.loadMore';
ext.containersTree = new AzExtTreeDataProvider(containersTreeItem, containersLoadMore);
ext.containersTree = new AzExtTreeDataProvider(ext.containersRoot, containersLoadMore);
ext.containersTreeView = window.createTreeView('dockerContainers', { treeDataProvider: ext.containersTree });
ext.context.subscriptions.push(ext.containersTreeView);
containersTreeItem.initAutoRefresh(ext.containersTreeView);
ext.containersRoot.registerRefreshEvents(ext.containersTreeView);
registerCommand(containersLoadMore, (context: IActionContext, node: AzExtTreeItem) => ext.containersTree.loadMore(node, context));
registerCommand('vscode-docker.containers.refresh', async (_context: IActionContext, node?: AzExtTreeItem) => ext.containersTree.refresh(node));
const networksTreeItem = new NetworksTreeItem(undefined);
ext.networksRoot = new NetworksTreeItem(undefined);
const networksLoadMore = 'vscode-docker.networks.loadMore';
ext.networksTree = new AzExtTreeDataProvider(networksTreeItem, networksLoadMore);
ext.networksTree = new AzExtTreeDataProvider(ext.networksRoot, networksLoadMore);
ext.networksTreeView = window.createTreeView('dockerNetworks', { treeDataProvider: ext.networksTree });
ext.context.subscriptions.push(ext.networksTreeView);
networksTreeItem.initAutoRefresh(ext.networksTreeView);
ext.networksRoot.registerRefreshEvents(ext.networksTreeView);
registerCommand(networksLoadMore, (context: IActionContext, node: AzExtTreeItem) => ext.networksTree.loadMore(node, context));
registerCommand('vscode-docker.networks.refresh', async (_context: IActionContext, node?: AzExtTreeItem) => ext.networksTree.refresh(node));
const imagesTreeItem = new ImagesTreeItem(undefined);
ext.imagesRoot = new ImagesTreeItem(undefined);
const imagesLoadMore = 'vscode-docker.images.loadMore';
ext.imagesTree = new AzExtTreeDataProvider(imagesTreeItem, imagesLoadMore);
ext.imagesTree = new AzExtTreeDataProvider(ext.imagesRoot, imagesLoadMore);
ext.imagesTreeView = window.createTreeView('dockerImages', { treeDataProvider: ext.imagesTree });
ext.context.subscriptions.push(ext.imagesTreeView);
imagesTreeItem.initAutoRefresh(ext.imagesTreeView);
ext.imagesRoot.registerRefreshEvents(ext.imagesTreeView);
registerCommand(imagesLoadMore, (context: IActionContext, node: AzExtTreeItem) => ext.imagesTree.loadMore(node, context));
registerCommand('vscode-docker.images.refresh', async (_context: IActionContext, node?: AzExtTreeItem) => ext.imagesTree.refresh(node));
@ -49,12 +49,12 @@ export function registerTrees(): void {
registerCommand(registriesLoadMore, (context: IActionContext, node: AzExtTreeItem) => ext.registriesTree.loadMore(node, context));
registerCommand('vscode-docker.registries.refresh', async (_context: IActionContext, node?: AzExtTreeItem) => ext.registriesTree.refresh(node));
const volumesTreeItem = new VolumesTreeItem(undefined);
ext.volumesRoot = new VolumesTreeItem(undefined);
const volumesLoadMore = 'vscode-docker.volumes.loadMore';
ext.volumesTree = new AzExtTreeDataProvider(volumesTreeItem, volumesLoadMore);
ext.volumesTree = new AzExtTreeDataProvider(ext.volumesRoot, volumesLoadMore);
ext.volumesTreeView = window.createTreeView('dockerVolumes', { treeDataProvider: ext.volumesTree });
ext.context.subscriptions.push(ext.volumesTreeView);
volumesTreeItem.initAutoRefresh(ext.volumesTreeView);
ext.volumesRoot.registerRefreshEvents(ext.volumesTreeView);
registerCommand(volumesLoadMore, (context: IActionContext, node: AzExtTreeItem) => ext.volumesTree.loadMore(node, context));
registerCommand('vscode-docker.volumes.refresh', async (_context: IActionContext, node?: AzExtTreeItem) => ext.volumesTree.refresh(node));

Просмотреть файл

@ -5,8 +5,8 @@
import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry';
import { Progress } from 'vscode';
import { ext } from 'vscode-azureappservice/out/src/extensionVariables';
import { AzureWizardExecuteStep, createAzureClient } from 'vscode-azureextensionui';
import { ext } from '../../../../extensionVariables';
import { nonNullProp } from '../../../../utils/nonNull';
import { IAzureRegistryWizardContext } from './IAzureRegistryWizardContext';

Просмотреть файл

@ -0,0 +1,49 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as moment from 'moment';
import { getThemedIconPath, IconPath } from '../IconPath';
import { ILocalItem } from '../LocalRootTreeItemBase';
import { ITreePropertyInfo } from './ITreeSettingInfo';
export type CommonProperty = 'CreatedTime';
export type CommonGroupBy = 'None';
export type CommonSortBy = 'CreatedTime' | 'Label';
export const commonProperties: ITreePropertyInfo<CommonProperty>[] = [
{ property: 'CreatedTime', exampleValue: '2 hours ago' },
];
export const groupByNoneProperty: ITreePropertyInfo<CommonGroupBy> = {
property: 'None',
description: 'No grouping'
};
export const sortByProperties: ITreePropertyInfo<CommonSortBy>[] = [
{ property: 'CreatedTime', description: 'Sort by newest' },
{ property: 'Label', description: 'Sort alphabetically by label' }
];
export function getCommonPropertyValue(item: ILocalItem, property: CommonProperty): string {
switch (property) {
case 'CreatedTime':
return moment(new Date(item.createdTime)).fromNow();
default:
throw new RangeError(`Unexpected property "${property}".`);
}
}
export function getCommonGroupIcon(property: CommonProperty | CommonGroupBy): IconPath {
let icon: string;
switch (property) {
case 'CreatedTime':
icon = 'time';
break;
default:
throw new RangeError(`Unexpected property "${property}".`);
}
return getThemedIconPath(icon);
}

Просмотреть файл

@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface ITreeSettingInfo<T extends string> {
properties: ITreePropertyInfo<T>[];
defaultProperty: T;
}
export interface ITreeArraySettingInfo<T extends string> {
properties: ITreePropertyInfo<T>[];
defaultProperty: T[];
}
export interface ITreePropertyInfo<T extends string> {
property: T;
exampleValue?: string;
description?: string;
}

Просмотреть файл

@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IActionContext } from "vscode-azureextensionui";
import { ITreeArraySettingInfo, ITreeSettingInfo } from "./ITreeSettingInfo";
export interface ITreeSettingWizardInfo {
setting: string;
label: string;
description: string;
settingInfo: ITreeSettingInfo<string> | ITreeArraySettingInfo<string>;
currentValue: string | string[];
}
export interface ITreeSettingsWizardContext extends IActionContext {
infoList: ITreeSettingWizardInfo[];
info?: ITreeSettingWizardInfo;
newValue?: string | string[];
}

Просмотреть файл

@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzureWizardPromptStep } from "vscode-azureextensionui";
import { ext } from "../../extensionVariables";
import { ITreeSettingsWizardContext } from "./ITreeSettingsWizardContext";
export class TreeSettingListStep extends AzureWizardPromptStep<ITreeSettingsWizardContext> {
public async prompt(context: ITreeSettingsWizardContext): Promise<void> {
const placeHolder = "Select a setting to change."
const picks = context.infoList.map(info => {
return {
label: `$(gear) ${info.label}`,
description: `Current: "${info.currentValue}"`,
detail: info.description,
data: info
}
})
context.info = (await ext.ui.showQuickPick(picks, { placeHolder, suppressPersistence: true, ignoreFocusOut: false })).data;
}
public shouldPrompt(context: ITreeSettingsWizardContext): boolean {
return !context.info;
}
}

Просмотреть файл

@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AzureWizardPromptStep, IAzureQuickPickItem, IAzureQuickPickOptions } from "vscode-azureextensionui";
import { ext } from "../../extensionVariables";
import { nonNullProp } from "../../utils/nonNull";
import { ITreePropertyInfo } from "./ITreeSettingInfo";
import { ITreeSettingsWizardContext } from "./ITreeSettingsWizardContext";
export class TreeSettingStep extends AzureWizardPromptStep<ITreeSettingsWizardContext> {
public async prompt(context: ITreeSettingsWizardContext): Promise<void> {
const info = nonNullProp(context, 'info');
context.telemetry.properties.setting = info.setting;
let picks: IAzureQuickPickItem<string>[] = info.settingInfo.properties.map(convertPropertyInfoToPick);
picks = picks.sort((p1, p2) => p1.label.localeCompare(p2.label));
let options: IAzureQuickPickOptions = {
placeHolder: info.description,
suppressPersistence: true
}
if (Array.isArray(info.currentValue)) {
options.isPickSelected = (p: Partial<IAzureQuickPickItem<string>>) => !!p.data && info.currentValue.includes(p.data);
const result = await ext.ui.showQuickPick(picks, { ...options, canPickMany: true });
context.newValue = result.map(p => p.data);
} else {
const result = await ext.ui.showQuickPick(picks, options);
context.newValue = result.data;
}
context.telemetry.properties.newValue = context.newValue.toString();
}
public shouldPrompt(_context: ITreeSettingsWizardContext): boolean {
return true;
}
}
function convertPropertyInfoToPick(info: ITreePropertyInfo<string>): IAzureQuickPickItem<string> {
let description: string | undefined;
let detail: string | undefined;
if (info.exampleValue) {
description = `e.g. "${info.exampleValue}"`;
detail = info.description;
} else {
description = info.description;
}
return {
label: info.property,
description,
detail,
data: info.property
};
}

Просмотреть файл

@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VolumeInspectInfo } from "dockerode";
import { ILocalItem } from "../LocalRootTreeItemBase";
/**
* Wrapper class for Dockerode item, which has inconsistent names/types
*/
export class LocalVolumeInfo implements ILocalItem {
public data: VolumeInspectInfo;
public constructor(data: VolumeInspectInfo) {
this.data = data;
}
public get createdTime(): number {
// tslint:disable-next-line: no-unsafe-any no-any
return new Date((<any>this.data).CreatedAt).valueOf();
}
public get volumeName(): string {
return this.data.Name;
}
public get treeId(): string {
return this.volumeName;
}
}

Просмотреть файл

@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getThemedIconPath, IconPath } from "../IconPath";
import { LocalGroupTreeItemBase } from "../LocalGroupTreeItemBase";
import { getCommonGroupIcon } from "../settings/CommonProperties";
import { LocalVolumeInfo } from "./LocalVolumeInfo";
import { VolumeProperty } from "./VolumeProperties";
export class VolumeGroupTreeItem extends LocalGroupTreeItemBase<LocalVolumeInfo, VolumeProperty> {
public static readonly contextValue: string = 'volumeGroup';
public readonly contextValue: string = VolumeGroupTreeItem.contextValue;
public childTypeLabel: string = 'volume';
public get iconPath(): IconPath {
let icon: string;
switch (this.parent.groupBySetting) {
case 'VolumeName':
icon = 'volume';
break;
default:
return getCommonGroupIcon(this.parent.groupBySetting);
}
return getThemedIconPath(icon);
}
}

Просмотреть файл

@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { commonProperties, CommonProperty } from "../settings/CommonProperties";
import { ITreePropertyInfo } from "../settings/ITreeSettingInfo";
export type VolumeProperty = CommonProperty | 'VolumeName';
export const volumeProperties: ITreePropertyInfo<VolumeProperty>[] = [
...commonProperties,
{ property: 'VolumeName', exampleValue: 'my-vol' },
];

Просмотреть файл

@ -3,44 +3,51 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VolumeInspectInfo } from "dockerode";
import * as moment from 'moment';
import { Volume } from "dockerode";
import { AzExtParentTreeItem, AzExtTreeItem } from "vscode-azureextensionui";
import { ext } from "../../extensionVariables";
import { getThemedIconPath, IconPath } from "../IconPath";
import { LocalVolumeInfo } from "./LocalVolumeInfo";
export class VolumeTreeItem extends AzExtTreeItem {
public static contextValue: string = 'volume';
public contextValue: string = VolumeTreeItem.contextValue;
public volume: VolumeInspectInfo;
private readonly _item: LocalVolumeInfo;
public constructor(parent: AzExtParentTreeItem, volume: VolumeInspectInfo) {
public constructor(parent: AzExtParentTreeItem, itemInfo: LocalVolumeInfo) {
super(parent);
this.volume = volume;
this._item = itemInfo;
}
public get id(): string {
return this._item.treeId;
}
public get createdTime(): number {
return this._item.createdTime;
}
public get volumeName(): string {
return this._item.volumeName;
}
public get label(): string {
return this.volume.Name;
return ext.volumesRoot.getTreeItemLabel(this._item);
}
public get description(): string | undefined {
return ext.volumesRoot.getTreeItemDescription(this._item);
}
public get iconPath(): IconPath {
return getThemedIconPath('volume');
}
public get description(): string | undefined {
return this.createdAt ? moment(new Date(this.createdAt)).fromNow() : undefined;
}
public get id(): string {
return this.volume.Name;
}
public get createdAt(): string | undefined {
// tslint:disable-next-line: no-unsafe-any no-any
return (<any>this.volume).CreatedAt;
public getVolume(): Volume {
return ext.dockerode.getVolume(this.volumeName);
}
public async deleteTreeItemImpl(): Promise<void> {
await ext.dockerode.getVolume(this.volume.Name).remove({ force: true });
await this.getVolume().remove({ force: true });
}
}

Просмотреть файл

@ -3,29 +3,53 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VolumeInspectInfo } from "dockerode";
import { AzExtTreeItem } from "vscode-azureextensionui";
import { ext } from "../../extensionVariables";
import { AutoRefreshTreeItemBase } from "../AutoRefreshTreeItemBase";
import { LocalChildGroupType, LocalChildType, LocalRootTreeItemBase } from "../LocalRootTreeItemBase";
import { CommonGroupBy, getCommonPropertyValue, groupByNoneProperty } from "../settings/CommonProperties";
import { ITreeArraySettingInfo, ITreeSettingInfo } from "../settings/ITreeSettingInfo";
import { LocalVolumeInfo } from "./LocalVolumeInfo";
import { VolumeGroupTreeItem } from "./VolumeGroupTreeItem";
import { volumeProperties, VolumeProperty } from "./VolumeProperties";
import { VolumeTreeItem } from "./VolumeTreeItem";
export class VolumesTreeItem extends AutoRefreshTreeItemBase<VolumeInspectInfo> {
public static contextValue: string = 'volumes';
public contextValue: string = VolumesTreeItem.contextValue;
export class VolumesTreeItem extends LocalRootTreeItemBase<LocalVolumeInfo, VolumeProperty> {
public treePrefix: string = 'volumes';
public label: string = 'Volumes';
public childTypeLabel: string = 'volume';
public noItemsMessage: string = "Successfully connected, but no volumes found.";
public configureExplorerTitle: string = 'Configure volumes explorer';
public childType: LocalChildType<LocalVolumeInfo> = VolumeTreeItem;
public childGroupType: LocalChildGroupType<LocalVolumeInfo, VolumeProperty> = VolumeGroupTreeItem;
public getItemID(item: VolumeInspectInfo): string {
return item.Name;
public labelSettingInfo: ITreeSettingInfo<VolumeProperty> = {
properties: volumeProperties,
defaultProperty: 'VolumeName',
};
public descriptionSettingInfo: ITreeArraySettingInfo<VolumeProperty> = {
properties: volumeProperties,
defaultProperty: ['CreatedTime'],
};
public groupBySettingInfo: ITreeSettingInfo<VolumeProperty | CommonGroupBy> = {
properties: [...volumeProperties, groupByNoneProperty],
defaultProperty: 'None',
};
public get childTypeLabel(): string {
return this.groupBySetting === 'None' ? 'volume' : 'volume group';
}
public async getItems(): Promise<VolumeInspectInfo[]> {
public async getItems(): Promise<LocalVolumeInfo[]> {
const result = await ext.dockerode.listVolumes();
return (result && result.Volumes) || [];
const volumes = (result && result.Volumes) || [];
return volumes.map(v => new LocalVolumeInfo(v));
}
public async convertToTreeItems(items: VolumeInspectInfo[]): Promise<AzExtTreeItem[]> {
return items.map(v => new VolumeTreeItem(this, v));
public getPropertyValue(item: LocalVolumeInfo, property: VolumeProperty): string {
switch (property) {
case 'VolumeName':
return item.volumeName;
default:
return getCommonPropertyValue(item, property);
}
}
}

Просмотреть файл

@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function trimWithElipsis(str: string, max: number): string {
const elipsis: string = "...";
const len: number = str.length;
if (max <= 0 || max >= 100) { return str; }
if (str.length <= max) { return str; }
if (max < 3) { return str.substr(0, max); }
const front: string = str.substr(0, (len / 2) - (-0.5 * (max - len - 3)));
const back: string = str.substr(len - (len / 2) + (-0.5 * (max - len - 3)));
return front + elipsis + back;
}

Просмотреть файл

@ -1,138 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { date } from 'azure-storage';
import { ImageInfo } from 'dockerode';
import { getImageLabel } from '../extension.bundle';
function testGetImageLabelTruncated(fullTag: string, labelTemplate: string, truncateLongRegistryPaths: boolean, truncateMaxLength: number, expected: string): void {
test(`${String(fullTag)}: "${labelTemplate}"/${truncateLongRegistryPaths}/${truncateMaxLength}`, () => {
let label = getImageLabel(fullTag, <ImageInfo>{}, labelTemplate, { truncateLongRegistryPaths, truncateMaxLength });
assert.equal(label, expected);
});
}
function testGetImageLabel(fullTag: string, labelTemplate: string, expected: string): void {
test(`${String(fullTag)}: "${labelTemplate}", no truncation`, () => {
let s2 = getImageLabel(fullTag, <ImageInfo>{}, labelTemplate, { truncateLongRegistryPaths: false, truncateMaxLength: 1 });
assert.equal(s2, expected);
});
}
suite('getImageLabel: full tag truncated', () => {
testGetImageLabelTruncated('', '{fullTag}', false, 0, '');
testGetImageLabelTruncated('', '{fullTag}', false, 1, '');
testGetImageLabelTruncated('', '{fullTag}', true, 0, '');
testGetImageLabelTruncated('', '{fullTag}', true, 1, '');
testGetImageLabelTruncated('a', '{fullTag}', false, 1, 'a');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', false, 0, 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', false, 1, 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', false, 25, 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', false, 90, 'abcdefghijklmnopqrstuvwxyz');
// No registry - use full image name
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', true, 0, 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', true, 1, 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', true, 2, 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', true, 10, 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', true, 99, 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', true, 10, 'abcdefghijklmnopqrstuvwxyz:latest');
// Registry + one level
testGetImageLabelTruncated('a/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', true, 10, 'a/abcdefghijklmnopqrstuvwxyz:latest');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', true, 10, 'abc...wxyz/abcdefghijklmnopqrstuvwxyz:latest');
// Registry + two or more levels
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', true, 10, 'abc...wxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', true, 10, 'abc...wxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest');
// Real examples
testGetImageLabelTruncated('registry.gitlab.com/sweatherford/hello-world/sub:latest', '{fullTag}', true, 7, 're...om/sweatherford/hello-world/sub:latest');
testGetImageLabelTruncated('127.0.0.1:5443/registry:v2', '{fullTag}', true, 7, '12...43/registry:v2');
testGetImageLabelTruncated('127.0.0.1:5443/hello-world/sub:latest', '{fullTag}', true, 7, '12...43/hello-world/sub:latest');
});
suite('getImageLabel: repository truncated', () => {
testGetImageLabelTruncated('', '{repository}', false, 0, '');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{repository}', false, 90, 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{repository}', true, 99, 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz:latest', '{repository}', true, 10, 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('a/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', true, 10, 'a/abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', true, 10, 'abc...wxyz/abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', true, 10, 'abc...wxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', true, 10, 'abc...wxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz');
testGetImageLabelTruncated('registry.gitlab.com/sweatherford/hello-world/sub:latest', '{repository}', true, 7, 're...om/sweatherford/hello-world/sub');
testGetImageLabelTruncated('127.0.0.1:5443/registry:v2', '{repository}', true, 7, '12...43/registry');
testGetImageLabelTruncated('127.0.0.1:5443/hello-world/sub:latest', '{repository}', true, 7, '12...43/hello-world/sub');
});
suite('getImageLabel: fullTag', () => {
testGetImageLabel('', '{fullTag}', '');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz', '{fullTag}', 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', 'abcdefghijklmnopqrstuvwxyz:latest');
testGetImageLabel('a/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', 'a/abcdefghijklmnopqrstuvwxyz:latest');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', 'abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', 'abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', 'abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest');
testGetImageLabel('registry.gitlab.com/sweatherford/hello-world/sub:latest', '{fullTag}', 'registry.gitlab.com/sweatherford/hello-world/sub:latest');
testGetImageLabel('127.0.0.1:5443/registry:v2', '{fullTag}', '127.0.0.1:5443/registry:v2');
testGetImageLabel('127.0.0.1:5443/hello-world/sub:latest', '{fullTag}', '127.0.0.1:5443/hello-world/sub:latest');
});
suite('getImageLabel: repository', () => {
testGetImageLabel('', '{repository}', '');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz', '{repository}', 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz:latest', '{repository}', 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabel('a/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', 'a/abcdefghijklmnopqrstuvwxyz');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', 'abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', 'abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', 'abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz');
testGetImageLabel('registry.gitlab.com/sweatherford/hello-world/sub:latest', '{repository}', 'registry.gitlab.com/sweatherford/hello-world/sub');
testGetImageLabel('127.0.0.1:5443/registry:v2', '{repository}', '127.0.0.1:5443/registry');
testGetImageLabel('127.0.0.1:5443/hello-world/sub:latest', '{repository}', '127.0.0.1:5443/hello-world/sub');
});
suite('getImageLabel: tag', () => {
testGetImageLabel('', '{tag}', '');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz', '{tag}', '');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz:latest', '{tag}', 'latest');
testGetImageLabel('a/abcdefghijklmnopqrstuvwxyz:latest', '{tag}', 'latest');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{tag}', 'latest');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{tag}', 'latest');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{tag}', 'latest');
testGetImageLabel('registry.gitlab.com/sweatherford/hello-world/sub:latest', '{tag}', 'latest');
testGetImageLabel('127.0.0.1:5443/registry:v2', '{tag}', 'v2');
testGetImageLabel('127.0.0.1:5443/hello-world/sub:latest', '{tag}', 'latest');
});
suite('getImageLabel: createdSince', () => {
let label = getImageLabel('hello', <ImageInfo>{ Created: date.daysFromNow(-1).valueOf() / 1000 }, '{createdSince}');
assert.equal(label, 'a day ago');
});
suite('getImageLabel: shortImageId', () => {
let label = getImageLabel('hello', <ImageInfo>{ Created: date.daysFromNow(-1).valueOf() / 1000, Id: 'sha256:d0eed8dad114db55d81c870efb8c148026da4a0f61dc7710c053da55f9604849' }, '{shortImageId}');
assert.equal(label, 'd0eed8dad114');
});
suite('getImageLabel: repositoryName', () => {
testGetImageLabel('', '{repositoryName}', '');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz', '{repositoryName}', 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz:latest', '{repositoryName}', 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabel('a/abcdefghijklmnopqrstuvwxyz:latest', '{repositoryName}', 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repositoryName}', 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repositoryName}', 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repositoryName}', 'abcdefghijklmnopqrstuvwxyz');
testGetImageLabel('registry.gitlab.com/sweatherford/hello-world/sub:latest', '{repositoryName}', 'sub');
testGetImageLabel('127.0.0.1:5443/registry:v2', '{repositoryName}', 'registry');
testGetImageLabel('127.0.0.1:5443/hello-world/sub:latest', '{repositoryName}', 'sub');
});
suite('getImageLabel: mixed', () => {
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repository} is {repositoryName} at {tag}', 'abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz is abcdefghijklmnopqrstuvwxyz at latest');
});

19
test/runWithSetting.ts Normal file
Просмотреть файл

@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ConfigurationTarget, workspace, WorkspaceConfiguration } from "vscode";
import { configPrefix } from "../extension.bundle";
export async function runWithSetting<T>(key: string, value: T | undefined, callback: () => Promise<void>): Promise<void> {
const config: WorkspaceConfiguration = workspace.getConfiguration(configPrefix);
const result = config.inspect<T>(key);
const oldValue: T | undefined = result && result.globalValue;
try {
await config.update(key, value, ConfigurationTarget.Global);
await callback();
} finally {
await config.update(key, oldValue, ConfigurationTarget.Global);
}
}

Просмотреть файл

@ -0,0 +1,383 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ContainerInfo } from 'dockerode';
import { ext } from '../../extension.bundle';
import { generateCreatedTimeInSec, ITestTreeItem, IValidateTreeOptions, validateTree } from './validateTree';
const testContainers: Partial<ContainerInfo>[] = [
{
Id: "9330566c414439f4873edd95689b559466993681f7b9741005b5a74786134202",
Names: ["/vigorous_booth"],
Image: "node:8.0",
ImageID: "sha256:065e283f68bd5ef3b079aee76d3aa55b5e56e8f9ede991a97ff15fdc556f8cfd",
Created: generateCreatedTimeInSec(1),
Ports: [],
State: "created",
Status: "Created",
},
{
Id: "faeb6f02af06df748a0040476ba7c335fb8aaefd76f6ea14a76800faf0fa3910",
Names: ["/elegant_knuth"],
Image: "registry:latest",
ImageID: "sha256:f32a97de94e13d29835a19851acd6cbc7979d1d50f703725541e44bb89a1ce91",
Created: generateCreatedTimeInSec(2),
Ports: [
{ "IP": "0.0.0.0", "PrivatePort": 5000, "PublicPort": 5000, "Type": "tcp" }
],
State: "running",
Status: "Up 6 minutes",
},
{
Id: "99636d5207b3da8a9865ef931aa3c758688e795e7787a6982fc7b5da07a5de8c",
Names: ["/focused_cori"],
Image: "mcr.microsoft.com/dotnet/core/sdk:latest",
ImageID: "sha256:bbae085fa7eb0725dd2647a357988095754620aaf64ddc4b152d6f1407111dc8",
Created: generateCreatedTimeInSec(3),
Ports: [],
State: "paused",
Status: "Up 8 minutes (Paused)",
},
{
Id: "49df1ed4a46c2617025298a8bdb01bc37267ecae82fc8ab88b0504314d94b983",
Names: ["/zealous_napier"],
Image: "emjacr2.azurecr.io/docker-django-webapp-linux:cj8",
ImageID: "sha256:d3eef98c0630cc7e2b81f37fe8c8db7b554aeff42d3bf193337842f80b208614",
Created: generateCreatedTimeInSec(35),
Ports: [
{ "IP": "0.0.0.0", "PrivatePort": 2222, "PublicPort": 2222, "Type": "tcp" },
{ "IP": "0.0.0.0", "PrivatePort": 8000, "PublicPort": 8000, "Type": "tcp" }
],
State: "running",
Status: "Up 8 minutes",
},
{
Id: "ee098ec2fb0b337e4f480a1a33dd1d396ef6b242579bb8b874e480957c053f34",
Names: ["/admiring_leavitt"],
Image: "vsc-js1-6b97c65e88377ff89a4eab7bc81b694d",
ImageID: "sha256:7804287702e2a3d7f44b46a9ce864951ed093227e1d4e1f67992760292bd8126",
Created: generateCreatedTimeInSec(36),
Ports: [],
State: "exited",
Status: "Exited (137) 12 hours ago",
},
{
Id: "5e25d05c0797d44c0efaf3479633316f9229e3f71feccfbe2278c35681c0436f",
Names: ["/inspiring_brattain"],
Image: "acr-build-helloworld-node:latest",
ImageID: "sha256:4d476c415ca931a558cfefe48f4f51e8b6bcbadf6f8820cf5a98a05794b59293",
Created: generateCreatedTimeInSec(37),
Ports: [{ "IP": "0.0.0.0", "PrivatePort": 80, "PublicPort": 80, "Type": "tcp" }],
State: "running",
Status: "Up 32 hours",
},
{
Id: "531005593f5da6f15ce13a6149a9b4866608fad5bddc600d37239e3d9976f00f",
Names: ["/elegant_mendel"],
Image: "test:latest",
ImageID: "sha256:93074a25f8cc8647a62dfc14d42710751d1f341479d0a6943384e618685db614",
Created: generateCreatedTimeInSec(90),
Ports: [],
State: "running",
Status: "Up 49 seconds"
},
{
Id: "99fd96f9cdf9fb7668887477f91b0c72682461690ff83030e8a6aa63a871f63a",
Names: ["/devtest"],
Image: "nginx:latest",
ImageID: "sha256:62c261073ecffe22a28f2ba67760a9320bc4bfe8136a83ba9b579983346564be",
Created: generateCreatedTimeInSec(365),
Ports: [],
State: "exited",
Status: "Exited (0) 2 days ago"
}
];
async function validateContainersTree(options: IValidateTreeOptions, expectedNodes: ITestTreeItem[]): Promise<void> {
await validateTree(ext.containersRoot, 'containers', options, { containers: testContainers }, expectedNodes);
}
suite('Containers Tree', async () => {
test('Default Settings', async () => {
await validateContainersTree(
{},
[
{ label: "node:8.0", description: "vigorous_booth - Created" },
{ label: "registry:latest", description: "elegant_knuth - Up 6 minutes" },
{ label: "mcr.microsoft.com/dotnet/core/sdk:latest", description: "focused_cori - Up 8 minutes (Paused)" },
{ label: "emjacr2.azurecr.io/docker-django-webapp-linux:cj8", description: "zealous_napier - Up 8 minutes" },
{ label: "vsc-js1-6b97c65e88377ff89a4eab7bc81b694d", description: "admiring_leavitt - Exited (137) 12 hours ago" },
{ label: "acr-build-helloworld-node:latest", description: "inspiring_brattain - Up 32 hours" },
{ label: "test:latest", description: "elegant_mendel - Up 49 seconds" },
{ label: "nginx:latest", description: "devtest - Exited (0) 2 days ago" },
]);
});
test('Invalid Settings', async () => {
await validateContainersTree(
{
description: ['test'],
groupBy: <any>true,
label: <any>22,
sortBy: 'test3'
},
[
{ label: "node:8.0", description: "vigorous_booth - Created" },
{ label: "registry:latest", description: "elegant_knuth - Up 6 minutes" },
{ label: "mcr.microsoft.com/dotnet/core/sdk:latest", description: "focused_cori - Up 8 minutes (Paused)" },
{ label: "emjacr2.azurecr.io/docker-django-webapp-linux:cj8", description: "zealous_napier - Up 8 minutes" },
{ label: "vsc-js1-6b97c65e88377ff89a4eab7bc81b694d", description: "admiring_leavitt - Exited (137) 12 hours ago" },
{ label: "acr-build-helloworld-node:latest", description: "inspiring_brattain - Up 32 hours" },
{ label: "test:latest", description: "elegant_mendel - Up 49 seconds" },
{ label: "nginx:latest", description: "devtest - Exited (0) 2 days ago" },
]);
});
test('ContainerName', async () => {
await validateContainersTree(
{
label: 'ContainerName',
description: []
},
[
{ label: "vigorous_booth" },
{ label: "elegant_knuth" },
{ label: "focused_cori" },
{ label: "zealous_napier" },
{ label: "admiring_leavitt" },
{ label: "inspiring_brattain" },
{ label: "elegant_mendel" },
{ label: "devtest" },
]);
});
test('ContainerId', async () => {
await validateContainersTree(
{
label: 'ContainerId',
description: []
},
[
{ label: "9330566c4144" },
{ label: "faeb6f02af06" },
{ label: "99636d5207b3" },
{ label: "49df1ed4a46c" },
{ label: "ee098ec2fb0b" },
{ label: "5e25d05c0797" },
{ label: "531005593f5d" },
{ label: "99fd96f9cdf9" },
]);
});
test('Ports', async () => {
await validateContainersTree(
{
label: 'Ports',
description: []
},
[
{ label: "<none>" },
{ label: "5000" },
{ label: "<none>" },
{ label: "2222,8000" },
{ label: "<none>" },
{ label: "80" },
{ label: "<none>" },
{ label: "<none>" },
]);
});
test('Status', async () => {
await validateContainersTree(
{
label: 'Status',
description: []
},
[
{ label: "Created" },
{ label: "Up 6 minutes" },
{ label: "Up 8 minutes (Paused)" },
{ label: "Up 8 minutes" },
{ label: "Exited (137) 12 hours ago" },
{ label: "Up 32 hours" },
{ label: "Up 49 seconds" },
{ label: "Exited (0) 2 days ago" },
]);
});
test('State', async () => {
await validateContainersTree(
{
label: 'State',
description: []
},
[
{ label: "created" },
{ label: "running" },
{ label: "paused" },
{ label: "running" },
{ label: "exited" },
{ label: "running" },
{ label: "running" },
{ label: "exited" },
]);
});
test('ContainerName sortBy CreatedTime', async () => {
await validateContainersTree(
{
label: 'ContainerName',
description: [],
sortBy: 'CreatedTime',
},
[
{ label: "vigorous_booth" },
{ label: "elegant_knuth" },
{ label: "focused_cori" },
{ label: "zealous_napier" },
{ label: "admiring_leavitt" },
{ label: "inspiring_brattain" },
{ label: "elegant_mendel" },
{ label: "devtest" },
]);
});
test('ContainerName sortBy Label', async () => {
await validateContainersTree(
{
label: 'ContainerName',
description: [],
sortBy: 'Label',
},
[
{ label: "admiring_leavitt" },
{ label: "devtest" },
{ label: "elegant_knuth" },
{ label: "elegant_mendel" },
{ label: "focused_cori" },
{ label: "inspiring_brattain" },
{ label: "vigorous_booth" },
{ label: "zealous_napier" },
]);
});
test('GroupBy Registry', async () => {
await validateContainersTree(
{
groupBy: 'Registry',
label: 'RepositoryNameAndTag',
description: ['Status']
},
[
{
label: "docker.io/library",
children: [
{ label: "node:8.0", description: "Created" },
{ label: "registry:latest", description: "Up 6 minutes" },
{ label: "vsc-js1-6b97c65e88377ff89a4eab7bc81b694d", description: "Exited (137) 12 hours ago" },
{ label: "acr-build-helloworld-node:latest", description: "Up 32 hours" },
{ label: "test:latest", description: "Up 49 seconds" },
{ label: "nginx:latest", description: "Exited (0) 2 days ago" }
]
},
{
label: "emjacr2.azurecr.io",
children: [
{ label: "docker-django-webapp-linux:cj8", description: "Up 8 minutes" }
]
},
{
label: "mcr.microsoft.com",
children: [
{ label: "dotnet/core/sdk:latest", description: "Up 8 minutes (Paused)" }
]
}
]);
});
test('GroupBy CreatedTime', async () => {
await validateContainersTree(
{
groupBy: 'CreatedTime',
label: 'ContainerName',
description: []
},
[
{
label: "a day ago",
children: [
{ label: "vigorous_booth" }
]
},
{
label: "2 days ago",
children: [
{ label: "elegant_knuth" }
]
},
{
label: "3 days ago",
children: [
{ label: "focused_cori" },
]
},
{
label: "a month ago",
children: [
{ label: "zealous_napier" },
{ label: "admiring_leavitt" },
{ label: "inspiring_brattain" },
]
},
{
label: "3 months ago",
children: [
{ label: "elegant_mendel" },
]
},
{
label: "a year ago",
children: [
{ label: "devtest" },
]
},
]);
});
test('GroupBy Tag', async () => {
await validateContainersTree(
{
groupBy: 'Tag',
label: 'Repository',
description: ["CreatedTime"]
},
[
{
label: "8.0",
children: [
{ label: "node", description: "a day ago" }
]
},
{
label: "cj8",
children: [
{ label: "emjacr2.azurecr.io/docker-django-webapp-linux", description: "a month ago" }
]
},
{
label: "latest",
children: [
{ label: "registry", description: "2 days ago" },
{ label: "mcr.microsoft.com/dotnet/core/sdk", description: "3 days ago" },
{ label: "vsc-js1-6b97c65e88377ff89a4eab7bc81b694d", description: "a month ago" },
{ label: "acr-build-helloworld-node", description: "a month ago" },
{ label: "test", description: "3 months ago" },
{ label: "nginx", description: "a year ago" }
]
}
]);
});
});

Просмотреть файл

@ -0,0 +1,991 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ImageInfo } from 'dockerode';
import { ext } from '../../extension.bundle';
import { runWithSetting } from '../runWithSetting';
import { generateCreatedTimeInSec, ITestTreeItem, IValidateTreeOptions, validateTree } from './validateTree';
const testImages: Partial<ImageInfo>[] = [
{
RepoTags: ['a'],
Id: 'sha256:b0648d86f18e6141a8bfa98d4d17d5180aa2699af7f27eac5491fd1f950f6f05',
Created: generateCreatedTimeInSec(2)
},
{
RepoTags: ['abcdefghijklmnopqrstuvwxyz'],
Id: 'sha256:678090bb0827fecbee9eb0bbc65200022bbc09c91a8bf4acf136f5e633260a93',
Created: generateCreatedTimeInSec(3)
},
{
RepoTags: ['abcdefghijklmnopqrstuvwxyz:version1.0.test'],
Id: 'sha256:0dbb0aabc7476292f98610d094a1bbc7f3012fd65cccc823e719a44267075bc7',
Created: generateCreatedTimeInSec(4)
},
{
RepoTags: ['a.b/abcdefghijklmnopqrstuvwxyz:latest'],
Id: 'sha256:28bd20772f5203d07fdbfa38438f17cf720aaf01f7b53c205ac7e25b0795b718',
Created: generateCreatedTimeInSec(5)
},
{
RepoTags: ['abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest'],
Id: 'sha256:38e8467493f68c24a78dafbe49587c07e78b0f84ec8cdc19a509ce3536f334fa',
Created: generateCreatedTimeInSec(6)
},
{
RepoTags: ['abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest'],
Id: 'sha256:1e6d05ff19d567a103b3d134aa793841b51345a45fb59fd0287fb9d96e55c51b',
Created: generateCreatedTimeInSec(7)
},
{
RepoTags: ['abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest'],
Id: 'sha256:16bba3882d727858afbb6dee098c5b5c9671bce8d347b995091f558afbdb18a5',
Created: generateCreatedTimeInSec(8)
},
{
RepoTags: ['registry.gitlab.com/sweatherford/hello-world/sub:latest'],
Id: 'sha256:a3f7187fcd572b4c2065f96abd87b759b9ab9ed58bf7ea3755714bcc8795cf8a',
Created: generateCreatedTimeInSec(9)
},
{
RepoTags: ['127.0.0.1:5443/registry:v2'],
Id: 'sha256:ad8fe06eeca42a64aa28ca767b0f3fbe8713c087a6dcc66be949cefbe2131287',
Created: generateCreatedTimeInSec(58)
},
{
RepoTags: ['127.0.0.1:5443/hello-world/sub:latest'],
Id: 'sha256:c8b4e4c47a8e6cc5e9c4f9cc9858f83d1d3e79c6ab4d890f7fb190a599d29903',
Created: generateCreatedTimeInSec(59)
},
{
RepoTags: [
'hello-world:latest',
'hello-world:v1'
],
Id: 'sha256:8a093bef2179f2c76b1b1d3254862e85ee6c26ee649fadad220e46527042f436',
Created: generateCreatedTimeInSec(60)
},
{
RepoTags: ['namespace1/abc:v3'],
Id: 'sha256:d0eed8dad114db55d81c870efb8c148026da4a0f61dc7710c053da55f9604849',
Created: generateCreatedTimeInSec(366)
},
{
RepoTags: ['localhost/abc:v4'],
Id: 'sha256:f61138f385d368484da055ecb085201ec06a524e92a10c64e6535bf6c32d15a4',
Created: generateCreatedTimeInSec(367)
},
{
RepoTags: ['localhost:8080/abc'],
Id: 'sha256:e05f39ada67afbe24e68a22eeb9a45c59d0aab31f0a1585870a75893981fae75',
Created: generateCreatedTimeInSec(368)
},
];
interface IValidateImagesTreeOptions extends IValidateTreeOptions {
truncate?: boolean;
truncateLength?: number;
}
async function validateImagesTree(options: IValidateImagesTreeOptions, expectedNodes: ITestTreeItem[]): Promise<void> {
await runWithSetting('truncateLongRegistryPaths', options.truncate, async () => {
await runWithSetting('truncateMaxLength', options.truncateLength, async () => {
await validateTree(ext.imagesRoot, 'images', options, { images: testImages }, expectedNodes);
});
});
}
suite('Images Tree', async () => {
test('Default Settings', async () => {
await validateImagesTree(
{},
[
{
label: "127.0.0.1:5443/hello-world/sub",
children: [
{ label: "latest", description: "2 months ago" }
]
},
{
label: "127.0.0.1:5443/registry",
children: [
{ label: "v2", description: "2 months ago" }
]
},
{
label: "a",
children: [
{ label: "latest", description: "2 days ago" }
]
},
{
label: "a.b/abcdefghijklmnopqrstuvwxyz",
children: [
{ label: "latest", description: "5 days ago" }
]
},
{
label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz",
children: [
{ label: "latest", description: "6 days ago" }
]
},
{
label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz",
children: [
{ label: "latest", description: "8 days ago" }
]
},
{
label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz",
children: [
{ label: "latest", description: "7 days ago" }
]
},
{
label: "abcdefghijklmnopqrstuvwxyz",
children: [
{ label: "latest", description: "3 days ago" },
{ label: "version1.0.test", description: "4 days ago" }
]
},
{
label: "hello-world",
children: [
{ label: "latest", description: "2 months ago" },
{
label: "v1",
description: "2 months ago"
}
]
},
{
label: "localhost:8080/abc",
children: [
{ label: "latest", description: "a year ago" }
]
},
{
label: "localhost/abc",
children: [
{ label: "v4", description: "a year ago" }
]
},
{
label: "namespace1/abc",
children: [
{ label: "v3", description: "a year ago" }
]
},
{
label: "registry.gitlab.com/sweatherford/hello-world/sub",
children: [
{ label: "latest", description: "9 days ago" }
]
}
]);
});
test('Invalid Settings', async () => {
await validateImagesTree(
{
description: <any>'test',
groupBy: 'test3',
label: 'test2',
sortBy: <any>[]
},
[
{
label: "127.0.0.1:5443/hello-world/sub",
children: [
{ label: "latest", description: "2 months ago" }
]
},
{
label: "127.0.0.1:5443/registry",
children: [
{ label: "v2", description: "2 months ago" }
]
},
{
label: "a",
children: [
{ label: "latest", description: "2 days ago" }
]
},
{
label: "a.b/abcdefghijklmnopqrstuvwxyz",
children: [
{ label: "latest", description: "5 days ago" }
]
},
{
label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz",
children: [
{ label: "latest", description: "6 days ago" }
]
},
{
label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz",
children: [
{ label: "latest", description: "8 days ago" }
]
},
{
label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz",
children: [
{ label: "latest", description: "7 days ago" }
]
},
{
label: "abcdefghijklmnopqrstuvwxyz",
children: [
{ label: "latest", description: "3 days ago" },
{ label: "version1.0.test", description: "4 days ago" }
]
},
{
label: "hello-world",
children: [
{ label: "latest", description: "2 months ago" },
{
label: "v1",
description: "2 months ago"
}
]
},
{
label: "localhost:8080/abc",
children: [
{ label: "latest", description: "a year ago" }
]
},
{
label: "localhost/abc",
children: [
{ label: "v4", description: "a year ago" }
]
},
{
label: "namespace1/abc",
children: [
{ label: "v3", description: "a year ago" }
]
},
{
label: "registry.gitlab.com/sweatherford/hello-world/sub",
children: [
{ label: "latest", description: "9 days ago" }
]
}
]);
});
test('CreatedTime', async () => {
await validateImagesTree(
{
label: 'CreatedTime',
description: [],
groupBy: 'None',
},
[
{ label: "2 days ago" },
{ label: "3 days ago" },
{ label: "4 days ago" },
{ label: "5 days ago" },
{ label: "6 days ago" },
{ label: "7 days ago" },
{ label: "8 days ago" },
{ label: "9 days ago" },
{ label: "2 months ago" },
{ label: "2 months ago" },
{ label: "2 months ago" },
{ label: "2 months ago" },
{ label: "a year ago" },
{ label: "a year ago" },
{ label: "a year ago" },
]);
});
test('FullTag', async () => {
await validateImagesTree(
{
label: 'FullTag',
description: [],
groupBy: 'None',
},
[
{ label: "a" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
{ label: "127.0.0.1:5443/registry:v2" },
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
{ label: "hello-world:latest" },
{ label: "hello-world:v1" },
{ label: 'namespace1/abc:v3' },
{ label: 'localhost/abc:v4' },
{ label: 'localhost:8080/abc' },
]);
});
test('FullTag sortBy CreatedTime', async () => {
await validateImagesTree(
{
label: 'FullTag',
description: [],
groupBy: 'None',
sortBy: 'CreatedTime',
},
[
{ label: "a" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
{ label: "127.0.0.1:5443/registry:v2" },
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
{ label: "hello-world:latest" },
{ label: "hello-world:v1" },
{ label: 'namespace1/abc:v3' },
{ label: 'localhost/abc:v4' },
{ label: 'localhost:8080/abc' },
]);
});
test('FullTag sortBy Label', async () => {
await validateImagesTree(
{
label: 'FullTag',
description: [],
groupBy: 'None',
sortBy: 'Label',
},
[
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
{ label: "127.0.0.1:5443/registry:v2" },
{ label: "a" },
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
{ label: "hello-world:latest" },
{ label: "hello-world:v1" },
{ label: 'localhost:8080/abc' },
{ label: 'localhost/abc:v4' },
{ label: 'namespace1/abc:v3' },
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
]);
});
test('FullTag truncate false', async () => {
await validateImagesTree(
{
label: 'FullTag',
description: [],
groupBy: 'None',
truncate: false,
},
[
{ label: "a" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
{ label: "127.0.0.1:5443/registry:v2" },
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
{ label: "hello-world:latest" },
{ label: "hello-world:v1" },
{ label: 'namespace1/abc:v3' },
{ label: 'localhost/abc:v4' },
{ label: 'localhost:8080/abc' },
]);
});
test('FullTag truncate false 1', async () => {
await validateImagesTree(
{
label: 'FullTag',
description: [],
groupBy: 'None',
truncate: false,
truncateLength: 1,
},
[
{ label: "a" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
{ label: "127.0.0.1:5443/registry:v2" },
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
{ label: "hello-world:latest" },
{ label: "hello-world:v1" },
{ label: 'namespace1/abc:v3' },
{ label: 'localhost/abc:v4' },
{ label: 'localhost:8080/abc' },
]);
});
test('FullTag truncate true', async () => {
await validateImagesTree(
{
label: 'FullTag',
description: [],
groupBy: 'None',
truncate: true,
},
[
{ label: "a" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "reg....com/sweatherford/hello-world/sub:latest" },
{ label: "127...5443/registry:v2" },
{ label: "127...5443/hello-world/sub:latest" },
{ label: "hello-world:latest" },
{ label: "hello-world:v1" },
{ label: 'namespace1/abc:v3' },
{ label: 'localhost/abc:v4' },
{ label: 'loc...8080/abc' },
]);
});
test('FullTag truncate true 0', async () => {
await validateImagesTree(
{
label: 'FullTag',
description: [],
groupBy: 'None',
truncate: true,
truncateLength: 0,
},
[
{ label: "a" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "reg....com/sweatherford/hello-world/sub:latest" },
{ label: "127...5443/registry:v2" },
{ label: "127...5443/hello-world/sub:latest" },
{ label: "hello-world:latest" },
{ label: "hello-world:v1" },
{ label: 'namespace1/abc:v3' },
{ label: 'localhost/abc:v4' },
{ label: 'loc...8080/abc' },
]);
});
test('FullTag truncate true 1', async () => {
await validateImagesTree(
{
label: 'FullTag',
description: [],
groupBy: 'None',
truncate: true,
truncateLength: 1,
},
[
{ label: "a" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
{ label: "a/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "a/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "a/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "a/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "r/sweatherford/hello-world/sub:latest" },
{ label: "1/registry:v2" },
{ label: "1/hello-world/sub:latest" },
{ label: "hello-world:latest" },
{ label: "hello-world:v1" },
{ label: 'namespace1/abc:v3' },
{ label: 'l/abc:v4' },
{ label: 'l/abc' },
]);
});
test('FullTag truncate true 7', async () => {
await validateImagesTree(
{
label: 'FullTag',
description: [],
groupBy: 'None',
truncate: true,
truncateLength: 7,
},
[
{ label: "a" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "ab...yz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "ab...yz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "ab...yz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "re...om/sweatherford/hello-world/sub:latest" },
{ label: "12...43/registry:v2" },
{ label: "12...43/hello-world/sub:latest" },
{ label: "hello-world:latest" },
{ label: "hello-world:v1" },
{ label: 'namespace1/abc:v3' },
{ label: 'lo...st/abc:v4' },
{ label: 'lo...80/abc' },
]);
});
test('FullTag truncate true 25', async () => {
await validateImagesTree(
{
label: 'FullTag',
description: [],
groupBy: 'None',
truncate: true,
truncateLength: 25,
},
[
{ label: "a" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijk...qrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijk...qrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijk...qrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
{ label: "127.0.0.1:5443/registry:v2" },
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
{ label: "hello-world:latest" },
{ label: "hello-world:v1" },
{ label: 'namespace1/abc:v3' },
{ label: 'localhost/abc:v4' },
{ label: 'localhost:8080/abc' },
]);
});
test('FullTag truncate true 90', async () => {
await validateImagesTree(
{
label: 'FullTag',
description: [],
groupBy: 'None',
truncate: true,
truncateLength: 90,
},
[
{ label: "a" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
{ label: "127.0.0.1:5443/registry:v2" },
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
{ label: "hello-world:latest" },
{ label: "hello-world:v1" },
{ label: 'namespace1/abc:v3' },
{ label: 'localhost/abc:v4' },
{ label: 'localhost:8080/abc' },
]);
});
test('ImageId', async () => {
await validateImagesTree(
{
label: 'ImageId',
description: [],
groupBy: 'None',
},
[
{ label: "b0648d86f18e" },
{ label: "678090bb0827" },
{ label: "0dbb0aabc747" },
{ label: "28bd20772f52" },
{ label: "38e8467493f6" },
{ label: "1e6d05ff19d5" },
{ label: "16bba3882d72" },
{ label: "a3f7187fcd57" },
{ label: "ad8fe06eeca4" },
{ label: "c8b4e4c47a8e" },
{ label: "8a093bef2179" },
{ label: "8a093bef2179" },
{ label: 'd0eed8dad114' },
{ label: 'f61138f385d3' },
{ label: 'e05f39ada67a' },
]);
});
test('Registry', async () => {
await validateImagesTree(
{
label: 'Registry',
description: [],
groupBy: 'None',
},
[
{ label: "docker.io/library" },
{ label: "docker.io/library" },
{ label: "docker.io/library" },
{ label: "a.b" },
{ label: "abcdefghijklmnopqrstuvw.xyz" },
{ label: "abcdefghijklmnopqrstuvw.xyz" },
{ label: "abcdefghijklmnopqrstuvw.xyz" },
{ label: "registry.gitlab.com" },
{ label: "127.0.0.1:5443" },
{ label: "127.0.0.1:5443" },
{ label: "docker.io/library" },
{ label: "docker.io/library" },
{ label: "docker.io/namespace1" },
{ label: 'localhost' },
{ label: 'localhost:8080' },
]);
});
test('Registry truncate true', async () => {
await validateImagesTree(
{
label: 'Registry',
description: [],
groupBy: 'None',
truncate: true,
},
[
{ label: "doc...rary" },
{ label: "doc...rary" },
{ label: "doc...rary" },
{ label: "a.b" },
{ label: "abc....xyz" },
{ label: "abc....xyz" },
{ label: "abc....xyz" },
{ label: "reg....com" },
{ label: "127...5443" },
{ label: "127...5443" },
{ label: "doc...rary" },
{ label: "doc...rary" },
{ label: "doc...ace1" },
{ label: 'localhost' },
{ label: 'loc...8080' },
]);
});
test('Repository', async () => {
await validateImagesTree(
{
label: 'Repository',
description: [],
groupBy: 'None',
},
[
{ label: "a" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "a.b/abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz" },
{ label: "registry.gitlab.com/sweatherford/hello-world/sub" },
{ label: "127.0.0.1:5443/registry" },
{ label: "127.0.0.1:5443/hello-world/sub" },
{ label: "hello-world" },
{ label: "hello-world" },
{ label: 'namespace1/abc' },
{ label: 'localhost/abc' },
{ label: 'localhost:8080/abc' },
]);
});
test('Repository truncate true', async () => {
await validateImagesTree(
{
label: 'Repository',
description: [],
groupBy: 'None',
truncate: true,
},
[
{ label: "a" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "a.b/abcdefghijklmnopqrstuvwxyz" },
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz" },
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz" },
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz" },
{ label: "reg....com/sweatherford/hello-world/sub" },
{ label: "127...5443/registry" },
{ label: "127...5443/hello-world/sub" },
{ label: "hello-world" },
{ label: "hello-world" },
{ label: 'namespace1/abc' },
{ label: 'localhost/abc' },
{ label: 'loc...8080/abc' },
]);
});
test('RepositoryName', async () => {
await validateImagesTree(
{
label: 'RepositoryName',
description: [],
groupBy: 'None',
},
[
{ label: "a" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz" },
{ label: "sweatherford/hello-world/sub" },
{ label: "registry" },
{ label: "hello-world/sub" },
{ label: "hello-world" },
{ label: "hello-world" },
{ label: 'abc' },
{ label: 'abc' },
{ label: 'abc' },
]);
});
test('RepositoryNameAndTag', async () => {
await validateImagesTree(
{
label: 'RepositoryNameAndTag',
description: [],
groupBy: 'None',
},
[
{ label: "a" },
{ label: "abcdefghijklmnopqrstuvwxyz" },
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
{ label: "abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
{ label: "sweatherford/hello-world/sub:latest" },
{ label: "registry:v2" },
{ label: "hello-world/sub:latest" },
{ label: "hello-world:latest" },
{ label: "hello-world:v1" },
{ label: 'abc:v3' },
{ label: 'abc:v4' },
{ label: 'abc' },
]);
});
test('Tag', async () => {
await validateImagesTree(
{
label: 'Tag',
description: [],
groupBy: 'None',
},
[
{ label: "latest" },
{ label: "latest" },
{ label: "version1.0.test" },
{ label: "latest" },
{ label: "latest" },
{ label: "latest" },
{ label: "latest" },
{ label: "latest" },
{ label: "v2" },
{ label: "latest" },
{ label: "latest" },
{ label: "v1" },
{ label: 'v3' },
{ label: 'v4' },
{ label: 'latest' },
]);
});
test('GroupBy Registry', async () => {
await validateImagesTree(
{
groupBy: 'Registry',
label: 'RepositoryNameAndTag'
},
[
{
label: "127.0.0.1:5443",
children: [
{ label: "registry:v2", description: "2 months ago" },
{ label: "hello-world/sub:latest", description: "2 months ago" }
]
},
{
label: "a.b",
children: [
{ label: "abcdefghijklmnopqrstuvwxyz:latest", description: "5 days ago" }
]
},
{
label: "abcdefghijklmnopqrstuvw.xyz",
children: [
{ label: "abcdefghijklmnopqrstuvwxyz:latest", description: "6 days ago" },
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest", description: "7 days ago" },
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest", description: "8 days ago" }
]
},
{
label: "docker.io/library",
children: [
{ label: "a", description: "2 days ago" },
{ label: "abcdefghijklmnopqrstuvwxyz", description: "3 days ago" },
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test", description: "4 days ago" },
{ label: "hello-world:latest", description: "2 months ago" },
{ label: "hello-world:v1", description: "2 months ago" },
]
},
{
label: "docker.io/namespace1",
children: [
{ label: "abc:v3", description: "a year ago" }
]
},
{
label: "localhost",
children: [
{ label: "abc:v4", description: "a year ago" }
]
},
{
label: "localhost:8080",
children: [
{ label: "abc", description: "a year ago" }
]
},
{
label: "registry.gitlab.com",
children: [
{ label: "sweatherford/hello-world/sub:latest", description: "9 days ago" }
]
}
]);
});
test('GroupBy CreatedTime', async () => {
await validateImagesTree(
{
groupBy: 'CreatedTime',
label: 'FullTag',
description: [],
},
[
{
label: "2 days ago",
children: [
{ label: "a" },
]
},
{
label: "3 days ago",
children: [
{ label: "abcdefghijklmnopqrstuvwxyz" },
]
},
{
label: "4 days ago",
children: [
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
]
},
{
label: "5 days ago",
children: [
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
]
},
{
label: "6 days ago",
children: [
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
]
},
{
label: "7 days ago",
children: [
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
]
},
{
label: "8 days ago",
children: [
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
]
},
{
label: "9 days ago",
children: [
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
]
},
{
label: "2 months ago",
children: [
{ label: "127.0.0.1:5443/registry:v2" },
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
{ label: "hello-world:latest" },
{ label: "hello-world:v1" },
]
},
{
label: "a year ago",
children: [
{ label: "namespace1/abc:v3" },
{ label: "localhost/abc:v4" },
{ label: "localhost:8080/abc" },
]
}
]);
});
test('Multiple descriptions', async () => {
await validateImagesTree(
{
label: 'RepositoryNameAndTag',
description: ['CreatedTime', 'Registry'],
groupBy: 'None',
},
[
{ label: "a", description: '2 days ago - docker.io/library' },
{ label: "abcdefghijklmnopqrstuvwxyz", description: '3 days ago - docker.io/library' },
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test", description: '4 days ago - docker.io/library' },
{ label: "abcdefghijklmnopqrstuvwxyz:latest", description: '5 days ago - a.b' },
{ label: "abcdefghijklmnopqrstuvwxyz:latest", description: '6 days ago - abcdefghijklmnopqrstuvw.xyz' },
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest", description: '7 days ago - abcdefghijklmnopqrstuvw.xyz' },
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest", description: '8 days ago - abcdefghijklmnopqrstuvw.xyz' },
{ label: "sweatherford/hello-world/sub:latest", description: '9 days ago - registry.gitlab.com' },
{ label: "registry:v2", description: '2 months ago - 127.0.0.1:5443' },
{ label: "hello-world/sub:latest", description: '2 months ago - 127.0.0.1:5443' },
{ label: "hello-world:latest", description: '2 months ago - docker.io/library' },
{ label: "hello-world:v1", description: '2 months ago - docker.io/library' },
{ label: 'abc:v3', description: 'a year ago - docker.io/namespace1' },
{ label: 'abc:v4', description: 'a year ago - localhost' },
{ label: 'abc', description: 'a year ago - localhost:8080' },
]);
});
});

Просмотреть файл

@ -0,0 +1,151 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NetworkInspectInfo } from 'dockerode';
import { ext } from '../../extension.bundle';
import { generateCreatedTimeISOString, ITestTreeItem, IValidateTreeOptions, validateTree } from './validateTree';
const testNetworks: Partial<NetworkInspectInfo>[] = [
{
Created: generateCreatedTimeISOString(1),
Name: "zzz-bridge",
Driver: "bridge",
Id: '7fc4ab013fd4aa4c2e749c443b066725eb5599a0d57a9f44951e7a45e8833883'
},
{
Created: generateCreatedTimeISOString(44),
Name: "net-host",
Driver: "host",
Id: '725558b7188f2fa22fce7868597e615c8a90682a2076fe15eee0404cb5f822b6'
},
{
Created: generateCreatedTimeISOString(45),
Name: "none",
Driver: "null",
Id: 'f34848d85589e45cd2856f9c4f3fff218e0ea2b9af76eb56d02607198eab2c1a'
}
];
async function validateNetworksTree(options: IValidateTreeOptions, expectedNodes: ITestTreeItem[]): Promise<void> {
await validateTree(ext.networksRoot, 'networks', options, { networks: testNetworks }, expectedNodes);
}
suite('Networks Tree', async () => {
test('Default Settings', async () => {
await validateNetworksTree(
{},
[
{ label: "zzz-bridge", description: "bridge - a day ago" },
{ label: "net-host", description: "host - a month ago" },
{ label: "none", description: "null - a month ago" },
]);
});
test('Invalid Settings', async () => {
await validateNetworksTree(
{
description: <any>[2, 3],
groupBy: '',
label: null,
sortBy: 'test45'
},
[
{ label: "zzz-bridge", description: "bridge - a day ago" },
{ label: "net-host", description: "host - a month ago" },
{ label: "none", description: "null - a month ago" },
]);
});
test('NetworkDriver', async () => {
await validateNetworksTree(
{
label: 'NetworkDriver',
description: []
},
[
{ label: "bridge" },
{ label: "host" },
{ label: "null" },
]);
});
test('NetworkId', async () => {
await validateNetworksTree(
{
label: 'NetworkId',
description: []
},
[
{ label: "7fc4ab013fd4" },
{ label: "725558b7188f" },
{ label: "f34848d85589" },
]);
});
test('NetworkName', async () => {
await validateNetworksTree(
{
label: 'NetworkName',
description: []
},
[
{ label: "zzz-bridge" },
{ label: "net-host" },
{ label: "none" },
]);
});
test('NetworkName sortBy CreatedTime', async () => {
await validateNetworksTree(
{
label: 'NetworkName',
description: [],
sortBy: 'CreatedTime',
},
[
{ label: "zzz-bridge" },
{ label: "net-host" },
{ label: "none" },
]);
});
test('NetworkName sortBy Label', async () => {
await validateNetworksTree(
{
label: 'NetworkName',
description: [],
sortBy: 'Label',
},
[
{ label: "net-host" },
{ label: "none" },
{ label: "zzz-bridge" },
]);
});
test('GroupBy CreatedTime', async () => {
await validateNetworksTree(
{
groupBy: 'CreatedTime',
description: []
},
[
{
label: "a day ago",
children: [
{ label: "zzz-bridge" },
]
},
{
label: "a month ago",
children: [
{ label: "net-host" },
{ label: "none" },
]
},
]);
});
});

94
test/tree/validateTree.ts Normal file
Просмотреть файл

@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as Dockerode from 'dockerode';
import { AzExtParentTreeItem, AzExtTreeItem, ext, IActionContext } from '../../extension.bundle';
import { runWithSetting } from '../runWithSetting';
export function generateCreatedTimeInSec(days: number): number {
const daysInSec = days * 24 * 60 * 60;
return new Date().valueOf() / 1000 - daysInSec;
}
export function generateCreatedTimeISOString(days: number): string {
const daysInMS = days * 24 * 60 * 60 * 1000;
return new Date(new Date().valueOf() - daysInMS).toISOString();
}
export interface IValidateTreeOptions {
label?: string;
description?: string[];
groupBy?: string;
sortBy?: string;
}
export interface ITestTreeItem {
label: string;
description?: string;
children?: ITestTreeItem[];
}
export async function validateTree(rootTreeItem: AzExtParentTreeItem, treePrefix: string, treeOptions: IValidateTreeOptions, dockerodeOptions: ITestDockerodeOptions, expectedNodes: ITestTreeItem[]): Promise<void> {
await runWithSetting(`${treePrefix}.sortBy`, treeOptions.sortBy, async () => {
await runWithSetting(`${treePrefix}.groupBy`, treeOptions.groupBy, async () => {
await runWithSetting(`${treePrefix}.label`, treeOptions.label, async () => {
await runWithSetting(`${treePrefix}.description`, treeOptions.description, async () => {
await runWithDockerode(dockerodeOptions, async () => {
await rootTreeItem.refresh();
const context: IActionContext = { telemetry: { properties: {}, measurements: {} }, errorHandling: {} };
const actualNodes = await rootTreeItem.getCachedChildren(context);
const actual = await Promise.all(actualNodes.map(async node => {
const actualNode: ITestTreeItem = convertToTestTreeItem(node);
if (node instanceof AzExtParentTreeItem) {
const children = await node.getCachedChildren(context);
actualNode.children = children.map(convertToTestTreeItem);
}
return actualNode;
}));
assert.deepStrictEqual(actual, expectedNodes);
});
});
});
});
});
}
interface ITestDockerodeOptions {
containers?: Partial<Dockerode.ContainerInfo>[],
images?: Partial<Dockerode.ImageInfo>[],
volumes?: Partial<Dockerode.VolumeInspectInfo>[],
networks?: Partial<Dockerode.NetworkInspectInfo>[]
}
async function runWithDockerode(options: ITestDockerodeOptions, callback: () => Promise<void>): Promise<void> {
const oldDockerode = ext.dockerode;
try {
ext.dockerode = <Dockerode><any>{
listContainers: async () => options.containers,
listImages: async () => options.images,
listVolumes: async () => { return { Volumes: options.volumes } },
listNetworks: async () => options.networks
};
await callback();
} finally {
ext.dockerode = oldDockerode;
}
}
function convertToTestTreeItem(node: AzExtTreeItem): ITestTreeItem {
const actualNode: ITestTreeItem = { label: node.label };
if (node.description) {
actualNode.description = node.description;
}
assert.ok(node.id);
assert.ok(node.contextValue);
assert.ok(node.iconPath);
return actualNode;
}

Просмотреть файл

@ -0,0 +1,134 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VolumeInspectInfo } from 'dockerode';
import { ext } from '../../extension.bundle';
import { generateCreatedTimeISOString, ITestTreeItem, IValidateTreeOptions, validateTree } from './validateTree';
const testVolumes: Partial<VolumeInspectInfo & { CreatedAt: string }>[] = [
{
CreatedAt: generateCreatedTimeISOString(1),
Name: "nginxVol",
},
{
CreatedAt: generateCreatedTimeISOString(44),
Name: "my-vol",
},
{
CreatedAt: generateCreatedTimeISOString(45),
Name: "zz",
},
{
CreatedAt: generateCreatedTimeISOString(90),
Name: "83c3eaffa92c0caf9ab34df3931f37b094464cb0daaab274c482010129fc7c73",
}
];
async function validateVolumesTree(options: IValidateTreeOptions, expectedNodes: ITestTreeItem[]): Promise<void> {
await validateTree(ext.volumesRoot, 'volumes', options, { volumes: testVolumes }, expectedNodes);
}
suite('Volumes Tree', async () => {
test('Default Settings', async () => {
await validateVolumesTree(
{},
[
{ label: "nginxVol", description: "a day ago" },
{ label: "my-vol", description: "a month ago" },
{ label: "zz", description: "a month ago" },
{ label: "83c3eaffa92c0caf9ab34df3931f37b094464cb0daaab274c482010129fc7c73", description: "3 months ago" },
]);
});
test('Invalid Settings', async () => {
await validateVolumesTree(
{
description: ['test2', 'test3'],
groupBy: 'test44',
label: <any>{},
sortBy: 'test45'
},
[
{ label: "nginxVol", description: "a day ago" },
{ label: "my-vol", description: "a month ago" },
{ label: "zz", description: "a month ago" },
{ label: "83c3eaffa92c0caf9ab34df3931f37b094464cb0daaab274c482010129fc7c73", description: "3 months ago" },
]);
});
test('VolumeName', async () => {
await validateVolumesTree(
{
label: 'VolumeName',
description: []
},
[
{ label: "nginxVol" },
{ label: "my-vol" },
{ label: "zz" },
{ label: "83c3eaffa92c0caf9ab34df3931f37b094464cb0daaab274c482010129fc7c73" },
]);
});
test('VolumeName sortBy CreatedTime', async () => {
await validateVolumesTree(
{
label: 'VolumeName',
description: [],
sortBy: 'CreatedTime',
},
[
{ label: "nginxVol" },
{ label: "my-vol" },
{ label: "zz" },
{ label: "83c3eaffa92c0caf9ab34df3931f37b094464cb0daaab274c482010129fc7c73" },
]);
});
test('VolumeName sortBy Label', async () => {
await validateVolumesTree(
{
label: 'VolumeName',
description: [],
sortBy: 'Label',
},
[
{ label: "83c3eaffa92c0caf9ab34df3931f37b094464cb0daaab274c482010129fc7c73" },
{ label: "my-vol" },
{ label: "nginxVol" },
{ label: "zz" },
]);
});
test('GroupBy CreatedTime', async () => {
await validateVolumesTree(
{
groupBy: 'CreatedTime',
description: []
},
[
{
label: "a day ago",
children: [
{ label: "nginxVol" },
]
},
{
label: "a month ago",
children: [
{ label: "my-vol" },
{ label: "zz" },
]
},
{
label: "3 months ago",
children: [
{ label: "83c3eaffa92c0caf9ab34df3931f37b094464cb0daaab274c482010129fc7c73" },
]
},
]);
});
});