From dd3a1ec0d350f2ac87d0678afa7da6f32732c79f Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Thu, 24 Sep 2020 09:38:40 -0400 Subject: [PATCH 1/8] Implement compose group commands --- package.json | 30 +++++++++++++++++++ package.nls.json | 2 ++ src/commands/compose.ts | 2 +- src/commands/registerCommands.ts | 3 ++ src/tree/containers/ContainerGroupTreeItem.ts | 10 +++++-- src/tree/containers/ContainerTreeItem.ts | 4 +++ 6 files changed, 48 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 862e8ef5f2..5010d3e1df 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,8 @@ "onCommand:vscode-docker.containers.start", "onCommand:vscode-docker.containers.stop", "onCommand:vscode-docker.containers.viewLogs", + "onCommand:vscode-docker.containers.composeGroup.restart", + "onCommand:vscode-docker.containers.composeGroup.down", "onCommand:vscode-docker.debugging.initializeForDebugging", "onCommand:vscode-docker.help.reportIssue", "onCommand:vscode-docker.images.build", @@ -145,6 +147,14 @@ { "command": "vscode-docker.registries.deployImageToAci", "when": "vscode-docker:newCliPresent" + }, + { + "command": "vscode-docker.containers.composeGroup.restart", + "when": "never" + }, + { + "command": "vscode-docker.containers.composeGroup.down", + "when": "never" } ], "editor/context": [ @@ -374,6 +384,16 @@ "when": "view == dockerContainers && viewItem =~ /container$/i", "group": "containers_2_destructive@1" }, + { + "command": "vscode-docker.containers.composeGroup.restart", + "when": "view == dockerContainers && viewItem =~ /composeGroup$/i && !vscode-docker:aciContext", + "group": "composeGroup_1_destructive@1" + }, + { + "command": "vscode-docker.containers.composeGroup.down", + "when": "view == dockerContainers && viewItem =~ /composeGroup$/i && !vscode-docker:aciContext", + "group": "composeGroup_1_destructive@2" + }, { "command": "vscode-docker.images.run", "when": "view == dockerImages && viewItem == image", @@ -2286,6 +2306,16 @@ "title": "%vscode-docker.commands.containers.viewLogs%", "category": "%vscode-docker.commands.category.dockerContainers%" }, + { + "command": "vscode-docker.containers.composeGroup.restart", + "title": "%vscode-docker.commands.containers.composeGroup.restart%", + "category": "%vscode-docker.commands.category.dockerContainers%" + }, + { + "command": "vscode-docker.containers.composeGroup.down", + "title": "%vscode-docker.commands.containers.composeGroup.down%", + "category": "%vscode-docker.commands.category.dockerContainers%" + }, { "command": "vscode-docker.debugging.initializeForDebugging", "title": "%vscode-docker.commands.debugging.initializeForDebugging%", diff --git a/package.nls.json b/package.nls.json index db6e436fd7..edf6c105cb 100644 --- a/package.nls.json +++ b/package.nls.json @@ -188,6 +188,8 @@ "vscode-docker.commands.containers.start": "Start", "vscode-docker.commands.containers.stop": "Stop", "vscode-docker.commands.containers.viewLogs": "View Logs", + "vscode-docker.commands.containers.composeGroup.restart": "Compose Restart", + "vscode-docker.commands.containers.composeGroup.down": "Compose Down", "vscode-docker.commands.debugging.initializeForDebugging": "Initialize for Docker debugging", "vscode-docker.commands.help.reportIssue": "Report Issue", "vscode-docker.commands.images.build": "Build Image...", diff --git a/src/commands/compose.ts b/src/commands/compose.ts index 88893312aa..ca295ab0cb 100644 --- a/src/commands/compose.ts +++ b/src/commands/compose.ts @@ -74,7 +74,7 @@ export async function composeRestart(context: IActionContext, dockerComposeFileU return await compose(context, ['down', 'up'], localize('vscode-docker.commands.compose.chooseRestart', 'Choose Docker Compose file to restart'), dockerComposeFileUri, selectedComposeFileUris); } -async function rewriteCommandForNewCliIfNeeded(command: string): Promise { +export async function rewriteCommandForNewCliIfNeeded(command: string): Promise { if ((await ext.dockerContextManager.getCurrentContext()).Type === 'aci') { // Replace 'docker-compose ' at the start of a string with 'docker compose ', and '--build' anywhere with '' return command.replace(/^docker-compose /, 'docker compose ').replace(/--build/, ''); diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index 59b186255e..9e0d7d5f37 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -14,6 +14,7 @@ import { viewAzureTaskLogs } from "../utils/lazyLoad"; import { composeDown, composeRestart, composeUp } from "./compose"; import { attachShellContainer } from "./containers/attachShellContainer"; import { browseContainer } from "./containers/browseContainer"; +import { composeGroupDown, composeGroupRestart } from "./containers/composeGroup"; import { configureContainersExplorer } from "./containers/configureContainersExplorer"; import { inspectContainer } from "./containers/inspectContainer"; import { pruneContainers } from "./containers/pruneContainers"; @@ -111,6 +112,8 @@ export function registerCommands(): void { registerCommand('vscode-docker.containers.start', startContainer); registerCommand('vscode-docker.containers.stop', stopContainer); registerWorkspaceCommand('vscode-docker.containers.viewLogs', viewContainerLogs); + registerCommand('vscode-docker.containers.composeGroup.restart', composeGroupRestart); + registerCommand('vscode-docker.containers.composeGroup.down', composeGroupDown); registerWorkspaceCommand('vscode-docker.images.build', buildImage); registerCommand('vscode-docker.images.configureExplorer', configureImagesExplorer); diff --git a/src/tree/containers/ContainerGroupTreeItem.ts b/src/tree/containers/ContainerGroupTreeItem.ts index e05dae53df..c4bbba3d08 100644 --- a/src/tree/containers/ContainerGroupTreeItem.ts +++ b/src/tree/containers/ContainerGroupTreeItem.ts @@ -11,10 +11,16 @@ import { LocalGroupTreeItemBase } from "../LocalGroupTreeItemBase"; import { ContainerProperty, getContainerStateIcon } from "./ContainerProperties"; export class ContainerGroupTreeItem extends LocalGroupTreeItemBase { - public static readonly contextValue: string = 'containerGroup'; - public readonly contextValue: string = ContainerGroupTreeItem.contextValue; public childTypeLabel: string = 'container'; + public get contextValue(): string { + if (this.parent.groupBySetting === 'Compose Project Name') { + return 'containerGroup;composeGroup'; + } + + return 'containerGroup'; + } + public get iconPath(): IconPath { let icon: string; switch (this.parent.groupBySetting) { diff --git a/src/tree/containers/ContainerTreeItem.ts b/src/tree/containers/ContainerTreeItem.ts index 5727b6ea91..2515ea0527 100644 --- a/src/tree/containers/ContainerTreeItem.ts +++ b/src/tree/containers/ContainerTreeItem.ts @@ -40,6 +40,10 @@ export class ContainerTreeItem extends AzExtTreeItem { return this._item.Image; } + public get labels(): { [key: string]: string } { + return this._item.Labels; + } + public get label(): string { return ext.containersRoot.getTreeItemLabel(this._item); } From 85f143478d64a5e05bda5df60fe9189fade8da6d Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Thu, 24 Sep 2020 09:38:54 -0400 Subject: [PATCH 2/8] Include that file... --- src/commands/containers/composeGroup.ts | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/commands/containers/composeGroup.ts diff --git a/src/commands/containers/composeGroup.ts b/src/commands/containers/composeGroup.ts new file mode 100644 index 0000000000..7d4afc9f3b --- /dev/null +++ b/src/commands/containers/composeGroup.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ContainerGroupTreeItem } from '../../tree/containers/ContainerGroupTreeItem'; +import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem'; +import { executeAsTask } from '../../utils/executeAsTask'; +import { rewriteCommandForNewCliIfNeeded } from '../compose'; + +export async function composeGroupRestart(context: IActionContext, node: ContainerGroupTreeItem): Promise { + return composeGroup(context, 'restart', node); +} + +export async function composeGroupDown(context: IActionContext, node: ContainerGroupTreeItem): Promise { + return composeGroup(context, 'down', node); +} + +async function composeGroup(context: IActionContext, composeCommand: 'restart' | 'down', node: ContainerGroupTreeItem): Promise { + const workingDirectory = getComposeWorkingDirectory(node); + const filesArgument = getComposeFiles(node).map(f => `-f ${f}`).join(' '); + + const terminalCommand = `docker-compose ${filesArgument} ${composeCommand}`; + + await executeAsTask(context, await rewriteCommandForNewCliIfNeeded(terminalCommand), 'Docker Compose', { addDockerEnv: true, cwd: workingDirectory, }); +} + +function getComposeWorkingDirectory(node: ContainerGroupTreeItem): string { + // Find a container with the `com.docker.compose.project.working_dir` label, which gives the working directory in which to execute the compose command + const container = (node.ChildTreeItems as ContainerTreeItem[]).find(c => c.labels['com.docker.compose.project.working_dir']); + return container.labels['com.docker.compose.project.working_dir']; +} + +function getComposeFiles(node: ContainerGroupTreeItem): string[] { + // Find a container with the `com.docker.compose.project.config_files` label, which gives all the compose files (within the working directory) used to up this container + const container = (node.ChildTreeItems as ContainerTreeItem[]).find(c => c.labels['com.docker.compose.project.config_files']); + const filesLabel = container.labels['com.docker.compose.project.config_files']; + return filesLabel.split(','); +} From 415be460f759b913758e5c1ddbbb17b2708e64be Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Thu, 24 Sep 2020 09:47:04 -0400 Subject: [PATCH 3/8] Make compose group by and expanded default --- package.json | 2 +- src/tree/containers/ContainerGroupTreeItem.ts | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5010d3e1df..958d36c6a4 100644 --- a/package.json +++ b/package.json @@ -1761,7 +1761,7 @@ }, "docker.containers.groupBy": { "type": "string", - "default": "None", + "default": "Compose Project Name", "description": "%vscode-docker.config.docker.containers.groupBy%", "enum": [ "Compose Project Name", diff --git a/src/tree/containers/ContainerGroupTreeItem.ts b/src/tree/containers/ContainerGroupTreeItem.ts index c4bbba3d08..61ef63f99e 100644 --- a/src/tree/containers/ContainerGroupTreeItem.ts +++ b/src/tree/containers/ContainerGroupTreeItem.ts @@ -3,16 +3,27 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { TreeItem, TreeItemCollapsibleState } from "vscode"; import { AzExtTreeItem } from "vscode-azureextensionui"; import { DockerContainer } from "../../docker/Containers"; import { getThemedIconPath, IconPath } from "../IconPath"; import { getImageGroupIcon } from "../images/ImageProperties"; import { LocalGroupTreeItemBase } from "../LocalGroupTreeItemBase"; +import { LocalRootTreeItemBase } from "../LocalRootTreeItemBase"; import { ContainerProperty, getContainerStateIcon } from "./ContainerProperties"; export class ContainerGroupTreeItem extends LocalGroupTreeItemBase { public childTypeLabel: string = 'container'; + public constructor(parent: LocalRootTreeItemBase, group: string, items: DockerContainer[]) { + super(parent, group, items); + + if (this.parent.groupBySetting === 'Compose Project Name') { + // Expand compose group nodes by default + (this as TreeItem).collapsibleState = TreeItemCollapsibleState.Expanded; + } + } + public get contextValue(): string { if (this.parent.groupBySetting === 'Compose Project Name') { return 'containerGroup;composeGroup'; From 288b8f22c1274beb73b1351628c90b129a872656 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Mon, 28 Sep 2020 12:13:04 -0400 Subject: [PATCH 4/8] Fix tests --- extension.bundle.ts | 1 + src/tree/containers/ContainersTreeItem.ts | 4 + test/tree/containersTree.test.ts | 265 +++++++++++++++++----- 3 files changed, 212 insertions(+), 58 deletions(-) diff --git a/extension.bundle.ts b/extension.bundle.ts index ce43870df5..ec0be91d93 100644 --- a/extension.bundle.ts +++ b/extension.bundle.ts @@ -43,5 +43,6 @@ export { DockerImage } from './src/docker/Images'; export { DockerNetwork } from './src/docker/Networks'; export { DockerVolume } from './src/docker/Volumes'; export { CommandTemplate, selectCommandTemplate, defaultCommandTemplates } from './src/commands/selectCommandTemplate'; +export { NonComposeGroupName } from './src/tree/containers/ContainersTreeItem'; export * from 'vscode-azureextensionui'; diff --git a/src/tree/containers/ContainersTreeItem.ts b/src/tree/containers/ContainersTreeItem.ts index 398505a7dd..fd863100a1 100644 --- a/src/tree/containers/ContainersTreeItem.ts +++ b/src/tree/containers/ContainersTreeItem.ts @@ -124,6 +124,10 @@ export class ContainersTreeItem extends LocalRootTreeItemBase ({ label: label, value: container.Labels[label] })); diff --git a/test/tree/containersTree.test.ts b/test/tree/containersTree.test.ts index 586e87fcc5..67cb3c59a7 100644 --- a/test/tree/containersTree.test.ts +++ b/test/tree/containersTree.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { ext, DockerContainer } from '../../extension.bundle'; +import { NonComposeGroupName } from '../../src/tree/containers/ContainersTreeItem'; import { generateCreatedTimeInMs, ITestTreeItem, IValidateTreeOptions, validateTree } from './validateTree'; const testContainers: DockerContainer[] = [ @@ -17,6 +18,7 @@ const testContainers: DockerContainer[] = [ Ports: [], State: "created", Status: "Created", + Labels: { "com.docker.compose.project": "proj1" } }, { Id: "faeb6f02af06df748a0040476ba7c335fb8aaefd76f6ea14a76800faf0fa3910", @@ -29,6 +31,7 @@ const testContainers: DockerContainer[] = [ ], State: "running", Status: "Up 6 minutes", + Labels: { "com.docker.compose.project": "proj1" } }, { Id: "99636d5207b3da8a9865ef931aa3c758688e795e7787a6982fc7b5da07a5de8c", @@ -39,6 +42,7 @@ const testContainers: DockerContainer[] = [ Ports: [], State: "paused", Status: "Up 8 minutes (Paused)", + Labels: { "com.docker.compose.project": "proj2" } }, { Id: "49df1ed4a46c2617025298a8bdb01bc37267ecae82fc8ab88b0504314d94b983", @@ -52,6 +56,7 @@ const testContainers: DockerContainer[] = [ ], State: "running", Status: "Up 8 minutes", + Labels: { "com.docker.compose.project": "proj2" } }, { Id: "ee098ec2fb0b337e4f480a1a33dd1d396ef6b242579bb8b874e480957c053f34", @@ -62,6 +67,7 @@ const testContainers: DockerContainer[] = [ Ports: [], State: "exited", Status: "Exited (137) 12 hours ago", + Labels: { "com.docker.compose.project": "proj3" } }, { Id: "5e25d05c0797d44c0efaf3479633316f9229e3f71feccfbe2278c35681c0436f", @@ -72,6 +78,7 @@ const testContainers: DockerContainer[] = [ Ports: [{ IP: "0.0.0.0", PrivatePort: 80, PublicPort: 80, Type: "tcp" }], State: "running", Status: "Up 32 hours", + Labels: { "com.docker.compose.project": "proj3" } }, { Id: "531005593f5da6f15ce13a6149a9b4866608fad5bddc600d37239e3d9976f00f", @@ -81,7 +88,8 @@ const testContainers: DockerContainer[] = [ CreatedTime: generateCreatedTimeInMs(90), Ports: [], State: "running", - Status: "Up 49 seconds" + Status: "Up 49 seconds", + Labels: { "com.docker.compose.project": "proj3" } }, { Id: "99fd96f9cdf9fb7668887477f91b0c72682461690ff83030e8a6aa63a871f63a", @@ -104,14 +112,34 @@ suite('Containers Tree', 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" }, + { + label: "proj1", + children: [ + { label: "node:8.0", description: "vigorous_booth - Created" }, + { label: "registry:latest", description: "elegant_knuth - Up 6 minutes" }, + ] + }, + { + label: "proj2", + children: [ + { 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: "proj3", + children: [ + { 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: NonComposeGroupName, + children: [ + { label: "nginx:latest", description: "devtest - Exited (0) 2 days ago" }, + ] + }, ]); }); @@ -142,14 +170,34 @@ suite('Containers Tree', async () => { description: [] }, [ - { label: "vigorous_booth" }, - { label: "elegant_knuth" }, - { label: "focused_cori" }, - { label: "zealous_napier" }, - { label: "admiring_leavitt" }, - { label: "inspiring_brattain" }, - { label: "elegant_mendel" }, - { label: "devtest" }, + { + label: "proj1", + children: [ + { label: "vigorous_booth" }, + { label: "elegant_knuth" }, + ] + }, + { + label: "proj2", + children: [ + { label: "focused_cori" }, + { label: "zealous_napier" }, + ] + }, + { + label: "proj3", + children: [ + { label: "admiring_leavitt" }, + { label: "inspiring_brattain" }, + { label: "elegant_mendel" }, + ] + }, + { + label: NonComposeGroupName, + children: [ + { label: "devtest" }, + ] + }, ]); }); @@ -160,14 +208,34 @@ suite('Containers Tree', async () => { description: [] }, [ - { label: "9330566c4144" }, - { label: "faeb6f02af06" }, - { label: "99636d5207b3" }, - { label: "49df1ed4a46c" }, - { label: "ee098ec2fb0b" }, - { label: "5e25d05c0797" }, - { label: "531005593f5d" }, - { label: "99fd96f9cdf9" }, + { + label: "proj1", + children: [ + { label: "9330566c4144" }, + { label: "faeb6f02af06" }, + ] + }, + { + label: "proj2", + children: [ + { label: "99636d5207b3" }, + { label: "49df1ed4a46c" }, + ] + }, + { + label: "proj3", + children: [ + { label: "ee098ec2fb0b" }, + { label: "5e25d05c0797" }, + { label: "531005593f5d" }, + ] + }, + { + label: NonComposeGroupName, + children: [ + { label: "99fd96f9cdf9" }, + ] + }, ]); }); @@ -178,14 +246,34 @@ suite('Containers Tree', async () => { description: [] }, [ - { label: "" }, - { label: "5000" }, - { label: "" }, - { label: "2222,8000" }, - { label: "" }, - { label: "80" }, - { label: "" }, - { label: "" }, + { + label: "proj1", + children: [ + { label: "" }, + { label: "5000" }, + ] + }, + { + label: "proj2", + children: [ + { label: "" }, + { label: "2222,8000" }, + ] + }, + { + label: "proj3", + children: [ + { label: "" }, + { label: "80" }, + { label: "" }, + ] + }, + { + label: NonComposeGroupName, + children: [ + { label: "" }, + ] + }, ]); }); @@ -196,14 +284,34 @@ suite('Containers Tree', async () => { 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" }, + { + label: "proj1", + children: [ + { label: "Created" }, + { label: "Up 6 minutes" }, + ] + }, + { + label: "proj2", + children: [ + { label: "Up 8 minutes (Paused)" }, + { label: "Up 8 minutes" }, + ] + }, + { + label: "proj3", + children: [ + { label: "Exited (137) 12 hours ago" }, + { label: "Up 32 hours" }, + { label: "Up 49 seconds" }, + ] + }, + { + label: NonComposeGroupName, + children: [ + { label: "Exited (0) 2 days ago" }, + ] + }, ]); }); @@ -214,14 +322,34 @@ suite('Containers Tree', async () => { description: [] }, [ - { label: "created" }, - { label: "running" }, - { label: "paused" }, - { label: "running" }, - { label: "exited" }, - { label: "running" }, - { label: "running" }, - { label: "exited" }, + { + label: "proj1", + children: [ + { label: "created" }, + { label: "running" }, + ] + }, + { + label: "proj2", + children: [ + { label: "paused" }, + { label: "running" }, + ] + }, + { + label: "proj3", + children: [ + { label: "exited" }, + { label: "running" }, + { label: "running" }, + ] + }, + { + label: NonComposeGroupName, + children: [ + { label: "exited" }, + ] + }, ]); }); @@ -233,20 +361,41 @@ suite('Containers Tree', async () => { 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" }, + { + label: "proj1", + children: [ + { label: "vigorous_booth" }, + { label: "elegant_knuth" }, + ] + }, + { + label: "proj2", + children: [ + { label: "focused_cori" }, + { label: "zealous_napier" }, + ] + }, + { + label: "proj3", + children: [ + { label: "admiring_leavitt" }, + { label: "inspiring_brattain" }, + { label: "elegant_mendel" }, + ] + }, + { + label: NonComposeGroupName, + children: [ + { label: "devtest" }, + ] + }, ]); }); test('ContainerName sortBy Label', async () => { await validateContainersTree( { + groupBy: 'None', label: 'ContainerName', description: [], sortBy: 'Label', @@ -404,7 +553,7 @@ suite('Containers Tree', async () => { { label: "registry:latest", description: "elegant_knuth - Up 6 minutes" }, ]; - const actualNodes = await validateTree(ext.containersRoot, 'containers', {}, { containers: containers }, expectedNodes); + const actualNodes = await validateTree(ext.containersRoot, 'containers', { groupBy: 'None' }, { containers: containers }, expectedNodes); assert.equal(actualNodes[0].contextValue, 'runningContainer', 'Must have context value "runningContainer"'); assert.equal((actualNodes[0]).containerDesc.Id, 'faeb6f02af06df748a0040476ba7c335fb8aaefd76f6ea14a76800faf0fa3910', 'Must have property "containerDesc"'); From 5b924129eccb700d18e413e51ba169bf2507d3a3 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Mon, 28 Sep 2020 12:41:13 -0400 Subject: [PATCH 5/8] Bug fixes --- src/commands/containers/composeGroup.ts | 21 ++++++++++++------- src/tree/containers/ContainerGroupTreeItem.ts | 3 ++- test/tree/containersTree.test.ts | 3 +-- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/commands/containers/composeGroup.ts b/src/commands/containers/composeGroup.ts index 7d4afc9f3b..6bb3b7aeb8 100644 --- a/src/commands/containers/composeGroup.ts +++ b/src/commands/containers/composeGroup.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext } from 'vscode-azureextensionui'; +import { localize } from '../../localize'; import { ContainerGroupTreeItem } from '../../tree/containers/ContainerGroupTreeItem'; import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem'; import { executeAsTask } from '../../utils/executeAsTask'; @@ -19,22 +20,26 @@ export async function composeGroupDown(context: IActionContext, node: ContainerG async function composeGroup(context: IActionContext, composeCommand: 'restart' | 'down', node: ContainerGroupTreeItem): Promise { const workingDirectory = getComposeWorkingDirectory(node); - const filesArgument = getComposeFiles(node).map(f => `-f ${f}`).join(' '); + const filesArgument = getComposeFiles(node)?.map(f => `-f ${f}`)?.join(' '); + + if (!workingDirectory || !filesArgument) { + context.errorHandling.suppressReportIssue = true; + throw new Error(localize('vscode-docker.commands.containers.composeGroup.noCompose', 'Unable to determine compose project info for container group \'{0}\'.', node.label)); + } const terminalCommand = `docker-compose ${filesArgument} ${composeCommand}`; await executeAsTask(context, await rewriteCommandForNewCliIfNeeded(terminalCommand), 'Docker Compose', { addDockerEnv: true, cwd: workingDirectory, }); } -function getComposeWorkingDirectory(node: ContainerGroupTreeItem): string { +function getComposeWorkingDirectory(node: ContainerGroupTreeItem): string | undefined { // Find a container with the `com.docker.compose.project.working_dir` label, which gives the working directory in which to execute the compose command - const container = (node.ChildTreeItems as ContainerTreeItem[]).find(c => c.labels['com.docker.compose.project.working_dir']); - return container.labels['com.docker.compose.project.working_dir']; + const container = (node.ChildTreeItems as ContainerTreeItem[]).find(c => c.labels?.['com.docker.compose.project.working_dir']); + return container?.labels?.['com.docker.compose.project.working_dir']; } -function getComposeFiles(node: ContainerGroupTreeItem): string[] { +function getComposeFiles(node: ContainerGroupTreeItem): string[] | undefined { // Find a container with the `com.docker.compose.project.config_files` label, which gives all the compose files (within the working directory) used to up this container - const container = (node.ChildTreeItems as ContainerTreeItem[]).find(c => c.labels['com.docker.compose.project.config_files']); - const filesLabel = container.labels['com.docker.compose.project.config_files']; - return filesLabel.split(','); + const container = (node.ChildTreeItems as ContainerTreeItem[]).find(c => c.labels?.['com.docker.compose.project.config_files']); + return container?.labels?.['com.docker.compose.project.config_files']?.split(','); } diff --git a/src/tree/containers/ContainerGroupTreeItem.ts b/src/tree/containers/ContainerGroupTreeItem.ts index 61ef63f99e..27a6e5ca7e 100644 --- a/src/tree/containers/ContainerGroupTreeItem.ts +++ b/src/tree/containers/ContainerGroupTreeItem.ts @@ -11,6 +11,7 @@ import { getImageGroupIcon } from "../images/ImageProperties"; import { LocalGroupTreeItemBase } from "../LocalGroupTreeItemBase"; import { LocalRootTreeItemBase } from "../LocalRootTreeItemBase"; import { ContainerProperty, getContainerStateIcon } from "./ContainerProperties"; +import { NonComposeGroupName } from "./ContainersTreeItem"; export class ContainerGroupTreeItem extends LocalGroupTreeItemBase { public childTypeLabel: string = 'container'; @@ -25,7 +26,7 @@ export class ContainerGroupTreeItem extends LocalGroupTreeItemBase Date: Tue, 29 Sep 2020 09:19:39 -0400 Subject: [PATCH 6/8] Karol's feedback --- src/commands/containers/composeGroup.ts | 3 ++- src/tree/containers/ContainersTreeItem.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/commands/containers/composeGroup.ts b/src/commands/containers/composeGroup.ts index 6bb3b7aeb8..08080b01f6 100644 --- a/src/commands/containers/composeGroup.ts +++ b/src/commands/containers/composeGroup.ts @@ -8,6 +8,7 @@ import { localize } from '../../localize'; import { ContainerGroupTreeItem } from '../../tree/containers/ContainerGroupTreeItem'; import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem'; import { executeAsTask } from '../../utils/executeAsTask'; +import { isWindows } from '../../utils/osUtils'; import { rewriteCommandForNewCliIfNeeded } from '../compose'; export async function composeGroupRestart(context: IActionContext, node: ContainerGroupTreeItem): Promise { @@ -20,7 +21,7 @@ export async function composeGroupDown(context: IActionContext, node: ContainerG async function composeGroup(context: IActionContext, composeCommand: 'restart' | 'down', node: ContainerGroupTreeItem): Promise { const workingDirectory = getComposeWorkingDirectory(node); - const filesArgument = getComposeFiles(node)?.map(f => `-f ${f}`)?.join(' '); + const filesArgument = getComposeFiles(node)?.map(f => isWindows() ? `-f "${f}"` : `-f '${f}'`)?.join(' '); if (!workingDirectory || !filesArgument) { context.errorHandling.suppressReportIssue = true; diff --git a/src/tree/containers/ContainersTreeItem.ts b/src/tree/containers/ContainersTreeItem.ts index fd863100a1..b831fc5aee 100644 --- a/src/tree/containers/ContainersTreeItem.ts +++ b/src/tree/containers/ContainersTreeItem.ts @@ -121,7 +121,7 @@ export class ContainersTreeItem extends LocalRootTreeItemBase Date: Tue, 29 Sep 2020 10:37:00 -0400 Subject: [PATCH 7/8] Fix issue with lazy's not clearing --- src/utils/lazy.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/lazy.ts b/src/utils/lazy.ts index 95801b61c8..4297928c3f 100644 --- a/src/utils/lazy.ts +++ b/src/utils/lazy.ts @@ -63,6 +63,7 @@ export class AsyncLazy { public clear(): void { this._isValueCreated = false; + this._valuePromise = undefined; } public async getValue(): Promise { From 57486c2815cefaad335b8f3a5b210b2e4ea6f74e Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Wed, 30 Sep 2020 13:55:17 -0400 Subject: [PATCH 8/8] Change to individual --- src/tree/containers/ContainersTreeItem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tree/containers/ContainersTreeItem.ts b/src/tree/containers/ContainersTreeItem.ts index b831fc5aee..f85d6491ed 100644 --- a/src/tree/containers/ContainersTreeItem.ts +++ b/src/tree/containers/ContainersTreeItem.ts @@ -121,7 +121,7 @@ export class ContainersTreeItem extends LocalRootTreeItemBase