diff --git a/package.json b/package.json index 3f593aa90b..56f5ac4cfd 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "onCommand:vscode-docker.images.build", "onCommand:vscode-docker.images.configureExplorer", "onCommand:vscode-docker.images.inspect", + "onCommand:vscode-docker.images.showDangling", "onCommand:vscode-docker.images.prune", "onCommand:vscode-docker.images.pull", "onCommand:vscode-docker.images.push", @@ -290,6 +291,11 @@ "when": "view == dockerImages && !vscode-docker:newSdkContext", "group": "navigation@2" }, + { + "command": "vscode-docker.images.showDangling", + "when": "view == dockerImages && !vscode-docker:newSdkContext", + "group": "navigation@2" + }, { "command": "vscode-docker.images.configureExplorer", "when": "view == dockerImages", @@ -2354,6 +2360,12 @@ "category": "%vscode-docker.commands.category.dockerImages%", "icon": "$(clear-all)" }, + { + "command": "vscode-docker.images.showDangling", + "title": "%vscode-docker.commands.images.showDangling%", + "category": "%vscode-docker.commands.category.dockerImages%", + "icon": "$(outline-view-icon)" + }, { "command": "vscode-docker.images.pull", "title": "%vscode-docker.commands.images.pull%", diff --git a/package.nls.json b/package.nls.json index bccc2458aa..0b3edf1d99 100644 --- a/package.nls.json +++ b/package.nls.json @@ -218,6 +218,7 @@ "vscode-docker.commands.images.build": "Build Image...", "vscode-docker.commands.images.configureExplorer": "Configure Explorer...", "vscode-docker.commands.images.inspect": "Inspect", + "vscode-docker.commands.images.showDangling": "Show dangling images", "vscode-docker.commands.images.prune": "Prune...", "vscode-docker.commands.images.pull": "Pull", "vscode-docker.commands.images.push": "Push...", diff --git a/src/commands/images/showDanglingImages.ts b/src/commands/images/showDanglingImages.ts new file mode 100644 index 0000000000..dc8d007f61 --- /dev/null +++ b/src/commands/images/showDanglingImages.ts @@ -0,0 +1,8 @@ + +import { ext } from "../../extensionVariables"; +import { IActionContext } from 'vscode-azureextensionui'; + +export async function showDanglingImages(context: IActionContext): Promise<void> { + const conf: boolean = await ext.context.globalState.get('vscode-docker.images.showDanglingImages', false); + await ext.context.globalState.update('vscode-docker.images.showDanglingImages', !conf); +} diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index 319ea12513..2681406fe8 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -34,6 +34,7 @@ import { buildImage } from "./images/buildImage"; import { configureImagesExplorer } from "./images/configureImagesExplorer"; import { copyFullTag } from "./images/copyFullTag"; import { inspectImage } from "./images/inspectImage"; +import { showDanglingImages } from "./images/showDanglingImages"; import { pruneImages } from "./images/pruneImages"; import { pullImage } from "./images/pullImage"; import { pushImage } from "./images/pushImage"; @@ -142,6 +143,7 @@ export function registerCommands(): void { registerCommand('vscode-docker.images.configureExplorer', configureImagesExplorer); registerCommand('vscode-docker.images.inspect', inspectImage); registerCommand('vscode-docker.images.prune', pruneImages); + registerCommand('vscode-docker.images.showDangling', showDanglingImages); registerWorkspaceCommand('vscode-docker.images.pull', pullImage); registerWorkspaceCommand('vscode-docker.images.push', pushImage); registerCommand('vscode-docker.images.remove', removeImage); diff --git a/src/docker/DockerApiClient.ts b/src/docker/DockerApiClient.ts index cccc53b3d7..af17e675c1 100644 --- a/src/docker/DockerApiClient.ts +++ b/src/docker/DockerApiClient.ts @@ -34,7 +34,7 @@ export interface DockerApiClient extends Disposable { stopContainer(context: IActionContext, ref: string, token?: CancellationToken): Promise<void>; removeContainer(context: IActionContext, ref: string, token?: CancellationToken): Promise<void>; - getImages(context: IActionContext, token?: CancellationToken): Promise<DockerImage[]>; + getImages(context: IActionContext, includeDangling: boolean, token?: CancellationToken): Promise<DockerImage[]>; inspectImage(context: IActionContext, ref: string, token?: CancellationToken): Promise<DockerImageInspection>; pruneImages(context: IActionContext, token?: CancellationToken): Promise<PruneResult | undefined>; tagImage(context: IActionContext, ref: string, tag: string, token?: CancellationToken): Promise<void>; diff --git a/src/docker/DockerServeClient/DockerServeClient.ts b/src/docker/DockerServeClient/DockerServeClient.ts index 0c18b138d1..5f60b6db95 100644 --- a/src/docker/DockerServeClient/DockerServeClient.ts +++ b/src/docker/DockerServeClient/DockerServeClient.ts @@ -139,7 +139,7 @@ export class DockerServeClient extends ContextChangeCancelClient implements Dock } // #region Not supported by the Docker SDK yet - public async getImages(context: IActionContext, token?: CancellationToken): Promise<DockerImage[]> { + public async getImages(context: IActionContext, includeDangling?: boolean, token?: CancellationToken): Promise<DockerImage[]> { throw new NotSupportedError(context); } diff --git a/src/docker/DockerodeApiClient/DockerodeApiClient.ts b/src/docker/DockerodeApiClient/DockerodeApiClient.ts index 057d402c1d..101a71d613 100644 --- a/src/docker/DockerodeApiClient/DockerodeApiClient.ts +++ b/src/docker/DockerodeApiClient/DockerodeApiClient.ts @@ -254,8 +254,12 @@ export class DockerodeApiClient extends ContextChangeCancelClient implements Doc return this.callWithErrorHandling(context, async () => container.remove({ force: true }), token); } - public async getImages(context: IActionContext, token?: CancellationToken): Promise<DockerImage[]> { - const images = await this.callWithErrorHandling(context, async () => this.dockerodeClient.listImages({ filters: { "dangling": ["false"] } }), token); + public async getImages(context: IActionContext, includeDangling: boolean = false, token?: CancellationToken): Promise<DockerImage[]> { + const filters = {}; + if (!includeDangling) { + filters['dangling'] = ["false"]; + } + const images = await this.callWithErrorHandling(context, async () => this.dockerodeClient.listImages({ filters: filters }), token); const result: DockerImage[] = []; for (const image of images) { diff --git a/src/tree/images/ImageTreeItem.ts b/src/tree/images/ImageTreeItem.ts index eada476fba..a918b6902e 100644 --- a/src/tree/images/ImageTreeItem.ts +++ b/src/tree/images/ImageTreeItem.ts @@ -67,10 +67,11 @@ export class ImageTreeItem extends AzExtTreeItemIntermediate { public async deleteTreeItemImpl(context: IActionContext): Promise<void> { let ref = this.fullTag; - // Dangling images are not shown in the explorer. However, an image can end up with <none> tag, if a new version of that particular tag is pulled. - if (ref.endsWith(':<none>') && this._item.RepoDigests?.length) { - // Image is tagged <none>. Need to delete by digest. - ref = this._item.RepoDigests[0]; + // Dangling images are shown in the explorer, depending on the setting. + // In this case, an image end up with <none> tag need to be deleted using the Id. + if (ref.endsWith('<none>')) { + // Image is tagged <none>. Need to delete by ID. + ref = this._item.Id; } return ext.dockerClient.removeImage(context, ref); diff --git a/src/tree/images/ImagesTreeItem.ts b/src/tree/images/ImagesTreeItem.ts index 2ec6618f40..bb3426fcef 100644 --- a/src/tree/images/ImagesTreeItem.ts +++ b/src/tree/images/ImagesTreeItem.ts @@ -58,7 +58,8 @@ export class ImagesTreeItem extends LocalRootTreeItemBase<DatedDockerImage, Imag } public async getItems(context: IActionContext): Promise<DatedDockerImage[]> { - const result = await ext.dockerClient.getImages(context); + const includeDangling = ext.context.globalState.get('vscode-docker.images.showDanglingImages', false); + const result = await ext.dockerClient.getImages(context, includeDangling); this.outdatedImageChecker.markOutdatedImages(result); return result; }