From 3b2ee0f5d9ccc14230ef1dd7d79ba9dd39112051 Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Tue, 8 Aug 2023 12:08:01 -0400 Subject: [PATCH 01/43] Registries V2 (#4021) * saving progress * added context values for copy full tag * added back copy remote image digest * comment out commands that are not implemented yet * added disconnext registry function back * small fixes to adjust to new changes --- package-lock.json | 46 +- package.json | 12 +- src/commands/images/buildImage.ts | 160 +++---- src/commands/images/pushImage.ts | 143 +++--- src/commands/images/tagImage.ts | 194 ++++---- src/commands/registerCommands.ts | 66 +-- .../azure/DockerAssignAcrPullRoleStep.ts | 178 +++---- .../registries/azure/DockerSiteCreateStep.ts | 279 ++++++----- .../azure/DockerWebhookCreateStep.ts | 174 +++---- .../azure/WebSitesPortPromptStep.ts | 56 +-- .../registries/azure/createAzureRegistry.ts | 15 +- .../registries/azure/deleteAzureRegistry.ts | 50 +- .../registries/azure/deleteAzureRepository.ts | 50 +- .../registries/azure/deployImageToAca.ts | 192 ++++---- .../registries/azure/deployImageToAzure.ts | 138 +++--- .../registries/azure/openInAzurePortal.ts | 35 +- .../azure/tasks/buildImageInAzure.ts | 61 ++- .../registries/azure/tasks/runAzureTask.ts | 27 +- .../azure/tasks/runFileAsAzureTask.ts | 26 +- .../azure/tasks/scheduleRunRequest.ts | 433 +++++++++--------- .../azure/tasks/viewAzureTaskLogs.ts | 48 +- .../registries/azure/untagAzureImage.ts | 62 +-- .../registries/azure/viewAzureProperties.ts | 32 +- src/commands/registries/connectRegistry.ts | 5 +- src/commands/registries/copyRemoteFullTag.ts | 21 +- .../registries/copyRemoteImageDigest.ts | 28 +- src/commands/registries/deleteRemoteImage.ts | 64 +-- src/commands/registries/disconnectRegistry.ts | 17 +- .../dockerHub/openDockerHubInBrowser.ts | 56 ++- src/commands/registries/logInToDockerCli.ts | 33 +- src/commands/registries/logOutOfDockerCli.ts | 48 +- src/commands/registries/pullImages.ts | 28 +- src/commands/registries/reconnectRegistry.ts | 32 +- src/extensionVariables.ts | 10 +- src/tree/RefreshManager.ts | 2 +- src/tree/registerTrees.ts | 27 +- src/tree/registries/Azure/ACROAuthProvider.ts | 110 +++++ .../Azure/AzureRegistryDataProvider.ts | 126 +++++ .../registries/ConnectedRegistriesTreeItem.ts | 31 -- .../registries/ICachedRegistryProvider.ts | 16 - src/tree/registries/IRegistryProvider.ts | 68 --- .../registries/IRegistryProviderTreeItem.ts | 10 - src/tree/registries/RegistriesTreeItem.ts | 233 ---------- .../RegistryConnectErrorTreeItem.ts | 24 - src/tree/registries/RegistryTreeItemBase.ts | 66 --- .../RemoteRepositoryTreeItemBase.ts | 49 -- src/tree/registries/RemoteTagTreeItem.ts | 52 --- .../UnifiedRegistryTreeDataProvider.ts | 131 ++++++ src/tree/registries/all/RegistryApi.ts | 22 - .../registries/all/getRegistryProviders.ts | 19 - .../registries/auth/AzureOAuthProvider.ts | 34 -- .../registries/auth/BasicOAuthProvider.ts | 65 --- src/tree/registries/auth/IAuthProvider.ts | 13 - .../registries/azure/AzureAccountTreeItem.ts | 40 -- .../registries/azure/AzureRegistryTreeItem.ts | 115 ----- .../azure/AzureRepositoryTreeItem.ts | 19 - .../registries/azure/AzureTaskRunTreeItem.ts | 80 ---- .../registries/azure/AzureTaskTreeItem.ts | 90 ---- .../registries/azure/AzureTasksTreeItem.ts | 72 --- .../registries/azure/SubscriptionTreeItem.ts | 75 --- .../registries/azure/azureRegistryProvider.ts | 27 -- .../createWizard/AzureRegistryCreateStep.ts | 57 --- .../createWizard/AzureRegistryNameStep.ts | 50 -- .../createWizard/AzureRegistrySkuStep.ts | 23 - .../IAzureRegistryWizardContext.ts | 13 - .../IConnectRegistryWizardContext.ts | 16 - .../IConnectRegistryWizardOptions.ts | 51 --- .../connectWizard/RegistryPasswordStep.ts | 27 -- .../connectWizard/RegistryUrlStep.ts | 49 -- .../connectWizard/RegistryUsernameStep.ts | 39 -- .../dockerHub/DockerHubAccountTreeItem.ts | 125 ----- .../dockerHub/DockerHubNamespaceTreeItem.ts | 76 --- .../dockerHub/DockerHubRepositoryTreeItem.ts | 52 --- .../dockerHub/dockerHubRegistryProvider.ts | 29 -- .../dockerV2/DockerV2RegistryTreeItemBase.ts | 75 --- .../dockerV2/DockerV2RepositoryTreeItem.ts | 89 ---- .../dockerV2/DockerV2TagTreeItem.ts | 44 -- .../GenericDockerV2RegistryTreeItem.ts | 56 --- .../genericDockerV2RegistryProvider.ts | 31 -- .../getInformationFromRegistryItem.ts | 36 ++ .../gitLab/GitLabAccountTreeItem.ts | 86 ---- .../gitLab/GitLabProjectTreeItem.ts | 80 ---- .../gitLab/GitLabRepositoryTreeItem.ts | 64 --- .../gitLab/gitLabRegistryProvider.ts | 25 - src/tree/registries/registryContextValues.ts | 64 --- src/tree/registries/registryPasswords.ts | 28 -- src/utils/lazyPackages.ts | 10 - 87 files changed, 1820 insertions(+), 3910 deletions(-) create mode 100644 src/tree/registries/Azure/ACROAuthProvider.ts create mode 100644 src/tree/registries/Azure/AzureRegistryDataProvider.ts delete mode 100644 src/tree/registries/ConnectedRegistriesTreeItem.ts delete mode 100644 src/tree/registries/ICachedRegistryProvider.ts delete mode 100644 src/tree/registries/IRegistryProvider.ts delete mode 100644 src/tree/registries/IRegistryProviderTreeItem.ts delete mode 100644 src/tree/registries/RegistriesTreeItem.ts delete mode 100644 src/tree/registries/RegistryConnectErrorTreeItem.ts delete mode 100644 src/tree/registries/RegistryTreeItemBase.ts delete mode 100644 src/tree/registries/RemoteRepositoryTreeItemBase.ts delete mode 100644 src/tree/registries/RemoteTagTreeItem.ts create mode 100644 src/tree/registries/UnifiedRegistryTreeDataProvider.ts delete mode 100644 src/tree/registries/all/RegistryApi.ts delete mode 100644 src/tree/registries/all/getRegistryProviders.ts delete mode 100644 src/tree/registries/auth/AzureOAuthProvider.ts delete mode 100644 src/tree/registries/auth/BasicOAuthProvider.ts delete mode 100644 src/tree/registries/auth/IAuthProvider.ts delete mode 100644 src/tree/registries/azure/AzureAccountTreeItem.ts delete mode 100644 src/tree/registries/azure/AzureRegistryTreeItem.ts delete mode 100644 src/tree/registries/azure/AzureRepositoryTreeItem.ts delete mode 100644 src/tree/registries/azure/AzureTaskRunTreeItem.ts delete mode 100644 src/tree/registries/azure/AzureTaskTreeItem.ts delete mode 100644 src/tree/registries/azure/AzureTasksTreeItem.ts delete mode 100644 src/tree/registries/azure/SubscriptionTreeItem.ts delete mode 100644 src/tree/registries/azure/azureRegistryProvider.ts delete mode 100644 src/tree/registries/azure/createWizard/AzureRegistryCreateStep.ts delete mode 100644 src/tree/registries/azure/createWizard/AzureRegistryNameStep.ts delete mode 100644 src/tree/registries/azure/createWizard/AzureRegistrySkuStep.ts delete mode 100644 src/tree/registries/azure/createWizard/IAzureRegistryWizardContext.ts delete mode 100644 src/tree/registries/connectWizard/IConnectRegistryWizardContext.ts delete mode 100644 src/tree/registries/connectWizard/IConnectRegistryWizardOptions.ts delete mode 100644 src/tree/registries/connectWizard/RegistryPasswordStep.ts delete mode 100644 src/tree/registries/connectWizard/RegistryUrlStep.ts delete mode 100644 src/tree/registries/connectWizard/RegistryUsernameStep.ts delete mode 100644 src/tree/registries/dockerHub/DockerHubAccountTreeItem.ts delete mode 100644 src/tree/registries/dockerHub/DockerHubNamespaceTreeItem.ts delete mode 100644 src/tree/registries/dockerHub/DockerHubRepositoryTreeItem.ts delete mode 100644 src/tree/registries/dockerHub/dockerHubRegistryProvider.ts delete mode 100644 src/tree/registries/dockerV2/DockerV2RegistryTreeItemBase.ts delete mode 100644 src/tree/registries/dockerV2/DockerV2RepositoryTreeItem.ts delete mode 100644 src/tree/registries/dockerV2/DockerV2TagTreeItem.ts delete mode 100644 src/tree/registries/dockerV2/GenericDockerV2RegistryTreeItem.ts delete mode 100644 src/tree/registries/dockerV2/genericDockerV2RegistryProvider.ts create mode 100644 src/tree/registries/getInformationFromRegistryItem.ts delete mode 100644 src/tree/registries/gitLab/GitLabAccountTreeItem.ts delete mode 100644 src/tree/registries/gitLab/GitLabProjectTreeItem.ts delete mode 100644 src/tree/registries/gitLab/GitLabRepositoryTreeItem.ts delete mode 100644 src/tree/registries/gitLab/gitLabRegistryProvider.ts delete mode 100644 src/tree/registries/registryContextValues.ts delete mode 100644 src/tree/registries/registryPasswords.ts diff --git a/package-lock.json b/package-lock.json index a95cfdef1d..d9409d10aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,11 @@ "@azure/storage-blob": "^12.14.0", "@microsoft/compose-language-service": "^0.2.0", "@microsoft/vscode-azext-azureappservice": "^1.0.2", + "@microsoft/vscode-azext-azureauth": "^1.1.2", "@microsoft/vscode-azext-azureutils": "^1.1.5", "@microsoft/vscode-azext-utils": "^1.2.2", "@microsoft/vscode-container-client": "^0.1.0", + "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", "dayjs": "^1.11.7", "dockerfile-language-server-nodejs": "^0.10.2", "fs-extra": "^11.1.1", @@ -259,6 +261,23 @@ "node": ">=14.0.0" } }, + "node_modules/@azure/arm-subscriptions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@azure/arm-subscriptions/-/arm-subscriptions-5.1.0.tgz", + "integrity": "sha512-6BeOF2eQWNLq22ch7xP9RxYnPjtGev54OUCGggKOWoOvmesK7jUZbIyLk8JeXDT21PEl7iyYnxw78gxJ7zBxQw==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.6.1", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.2.0", + "@azure/core-rest-pipeline": "^1.8.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@azure/core-auth": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz", @@ -405,8 +424,7 @@ "node_modules/@azure/ms-rest-azure-env": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-env/-/ms-rest-azure-env-2.0.0.tgz", - "integrity": "sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw==", - "peer": true + "integrity": "sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw==" }, "node_modules/@azure/storage-blob": { "version": "12.14.0", @@ -714,6 +732,15 @@ "@microsoft/vscode-azext-utils": "^1.2.1" } }, + "node_modules/@microsoft/vscode-azext-azureauth": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureauth/-/vscode-azext-azureauth-1.1.2.tgz", + "integrity": "sha512-cacmiEv1GiofOKaQzgRqXrDqPoFc3HAGiaer+ENNrdeW6elks0o5Z0oXkTIauS26yeYQUUaooB6DezvQU6LC+Q==", + "dependencies": { + "@azure/arm-subscriptions": "^5.1.0", + "@azure/ms-rest-azure-env": "^2.0.0" + } + }, "node_modules/@microsoft/vscode-azext-azureutils": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureutils/-/vscode-azext-azureutils-1.1.5.tgz", @@ -784,6 +811,15 @@ "tree-kill": "^1.2.2" } }, + "node_modules/@microsoft/vscode-docker-registries": { + "version": "0.0.1-alpha", + "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", + "integrity": "sha512-b7VTChrnCksWeobO7jgCqdoobtjJpAJKKVJg+dzF3fdw6ypxAXsmG70aiqKZGdkJaJJGq5rIn1lYc8JcCGlEJQ==", + "license": "See LICENSE in the project root for license information.", + "dependencies": { + "node-fetch": "^2.6.11" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4498,9 +4534,9 @@ "optional": true }, "node_modules/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", "dependencies": { "whatwg-url": "^5.0.0" }, diff --git a/package.json b/package.json index d93f461433..4490647747 100644 --- a/package.json +++ b/package.json @@ -491,7 +491,7 @@ }, { "command": "vscode-docker.registries.pullRepository", - "when": "view == dockerRegistries && viewItem =~ /Repository;/", + "when": "view == dockerRegistries && viewItem =~ /commonrepository/", "group": "regs_repo_1_general@1" }, { @@ -501,17 +501,17 @@ }, { "command": "vscode-docker.registries.pullImage", - "when": "view == dockerRegistries && viewItem =~ /Tag;/", + "when": "view == dockerRegistries && viewItem =~ /commontag/i", "group": "regs_tag_1_general@1" }, { "command": "vscode-docker.registries.copyRemoteFullTag", - "when": "view == dockerRegistries && viewItem =~ /(DockerV2|DockerHubV2);Tag;/", + "when": "view == dockerRegistries && viewItem =~ /(dockerHubTag|registryV2Tag)/i", "group": "regs_tag_1_general@2" }, { "command": "vscode-docker.registries.copyImageDigest", - "when": "view == dockerRegistries && viewItem =~ /DockerV2;Tag;/", + "when": "view == dockerRegistries && viewItem =~ /registryV2Tag/", "group": "regs_tag_1_general@3" }, { @@ -551,7 +551,7 @@ }, { "command": "vscode-docker.registries.disconnectRegistry", - "when": "view == dockerRegistries && viewItem =~ /RegistryProvider;/", + "when": "view == dockerRegistries && viewItem =~ /commonregistryroot/i && !(viewItem =~ /genericregistryrootv2/i)", "group": "regs_yyy_destructive@1" }, { @@ -3031,9 +3031,11 @@ "@azure/storage-blob": "^12.14.0", "@microsoft/compose-language-service": "^0.2.0", "@microsoft/vscode-azext-azureappservice": "^1.0.2", + "@microsoft/vscode-azext-azureauth": "^1.1.2", "@microsoft/vscode-azext-azureutils": "^1.1.5", "@microsoft/vscode-azext-utils": "^1.2.2", "@microsoft/vscode-container-client": "^0.1.0", + "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", "dayjs": "^1.11.7", "dockerfile-language-server-nodejs": "^0.10.2", "fs-extra": "^11.1.1", diff --git a/src/commands/images/buildImage.ts b/src/commands/images/buildImage.ts index db6660c458..a51eb7a44b 100644 --- a/src/commands/images/buildImage.ts +++ b/src/commands/images/buildImage.ts @@ -1,80 +1,80 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IActionContext, UserCancelledError } from "@microsoft/vscode-azext-utils"; -import * as path from "path"; -import * as vscode from "vscode"; -import { ext } from "../../extensionVariables"; -import { TaskCommandRunnerFactory } from "../../runtimes/runners/TaskCommandRunnerFactory"; -import { getOfficialBuildTaskForDockerfile } from "../../tasks/TaskHelper"; -import { getValidImageNameFromPath } from "../../utils/getValidImageName"; -import { delay } from "../../utils/promiseUtils"; -import { quickPickDockerFileItem } from "../../utils/quickPickFile"; -import { quickPickWorkspaceFolder } from "../../utils/quickPickWorkspaceFolder"; -import { selectBuildCommand } from "../selectCommandTemplate"; -import { addImageTaggingTelemetry, getTagFromUserInput } from "./tagImage"; - -const tagRegex: RegExp = /\$\{tag\}/i; - -export async function buildImage(context: IActionContext, dockerFileUri: vscode.Uri | undefined): Promise { - if (!vscode.workspace.isTrusted) { - throw new UserCancelledError('enforceTrust'); - } - - const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('docker'); - const defaultContextPath = configOptions.get('imageBuildContextPath', ''); - - let rootFolder: vscode.WorkspaceFolder; - if (dockerFileUri) { - rootFolder = vscode.workspace.getWorkspaceFolder(dockerFileUri); - } - - rootFolder = rootFolder || await quickPickWorkspaceFolder(context, vscode.l10n.t('To build Docker files you must first open a folder or workspace in VS Code.')); - - const dockerFileItem = await quickPickDockerFileItem(context, dockerFileUri, rootFolder); - const task = await getOfficialBuildTaskForDockerfile(context, dockerFileItem.absoluteFilePath, rootFolder); - - if (task) { - await vscode.tasks.executeTask(task); - } else { - const contextPath: string = defaultContextPath || dockerFileItem.relativeFolderPath; - - const terminalCommand = await selectBuildCommand( - context, - rootFolder, - dockerFileItem.relativeFilePath, - contextPath - ); - - // Replace '${tag}' if needed. Tag is a special case because we don't want to prompt unless necessary, so must manually replace it. - if (tagRegex.test(terminalCommand.command)) { - const absFilePath: string = path.join(rootFolder.uri.fsPath, dockerFileItem.relativeFilePath); - const dockerFileKey = `buildTag_${absFilePath}`; - const prevImageName: string | undefined = ext.context.workspaceState.get(dockerFileKey); - - // Get imageName based previous entries, else on name of subfolder containing the Dockerfile - const suggestedImageName = prevImageName || getValidImageNameFromPath(dockerFileItem.absoluteFolderPath, 'latest'); - - // Temporary work-around for vscode bug where valueSelection can be messed up if a quick pick is followed by a showInputBox - await delay(500); - - addImageTaggingTelemetry(context, suggestedImageName, '.before'); - const imageName: string = await getTagFromUserInput(context, suggestedImageName); - addImageTaggingTelemetry(context, imageName, '.after'); - - await ext.context.workspaceState.update(dockerFileKey, imageName); - terminalCommand.command = terminalCommand.command.replace(tagRegex, imageName); - } - - const client = await ext.runtimeManager.getClient(); - const taskCRF = new TaskCommandRunnerFactory({ - taskName: client.displayName, - workspaceFolder: rootFolder, - focus: true, - }); - - await taskCRF.getCommandRunner()(terminalCommand); - } -} +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ + +// import { IActionContext, UserCancelledError } from "@microsoft/vscode-azext-utils"; +// import * as path from "path"; +// import * as vscode from "vscode"; +// import { ext } from "../../extensionVariables"; +// import { TaskCommandRunnerFactory } from "../../runtimes/runners/TaskCommandRunnerFactory"; +// import { getOfficialBuildTaskForDockerfile } from "../../tasks/TaskHelper"; +// import { getValidImageNameFromPath } from "../../utils/getValidImageName"; +// import { delay } from "../../utils/promiseUtils"; +// import { quickPickDockerFileItem } from "../../utils/quickPickFile"; +// import { quickPickWorkspaceFolder } from "../../utils/quickPickWorkspaceFolder"; +// import { selectBuildCommand } from "../selectCommandTemplate"; +// import { addImageTaggingTelemetry, getTagFromUserInput } from "./tagImage"; + +// const tagRegex: RegExp = /\$\{tag\}/i; + +// export async function buildImage(context: IActionContext, dockerFileUri: vscode.Uri | undefined): Promise { +// if (!vscode.workspace.isTrusted) { +// throw new UserCancelledError('enforceTrust'); +// } + +// const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('docker'); +// const defaultContextPath = configOptions.get('imageBuildContextPath', ''); + +// let rootFolder: vscode.WorkspaceFolder; +// if (dockerFileUri) { +// rootFolder = vscode.workspace.getWorkspaceFolder(dockerFileUri); +// } + +// rootFolder = rootFolder || await quickPickWorkspaceFolder(context, vscode.l10n.t('To build Docker files you must first open a folder or workspace in VS Code.')); + +// const dockerFileItem = await quickPickDockerFileItem(context, dockerFileUri, rootFolder); +// const task = await getOfficialBuildTaskForDockerfile(context, dockerFileItem.absoluteFilePath, rootFolder); + +// if (task) { +// await vscode.tasks.executeTask(task); +// } else { +// const contextPath: string = defaultContextPath || dockerFileItem.relativeFolderPath; + +// const terminalCommand = await selectBuildCommand( +// context, +// rootFolder, +// dockerFileItem.relativeFilePath, +// contextPath +// ); + +// // Replace '${tag}' if needed. Tag is a special case because we don't want to prompt unless necessary, so must manually replace it. +// if (tagRegex.test(terminalCommand.command)) { +// const absFilePath: string = path.join(rootFolder.uri.fsPath, dockerFileItem.relativeFilePath); +// const dockerFileKey = `buildTag_${absFilePath}`; +// const prevImageName: string | undefined = ext.context.workspaceState.get(dockerFileKey); + +// // Get imageName based previous entries, else on name of subfolder containing the Dockerfile +// const suggestedImageName = prevImageName || getValidImageNameFromPath(dockerFileItem.absoluteFolderPath, 'latest'); + +// // Temporary work-around for vscode bug where valueSelection can be messed up if a quick pick is followed by a showInputBox +// await delay(500); + +// addImageTaggingTelemetry(context, suggestedImageName, '.before'); +// const imageName: string = await getTagFromUserInput(context, suggestedImageName); +// addImageTaggingTelemetry(context, imageName, '.after'); + +// await ext.context.workspaceState.update(dockerFileKey, imageName); +// terminalCommand.command = terminalCommand.command.replace(tagRegex, imageName); +// } + +// const client = await ext.runtimeManager.getClient(); +// const taskCRF = new TaskCommandRunnerFactory({ +// taskName: client.displayName, +// workspaceFolder: rootFolder, +// focus: true, +// }); + +// await taskCRF.getCommandRunner()(terminalCommand); +// } +// } diff --git a/src/commands/images/pushImage.ts b/src/commands/images/pushImage.ts index 5ac02c613e..114748bb41 100644 --- a/src/commands/images/pushImage.ts +++ b/src/commands/images/pushImage.ts @@ -1,89 +1,78 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ -import { IActionContext, NoResourceFoundError } from '@microsoft/vscode-azext-utils'; -import * as vscode from 'vscode'; -import { ext } from '../../extensionVariables'; -import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; -import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; -import { registryExpectedContextValues } from '../../tree/registries/registryContextValues'; -import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase'; -import { addImageTaggingTelemetry, tagImage } from './tagImage'; +// import { IActionContext, NoResourceFoundError, contextValueExperience } from '@microsoft/vscode-azext-utils'; +// import * as vscode from 'vscode'; +// import { ext } from '../../extensionVariables'; +// import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; +// import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; +// import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; +// import { addImageTaggingTelemetry, tagImage } from './tagImage'; -export async function pushImage(context: IActionContext, node: ImageTreeItem | undefined): Promise { - if (!node) { - await ext.imagesTree.refresh(context); - node = await ext.imagesTree.showTreeItemPicker(ImageTreeItem.contextValue, { - ...context, - noItemFoundErrorMessage: vscode.l10n.t('No images are available to push'), - }); - } +// export async function pushImage(context: IActionContext, node: ImageTreeItem | undefined): Promise { +// if (!node) { +// await ext.imagesTree.refresh(context); +// node = await ext.imagesTree.showTreeItemPicker(ImageTreeItem.contextValue, { +// ...context, +// noItemFoundErrorMessage: vscode.l10n.t('No images are available to push'), +// }); +// } - let connectedRegistry: RegistryTreeItemBase | undefined; +// let connectedRegistry: UnifiedRegistryItem | undefined; - if (!node.fullTag.includes('/')) { - // The registry to push to is indeterminate--could be Docker Hub, or could need tagging. - const prompt: boolean = vscode.workspace.getConfiguration('docker').get('promptForRegistryWhenPushingImages', true); +// if (!node.fullTag.includes('/')) { +// // The registry to push to is indeterminate--could be Docker Hub, or could need tagging. +// const prompt: boolean = vscode.workspace.getConfiguration('docker').get('promptForRegistryWhenPushingImages', true); - // If the prompt setting is true, we'll ask; if not we'll assume Docker Hub. - if (prompt) { - try { - connectedRegistry = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.all.registry, context); - } catch (error) { - if (error instanceof NoResourceFoundError) { - // Do nothing, move on without a selected registry - } else { - // Rethrow - throw error; - } - } - } else { - // Try to find a connected Docker Hub registry (primarily for login credentials) - connectedRegistry = await tryGetDockerHubRegistry(context); - } - } else { - // The registry to push to is determinate. If there's a connected registry in the tree view, we'll try to find it, to perform login ahead of time. - // Registry path is everything up to the last slash. - const baseImagePath = node.fullTag.substring(0, node.fullTag.lastIndexOf('/')); +// // If the prompt setting is true, we'll ask; if not we'll assume Docker Hub. +// if (prompt) { +// try { +// connectedRegistry = await contextValueExperience(context, ext.registriesRoot, { include: 'commonregistry' }); +// } catch (error) { +// if (error instanceof NoResourceFoundError) { +// // Do nothing, move on without a selected registry +// } else { +// // Rethrow +// throw error; +// } +// } +// } else { +// // Try to find a connected Docker Hub registry (primarily for login credentials) +// connectedRegistry = await contextValueExperience(context, ext.registriesRoot, { include: 'dockerHubRegistry' }); +// } +// } else { +// // The registry to push to is determinate. If there's a connected registry in the tree view, we'll try to find it, to perform login ahead of time. +// // Registry path is everything up to the last slash. +// const baseImagePath = node.fullTag.substring(0, node.fullTag.lastIndexOf('/')); - const progressOptions: vscode.ProgressOptions = { - location: vscode.ProgressLocation.Notification, - title: vscode.l10n.t('Fetching login credentials...'), - }; +// const progressOptions: vscode.ProgressOptions = { +// location: vscode.ProgressLocation.Notification, +// title: vscode.l10n.t('Fetching login credentials...'), +// }; - connectedRegistry = await vscode.window.withProgress(progressOptions, async () => await tryGetConnectedRegistryForPath(context, baseImagePath)); - } +// // connectedRegistry = await vscode.window.withProgress(progressOptions, async () => await tryGetConnectedRegistryForPath(context, baseImagePath)); TODO: review this later +// } - // Give the user a chance to modify the tag however they want - const finalTag = await tagImage(context, node, connectedRegistry); +// // Give the user a chance to modify the tag however they want +// const finalTag = await tagImage(context, node, connectedRegistry); - if (connectedRegistry && finalTag.startsWith(connectedRegistry.baseImagePath)) { - // If a registry was found/chosen and is still the same as the final tag's registry, try logging in - await vscode.commands.executeCommand('vscode-docker.registries.logInToDockerCli', connectedRegistry); - } +// // if (connectedRegistry && finalTag.startsWith(connectedRegistry.baseImagePath)) { +// // // If a registry was found/chosen and is still the same as the final tag's registry, try logging in +// // await vscode.commands.executeCommand('vscode-docker.registries.logInToDockerCli', connectedRegistry); +// // } TODO: review this later - addImageTaggingTelemetry(context, finalTag, ''); +// addImageTaggingTelemetry(context, finalTag, ''); - const client = await ext.runtimeManager.getClient(); - const taskCRF = new TaskCommandRunnerFactory( - { - taskName: finalTag - } - ); +// const client = await ext.runtimeManager.getClient(); +// const taskCRF = new TaskCommandRunnerFactory( +// { +// taskName: finalTag +// } +// ); - await taskCRF.getCommandRunner()( - client.pushImage({ imageRef: finalTag }) - ); -} - -async function tryGetConnectedRegistryForPath(context: IActionContext, baseImagePath: string): Promise { - const allRegistries = await ext.registriesRoot.getAllConnectedRegistries(context); - return allRegistries.find(r => r.baseImagePath === baseImagePath); -} - -async function tryGetDockerHubRegistry(context: IActionContext): Promise { - const allRegistries = await ext.registriesRoot.getAllConnectedRegistries(context); - return allRegistries.find(r => r.contextValue.match(registryExpectedContextValues.dockerHub.registry)); -} +// await taskCRF.getCommandRunner()( +// client.pushImage({ imageRef: finalTag }) +// ); +// } diff --git a/src/commands/images/tagImage.ts b/src/commands/images/tagImage.ts index 8f00530ce8..dda54f7e1c 100644 --- a/src/commands/images/tagImage.ts +++ b/src/commands/images/tagImage.ts @@ -1,97 +1,97 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IActionContext, TelemetryProperties } from '@microsoft/vscode-azext-utils'; -import * as vscode from 'vscode'; -import { ext } from '../../extensionVariables'; -import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; -import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase'; - -export async function tagImage(context: IActionContext, node?: ImageTreeItem, registry?: RegistryTreeItemBase): Promise { - if (!node) { - await ext.imagesTree.refresh(context); - node = await ext.imagesTree.showTreeItemPicker(ImageTreeItem.contextValue, { - ...context, - noItemFoundErrorMessage: vscode.l10n.t('No images are available to tag') - }); - } - - addImageTaggingTelemetry(context, node.fullTag, '.before'); - const newTaggedName: string = await getTagFromUserInput(context, node.fullTag, registry?.baseImagePath); - addImageTaggingTelemetry(context, newTaggedName, '.after'); - - await ext.runWithDefaults(client => - client.tagImage({ fromImageRef: node.imageId, toImageRef: newTaggedName }) - ); - - return newTaggedName; -} - -export async function getTagFromUserInput(context: IActionContext, fullTag: string, baseImagePath?: string): Promise { - const opt: vscode.InputBoxOptions = { - ignoreFocusOut: true, - prompt: vscode.l10n.t('Tag image as...'), - }; - - if (fullTag.includes('/')) { - opt.valueSelection = [0, fullTag.lastIndexOf('/')]; - } else if (baseImagePath) { - fullTag = `${baseImagePath}/${fullTag}`; - opt.valueSelection = [0, fullTag.lastIndexOf('/')]; - } - - opt.value = fullTag; - - return await context.ui.showInputBox(opt); -} - -const KnownRegistries: { type: string, regex: RegExp }[] = [ - // Like username/path - { type: 'dockerhub-namespace', regex: /^[^.:]+\/[^.:]+$/ }, - - { type: 'dockerhub-dockerio', regex: /^docker.io.*\// }, - { type: 'github', regex: /ghcr\.io.*\// }, - { type: 'gitlab', regex: /gitlab.*\// }, - { type: 'ACR', regex: /azurecr\.io.*\// }, - { type: 'GCR', regex: /gcr\.io.*\// }, - { type: 'ECR', regex: /\.ecr\..*\// }, - { type: 'localhost', regex: /localhost:.*\// }, - - // Has a port, probably a private registry - { type: 'privateWithPort', regex: /:[0-9]+\// }, - - // Match anything remaining - { type: 'other', regex: /\// }, // has a slash - { type: 'none', regex: /./ } // no slash -]; - -export function addImageTaggingTelemetry(context: IActionContext, fullImageName: string, propertyPostfix: '.before' | '.after' | ''): void { - try { - const properties: TelemetryProperties = {}; - - const [, repository, tag] = /^(.*):(.*)$/.exec(fullImageName) ?? [undefined, fullImageName, '']; - - if (!!tag.match(/^[0-9.-]*(|alpha|beta|latest|edge|v|version)?[0-9.-]*$/)) { - properties.safeTag = tag; - } - properties.hasTag = String(!!tag); - properties.numSlashes = String(numberMatches(repository.match(/\//g))); - - const knownRegistry = KnownRegistries.find(kr => !!repository.match(kr.regex)); - if (knownRegistry) { - properties.registryType = knownRegistry.type; - } - - for (const propertyName of Object.keys(properties)) { - context.telemetry.properties[propertyName + propertyPostfix] = properties[propertyName]; - } - } catch (error) { - console.error(error); - } -} - -function numberMatches(matches: RegExpMatchArray | null): number { - return matches ? matches.length : 0; -} +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ + +// import { IActionContext, TelemetryProperties } from '@microsoft/vscode-azext-utils'; +// import * as vscode from 'vscode'; +// import { ext } from '../../extensionVariables'; +// import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; +// import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; + +// export async function tagImage(context: IActionContext, node?: ImageTreeItem, registry?: UnifiedRegistryItem): Promise { +// if (!node) { +// await ext.imagesTree.refresh(context); +// node = await ext.imagesTree.showTreeItemPicker(ImageTreeItem.contextValue, { +// ...context, +// noItemFoundErrorMessage: vscode.l10n.t('No images are available to tag') +// }); +// } + +// addImageTaggingTelemetry(context, node.fullTag, '.before'); +// const newTaggedName: string = await getTagFromUserInput(context, node.fullTag, ''); // TODO: review this later +// addImageTaggingTelemetry(context, newTaggedName, '.after'); + +// await ext.runWithDefaults(client => +// client.tagImage({ fromImageRef: node.imageId, toImageRef: newTaggedName }) +// ); + +// return newTaggedName; +// } + +// export async function getTagFromUserInput(context: IActionContext, fullTag: string, baseImagePath?: string): Promise { +// const opt: vscode.InputBoxOptions = { +// ignoreFocusOut: true, +// prompt: vscode.l10n.t('Tag image as...'), +// }; + +// if (fullTag.includes('/')) { +// opt.valueSelection = [0, fullTag.lastIndexOf('/')]; +// } else if (baseImagePath) { +// fullTag = `${baseImagePath}/${fullTag}`; +// opt.valueSelection = [0, fullTag.lastIndexOf('/')]; +// } + +// opt.value = fullTag; + +// return await context.ui.showInputBox(opt); +// } + +// const KnownRegistries: { type: string, regex: RegExp }[] = [ +// // Like username/path +// { type: 'dockerhub-namespace', regex: /^[^.:]+\/[^.:]+$/ }, + +// { type: 'dockerhub-dockerio', regex: /^docker.io.*\// }, +// { type: 'github', regex: /ghcr\.io.*\// }, +// { type: 'gitlab', regex: /gitlab.*\// }, +// { type: 'ACR', regex: /azurecr\.io.*\// }, +// { type: 'GCR', regex: /gcr\.io.*\// }, +// { type: 'ECR', regex: /\.ecr\..*\// }, +// { type: 'localhost', regex: /localhost:.*\// }, + +// // Has a port, probably a private registry +// { type: 'privateWithPort', regex: /:[0-9]+\// }, + +// // Match anything remaining +// { type: 'other', regex: /\// }, // has a slash +// { type: 'none', regex: /./ } // no slash +// ]; + +// export function addImageTaggingTelemetry(context: IActionContext, fullImageName: string, propertyPostfix: '.before' | '.after' | ''): void { +// try { +// const properties: TelemetryProperties = {}; + +// const [, repository, tag] = /^(.*):(.*)$/.exec(fullImageName) ?? [undefined, fullImageName, '']; + +// if (!!tag.match(/^[0-9.-]*(|alpha|beta|latest|edge|v|version)?[0-9.-]*$/)) { +// properties.safeTag = tag; +// } +// properties.hasTag = String(!!tag); +// properties.numSlashes = String(numberMatches(repository.match(/\//g))); + +// const knownRegistry = KnownRegistries.find(kr => !!repository.match(kr.regex)); +// if (knownRegistry) { +// properties.registryType = knownRegistry.type; +// } + +// for (const propertyName of Object.keys(properties)) { +// context.telemetry.properties[propertyName + propertyPostfix] = properties[propertyName]; +// } +// } catch (error) { +// console.error(error); +// } +// } + +// function numberMatches(matches: RegExpMatchArray | null): number { +// return matches ? matches.length : 0; +// } diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index 00916b14cd..ee3350847a 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -31,19 +31,17 @@ import { inspectDockerContext } from "./context/inspectDockerContext"; import { removeDockerContext } from "./context/removeDockerContext"; import { useDockerContext } from "./context/useDockerContext"; import { help } from "./help"; -import { buildImage } from "./images/buildImage"; +// import { buildImage } from "./images/buildImage"; import { configureImagesExplorer } from "./images/configureImagesExplorer"; import { copyFullTag } from "./images/copyFullTag"; import { inspectImage } from "./images/inspectImage"; import { pruneImages } from "./images/pruneImages"; import { pullImage } from "./images/pullImage"; -import { pushImage } from "./images/pushImage"; import { removeImage } from "./images/removeImage"; import { removeImageGroup } from "./images/removeImageGroup"; import { runAzureCliImage } from "./images/runAzureCliImage"; import { runImage, runImageInteractive } from "./images/runImage"; import { hideDanglingImages, setInitialDanglingContextValue, showDanglingImages } from "./images/showDanglingImages"; -import { tagImage } from "./images/tagImage"; import { configureNetworksExplorer } from "./networks/configureNetworksExplorer"; import { createNetwork } from "./networks/createNetwork"; import { inspectNetwork } from "./networks/inspectNetwork"; @@ -52,27 +50,29 @@ import { removeNetwork } from "./networks/removeNetwork"; import { pruneSystem } from "./pruneSystem"; import { registerWorkspaceCommand } from "./registerWorkspaceCommand"; import { createAzureRegistry } from "./registries/azure/createAzureRegistry"; -import { deleteAzureRegistry } from "./registries/azure/deleteAzureRegistry"; -import { deleteAzureRepository } from "./registries/azure/deleteAzureRepository"; -import { deployImageToAca } from "./registries/azure/deployImageToAca"; -import { deployImageToAzure } from "./registries/azure/deployImageToAzure"; +// import { deleteAzureRegistry } from "./registries/azure/deleteAzureRegistry"; +// import { deleteAzureRepository } from "./registries/azure/deleteAzureRepository"; +// import { deployImageToAzure } from "./registries/azure/deployImageToAzure"; import { openInAzurePortal } from "./registries/azure/openInAzurePortal"; -import { buildImageInAzure } from "./registries/azure/tasks/buildImageInAzure"; +// import { buildImageInAzure } from "./registries/azure/tasks/buildImageInAzure"; import { runAzureTask } from "./registries/azure/tasks/runAzureTask"; -import { runFileAsAzureTask } from "./registries/azure/tasks/runFileAsAzureTask"; -import { viewAzureTaskLogs } from "./registries/azure/tasks/viewAzureTaskLogs"; -import { untagAzureImage } from "./registries/azure/untagAzureImage"; -import { viewAzureProperties } from "./registries/azure/viewAzureProperties"; +// import { runFileAsAzureTask } from "./registries/azure/tasks/runFileAsAzureTask"; +// import { viewAzureTaskLogs } from "./registries/azure/tasks/viewAzureTaskLogs"; +// import { untagAzureImage } from "./registries/azure/untagAzureImage"; +// import { viewAzureProperties } from "./registries/azure/viewAzureProperties"; import { connectRegistry } from "./registries/connectRegistry"; -import { copyRemoteFullTag } from './registries/copyRemoteFullTag'; +// import { copyRemoteFullTag } from './registries/copyRemoteFullTag'; +// import { copyRemoteImageDigest } from "./registries/copyRemoteImageDigest"; +// import { deleteRemoteImage } from "./registries/deleteRemoteImage"; +// import { logInToDockerCli } from "./registries/logInToDockerCli"; +// import { logOutOfDockerCli } from "./registries/logOutOfDockerCli"; +// import { pullImageFromRepository, pullRepository } from "./registries/pullImages"; +// import { reconnectRegistry } from "./registries/reconnectRegistry"; +import { copyRemoteFullTag } from "./registries/copyRemoteFullTag"; import { copyRemoteImageDigest } from "./registries/copyRemoteImageDigest"; -import { deleteRemoteImage } from "./registries/deleteRemoteImage"; import { disconnectRegistry } from "./registries/disconnectRegistry"; -import { openDockerHubInBrowser } from "./registries/dockerHub/openDockerHubInBrowser"; import { logInToDockerCli } from "./registries/logInToDockerCli"; -import { logOutOfDockerCli } from "./registries/logOutOfDockerCli"; import { pullImageFromRepository, pullRepository } from "./registries/pullImages"; -import { reconnectRegistry } from "./registries/reconnectRegistry"; import { registryHelp } from "./registries/registryHelp"; import { openDockerDownloadPage } from "./showDockerLearnMoreNotification"; import { configureVolumesExplorer } from "./volumes/configureVolumesExplorer"; @@ -143,7 +143,7 @@ export function registerCommands(): void { registerWorkspaceCommand('vscode-docker.containers.composeGroup.restart', composeGroupRestart); registerWorkspaceCommand('vscode-docker.containers.composeGroup.down', composeGroupDown); - registerWorkspaceCommand('vscode-docker.images.build', buildImage); + // registerWorkspaceCommand('vscode-docker.images.build', buildImage); registerCommand('vscode-docker.images.configureExplorer', configureImagesExplorer); registerCommand('vscode-docker.images.inspect', inspectImage); registerCommand('vscode-docker.images.prune', pruneImages); @@ -151,13 +151,13 @@ export function registerCommands(): void { registerCommand('vscode-docker.images.hideDangling', hideDanglingImages); setInitialDanglingContextValue(); registerWorkspaceCommand('vscode-docker.images.pull', pullImage); - registerWorkspaceCommand('vscode-docker.images.push', pushImage); + // registerWorkspaceCommand('vscode-docker.images.push', pushImage); registerCommand('vscode-docker.images.remove', removeImage); registerCommand('vscode-docker.images.group.remove', removeImageGroup); registerWorkspaceCommand('vscode-docker.images.run', runImage); registerWorkspaceCommand('vscode-docker.images.runAzureCli', runAzureCliImage); registerWorkspaceCommand('vscode-docker.images.runInteractive', runImageInteractive); - registerCommand('vscode-docker.images.tag', tagImage); + // registerCommand('vscode-docker.images.tag', tagImage); registerCommand('vscode-docker.images.copyFullTag', copyFullTag); registerCommand('vscode-docker.networks.configureExplorer', configureNetworksExplorer); @@ -169,30 +169,30 @@ export function registerCommands(): void { registerCommand('vscode-docker.registries.connectRegistry', connectRegistry); registerCommand('vscode-docker.registries.copyImageDigest', copyRemoteImageDigest); registerCommand('vscode-docker.registries.copyRemoteFullTag', copyRemoteFullTag); - registerCommand('vscode-docker.registries.deleteImage', deleteRemoteImage); - registerCommand('vscode-docker.registries.deployImageToAzure', deployImageToAzure); - registerCommand('vscode-docker.registries.deployImageToAca', deployImageToAca); + // registerCommand('vscode-docker.registries.deleteImage', deleteRemoteImage); + // registerCommand('vscode-docker.registries.deployImageToAzure', deployImageToAzure); + // registerCommand('vscode-docker.registries.deployImageToAca', deployImageToAca); registerCommand('vscode-docker.registries.disconnectRegistry', disconnectRegistry); registerCommand('vscode-docker.registries.help', registryHelp); registerWorkspaceCommand('vscode-docker.registries.logInToDockerCli', logInToDockerCli); - registerWorkspaceCommand('vscode-docker.registries.logOutOfDockerCli', logOutOfDockerCli); + // registerWorkspaceCommand('vscode-docker.registries.logOutOfDockerCli', logOutOfDockerCli); registerWorkspaceCommand('vscode-docker.registries.pullImage', pullImageFromRepository); registerWorkspaceCommand('vscode-docker.registries.pullRepository', pullRepository); - registerCommand('vscode-docker.registries.reconnectRegistry', reconnectRegistry); + // registerCommand('vscode-docker.registries.reconnectRegistry', reconnectRegistry); - registerCommand('vscode-docker.registries.dockerHub.openInBrowser', openDockerHubInBrowser); + // registerCommand('vscode-docker.registries.dockerHub.openInBrowser', openDockerHubInBrowser); - registerWorkspaceCommand('vscode-docker.registries.azure.buildImage', buildImageInAzure); + // registerWorkspaceCommand('vscode-docker.registries.azure.buildImage', buildImageInAzure); registerCommand('vscode-docker.registries.azure.createRegistry', createAzureRegistry); - registerCommand('vscode-docker.registries.azure.deleteRegistry', deleteAzureRegistry); - registerCommand('vscode-docker.registries.azure.deleteRepository', deleteAzureRepository); + // registerCommand('vscode-docker.registries.azure.deleteRegistry', deleteAzureRegistry); + // registerCommand('vscode-docker.registries.azure.deleteRepository', deleteAzureRepository); registerCommand('vscode-docker.registries.azure.openInPortal', openInAzurePortal); registerCommand('vscode-docker.registries.azure.runTask', runAzureTask); - registerWorkspaceCommand('vscode-docker.registries.azure.runFileAsTask', runFileAsAzureTask); + // registerWorkspaceCommand('vscode-docker.registries.azure.runFileAsTask', runFileAsAzureTask); registerCommand('vscode-docker.registries.azure.selectSubscriptions', () => commands.executeCommand("azure-account.selectSubscriptions")); - registerCommand('vscode-docker.registries.azure.untagImage', untagAzureImage); - registerCommand('vscode-docker.registries.azure.viewProperties', viewAzureProperties); - registerCommand('vscode-docker.registries.azure.viewTaskLogs', viewAzureTaskLogs); + // registerCommand('vscode-docker.registries.azure.untagImage', untagAzureImage); + // registerCommand('vscode-docker.registries.azure.viewProperties', viewAzureProperties); + // registerCommand('vscode-docker.registries.azure.viewTaskLogs', viewAzureTaskLogs); registerCommand('vscode-docker.volumes.configureExplorer', configureVolumesExplorer); registerCommand('vscode-docker.volumes.inspect', inspectVolume); diff --git a/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts b/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts index 1b310433f0..74095300da 100644 --- a/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts +++ b/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts @@ -1,89 +1,89 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import type { IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy -import { AzureWizardExecuteStep } from "@microsoft/vscode-azext-utils"; -import { randomUUID } from "crypto"; -import { l10n, Progress } from "vscode"; -import { ext } from "../../../extensionVariables"; -import { AzureRegistryTreeItem } from '../../../tree/registries/azure/AzureRegistryTreeItem'; -import { RemoteTagTreeItem } from '../../../tree/registries/RemoteTagTreeItem'; -import { getArmAuth, getArmContainerRegistry, getAzExtAppService, getAzExtAzureUtils } from "../../../utils/lazyPackages"; - -export class DockerAssignAcrPullRoleStep extends AzureWizardExecuteStep { - public priority: number = 141; // execute after DockerSiteCreateStep - - public constructor(private readonly tagTreeItem: RemoteTagTreeItem) { - super(); - } - - public async execute(context: IAppServiceWizardContext, progress: Progress<{ message?: string; increment?: number }>): Promise { - const message: string = l10n.t('Granting permission for App Service to pull image from ACR...'); - ext.outputChannel.info(message); - progress.report({ message: message }); - - const azExtAzureUtils = await getAzExtAzureUtils(); - const vscAzureAppService = await getAzExtAppService(); - const armAuth = await getArmAuth(); - const armContainerRegistry = await getArmContainerRegistry(); - const authClient = azExtAzureUtils.createAzureClient(context, armAuth.AuthorizationManagementClient); - const crmClient = azExtAzureUtils.createAzureClient(context, armContainerRegistry.ContainerRegistryManagementClient); - const appSvcClient = await vscAzureAppService.createWebSiteClient(context); - - // If we're in `execute`, then `shouldExecute` passed and `this.tagTreeItem.parent.parent` is guaranteed to be an AzureRegistryTreeItem - const registryTreeItem: AzureRegistryTreeItem = this.tagTreeItem.parent.parent as unknown as AzureRegistryTreeItem; - - // 1. Get the registry resource. We will need the ID. - const registry = await crmClient.registries.get(registryTreeItem.resourceGroup, registryTreeItem.registryName); - - if (!(registry?.id)) { - throw new Error( - l10n.t('Unable to get details from Container Registry {0}', registryTreeItem.baseUrl) - ); - } - - // 2. Get the role definition for the AcrPull role. We will need the definition ID. This role is built-in and should always exist. - const acrPullRoleDefinition = (await azExtAzureUtils.uiUtils.listAllIterator(authClient.roleDefinitions.list(registry.id, { filter: `roleName eq 'AcrPull'` })))[0]; - - if (!(acrPullRoleDefinition?.id)) { - throw new Error( - l10n.t('Unable to get AcrPull role definition on subscription {0}', context.subscriptionId) - ); - } - - // 3. Get the info for the now-created web site. We will need the principal ID. - const siteInfo = await appSvcClient.webApps.get(context.site.resourceGroup, context.site.name); - - if (!(siteInfo?.identity?.principalId)) { - throw new Error( - l10n.t('Unable to get identity principal ID for web site {0}', context.site.name) - ); - } - - // 4. On the registry, assign the AcrPull role to the principal representing the website - await authClient.roleAssignments.create(registry.id, randomUUID(), { - principalId: siteInfo.identity.principalId, - roleDefinitionId: acrPullRoleDefinition.id, - principalType: 'ServicePrincipal', - }); - - // 5. Set the web app to use the desired ACR image, which was not done in DockerSiteCreateStep. Get the config and then update it. - const config = await appSvcClient.webApps.getConfiguration(context.site.resourceGroup, context.site.name); - - if (!config) { - throw new Error( - l10n.t('Unable to get configuration for web site {0}', context.site.name) - ); - } - - config.linuxFxVersion = `DOCKER|${this.tagTreeItem.fullTag}`; - await appSvcClient.webApps.updateConfiguration(context.site.resourceGroup, context.site.name, config); - } - - public shouldExecute(context: IAppServiceWizardContext): boolean { - return !!(context.site) && !!(this.tagTreeItem?.parent?.parent) && this.tagTreeItem.parent.parent instanceof AzureRegistryTreeItem - && !context.customLocation; - } -} +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ + +// import type { IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy +// import { AzureWizardExecuteStep } from "@microsoft/vscode-azext-utils"; +// import { randomUUID } from "crypto"; +// import { l10n, Progress } from "vscode"; +// import { ext } from "../../../extensionVariables"; +// import { AzureRegistryTreeItem } from '../../../tree/registries/azure/AzureRegistryTreeItem'; +// import { RemoteTagTreeItem } from '../../../tree/registries/RemoteTagTreeItem'; +// import { getArmAuth, getArmContainerRegistry, getAzExtAppService, getAzExtAzureUtils } from "../../../utils/lazyPackages"; + +// export class DockerAssignAcrPullRoleStep extends AzureWizardExecuteStep { +// public priority: number = 141; // execute after DockerSiteCreateStep + +// public constructor(private readonly tagTreeItem: RemoteTagTreeItem) { +// super(); +// } + +// public async execute(context: IAppServiceWizardContext, progress: Progress<{ message?: string; increment?: number }>): Promise { +// const message: string = l10n.t('Granting permission for App Service to pull image from ACR...'); +// ext.outputChannel.info(message); +// progress.report({ message: message }); + +// const azExtAzureUtils = await getAzExtAzureUtils(); +// const vscAzureAppService = await getAzExtAppService(); +// const armAuth = await getArmAuth(); +// const armContainerRegistry = await getArmContainerRegistry(); +// const authClient = azExtAzureUtils.createAzureClient(context, armAuth.AuthorizationManagementClient); +// const crmClient = azExtAzureUtils.createAzureClient(context, armContainerRegistry.ContainerRegistryManagementClient); +// const appSvcClient = await vscAzureAppService.createWebSiteClient(context); + +// // If we're in `execute`, then `shouldExecute` passed and `this.tagTreeItem.parent.parent` is guaranteed to be an AzureRegistryTreeItem +// const registryTreeItem: AzureRegistryTreeItem = this.tagTreeItem.parent.parent as unknown as AzureRegistryTreeItem; + +// // 1. Get the registry resource. We will need the ID. +// const registry = await crmClient.registries.get(registryTreeItem.resourceGroup, registryTreeItem.registryName); + +// if (!(registry?.id)) { +// throw new Error( +// l10n.t('Unable to get details from Container Registry {0}', registryTreeItem.baseUrl) +// ); +// } + +// // 2. Get the role definition for the AcrPull role. We will need the definition ID. This role is built-in and should always exist. +// const acrPullRoleDefinition = (await azExtAzureUtils.uiUtils.listAllIterator(authClient.roleDefinitions.list(registry.id, { filter: `roleName eq 'AcrPull'` })))[0]; + +// if (!(acrPullRoleDefinition?.id)) { +// throw new Error( +// l10n.t('Unable to get AcrPull role definition on subscription {0}', context.subscriptionId) +// ); +// } + +// // 3. Get the info for the now-created web site. We will need the principal ID. +// const siteInfo = await appSvcClient.webApps.get(context.site.resourceGroup, context.site.name); + +// if (!(siteInfo?.identity?.principalId)) { +// throw new Error( +// l10n.t('Unable to get identity principal ID for web site {0}', context.site.name) +// ); +// } + +// // 4. On the registry, assign the AcrPull role to the principal representing the website +// await authClient.roleAssignments.create(registry.id, randomUUID(), { +// principalId: siteInfo.identity.principalId, +// roleDefinitionId: acrPullRoleDefinition.id, +// principalType: 'ServicePrincipal', +// }); + +// // 5. Set the web app to use the desired ACR image, which was not done in DockerSiteCreateStep. Get the config and then update it. +// const config = await appSvcClient.webApps.getConfiguration(context.site.resourceGroup, context.site.name); + +// if (!config) { +// throw new Error( +// l10n.t('Unable to get configuration for web site {0}', context.site.name) +// ); +// } + +// config.linuxFxVersion = `DOCKER|${this.tagTreeItem.fullTag}`; +// await appSvcClient.webApps.updateConfiguration(context.site.resourceGroup, context.site.name, config); +// } + +// public shouldExecute(context: IAppServiceWizardContext): boolean { +// return !!(context.site) && !!(this.tagTreeItem?.parent?.parent) && this.tagTreeItem.parent.parent instanceof AzureRegistryTreeItem +// && !context.customLocation; +// } +// } diff --git a/src/commands/registries/azure/DockerSiteCreateStep.ts b/src/commands/registries/azure/DockerSiteCreateStep.ts index 9da882c4f1..6d05b1f474 100644 --- a/src/commands/registries/azure/DockerSiteCreateStep.ts +++ b/src/commands/registries/azure/DockerSiteCreateStep.ts @@ -1,141 +1,138 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import type { NameValuePair, Site, SiteConfig, WebSiteManagementClient } from '@azure/arm-appservice'; // These are only dev-time imports so don't need to be lazy -import type { CustomLocation } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy -import type { AzExtLocation } from '@microsoft/vscode-azext-azureutils'; // These are only dev-time imports so don't need to be lazy -import { AzureWizardExecuteStep, nonNullProp, nonNullValueAndProp } from "@microsoft/vscode-azext-utils"; -import { l10n, Progress } from "vscode"; -import { ext } from "../../../extensionVariables"; -import { AzureRegistryTreeItem } from '../../../tree/registries/azure/AzureRegistryTreeItem'; -import { DockerHubNamespaceTreeItem } from '../../../tree/registries/dockerHub/DockerHubNamespaceTreeItem'; -import { DockerV2RegistryTreeItemBase } from '../../../tree/registries/dockerV2/DockerV2RegistryTreeItemBase'; -import { GenericDockerV2RegistryTreeItem } from '../../../tree/registries/dockerV2/GenericDockerV2RegistryTreeItem'; -import { getRegistryPassword } from '../../../tree/registries/registryPasswords'; -import { RegistryTreeItemBase } from '../../../tree/registries/RegistryTreeItemBase'; -import { RemoteTagTreeItem } from '../../../tree/registries/RemoteTagTreeItem'; -import { getAzExtAppService, getAzExtAzureUtils } from '../../../utils/lazyPackages'; -import { IAppServiceContainerWizardContext } from './deployImageToAzure'; - -export class DockerSiteCreateStep extends AzureWizardExecuteStep { - public priority: number = 140; - - public constructor(private readonly node: RemoteTagTreeItem) { - super(); - } - - public async execute(context: IAppServiceContainerWizardContext, progress: Progress<{ message?: string; increment?: number }>): Promise { - const creatingNewApp: string = l10n.t('Creating web app "{0}"...', context.newSiteName); - ext.outputChannel.info(creatingNewApp); - progress.report({ message: creatingNewApp }); - const siteConfig = await this.getNewSiteConfig(context); - - const azExtAzureUtils = await getAzExtAzureUtils(); - const vscAzureAppService = await getAzExtAppService(); - - const location: AzExtLocation = await azExtAzureUtils.LocationListStep.getLocation(context); - const locationName: string = nonNullProp(location, 'name'); - - const client: WebSiteManagementClient = await vscAzureAppService.createWebSiteClient(context); - const siteEnvelope: Site = { - name: context.newSiteName, - location: locationName, - serverFarmId: nonNullValueAndProp(context.plan, 'id'), - siteConfig: siteConfig - }; - - if (context.customLocation) { - // deploying to Azure Arc - siteEnvelope.kind = 'app,linux,kubernetes,container'; - await this.addCustomLocationProperties(siteEnvelope, context.customLocation); - } else { - siteEnvelope.identity = { - type: 'SystemAssigned' - }; - } - - context.site = await client.webApps.beginCreateOrUpdateAndWait(nonNullValueAndProp(context.resourceGroup, 'name'), nonNullProp(context, 'newSiteName'), siteEnvelope); - } - - private async getNewSiteConfig(context: IAppServiceContainerWizardContext): Promise { - const registryTI: RegistryTreeItemBase = this.node.parent.parent; - - let username: string | undefined; - let password: string | undefined; - let registryUrl: string | undefined; - const appSettings: NameValuePair[] = []; - - // Scenarios: - // ACR -> App Service, NOT Arc App Service. Use managed service identity. - if (registryTI instanceof AzureRegistryTreeItem && !context.customLocation) { - appSettings.push({ name: 'DOCKER_ENABLE_CI', value: 'true' }); - - // Don't need an image, username, or password--just create an empty web app to assign permissions and then configure with an image - // `DockerAssignAcrPullRoleStep` handles it after that - return { - acrUseManagedIdentityCreds: true, - appSettings - }; - } - // ACR -> Arc App Service. Use regular auth. Same as any V2 registry but different way of getting auth. - else if (registryTI instanceof AzureRegistryTreeItem && context.customLocation) { - const cred = await registryTI.tryGetAdminCredentials(context); - if (!cred?.username || !cred?.passwords?.[0]?.value) { - throw new Error(l10n.t('Azure App service deployment on Azure Arc only supports running images from Azure Container Registries with admin enabled')); - } - - username = cred.username; - password = cred.passwords[0].value; - registryUrl = registryTI.baseUrl; - } - // Docker Hub -> App Service *OR* Arc App Service - else if (registryTI instanceof DockerHubNamespaceTreeItem) { - username = registryTI.parent.username; - password = await registryTI.parent.getPassword(); - registryUrl = 'https://index.docker.io'; - } - // Generic registry -> App Service *OR* Arc App Service - else if (registryTI instanceof DockerV2RegistryTreeItemBase) { - if (registryTI instanceof GenericDockerV2RegistryTreeItem) { - username = registryTI.cachedProvider.username; - password = await getRegistryPassword(registryTI.cachedProvider); - } else { - throw new RangeError(l10n.t('Unrecognized node type "{0}"', registryTI.constructor.name)); - } - - registryUrl = registryTI.baseUrl; - } else { - throw new RangeError(l10n.t('Unrecognized node type "{0}"', registryTI.constructor.name)); - } - - if (username && password) { - appSettings.push({ name: "DOCKER_REGISTRY_SERVER_USERNAME", value: username }); - appSettings.push({ name: "DOCKER_REGISTRY_SERVER_PASSWORD", value: password }); - } - - if (registryUrl) { - appSettings.push({ name: 'DOCKER_REGISTRY_SERVER_URL', value: registryUrl }); - } - - if (context.webSitesPort) { - appSettings.push({ name: "WEBSITES_PORT", value: context.webSitesPort.toString() }); - } - - const linuxFxVersion = `DOCKER|${this.node.fullTag}`; - - return { - linuxFxVersion, - appSettings - }; - } - - private addCustomLocationProperties(site: Site, customLocation: CustomLocation): void { - site.extendedLocation = { name: customLocation.id, type: 'customLocation' }; - } - - public shouldExecute(context: IAppServiceContainerWizardContext): boolean { - return !context.site; - } -} +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ + +// import type { NameValuePair, Site, SiteConfig, WebSiteManagementClient } from '@azure/arm-appservice'; // These are only dev-time imports so don't need to be lazy +// import type { CustomLocation } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy +// import type { AzExtLocation } from '@microsoft/vscode-azext-azureutils'; // These are only dev-time imports so don't need to be lazy +// import { AzureWizardExecuteStep, nonNullProp, nonNullValueAndProp } from "@microsoft/vscode-azext-utils"; +// import { Progress, l10n } from "vscode"; +// import { ext } from "../../../extensionVariables"; +// import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +// import { getAzExtAppService, getAzExtAzureUtils } from '../../../utils/lazyPackages'; +// import { IAppServiceContainerWizardContext } from './deployImageToAzure'; + +// export class DockerSiteCreateStep extends AzureWizardExecuteStep { +// public priority: number = 140; + +// public constructor(private readonly node: UnifiedRegistryItem) { +// super(); +// } + +// public async execute(context: IAppServiceContainerWizardContext, progress: Progress<{ message?: string; increment?: number }>): Promise { +// const creatingNewApp: string = l10n.t('Creating web app "{0}"...', context.newSiteName); +// ext.outputChannel.info(creatingNewApp); +// progress.report({ message: creatingNewApp }); +// const siteConfig = await this.getNewSiteConfig(context); + +// const azExtAzureUtils = await getAzExtAzureUtils(); +// const vscAzureAppService = await getAzExtAppService(); + +// const location: AzExtLocation = await azExtAzureUtils.LocationListStep.getLocation(context); +// const locationName: string = nonNullProp(location, 'name'); + +// const client: WebSiteManagementClient = await vscAzureAppService.createWebSiteClient(context); +// const siteEnvelope: Site = { +// name: context.newSiteName, +// location: locationName, +// serverFarmId: nonNullValueAndProp(context.plan, 'id'), +// siteConfig: siteConfig +// }; + +// if (context.customLocation) { +// // deploying to Azure Arc +// siteEnvelope.kind = 'app,linux,kubernetes,container'; +// await this.addCustomLocationProperties(siteEnvelope, context.customLocation); +// } else { +// siteEnvelope.identity = { +// type: 'SystemAssigned' +// }; +// } + +// context.site = await client.webApps.beginCreateOrUpdateAndWait(nonNullValueAndProp(context.resourceGroup, 'name'), nonNullProp(context, 'newSiteName'), siteEnvelope); +// } + +// private async getNewSiteConfig(context: IAppServiceContainerWizardContext): Promise { +// const registryTI: UnifiedRegistryItem = this.node.parent.parent; + +// let username: string | undefined; +// let password: string | undefined; +// let registryUrl: string | undefined; +// const appSettings: NameValuePair[] = []; + +// // Scenarios: +// // ACR -> App Service, NOT Arc App Service. Use managed service identity. +// if (registryTI instanceof AzureRegistryTreeItem && !context.customLocation) { +// appSettings.push({ name: 'DOCKER_ENABLE_CI', value: 'true' }); + +// // Don't need an image, username, or password--just create an empty web app to assign permissions and then configure with an image +// // `DockerAssignAcrPullRoleStep` handles it after that +// return { +// acrUseManagedIdentityCreds: true, +// appSettings +// }; +// } +// // ACR -> Arc App Service. Use regular auth. Same as any V2 registry but different way of getting auth. +// else if (registryTI instanceof AzureRegistryTreeItem && context.customLocation) { +// const cred = await registryTI.tryGetAdminCredentials(context); +// if (!cred?.username || !cred?.passwords?.[0]?.value) { +// throw new Error(l10n.t('Azure App service deployment on Azure Arc only supports running images from Azure Container Registries with admin enabled')); +// } + +// username = cred.username; +// password = cred.passwords[0].value; +// registryUrl = registryTI.baseUrl; +// } +// // Docker Hub -> App Service *OR* Arc App Service +// else if (registryTI instanceof DockerHubNamespaceTreeItem) { +// username = registryTI.parent.username; +// password = await registryTI.parent.getPassword(); +// registryUrl = 'https://index.docker.io'; +// } +// // Generic registry -> App Service *OR* Arc App Service +// else if (registryTI instanceof DockerV2RegistryTreeItemBase) { +// if (registryTI instanceof GenericDockerV2RegistryTreeItem) { +// username = registryTI.cachedProvider.username; +// password = await getRegistryPassword(registryTI.cachedProvider); +// } else { +// throw new RangeError(l10n.t('Unrecognized node type "{0}"', registryTI.constructor.name)); +// } + +// registryUrl = registryTI.baseUrl; +// } else { +// throw new RangeError(l10n.t('Unrecognized node type "{0}"', registryTI.constructor.name)); +// } + +// if (username && password) { +// appSettings.push({ name: "DOCKER_REGISTRY_SERVER_USERNAME", value: username }); +// appSettings.push({ name: "DOCKER_REGISTRY_SERVER_PASSWORD", value: password }); +// } + +// if (registryUrl) { +// appSettings.push({ name: 'DOCKER_REGISTRY_SERVER_URL', value: registryUrl }); +// } + +// if (context.webSitesPort) { +// appSettings.push({ name: "WEBSITES_PORT", value: context.webSitesPort.toString() }); +// } + +// const linuxFxVersion = `DOCKER|${this.node.fullTag}`; +// TODO: review this later +// const linuxFxVersion = ''; +// return { +// linuxFxVersion, +// appSettings +// }; +// } + +// private addCustomLocationProperties(site: Site, customLocation: CustomLocation): void { +// site.extendedLocation = { name: customLocation.id, type: 'customLocation' }; +// } + +// public shouldExecute(context: IAppServiceContainerWizardContext): boolean { +// return !context.site; +// } +// } + +// TODO: review this later diff --git a/src/commands/registries/azure/DockerWebhookCreateStep.ts b/src/commands/registries/azure/DockerWebhookCreateStep.ts index c6cb6cc240..25f56c23d1 100644 --- a/src/commands/registries/azure/DockerWebhookCreateStep.ts +++ b/src/commands/registries/azure/DockerWebhookCreateStep.ts @@ -1,96 +1,96 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ -import type { Site } from '@azure/arm-appservice'; // These are only dev-time imports so don't need to be lazy -import type { Webhook, WebhookCreateParameters } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy -import type { IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy -import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils"; -import * as vscode from "vscode"; -import { ext } from "../../../extensionVariables"; -import { AzureRegistryTreeItem } from '../../../tree/registries/azure/AzureRegistryTreeItem'; -import { AzureRepositoryTreeItem } from '../../../tree/registries/azure/AzureRepositoryTreeItem'; -import { DockerHubRepositoryTreeItem } from '../../../tree/registries/dockerHub/DockerHubRepositoryTreeItem'; -import { RemoteTagTreeItem } from '../../../tree/registries/RemoteTagTreeItem'; -import { cryptoUtils } from '../../../utils/cryptoUtils'; -import { getArmContainerRegistry, getAzExtAppService, getAzExtAzureUtils } from "../../../utils/lazyPackages"; +// import type { Site } from '@azure/arm-appservice'; // These are only dev-time imports so don't need to be lazy +// import type { Webhook, WebhookCreateParameters } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy +// import type { IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy +// import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils"; +// import * as vscode from "vscode"; +// import { ext } from "../../../extensionVariables"; +// import { AzureRegistryTreeItem } from '../../../tree/registries/azure/AzureRegistryTreeItem'; +// import { AzureRepositoryTreeItem } from '../../../tree/registries/azure/AzureRepositoryTreeItem'; +// import { DockerHubRepositoryTreeItem } from '../../../tree/registries/dockerHub/DockerHubRepositoryTreeItem'; +// import { RemoteTagTreeItem } from '../../../tree/registries/RemoteTagTreeItem'; +// import { cryptoUtils } from '../../../utils/cryptoUtils'; +// import { getArmContainerRegistry, getAzExtAppService, getAzExtAzureUtils } from "../../../utils/lazyPackages"; -export class DockerWebhookCreateStep extends AzureWizardExecuteStep { - public priority: number = 142; // execute after DockerAssignAcrPullRoleStep - private _treeItem: RemoteTagTreeItem; - public constructor(treeItem: RemoteTagTreeItem) { - super(); - this._treeItem = treeItem; - } +// export class DockerWebhookCreateStep extends AzureWizardExecuteStep { +// public priority: number = 142; // execute after DockerAssignAcrPullRoleStep +// private _treeItem: RemoteTagTreeItem; +// public constructor(treeItem: RemoteTagTreeItem) { +// super(); +// this._treeItem = treeItem; +// } - public async execute(context: IAppServiceWizardContext, progress: vscode.Progress<{ - message?: string; - increment?: number; - }>): Promise { - const vscAzureAppService = await getAzExtAppService(); - vscAzureAppService.registerAppServiceExtensionVariables(ext); - const site: Site = nonNullProp(context, 'site'); - const parsedSite = new vscAzureAppService.ParsedSite(site, context); - const siteClient = await parsedSite.createClient(context); - const appUri: string = (await siteClient.getWebAppPublishCredential()).scmUri; - if (this._treeItem.parent instanceof AzureRepositoryTreeItem) { - const creatingNewWebhook: string = vscode.l10n.t('Creating webhook for web app "{0}"...', context.newSiteName); - ext.outputChannel.info(creatingNewWebhook); - progress.report({ message: creatingNewWebhook }); - const webhook = await this.createWebhookForApp(context, this._treeItem, context.site, appUri); - ext.outputChannel.info(vscode.l10n.t('Created webhook "{0}" with scope "{1}", id: "{2}" and location: "{3}"', webhook.name, webhook.scope, webhook.id, webhook.location)); - } else if (this._treeItem.parent instanceof DockerHubRepositoryTreeItem) { - // point to dockerhub to create a webhook - // http://cloud.docker.com/repository/docker///webHooks - const dockerhubPrompt: string = vscode.l10n.t('Copy & Open'); - const dockerhubUri: string = `https://cloud.docker.com/repository/docker/${this._treeItem.parent.parent.namespace}/${this._treeItem.parent.repoName}/webHooks`; +// public async execute(context: IAppServiceWizardContext, progress: vscode.Progress<{ +// message?: string; +// increment?: number; +// }>): Promise { +// const vscAzureAppService = await getAzExtAppService(); +// vscAzureAppService.registerAppServiceExtensionVariables(ext); +// const site: Site = nonNullProp(context, 'site'); +// const parsedSite = new vscAzureAppService.ParsedSite(site, context); +// const siteClient = await parsedSite.createClient(context); +// const appUri: string = (await siteClient.getWebAppPublishCredential()).scmUri; +// if (this._treeItem.parent instanceof AzureRepositoryTreeItem) { +// const creatingNewWebhook: string = vscode.l10n.t('Creating webhook for web app "{0}"...', context.newSiteName); +// ext.outputChannel.info(creatingNewWebhook); +// progress.report({ message: creatingNewWebhook }); +// const webhook = await this.createWebhookForApp(context, this._treeItem, context.site, appUri); +// ext.outputChannel.info(vscode.l10n.t('Created webhook "{0}" with scope "{1}", id: "{2}" and location: "{3}"', webhook.name, webhook.scope, webhook.id, webhook.location)); +// } else if (this._treeItem.parent instanceof DockerHubRepositoryTreeItem) { +// // point to dockerhub to create a webhook +// // http://cloud.docker.com/repository/docker///webHooks +// const dockerhubPrompt: string = vscode.l10n.t('Copy & Open'); +// const dockerhubUri: string = `https://cloud.docker.com/repository/docker/${this._treeItem.parent.parent.namespace}/${this._treeItem.parent.repoName}/webHooks`; - // NOTE: The response to the information message is not awaited but handled independently of the wizard steps. - // VS Code will hide such messages in the notifications pane after a period of time; awaiting them risks - // the user never noticing them in the first place, which means the wizard would never complete, and the - // user left with the impression that the action never completes. +// // NOTE: The response to the information message is not awaited but handled independently of the wizard steps. +// // VS Code will hide such messages in the notifications pane after a period of time; awaiting them risks +// // the user never noticing them in the first place, which means the wizard would never complete, and the +// // user left with the impression that the action never completes. - /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - vscode.window - .showInformationMessage(vscode.l10n.t('To set up a CI/CD webhook, open the page "{0}" and enter the URI to the created web app in your dockerhub account', dockerhubUri), dockerhubPrompt) - .then(response => { - if (response) { - void vscode.env.clipboard.writeText(appUri); - void vscode.env.openExternal(vscode.Uri.parse(dockerhubUri)); - } - }); - } - } +// /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ +// vscode.window +// .showInformationMessage(vscode.l10n.t('To set up a CI/CD webhook, open the page "{0}" and enter the URI to the created web app in your dockerhub account', dockerhubUri), dockerhubPrompt) +// .then(response => { +// if (response) { +// void vscode.env.clipboard.writeText(appUri); +// void vscode.env.openExternal(vscode.Uri.parse(dockerhubUri)); +// } +// }); +// } +// } - public shouldExecute(context: IAppServiceWizardContext): boolean { - return !!context.site && (this._treeItem.parent instanceof AzureRepositoryTreeItem || this._treeItem.parent instanceof DockerHubRepositoryTreeItem); - } +// public shouldExecute(context: IAppServiceWizardContext): boolean { +// return !!context.site && (this._treeItem.parent instanceof AzureRepositoryTreeItem || this._treeItem.parent instanceof DockerHubRepositoryTreeItem); +// } - private async createWebhookForApp(context: IAppServiceWizardContext, node: RemoteTagTreeItem, site: Site, appUri: string): Promise { - const maxLength: number = 50; - const numRandomChars: number = 6; +// private async createWebhookForApp(context: IAppServiceWizardContext, node: RemoteTagTreeItem, site: Site, appUri: string): Promise { +// const maxLength: number = 50; +// const numRandomChars: number = 6; - let webhookName: string = site.name; - // remove disallowed characters - webhookName = webhookName.replace(/[^a-zA-Z0-9]/g, ''); - // trim to max length - webhookName = webhookName.substr(0, maxLength - numRandomChars); - // add random chars for uniqueness and to ensure min length is met - webhookName += cryptoUtils.getRandomHexString(numRandomChars); +// let webhookName: string = site.name; +// // remove disallowed characters +// webhookName = webhookName.replace(/[^a-zA-Z0-9]/g, ''); +// // trim to max length +// webhookName = webhookName.substr(0, maxLength - numRandomChars); +// // add random chars for uniqueness and to ensure min length is met +// webhookName += cryptoUtils.getRandomHexString(numRandomChars); - // variables derived from the container registry - const registryTreeItem: AzureRegistryTreeItem = (node.parent).parent; - const armContainerRegistry = await getArmContainerRegistry(); - const azExtAzureUtils = await getAzExtAzureUtils(); - const crmClient = azExtAzureUtils.createAzureClient(context, armContainerRegistry.ContainerRegistryManagementClient); - const webhookCreateParameters: WebhookCreateParameters = { - location: registryTreeItem.registryLocation, - serviceUri: appUri, - scope: `${node.parent.repoName}:${node.tag}`, - actions: ["push"], - status: 'enabled' - }; - return await crmClient.webhooks.beginCreateAndWait(registryTreeItem.resourceGroup, registryTreeItem.registryName, webhookName, webhookCreateParameters); - } -} +// // variables derived from the container registry +// const registryTreeItem: AzureRegistryTreeItem = (node.parent).parent; +// const armContainerRegistry = await getArmContainerRegistry(); +// const azExtAzureUtils = await getAzExtAzureUtils(); +// const crmClient = azExtAzureUtils.createAzureClient(context, armContainerRegistry.ContainerRegistryManagementClient); +// const webhookCreateParameters: WebhookCreateParameters = { +// location: registryTreeItem.registryLocation, +// serviceUri: appUri, +// scope: `${node.parent.repoName}:${node.tag}`, +// actions: ["push"], +// status: 'enabled' +// }; +// return await crmClient.webhooks.beginCreateAndWait(registryTreeItem.resourceGroup, registryTreeItem.registryName, webhookName, webhookCreateParameters); +// } +// } diff --git a/src/commands/registries/azure/WebSitesPortPromptStep.ts b/src/commands/registries/azure/WebSitesPortPromptStep.ts index 48667641c0..e16ca26dab 100644 --- a/src/commands/registries/azure/WebSitesPortPromptStep.ts +++ b/src/commands/registries/azure/WebSitesPortPromptStep.ts @@ -1,35 +1,35 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * 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 "@microsoft/vscode-azext-utils"; -import { l10n } from 'vscode'; -import { IAppServiceContainerWizardContext } from './deployImageToAzure'; +// import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils"; +// import { l10n } from 'vscode'; +// import { IAppServiceContainerWizardContext } from './deployImageToAzure'; -export class WebSitesPortPromptStep extends AzureWizardPromptStep { +// export class WebSitesPortPromptStep extends AzureWizardPromptStep { - public async prompt(context: IAppServiceContainerWizardContext): Promise { - const prompt: string = l10n.t('What port does your app listen on?'); - const placeHolder: string = '80'; - const value: string = '80'; - const portString: string = await context.ui.showInputBox({ prompt, placeHolder, value, validateInput }); - context.webSitesPort = parseInt(portString, 10); - } +// public async prompt(context: IAppServiceContainerWizardContext): Promise { +// const prompt: string = l10n.t('What port does your app listen on?'); +// const placeHolder: string = '80'; +// const value: string = '80'; +// const portString: string = await context.ui.showInputBox({ prompt, placeHolder, value, validateInput }); +// context.webSitesPort = parseInt(portString, 10); +// } - public shouldPrompt(context: IAppServiceContainerWizardContext): boolean { - return !!context.customLocation; - } -} +// public shouldPrompt(context: IAppServiceContainerWizardContext): boolean { +// return !!context.customLocation; +// } +// } -function validateInput(value: string | undefined): string | undefined { - if (Number(value)) { - const port: number = parseInt(value, 10); - if (port >= 1 && port <= 65535) { - return undefined; - } - } +// function validateInput(value: string | undefined): string | undefined { +// if (Number(value)) { +// const port: number = parseInt(value, 10); +// if (port >= 1 && port <= 65535) { +// return undefined; +// } +// } - return l10n.t('Port must be a positive integer (1 to 65535).'); -} +// return l10n.t('Port must be a positive integer (1 to 65535).'); +// } diff --git a/src/commands/registries/azure/createAzureRegistry.ts b/src/commands/registries/azure/createAzureRegistry.ts index e68cf2c0a4..a569cd5833 100644 --- a/src/commands/registries/azure/createAzureRegistry.ts +++ b/src/commands/registries/azure/createAzureRegistry.ts @@ -4,16 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext } from '@microsoft/vscode-azext-utils'; -import { ext } from '../../../extensionVariables'; -import type { SubscriptionTreeItem } from '../../../tree/registries/azure/SubscriptionTreeItem'; // These are only dev-time imports so don't need to be lazy -import { getAzSubTreeItem } from '../../../utils/lazyPackages'; +// import type { SubscriptionTreeItem } from '../../../tree/registries/azure/SubscriptionTreeItem'; // These are only dev-time imports so don't need to be lazy +import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +// import { getAzSubTreeItem } from '../../../utils/lazyPackages'; -export async function createAzureRegistry(context: IActionContext, node?: SubscriptionTreeItem): Promise { - const azSubTreeItem = await getAzSubTreeItem(); +export async function createAzureRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { + // const azSubTreeItem = await getAzSubTreeItem(); if (!node) { - node = await ext.registriesTree.showTreeItemPicker(azSubTreeItem.SubscriptionTreeItem.contextValue, context); + // node = await ext.registriesTree.showTreeItemPicker(azSubTreeItem.SubscriptionTreeItem.contextValue, context); } - await node.createChild(context); + // await node.createChild(context); + // TODO: review this later } diff --git a/src/commands/registries/azure/deleteAzureRegistry.ts b/src/commands/registries/azure/deleteAzureRegistry.ts index 862a3582d6..bfac41cc95 100644 --- a/src/commands/registries/azure/deleteAzureRegistry.ts +++ b/src/commands/registries/azure/deleteAzureRegistry.ts @@ -1,30 +1,30 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ -import { DialogResponses, IActionContext } from '@microsoft/vscode-azext-utils'; -import { l10n, ProgressLocation, window } from 'vscode'; -import { ext } from '../../../extensionVariables'; -import type { AzureRegistryTreeItem } from '../../../tree/registries/azure/AzureRegistryTreeItem'; -import { registryExpectedContextValues } from '../../../tree/registries/registryContextValues'; +// import { DialogResponses, IActionContext } from '@microsoft/vscode-azext-utils'; +// import { l10n, ProgressLocation, window } from 'vscode'; +// import { ext } from '../../../extensionVariables'; +// import type { AzureRegistryTreeItem } from '../../../tree/registries/azure/AzureRegistryTreeItem'; +// import { registryExpectedContextValues } from '../../../tree/registries/registryContextValues'; -export async function deleteAzureRegistry(context: IActionContext, node?: AzureRegistryTreeItem): Promise { - if (!node) { - node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.registry, { ...context, suppressCreatePick: true }); - } +// export async function deleteAzureRegistry(context: IActionContext, node?: AzureRegistryTreeItem): Promise { +// if (!node) { +// node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.registry, { ...context, suppressCreatePick: true }); +// } - const confirmDelete: string = l10n.t('Are you sure you want to delete registry "{0}" and its associated images?', node.registryName); - // no need to check result - cancel will throw a UserCancelledError - await context.ui.showWarningMessage(confirmDelete, { modal: true }, DialogResponses.deleteResponse); +// const confirmDelete: string = l10n.t('Are you sure you want to delete registry "{0}" and its associated images?', node.registryName); +// // no need to check result - cancel will throw a UserCancelledError +// await context.ui.showWarningMessage(confirmDelete, { modal: true }, DialogResponses.deleteResponse); - const deleting = l10n.t('Deleting registry "{0}"...', node.registryName); - await window.withProgress({ location: ProgressLocation.Notification, title: deleting }, async () => { - await node.deleteTreeItem(context); - }); +// const deleting = l10n.t('Deleting registry "{0}"...', node.registryName); +// await window.withProgress({ location: ProgressLocation.Notification, title: deleting }, async () => { +// await node.deleteTreeItem(context); +// }); - const message = l10n.t('Successfully deleted registry "{0}".', node.registryName); - // don't wait - /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - window.showInformationMessage(message); -} +// const message = l10n.t('Successfully deleted registry "{0}".', node.registryName); +// // don't wait +// /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ +// window.showInformationMessage(message); +// } diff --git a/src/commands/registries/azure/deleteAzureRepository.ts b/src/commands/registries/azure/deleteAzureRepository.ts index c3be2f59f5..57e8e4de49 100644 --- a/src/commands/registries/azure/deleteAzureRepository.ts +++ b/src/commands/registries/azure/deleteAzureRepository.ts @@ -1,30 +1,30 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ -import { DialogResponses, IActionContext } from '@microsoft/vscode-azext-utils'; -import { l10n, ProgressLocation, window } from 'vscode'; -import { ext } from '../../../extensionVariables'; -import type { AzureRepositoryTreeItem } from '../../../tree/registries/azure/AzureRepositoryTreeItem'; -import { registryExpectedContextValues } from '../../../tree/registries/registryContextValues'; +// import { DialogResponses, IActionContext } from '@microsoft/vscode-azext-utils'; +// import { l10n, ProgressLocation, window } from 'vscode'; +// import { ext } from '../../../extensionVariables'; +// import type { AzureRepositoryTreeItem } from '../../../tree/registries/azure/AzureRepositoryTreeItem'; +// import { registryExpectedContextValues } from '../../../tree/registries/registryContextValues'; -export async function deleteAzureRepository(context: IActionContext, node?: AzureRepositoryTreeItem): Promise { - if (!node) { - node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.repository, { ...context, suppressCreatePick: true }); - } +// export async function deleteAzureRepository(context: IActionContext, node?: AzureRepositoryTreeItem): Promise { +// if (!node) { +// node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.repository, { ...context, suppressCreatePick: true }); +// } - const confirmDelete = l10n.t('Are you sure you want to delete repository "{0}" and its associated images?', node.repoName); - // no need to check result - cancel will throw a UserCancelledError - await context.ui.showWarningMessage(confirmDelete, { modal: true }, DialogResponses.deleteResponse); +// const confirmDelete = l10n.t('Are you sure you want to delete repository "{0}" and its associated images?', node.repoName); +// // no need to check result - cancel will throw a UserCancelledError +// await context.ui.showWarningMessage(confirmDelete, { modal: true }, DialogResponses.deleteResponse); - const deleting = l10n.t('Deleting repository "{0}"...', node.repoName); - await window.withProgress({ location: ProgressLocation.Notification, title: deleting }, async () => { - await node.deleteTreeItem(context); - }); +// const deleting = l10n.t('Deleting repository "{0}"...', node.repoName); +// await window.withProgress({ location: ProgressLocation.Notification, title: deleting }, async () => { +// await node.deleteTreeItem(context); +// }); - const deleteSucceeded = l10n.t('Successfully deleted repository "{0}".', node.repoName); - // don't wait - /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - window.showInformationMessage(deleteSucceeded); -} +// const deleteSucceeded = l10n.t('Successfully deleted repository "{0}".', node.repoName); +// // don't wait +// /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ +// window.showInformationMessage(deleteSucceeded); +// } diff --git a/src/commands/registries/azure/deployImageToAca.ts b/src/commands/registries/azure/deployImageToAca.ts index f31a57c7cd..da4d9a495a 100644 --- a/src/commands/registries/azure/deployImageToAca.ts +++ b/src/commands/registries/azure/deployImageToAca.ts @@ -1,98 +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 { IActionContext, nonNullProp, UserCancelledError } from '@microsoft/vscode-azext-utils'; -import { parseDockerLikeImageName } from '@microsoft/vscode-container-client'; -import * as semver from 'semver'; -import * as vscode from 'vscode'; -import { ext } from '../../../extensionVariables'; -import { AzureRegistryTreeItem } from '../../../tree/registries/azure/AzureRegistryTreeItem'; -import { DockerHubNamespaceTreeItem } from '../../../tree/registries/dockerHub/DockerHubNamespaceTreeItem'; -import { registryExpectedContextValues } from '../../../tree/registries/registryContextValues'; -import { RegistryTreeItemBase } from '../../../tree/registries/RegistryTreeItemBase'; -import { RemoteTagTreeItem } from '../../../tree/registries/RemoteTagTreeItem'; -import { installExtension } from '../../../utils/installExtension'; -import { addImageTaggingTelemetry } from '../../images/tagImage'; - -const acaExtensionId = 'ms-azuretools.vscode-azurecontainerapps'; -const minimumAcaExtensionVersion = '0.4.0'; - -// The interface of the command options passed to the Azure Container Apps extension's deployImageToAca command -interface DeployImageToAcaOptionsContract { - image: string; - registryName: string; - username?: string; - secret?: string; -} - -export async function deployImageToAca(context: IActionContext, node?: RemoteTagTreeItem): Promise { - // Assert installation of the ACA extension - if (!isAcaExtensionInstalled()) { - // This will always throw a `UserCancelledError` but with the appropriate step name - // based on user choice about installation - await openAcaInstallPage(context); - } - - if (!node) { - node = await ext.registriesTree.showTreeItemPicker([registryExpectedContextValues.dockerHub.tag, registryExpectedContextValues.dockerV2.tag], context); - } - - const commandOptions: Partial = { - image: node.fullTag, - }; - - addImageTaggingTelemetry(context, commandOptions.image, ''); - - const registry: RegistryTreeItemBase = node.parent.parent; - if (registry instanceof AzureRegistryTreeItem) { - // No additional work to do; ACA can handle this on its own - } else { - const { auth } = await registry.getDockerCliCredentials() as { auth?: { username?: string, password?: string } }; - - if (!auth?.username || !auth?.password) { - throw new Error(vscode.l10n.t('No credentials found for registry "{0}".', registry.label)); - } - - if (registry instanceof DockerHubNamespaceTreeItem) { - // Ensure Docker Hub images are prefixed with 'docker.io/...' - if (!/^docker\.io\//i.test(commandOptions.image)) { - commandOptions.image = 'docker.io/' + commandOptions.image; - } - } - - commandOptions.username = auth.username; - commandOptions.secret = auth.password; - } - - commandOptions.registryName = nonNullProp(parseDockerLikeImageName(commandOptions.image), 'registry'); - - // Don't wait - void vscode.commands.executeCommand('containerApps.deployImageApi', commandOptions); -} - -function isAcaExtensionInstalled(): boolean { - const acaExtension = vscode.extensions.getExtension(acaExtensionId); - - if (!acaExtension?.packageJSON?.version) { - // If the ACA extension is not present, or the package JSON didn't come through, or the version is not present, then it's not installed - return false; - } - - const acaVersion = semver.coerce(acaExtension.packageJSON.version); - const minVersion = semver.coerce(minimumAcaExtensionVersion); - - return semver.gte(acaVersion, minVersion); -} - -async function openAcaInstallPage(context: IActionContext): Promise { - const message = vscode.l10n.t( - 'Version {0} or higher of the Azure Container Apps extension is required to deploy to Azure Container Apps. Would you like to install it now?', - minimumAcaExtensionVersion - ); - - await installExtension(context, acaExtensionId, message); - - throw new UserCancelledError('installAcaExtensionAccepted'); -} +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ + +// import { contextValueExperience, IActionContext, UserCancelledError } from '@microsoft/vscode-azext-utils'; +// import * as semver from 'semver'; +// import * as vscode from 'vscode'; +// import { ext } from '../../../extensionVariables'; +// import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +// import { installExtension } from '../../../utils/installExtension'; +// import { addImageTaggingTelemetry } from '../../images/tagImage'; + +// const acaExtensionId = 'ms-azuretools.vscode-azurecontainerapps'; +// const minimumAcaExtensionVersion = '0.4.0'; + +// // The interface of the command options passed to the Azure Container Apps extension's deployImageToAca command +// interface DeployImageToAcaOptionsContract { +// image: string; +// registryName: string; +// username?: string; +// secret?: string; +// } + +// export async function deployImageToAca(context: IActionContext, node?: UnifiedRegistryItem): Promise { +// // Assert installation of the ACA extension +// if (!isAcaExtensionInstalled()) { +// // This will always throw a `UserCancelledError` but with the appropriate step name +// // based on user choice about installation +// await openAcaInstallPage(context); +// } + +// if (!node) { +// node = await contextValueExperience(context, ext.registriesRoot, { include: 'azurecontainerregistry' }); +// } + +// const commandOptions: Partial = { +// // image: node.fullTag, +// }; + +// addImageTaggingTelemetry(context, commandOptions.image, ''); + +// // const registry: RegistryTreeItemBase = node.parent.parent; +// // if (registry instanceof AzureRegistryTreeItem) { +// // // No additional work to do; ACA can handle this on its own +// // } else { +// // const { auth } = await registry.getDockerCliCredentials() as { auth?: { username?: string, password?: string } }; + +// // if (!auth?.username || !auth?.password) { +// // throw new Error(vscode.l10n.t('No credentials found for registry "{0}".', registry.label)); +// // } + +// // if (registry instanceof DockerHubNamespaceTreeItem) { +// // // Ensure Docker Hub images are prefixed with 'docker.io/...' +// // if (!/^docker\.io\//i.test(commandOptions.image)) { +// // commandOptions.image = 'docker.io/' + commandOptions.image; +// // } +// // } + +// // commandOptions.username = auth.username; +// // commandOptions.secret = auth.password; +// // } + +// // commandOptions.registryName = nonNullProp(parseDockerLikeImageName(commandOptions.image), 'registry'); + +// // // Don't wait +// // void vscode.commands.executeCommand('containerApps.deployImageApi', commandOptions); +// // TODO: review this later +// } + +// function isAcaExtensionInstalled(): boolean { +// const acaExtension = vscode.extensions.getExtension(acaExtensionId); + +// if (!acaExtension?.packageJSON?.version) { +// // If the ACA extension is not present, or the package JSON didn't come through, or the version is not present, then it's not installed +// return false; +// } + +// const acaVersion = semver.coerce(acaExtension.packageJSON.version); +// const minVersion = semver.coerce(minimumAcaExtensionVersion); + +// return semver.gte(acaVersion, minVersion); +// } + +// async function openAcaInstallPage(context: IActionContext): Promise { +// const message = vscode.l10n.t( +// 'Version {0} or higher of the Azure Container Apps extension is required to deploy to Azure Container Apps. Would you like to install it now?', +// minimumAcaExtensionVersion +// ); + +// await installExtension(context, acaExtensionId, message); + +// throw new UserCancelledError('installAcaExtensionAccepted'); +// } diff --git a/src/commands/registries/azure/deployImageToAzure.ts b/src/commands/registries/azure/deployImageToAzure.ts index bbc3cee850..375982c4ea 100644 --- a/src/commands/registries/azure/deployImageToAzure.ts +++ b/src/commands/registries/azure/deployImageToAzure.ts @@ -1,80 +1,80 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ -import type { Site } from '@azure/arm-appservice'; // These are only dev-time imports so don't need to be lazy -import type { IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy -import { AzureWizard, AzureWizardExecuteStep, AzureWizardPromptStep, IActionContext, nonNullProp } from "@microsoft/vscode-azext-utils"; -import { env, l10n, Uri, window } from "vscode"; -import { ext } from "../../../extensionVariables"; -import { RegistryApi } from '../../../tree/registries/all/RegistryApi'; -import { azureRegistryProviderId } from '../../../tree/registries/azure/azureRegistryProvider'; -import { registryExpectedContextValues } from '../../../tree/registries/registryContextValues'; -import { RemoteTagTreeItem } from '../../../tree/registries/RemoteTagTreeItem'; -import { getAzActTreeItem, getAzExtAppService, getAzExtAzureUtils } from '../../../utils/lazyPackages'; -import { DockerAssignAcrPullRoleStep } from './DockerAssignAcrPullRoleStep'; -import { DockerSiteCreateStep } from './DockerSiteCreateStep'; -import { DockerWebhookCreateStep } from './DockerWebhookCreateStep'; -import { WebSitesPortPromptStep } from './WebSitesPortPromptStep'; +// import type { Site } from '@azure/arm-appservice'; // These are only dev-time imports so don't need to be lazy +// import type { IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy +// import { AzureWizard, AzureWizardExecuteStep, AzureWizardPromptStep, IActionContext, nonNullProp } from "@microsoft/vscode-azext-utils"; +// import { env, l10n, Uri, window } from "vscode"; +// import { ext } from "../../../extensionVariables"; +// import { RegistryApi } from '../../../tree/registries/all/RegistryApi'; +// import { azureRegistryProviderId } from '../../../tree/registries/azure/azureRegistryProvider'; +// import { registryExpectedContextValues } from '../../../tree/registries/registryContextValues'; +// import { RemoteTagTreeItem } from '../../../tree/registries/RemoteTagTreeItem'; +// import { getAzActTreeItem, getAzExtAppService, getAzExtAzureUtils } from '../../../utils/lazyPackages'; +// import { DockerAssignAcrPullRoleStep } from './DockerAssignAcrPullRoleStep'; +// import { DockerSiteCreateStep } from './DockerSiteCreateStep'; +// import { DockerWebhookCreateStep } from './DockerWebhookCreateStep'; +// import { WebSitesPortPromptStep } from './WebSitesPortPromptStep'; -export interface IAppServiceContainerWizardContext extends IAppServiceWizardContext { - webSitesPort?: number; -} +// export interface IAppServiceContainerWizardContext extends IAppServiceWizardContext { +// webSitesPort?: number; +// } -export async function deployImageToAzure(context: IActionContext, node?: RemoteTagTreeItem): Promise { - if (!node) { - node = await ext.registriesTree.showTreeItemPicker([registryExpectedContextValues.dockerHub.tag, registryExpectedContextValues.dockerV2.tag], context); - } +// export async function deployImageToAzure(context: IActionContext, node?: RemoteTagTreeItem): Promise { +// if (!node) { +// node = await ext.registriesTree.showTreeItemPicker([registryExpectedContextValues.dockerHub.tag, registryExpectedContextValues.dockerV2.tag], context); +// } - const azExtAzureUtils = await getAzExtAzureUtils(); - const vscAzureAppService = await getAzExtAppService(); - const azActTreeItem = await getAzActTreeItem(); +// const azExtAzureUtils = await getAzExtAzureUtils(); +// const vscAzureAppService = await getAzExtAppService(); +// const azActTreeItem = await getAzActTreeItem(); - const wizardContext: IActionContext & Partial = { - ...context, - newSiteOS: vscAzureAppService.WebsiteOS.linux, - newSiteKind: vscAzureAppService.AppKind.app - }; - const promptSteps: AzureWizardPromptStep[] = []; - // Create a temporary azure account tree item since Azure might not be connected - const azureAccountTreeItem = new azActTreeItem.AzureAccountTreeItem(ext.registriesRoot, { id: azureRegistryProviderId, api: RegistryApi.DockerV2 }); - const subscriptionStep = await azureAccountTreeItem.getSubscriptionPromptStep(wizardContext); - if (subscriptionStep) { - promptSteps.push(subscriptionStep); - } +// const wizardContext: IActionContext & Partial = { +// ...context, +// newSiteOS: vscAzureAppService.WebsiteOS.linux, +// newSiteKind: vscAzureAppService.AppKind.app +// }; +// const promptSteps: AzureWizardPromptStep[] = []; +// // Create a temporary azure account tree item since Azure might not be connected +// const azureAccountTreeItem = new azActTreeItem.AzureAccountTreeItem(ext.registriesRoot, { id: azureRegistryProviderId, api: RegistryApi.DockerV2 }); +// const subscriptionStep = await azureAccountTreeItem.getSubscriptionPromptStep(wizardContext); +// if (subscriptionStep) { +// promptSteps.push(subscriptionStep); +// } - promptSteps.push(new vscAzureAppService.SiteNameStep()); - promptSteps.push(new azExtAzureUtils.ResourceGroupListStep()); - vscAzureAppService.CustomLocationListStep.addStep(wizardContext, promptSteps); - promptSteps.push(new WebSitesPortPromptStep()); - promptSteps.push(new vscAzureAppService.AppServicePlanListStep()); +// promptSteps.push(new vscAzureAppService.SiteNameStep()); +// promptSteps.push(new azExtAzureUtils.ResourceGroupListStep()); +// vscAzureAppService.CustomLocationListStep.addStep(wizardContext, promptSteps); +// promptSteps.push(new WebSitesPortPromptStep()); +// promptSteps.push(new vscAzureAppService.AppServicePlanListStep()); - // Get site config before running the wizard so that any problems with the tag tree item are shown at the beginning of the process - const executeSteps: AzureWizardExecuteStep[] = [ - new DockerSiteCreateStep(node), - new DockerAssignAcrPullRoleStep(node), - new DockerWebhookCreateStep(node), - ]; +// // Get site config before running the wizard so that any problems with the tag tree item are shown at the beginning of the process +// const executeSteps: AzureWizardExecuteStep[] = [ +// new DockerSiteCreateStep(node), +// new DockerAssignAcrPullRoleStep(node), +// new DockerWebhookCreateStep(node), +// ]; - const title = l10n.t('Create new web app'); - const wizard = new AzureWizard(wizardContext, { title, promptSteps, executeSteps }); - await wizard.prompt(); - await wizard.execute(); +// const title = l10n.t('Create new web app'); +// const wizard = new AzureWizard(wizardContext, { title, promptSteps, executeSteps }); +// await wizard.prompt(); +// await wizard.execute(); - const site: Site = nonNullProp(wizardContext, 'site'); - const siteUri: string = `https://${site.defaultHostName}`; - const createdNewWebApp: string = l10n.t('Successfully created web app "{0}": {1}', site.name, siteUri); - ext.outputChannel.info(createdNewWebApp); +// const site: Site = nonNullProp(wizardContext, 'site'); +// const siteUri: string = `https://${site.defaultHostName}`; +// const createdNewWebApp: string = l10n.t('Successfully created web app "{0}": {1}', site.name, siteUri); +// ext.outputChannel.info(createdNewWebApp); - const openSite: string = l10n.t('Open Site'); - // don't wait - /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - window.showInformationMessage(createdNewWebApp, ...[openSite]).then((selection) => { - if (selection === openSite) { - /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - env.openExternal(Uri.parse(siteUri)); - } - }); -} +// const openSite: string = l10n.t('Open Site'); +// // don't wait +// /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ +// window.showInformationMessage(createdNewWebApp, ...[openSite]).then((selection) => { +// if (selection === openSite) { +// /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ +// env.openExternal(Uri.parse(siteUri)); +// } +// }); +// } diff --git a/src/commands/registries/azure/openInAzurePortal.ts b/src/commands/registries/azure/openInAzurePortal.ts index bec849c454..71c284f7d3 100644 --- a/src/commands/registries/azure/openInAzurePortal.ts +++ b/src/commands/registries/azure/openInAzurePortal.ts @@ -4,26 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext } from '@microsoft/vscode-azext-utils'; -import { ext } from '../../../extensionVariables'; -import { AzureRegistryTreeItem } from '../../../tree/registries/azure/AzureRegistryTreeItem'; -import { AzureRepositoryTreeItem } from '../../../tree/registries/azure/AzureRepositoryTreeItem'; -import type { SubscriptionTreeItem } from '../../../tree/registries/azure/SubscriptionTreeItem'; // These are only dev-time imports so don't need to be lazy -import { registryExpectedContextValues } from '../../../tree/registries/registryContextValues'; -import { getAzExtAzureUtils, getAzSubTreeItem } from '../../../utils/lazyPackages'; +import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; -export async function openInAzurePortal(context: IActionContext, node?: SubscriptionTreeItem | AzureRegistryTreeItem | AzureRepositoryTreeItem): Promise { - if (!node) { - node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.registry, context); - } +export async function openInAzurePortal(context: IActionContext, node?: UnifiedRegistryItem): Promise { + // if (!node) { + // node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.registry, context); + // } - const azSubTreeItem = await getAzSubTreeItem(); - const azExtAzureUtils = await getAzExtAzureUtils(); + // const azSubTreeItem = await getAzSubTreeItem(); + // const azExtAzureUtils = await getAzExtAzureUtils(); - if (node instanceof azSubTreeItem.SubscriptionTreeItem) { - await azExtAzureUtils.openInPortal(node.subscription, node.subscription.subscriptionId); - } else if (node instanceof AzureRegistryTreeItem) { - await azExtAzureUtils.openInPortal(node.parent.subscription, node.registryId); - } else { - await azExtAzureUtils.openInPortal(node.parent.parent.subscription, `${node.parent.registryId}/repository`); - } + // if (node instanceof azSubTreeItem.SubscriptionTreeItem) { + // await azExtAzureUtils.openInPortal(node.subscription, node.subscription.subscriptionId); + // } else if (node instanceof AzureRegistryTreeItem) { + // await azExtAzureUtils.openInPortal(node.parent.subscription, node.registryId); + // } else { + // await azExtAzureUtils.openInPortal(node.parent.parent.subscription, `${node.parent.registryId}/repository`); + // } + + // TODO: review this later } diff --git a/src/commands/registries/azure/tasks/buildImageInAzure.ts b/src/commands/registries/azure/tasks/buildImageInAzure.ts index 346606bde5..f2d25ba469 100644 --- a/src/commands/registries/azure/tasks/buildImageInAzure.ts +++ b/src/commands/registries/azure/tasks/buildImageInAzure.ts @@ -1,37 +1,36 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; -import { IActionContext, UserCancelledError } from '@microsoft/vscode-azext-utils'; -import type { Run } from '@azure/arm-containerregistry'; -import { scheduleRunRequest, RootStrategy } from './scheduleRunRequest'; -import { delay } from '../../../../utils/promiseUtils'; -import { getArmContainerRegistry } from '../../../../utils/lazyPackages'; +// import type { Run } from '@azure/arm-containerregistry'; +// import { IActionContext, UserCancelledError } from '@microsoft/vscode-azext-utils'; +// import * as vscode from 'vscode'; +// import { getArmContainerRegistry } from '../../../../utils/lazyPackages'; +// import { delay } from '../../../../utils/promiseUtils'; -const WAIT_MS = 5000; +// const WAIT_MS = 5000; -export async function buildImageInAzure(context: IActionContext, uri?: vscode.Uri | undefined, rootStrategy?: RootStrategy | undefined): Promise { - if (!vscode.workspace.isTrusted) { - throw new UserCancelledError('enforceTrust'); - } +// export async function buildImageInAzure(context: IActionContext, uri?: vscode.Uri | undefined, rootStrategy?: RootStrategy | undefined): Promise { +// if (!vscode.workspace.isTrusted) { +// throw new UserCancelledError('enforceTrust'); +// } - const getRun = await scheduleRunRequest(context, "DockerBuildRequest", uri, rootStrategy); - - let run = await getRun(); - const { KnownRunStatus } = await getArmContainerRegistry(); - while ( - run.status === KnownRunStatus.Started || - run.status === KnownRunStatus.Queued || - run.status === KnownRunStatus.Running - ) { - await delay(WAIT_MS); - run = await getRun(); - } +// const getRun = await scheduleRunRequest(context, "DockerBuildRequest", uri, rootStrategy); - // we are returning the run so that other extensions can consume this with the vscode.commands.executeCommand - // currently it is used by the ms-kubernetes-tools.aks-devx-tools extension (https://github.com/Azure/aks-devx-tools) - return run; -} +// let run = await getRun(); +// const { KnownRunStatus } = await getArmContainerRegistry(); +// while ( +// run.status === KnownRunStatus.Started || +// run.status === KnownRunStatus.Queued || +// run.status === KnownRunStatus.Running +// ) { +// await delay(WAIT_MS); +// run = await getRun(); +// } + +// // we are returning the run so that other extensions can consume this with the vscode.commands.executeCommand +// // currently it is used by the ms-kubernetes-tools.aks-devx-tools extension (https://github.com/Azure/aks-devx-tools) +// return run; +// } diff --git a/src/commands/registries/azure/tasks/runAzureTask.ts b/src/commands/registries/azure/tasks/runAzureTask.ts index 0be5e31898..5c1d73865d 100644 --- a/src/commands/registries/azure/tasks/runAzureTask.ts +++ b/src/commands/registries/azure/tasks/runAzureTask.ts @@ -3,22 +3,19 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { TaskRunRequest } from "@azure/arm-containerregistry"; // These are only dev-time imports so don't need to be lazy import { IActionContext } from "@microsoft/vscode-azext-utils"; -import { l10n, window } from "vscode"; -import { ext } from "../../../../extensionVariables"; -import { AzureTaskTreeItem } from "../../../../tree/registries/azure/AzureTaskTreeItem"; +import { UnifiedRegistryItem } from "../../../../tree/registries/UnifiedRegistryTreeDataProvider"; -export async function runAzureTask(context: IActionContext, node?: AzureTaskTreeItem): Promise { - if (!node) { - node = await ext.registriesTree.showTreeItemPicker(AzureTaskTreeItem.contextValue, context); - } +export async function runAzureTask(context: IActionContext, node?: UnifiedRegistryItem): Promise { + // if (!node) { + // node = await ext.registriesTree.showTreeItemPicker(AzureTaskTreeItem.contextValue, context); + // } - const registryTI = node.parent.parent; - const runRequest: TaskRunRequest = { type: 'TaskRunRequest', taskId: node.id }; - const run = await (await registryTI.getClient(context)).registries.beginScheduleRunAndWait(registryTI.resourceGroup, registryTI.registryName, runRequest); - await node.parent.refresh(context); - // don't wait - /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - window.showInformationMessage(l10n.t('Successfully scheduled run "{0}" for task "{1}".', run.runId, node.taskName)); + // const registryTI = node.parent.parent; + // const runRequest: TaskRunRequest = { type: 'TaskRunRequest', taskId: node.id }; + // const run = await (await registryTI.getClient(context)).registries.beginScheduleRunAndWait(registryTI.resourceGroup, registryTI.registryName, runRequest); + // await node.parent.refresh(context); + // // don't wait + // /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ + // window.showInformationMessage(l10n.t('Successfully scheduled run "{0}" for task "{1}".', run.runId, node.taskName)); } diff --git a/src/commands/registries/azure/tasks/runFileAsAzureTask.ts b/src/commands/registries/azure/tasks/runFileAsAzureTask.ts index 92e5ac28b9..f6ba0f5831 100644 --- a/src/commands/registries/azure/tasks/runFileAsAzureTask.ts +++ b/src/commands/registries/azure/tasks/runFileAsAzureTask.ts @@ -1,16 +1,16 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; -import { IActionContext, UserCancelledError } from '@microsoft/vscode-azext-utils'; -import { scheduleRunRequest } from './scheduleRunRequest'; +// import * as vscode from 'vscode'; +// import { IActionContext, UserCancelledError } from '@microsoft/vscode-azext-utils'; +// import { scheduleRunRequest } from './scheduleRunRequest'; -export async function runFileAsAzureTask(context: IActionContext, uri?: vscode.Uri): Promise { - if (!vscode.workspace.isTrusted) { - throw new UserCancelledError('enforceTrust'); - } +// export async function runFileAsAzureTask(context: IActionContext, uri?: vscode.Uri): Promise { +// if (!vscode.workspace.isTrusted) { +// throw new UserCancelledError('enforceTrust'); +// } - await scheduleRunRequest(context, "FileTaskRunRequest", uri); -} +// await scheduleRunRequest(context, "FileTaskRunRequest", uri); +// } diff --git a/src/commands/registries/azure/tasks/scheduleRunRequest.ts b/src/commands/registries/azure/tasks/scheduleRunRequest.ts index 66f0dfc5f9..e47f9d3460 100644 --- a/src/commands/registries/azure/tasks/scheduleRunRequest.ts +++ b/src/commands/registries/azure/tasks/scheduleRunRequest.ts @@ -1,217 +1,216 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import type { ContainerRegistryManagementClient, DockerBuildRequest as AcrDockerBuildRequest, FileTaskRunRequest as AcrFileTaskRunRequest, OS as AcrOS, Run as AcrRun } from "@azure/arm-containerregistry"; // These are only dev-time imports so don't need to be lazy -import { IActionContext, IAzureQuickPickItem, nonNullProp } from '@microsoft/vscode-azext-utils'; -import * as fse from 'fs-extra'; -import * as os from 'os'; -import * as path from 'path'; -import * as readline from 'readline'; -import * as tar from 'tar'; -import * as vscode from 'vscode'; -import { ext } from '../../../../extensionVariables'; -import { AzureRegistryTreeItem } from '../../../../tree/registries/azure/AzureRegistryTreeItem'; -import { registryExpectedContextValues } from "../../../../tree/registries/registryContextValues"; -import { getStorageBlob } from '../../../../utils/lazyPackages'; -import { delay } from '../../../../utils/promiseUtils'; -import { Item, quickPickDockerFileItem, quickPickYamlFileItem } from '../../../../utils/quickPickFile'; -import { quickPickWorkspaceFolder } from '../../../../utils/quickPickWorkspaceFolder'; -import { addImageTaggingTelemetry, getTagFromUserInput } from '../../../images/tagImage'; - -const idPrecision = 6; -const vcsIgnoreList = ['.git', '.gitignore', '.bzr', 'bzrignore', '.hg', '.hgignore', '.svn']; - -// this is used by the ms-kubernetes-tools.aks-devx-tools extension (https://github.com/Azure/aks-devx-tools) -export enum RootStrategy { - Default = 'Default', - DockerfileFolder = 'DockerfileFolder', -} - -export async function scheduleRunRequest(context: IActionContext, requestType: 'DockerBuildRequest' | 'FileTaskRunRequest', uri: vscode.Uri | undefined, rootStrategy?: RootStrategy | undefined): Promise<() => Promise> { - // Acquire information. - let rootFolder: vscode.WorkspaceFolder; - let fileItem: Item; - let imageName: string; - if (requestType === 'DockerBuildRequest') { - rootFolder = await quickPickWorkspaceFolder(context, vscode.l10n.t('To quick build Docker files you must first open a folder or workspace in VS Code.')); - fileItem = await quickPickDockerFileItem(context, uri, rootFolder); - imageName = await quickPickImageName(context, rootFolder, fileItem); - } else if (requestType === 'FileTaskRunRequest') { - rootFolder = await quickPickWorkspaceFolder(context, vscode.l10n.t('To run a task from a .yaml file you must first open a folder or workspace in VS Code.')); - fileItem = await quickPickYamlFileItem(context, uri, rootFolder, vscode.l10n.t('To run a task from a .yaml file you must have yaml file in your VS Code workspace.')); - } else { - throw new Error(vscode.l10n.t('Run Request Type Currently not supported.')); - } - - const node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.registry, context); - - const osPick = ['Linux', 'Windows'].map(item => >{ label: item, data: item }); - const osType: AcrOS = (await context.ui.showQuickPick(osPick, { placeHolder: vscode.l10n.t('Select image base OS') })).data; - - const tarFilePath: string = getTempSourceArchivePath(); - - try { - // Prepare to run. - ext.outputChannel.show(); - - let rootUri = rootFolder.uri; - if (rootStrategy === RootStrategy.DockerfileFolder) { - // changes the root to the folder where the Dockerfile is - // it is used by the ms-kubernetes-tools.aks-devx-tools extension (https://github.com/Azure/aks-devx-tools) - rootUri = vscode.Uri.file(path.dirname(fileItem.absoluteFilePath)); - } - - const uploadedSourceLocation: string = await uploadSourceCode(await node.getClient(context), node.registryName, node.resourceGroup, rootUri, tarFilePath); - ext.outputChannel.info(vscode.l10n.t('Uploaded source code from {0}', tarFilePath)); - - let runRequest: AcrDockerBuildRequest | AcrFileTaskRunRequest; - if (requestType === 'DockerBuildRequest') { - runRequest = { - type: requestType, - imageNames: [imageName], - isPushEnabled: true, - sourceLocation: uploadedSourceLocation, - platform: { os: osType }, - dockerFilePath: path.relative(rootUri.fsPath, fileItem.absoluteFilePath) - }; - } else { - runRequest = { - type: 'FileTaskRunRequest', - taskFilePath: path.relative(rootUri.fsPath, fileItem.absoluteFilePath), - sourceLocation: uploadedSourceLocation, - platform: { os: osType } - }; - } - - // Schedule the run and Clean up. - ext.outputChannel.info(vscode.l10n.t('Set up run request')); - - const client = await node.getClient(context); - const run = await client.registries.beginScheduleRunAndWait(node.resourceGroup, node.registryName, runRequest); - ext.outputChannel.info(vscode.l10n.t('Scheduled run {0}', run.runId)); - - void streamLogs(context, node, run); - - // function returns the AcrRun info - return async () => client.runs.get(node.resourceGroup, node.registryName, run.runId); - } finally { - if (await fse.pathExists(tarFilePath)) { - await fse.unlink(tarFilePath); - } - } -} - -async function quickPickImageName(context: IActionContext, rootFolder: vscode.WorkspaceFolder, dockerFileItem: Item | undefined): Promise { - const absFilePath: string = path.join(rootFolder.uri.fsPath, dockerFileItem.relativeFilePath); - const dockerFileKey = `ACR_buildTag_${absFilePath}`; - const prevImageName: string | undefined = ext.context.workspaceState.get(dockerFileKey); - let suggestedImageName: string; - - if (!prevImageName) { - // Get imageName based on name of subfolder containing the Dockerfile, or else workspacefolder - suggestedImageName = path.basename(dockerFileItem.relativeFolderPath).toLowerCase(); - if (suggestedImageName === '.') { - suggestedImageName = path.basename(rootFolder.uri.fsPath).toLowerCase().replace(/\s/g, ''); - } - - suggestedImageName += ":{{.Run.ID}}"; - } else { - suggestedImageName = prevImageName; - } - - // Temporary work-around for vscode bug where valueSelection can be messed up if a quick pick is followed by a showInputBox - await delay(500); - - addImageTaggingTelemetry(context, suggestedImageName, '.before'); - const imageName: string = await getTagFromUserInput(context, suggestedImageName); - addImageTaggingTelemetry(context, imageName, '.after'); - - await ext.context.workspaceState.update(dockerFileKey, imageName); - return imageName; -} - -async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, rootFolder: vscode.Uri, tarFilePath: string): Promise { - ext.outputChannel.info(vscode.l10n.t(' Sending source code to temp file')); - const source: string = rootFolder.fsPath; - let items = await fse.readdir(source); - items = items.filter(i => !(i in vcsIgnoreList)); - // tslint:disable-next-line:no-unsafe-any - tar.c({ cwd: source }, items).pipe(fse.createWriteStream(tarFilePath)); - - ext.outputChannel.info(vscode.l10n.t(' Getting build source upload URL')); - const sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); - const uploadUrl: string = sourceUploadLocation.uploadUrl; - const relativePath: string = sourceUploadLocation.relativePath; - - const storageBlob = await getStorageBlob(); - const blobClient = new storageBlob.BlockBlobClient(uploadUrl); - ext.outputChannel.info(vscode.l10n.t(' Creating block blob')); - await blobClient.uploadFile(tarFilePath); - - return relativePath; -} - -const blobCheckInterval = 1000; -const maxBlobChecks = 30; -async function streamLogs(context: IActionContext, node: AzureRegistryTreeItem, run: AcrRun): Promise { - const result = await (await node.getClient(context)).runs.getLogSasUrl(node.resourceGroup, node.registryName, run.runId); - - const storageBlob = await getStorageBlob(); - const blobClient = new storageBlob.BlobClient(nonNullProp(result, 'logLink')); - - // Start streaming the response to the output channel - let byteOffset = 0; - let totalChecks = 0; - let exists = false; - - await new Promise((resolve, reject) => { - const timer = setInterval( - async () => { - try { - if (!exists && !(exists = await blobClient.exists())) { - totalChecks++; - if (totalChecks >= maxBlobChecks) { - clearInterval(timer); - reject('Not found'); - } - } - - const properties = await blobClient.getProperties(); - if (properties.contentLength > byteOffset) { - // New data available - const response = await blobClient.download(byteOffset); - byteOffset += response.contentLength; - - const lineReader = readline.createInterface(response.readableStreamBody); - for await (const line of lineReader) { - const sanitizedLine = line - // eslint-disable-next-line no-control-regex - .replace(/[\x00-\x09\x0B-\x0C\x0E-\x1F]/g, ''); // Remove non-printing control characters - ext.outputChannel.info(sanitizedLine); - } - } - - if (properties.metadata?.complete) { - clearInterval(timer); - resolve(); - } - } catch (err) { - clearInterval(timer); - reject(err); - } - }, - blobCheckInterval - ); - }); -} - -function getTempSourceArchivePath(): string { - /* tslint:disable-next-line:insecure-random */ - const id: number = Math.floor(Math.random() * Math.pow(10, idPrecision)); - const archive = `sourceArchive${id}.tar.gz`; - ext.outputChannel.info(vscode.l10n.t('Setting up temp file with \'{0}\'', archive)); - const tarFilePath: string = path.join(os.tmpdir(), archive); - return tarFilePath; -} +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ + +// import type { DockerBuildRequest as AcrDockerBuildRequest, FileTaskRunRequest as AcrFileTaskRunRequest, OS as AcrOS, Run as AcrRun, ContainerRegistryManagementClient } from "@azure/arm-containerregistry"; // These are only dev-time imports so don't need to be lazy +// import { IActionContext, IAzureQuickPickItem, nonNullProp } from '@microsoft/vscode-azext-utils'; +// import * as fse from 'fs-extra'; +// import * as os from 'os'; +// import * as path from 'path'; +// import * as readline from 'readline'; +// import * as tar from 'tar'; +// import * as vscode from 'vscode'; +// import { ext } from '../../../../extensionVariables'; +// import { getStorageBlob } from '../../../../utils/lazyPackages'; +// import { delay } from '../../../../utils/promiseUtils'; +// import { Item, quickPickDockerFileItem, quickPickYamlFileItem } from '../../../../utils/quickPickFile'; +// import { quickPickWorkspaceFolder } from '../../../../utils/quickPickWorkspaceFolder'; +// import { addImageTaggingTelemetry, getTagFromUserInput } from '../../../images/tagImage'; + +// const idPrecision = 6; +// const vcsIgnoreList = ['.git', '.gitignore', '.bzr', 'bzrignore', '.hg', '.hgignore', '.svn']; + +// // this is used by the ms-kubernetes-tools.aks-devx-tools extension (https://github.com/Azure/aks-devx-tools) +// export enum RootStrategy { +// Default = 'Default', +// DockerfileFolder = 'DockerfileFolder', +// } + +// export async function scheduleRunRequest(context: IActionContext, requestType: 'DockerBuildRequest' | 'FileTaskRunRequest', uri: vscode.Uri | undefined, rootStrategy?: RootStrategy | undefined): Promise<() => Promise> { +// // Acquire information. +// let rootFolder: vscode.WorkspaceFolder; +// let fileItem: Item; +// let imageName: string; +// if (requestType === 'DockerBuildRequest') { +// rootFolder = await quickPickWorkspaceFolder(context, vscode.l10n.t('To quick build Docker files you must first open a folder or workspace in VS Code.')); +// fileItem = await quickPickDockerFileItem(context, uri, rootFolder); +// imageName = await quickPickImageName(context, rootFolder, fileItem); +// } else if (requestType === 'FileTaskRunRequest') { +// rootFolder = await quickPickWorkspaceFolder(context, vscode.l10n.t('To run a task from a .yaml file you must first open a folder or workspace in VS Code.')); +// fileItem = await quickPickYamlFileItem(context, uri, rootFolder, vscode.l10n.t('To run a task from a .yaml file you must have yaml file in your VS Code workspace.')); +// } else { +// throw new Error(vscode.l10n.t('Run Request Type Currently not supported.')); +// } + +// const node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.registry, context); + +// const osPick = ['Linux', 'Windows'].map(item => >{ label: item, data: item }); +// const osType: AcrOS = (await context.ui.showQuickPick(osPick, { placeHolder: vscode.l10n.t('Select image base OS') })).data; + +// const tarFilePath: string = getTempSourceArchivePath(); + +// try { +// // Prepare to run. +// ext.outputChannel.show(); + +// let rootUri = rootFolder.uri; +// if (rootStrategy === RootStrategy.DockerfileFolder) { +// // changes the root to the folder where the Dockerfile is +// // it is used by the ms-kubernetes-tools.aks-devx-tools extension (https://github.com/Azure/aks-devx-tools) +// rootUri = vscode.Uri.file(path.dirname(fileItem.absoluteFilePath)); +// } + +// const uploadedSourceLocation: string = await uploadSourceCode(await node.getClient(context), node.registryName, node.resourceGroup, rootUri, tarFilePath); +// ext.outputChannel.info(vscode.l10n.t('Uploaded source code from {0}', tarFilePath)); + +// let runRequest: AcrDockerBuildRequest | AcrFileTaskRunRequest; +// if (requestType === 'DockerBuildRequest') { +// runRequest = { +// type: requestType, +// imageNames: [imageName], +// isPushEnabled: true, +// sourceLocation: uploadedSourceLocation, +// platform: { os: osType }, +// dockerFilePath: path.relative(rootUri.fsPath, fileItem.absoluteFilePath) +// }; +// } else { +// runRequest = { +// type: 'FileTaskRunRequest', +// taskFilePath: path.relative(rootUri.fsPath, fileItem.absoluteFilePath), +// sourceLocation: uploadedSourceLocation, +// platform: { os: osType } +// }; +// } + +// // Schedule the run and Clean up. +// ext.outputChannel.info(vscode.l10n.t('Set up run request')); + +// const client = await node.getClient(context); +// const run = await client.registries.beginScheduleRunAndWait(node.resourceGroup, node.registryName, runRequest); +// ext.outputChannel.info(vscode.l10n.t('Scheduled run {0}', run.runId)); + +// void streamLogs(context, node, run); + +// // function returns the AcrRun info +// return async () => client.runs.get(node.resourceGroup, node.registryName, run.runId); +// } finally { +// if (await fse.pathExists(tarFilePath)) { +// await fse.unlink(tarFilePath); +// } +// } +// } + +// async function quickPickImageName(context: IActionContext, rootFolder: vscode.WorkspaceFolder, dockerFileItem: Item | undefined): Promise { +// const absFilePath: string = path.join(rootFolder.uri.fsPath, dockerFileItem.relativeFilePath); +// const dockerFileKey = `ACR_buildTag_${absFilePath}`; +// const prevImageName: string | undefined = ext.context.workspaceState.get(dockerFileKey); +// let suggestedImageName: string; + +// if (!prevImageName) { +// // Get imageName based on name of subfolder containing the Dockerfile, or else workspacefolder +// suggestedImageName = path.basename(dockerFileItem.relativeFolderPath).toLowerCase(); +// if (suggestedImageName === '.') { +// suggestedImageName = path.basename(rootFolder.uri.fsPath).toLowerCase().replace(/\s/g, ''); +// } + +// suggestedImageName += ":{{.Run.ID}}"; +// } else { +// suggestedImageName = prevImageName; +// } + +// // Temporary work-around for vscode bug where valueSelection can be messed up if a quick pick is followed by a showInputBox +// await delay(500); + +// addImageTaggingTelemetry(context, suggestedImageName, '.before'); +// const imageName: string = await getTagFromUserInput(context, suggestedImageName); +// addImageTaggingTelemetry(context, imageName, '.after'); + +// await ext.context.workspaceState.update(dockerFileKey, imageName); +// return imageName; +// } + +// async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, rootFolder: vscode.Uri, tarFilePath: string): Promise { +// ext.outputChannel.info(vscode.l10n.t(' Sending source code to temp file')); +// const source: string = rootFolder.fsPath; +// let items = await fse.readdir(source); +// items = items.filter(i => !(i in vcsIgnoreList)); +// // tslint:disable-next-line:no-unsafe-any +// tar.c({ cwd: source }, items).pipe(fse.createWriteStream(tarFilePath)); + +// ext.outputChannel.info(vscode.l10n.t(' Getting build source upload URL')); +// const sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); +// const uploadUrl: string = sourceUploadLocation.uploadUrl; +// const relativePath: string = sourceUploadLocation.relativePath; + +// const storageBlob = await getStorageBlob(); +// const blobClient = new storageBlob.BlockBlobClient(uploadUrl); +// ext.outputChannel.info(vscode.l10n.t(' Creating block blob')); +// await blobClient.uploadFile(tarFilePath); + +// return relativePath; +// } + +// const blobCheckInterval = 1000; +// const maxBlobChecks = 30; +// async function streamLogs(context: IActionContext, node: AzureRegistryTreeItem, run: AcrRun): Promise { +// const result = await (await node.getClient(context)).runs.getLogSasUrl(node.resourceGroup, node.registryName, run.runId); + +// const storageBlob = await getStorageBlob(); +// const blobClient = new storageBlob.BlobClient(nonNullProp(result, 'logLink')); + +// // Start streaming the response to the output channel +// let byteOffset = 0; +// let totalChecks = 0; +// let exists = false; + +// await new Promise((resolve, reject) => { +// const timer = setInterval( +// async () => { +// try { +// if (!exists && !(exists = await blobClient.exists())) { +// totalChecks++; +// if (totalChecks >= maxBlobChecks) { +// clearInterval(timer); +// reject('Not found'); +// } +// } + +// const properties = await blobClient.getProperties(); +// if (properties.contentLength > byteOffset) { +// // New data available +// const response = await blobClient.download(byteOffset); +// byteOffset += response.contentLength; + +// const lineReader = readline.createInterface(response.readableStreamBody); +// for await (const line of lineReader) { +// const sanitizedLine = line +// // eslint-disable-next-line no-control-regex +// .replace(/[\x00-\x09\x0B-\x0C\x0E-\x1F]/g, ''); // Remove non-printing control characters +// ext.outputChannel.info(sanitizedLine); +// } +// } + +// if (properties.metadata?.complete) { +// clearInterval(timer); +// resolve(); +// } +// } catch (err) { +// clearInterval(timer); +// reject(err); +// } +// }, +// blobCheckInterval +// ); +// }); +// } + +// function getTempSourceArchivePath(): string { +// /* tslint:disable-next-line:insecure-random */ +// const id: number = Math.floor(Math.random() * Math.pow(10, idPrecision)); +// const archive = `sourceArchive${id}.tar.gz`; +// ext.outputChannel.info(vscode.l10n.t('Setting up temp file with \'{0}\'', archive)); +// const tarFilePath: string = path.join(os.tmpdir(), archive); +// return tarFilePath; +// } +// TODO: review this later diff --git a/src/commands/registries/azure/tasks/viewAzureTaskLogs.ts b/src/commands/registries/azure/tasks/viewAzureTaskLogs.ts index 4ed044abd4..1fd008f1ee 100644 --- a/src/commands/registries/azure/tasks/viewAzureTaskLogs.ts +++ b/src/commands/registries/azure/tasks/viewAzureTaskLogs.ts @@ -1,29 +1,29 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ -import { IActionContext, nonNullProp, openReadOnlyContent } from "@microsoft/vscode-azext-utils"; -import { l10n } from 'vscode'; -import { ext } from "../../../../extensionVariables"; -import { AzureTaskRunTreeItem } from "../../../../tree/registries/azure/AzureTaskRunTreeItem"; -import { bufferToString } from "../../../../utils/execAsync"; -import { getStorageBlob } from "../../../../utils/lazyPackages"; +// import { IActionContext, nonNullProp, openReadOnlyContent } from "@microsoft/vscode-azext-utils"; +// import { l10n } from 'vscode'; +// import { ext } from "../../../../extensionVariables"; +// import { AzureTaskRunTreeItem } from "../../../../tree/registries/azure/AzureTaskRunTreeItem"; +// import { bufferToString } from "../../../../utils/execAsync"; +// import { getStorageBlob } from "../../../../utils/lazyPackages"; -export async function viewAzureTaskLogs(context: IActionContext, node?: AzureTaskRunTreeItem): Promise { - if (!node) { - node = await ext.registriesTree.showTreeItemPicker(AzureTaskRunTreeItem.contextValue, context); - } +// export async function viewAzureTaskLogs(context: IActionContext, node?: AzureTaskRunTreeItem): Promise { +// if (!node) { +// node = await ext.registriesTree.showTreeItemPicker(AzureTaskRunTreeItem.contextValue, context); +// } - const registryTI = node.parent.parent.parent; - await node.runWithTemporaryDescription(context, l10n.t('Retrieving logs...'), async () => { - const result = await (await registryTI.getClient(context)).runs.getLogSasUrl(registryTI.resourceGroup, registryTI.registryName, node.runId); +// const registryTI = node.parent.parent.parent; +// await node.runWithTemporaryDescription(context, l10n.t('Retrieving logs...'), async () => { +// const result = await (await registryTI.getClient(context)).runs.getLogSasUrl(registryTI.resourceGroup, registryTI.registryName, node.runId); - const storageBlob = await getStorageBlob(); - const blobClient = new storageBlob.BlobClient(nonNullProp(result, 'logLink')); - const contentBuffer = await blobClient.downloadToBuffer(); - const content = bufferToString(contentBuffer); +// const storageBlob = await getStorageBlob(); +// const blobClient = new storageBlob.BlobClient(nonNullProp(result, 'logLink')); +// const contentBuffer = await blobClient.downloadToBuffer(); +// const content = bufferToString(contentBuffer); - await openReadOnlyContent(node, content, '.log'); - }); -} +// await openReadOnlyContent(node, content, '.log'); +// }); +// } diff --git a/src/commands/registries/azure/untagAzureImage.ts b/src/commands/registries/azure/untagAzureImage.ts index 4454bf5635..7db25a4dc5 100644 --- a/src/commands/registries/azure/untagAzureImage.ts +++ b/src/commands/registries/azure/untagAzureImage.ts @@ -1,36 +1,36 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * 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 "@microsoft/vscode-azext-utils"; -import { l10n, ProgressLocation, window } from "vscode"; -import { ext } from "../../../extensionVariables"; -import { registryExpectedContextValues } from "../../../tree/registries/registryContextValues"; -import { RemoteTagTreeItem } from "../../../tree/registries/RemoteTagTreeItem"; -import { registryRequest } from "../../../utils/registryRequestUtils"; +// import { IActionContext } from "@microsoft/vscode-azext-utils"; +// import { l10n, ProgressLocation, window } from "vscode"; +// import { ext } from "../../../extensionVariables"; +// import { registryExpectedContextValues } from "../../../tree/registries/registryContextValues"; +// import { RemoteTagTreeItem } from "../../../tree/registries/RemoteTagTreeItem"; +// import { registryRequest } from "../../../utils/registryRequestUtils"; -export async function untagAzureImage(context: IActionContext, node?: RemoteTagTreeItem): Promise { - if (!node) { - node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.tag, { - ...context, - suppressCreatePick: true, - noItemFoundErrorMessage: l10n.t('No images are available to untag') - }); - } +// export async function untagAzureImage(context: IActionContext, node?: RemoteTagTreeItem): Promise { +// if (!node) { +// node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.tag, { +// ...context, +// suppressCreatePick: true, +// noItemFoundErrorMessage: l10n.t('No images are available to untag') +// }); +// } - const confirmUntag: string = l10n.t('Are you sure you want to untag image "{0}"? This does not delete the manifest referenced by the tag.', node.repoNameAndTag); - // no need to check result - cancel will throw a UserCancelledError - await context.ui.showWarningMessage(confirmUntag, { modal: true }, { title: "Untag" }); +// const confirmUntag: string = l10n.t('Are you sure you want to untag image "{0}"? This does not delete the manifest referenced by the tag.', node.repoNameAndTag); +// // no need to check result - cancel will throw a UserCancelledError +// await context.ui.showWarningMessage(confirmUntag, { modal: true }, { title: "Untag" }); - const untagging = l10n.t('Untagging image "{0}"...', node.repoNameAndTag); - const repoTI = node.parent; - await window.withProgress({ location: ProgressLocation.Notification, title: untagging }, async () => { - await registryRequest(repoTI, 'DELETE', `v2/_acr/${repoTI.repoName}/tags/${node.tag}`); - await repoTI.refresh(context); - }); +// const untagging = l10n.t('Untagging image "{0}"...', node.repoNameAndTag); +// const repoTI = node.parent; +// await window.withProgress({ location: ProgressLocation.Notification, title: untagging }, async () => { +// await registryRequest(repoTI, 'DELETE', `v2/_acr/${repoTI.repoName}/tags/${node.tag}`); +// await repoTI.refresh(context); +// }); - // don't wait - /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - window.showInformationMessage(l10n.t('Successfully untagged image "{0}".', node.repoNameAndTag)); -} +// // don't wait +// /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ +// window.showInformationMessage(l10n.t('Successfully untagged image "{0}".', node.repoNameAndTag)); +// } diff --git a/src/commands/registries/azure/viewAzureProperties.ts b/src/commands/registries/azure/viewAzureProperties.ts index 67804732ca..4de3cd4a05 100644 --- a/src/commands/registries/azure/viewAzureProperties.ts +++ b/src/commands/registries/azure/viewAzureProperties.ts @@ -1,19 +1,19 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ -import { IActionContext, openReadOnlyJson } from "@microsoft/vscode-azext-utils"; -import { ext } from "../../../extensionVariables"; -import { AzureRegistryTreeItem } from "../../../tree/registries/azure/AzureRegistryTreeItem"; -import { AzureTaskRunTreeItem } from "../../../tree/registries/azure/AzureTaskRunTreeItem"; -import { AzureTaskTreeItem } from "../../../tree/registries/azure/AzureTaskTreeItem"; -import { registryExpectedContextValues } from "../../../tree/registries/registryContextValues"; +// import { IActionContext, openReadOnlyJson } from "@microsoft/vscode-azext-utils"; +// import { ext } from "../../../extensionVariables"; +// import { AzureRegistryTreeItem } from "../../../tree/registries/azure/AzureRegistryTreeItem"; +// import { AzureTaskRunTreeItem } from "../../../tree/registries/azure/AzureTaskRunTreeItem"; +// import { AzureTaskTreeItem } from "../../../tree/registries/azure/AzureTaskTreeItem"; +// import { registryExpectedContextValues } from "../../../tree/registries/registryContextValues"; -export async function viewAzureProperties(context: IActionContext, node?: AzureRegistryTreeItem | AzureTaskTreeItem | AzureTaskRunTreeItem): Promise { - if (!node) { - node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.registry, context); - } +// export async function viewAzureProperties(context: IActionContext, node?: AzureRegistryTreeItem | AzureTaskTreeItem | AzureTaskRunTreeItem): Promise { +// if (!node) { +// node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.registry, context); +// } - await openReadOnlyJson(node, node.properties); -} +// await openReadOnlyJson(node, node.properties); +// } diff --git a/src/commands/registries/connectRegistry.ts b/src/commands/registries/connectRegistry.ts index 93b747c7d8..dd4067a106 100644 --- a/src/commands/registries/connectRegistry.ts +++ b/src/commands/registries/connectRegistry.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext } from "@microsoft/vscode-azext-utils"; import { ext } from "../../extensionVariables"; -export async function connectRegistry(context: IActionContext): Promise { - await ext.registriesRoot.connectRegistry(context); +export async function connectRegistry(): Promise { + await ext.registriesRoot.connectRegistryProvider(); } diff --git a/src/commands/registries/copyRemoteFullTag.ts b/src/commands/registries/copyRemoteFullTag.ts index 5e557dc43a..a85d67a329 100644 --- a/src/commands/registries/copyRemoteFullTag.ts +++ b/src/commands/registries/copyRemoteFullTag.ts @@ -3,21 +3,18 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext } from '@microsoft/vscode-azext-utils'; +import { IActionContext, contextValueExperience } from '@microsoft/vscode-azext-utils'; +import { CommonTag } from '@microsoft/vscode-docker-registries'; import * as vscode from 'vscode'; import { ext } from '../../extensionVariables'; -import { registryExpectedContextValues } from '../../tree/registries/registryContextValues'; -import { RemoteTagTreeItem } from '../../tree/registries/RemoteTagTreeItem'; +import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { getFullImageNameFromRegistryItem } from '../../tree/registries/getInformationFromRegistryItem'; -export async function copyRemoteFullTag(context: IActionContext, node?: RemoteTagTreeItem): Promise { +export async function copyRemoteFullTag(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await ext.registriesTree.showTreeItemPicker([registryExpectedContextValues.dockerV2.tag, registryExpectedContextValues.dockerHub.tag], { - ...context, - noItemFoundErrorMessage: vscode.l10n.t('No remote images are available to copy the full tag') - }); + node = await contextValueExperience(context, ext.registriesTree, { include: ['registryV2Tag', 'dockerHubTag'] }); } - // eslint-disable-next-line @typescript-eslint/no-floating-promises - // Don't wait - void vscode.env.clipboard.writeText(node.fullTag); - return node.fullTag; + const fullTag = getFullImageNameFromRegistryItem(node); + void vscode.env.clipboard.writeText(fullTag); + return fullTag; } diff --git a/src/commands/registries/copyRemoteImageDigest.ts b/src/commands/registries/copyRemoteImageDigest.ts index f31ab24f2b..1e022c75cf 100644 --- a/src/commands/registries/copyRemoteImageDigest.ts +++ b/src/commands/registries/copyRemoteImageDigest.ts @@ -3,33 +3,19 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, nonNullProp } from "@microsoft/vscode-azext-utils"; +import { IActionContext, contextValueExperience } from "@microsoft/vscode-azext-utils"; +import { CommonTag, RegistryV2DataProvider, V2Tag } from "@microsoft/vscode-docker-registries"; import * as vscode from "vscode"; import { ext } from "../../extensionVariables"; -import { AzureTaskRunTreeItem } from "../../tree/registries/azure/AzureTaskRunTreeItem"; -import { DockerV2TagTreeItem } from "../../tree/registries/dockerV2/DockerV2TagTreeItem"; -import { registryExpectedContextValues } from "../../tree/registries/registryContextValues"; +import { UnifiedRegistryItem } from "../../tree/registries/UnifiedRegistryTreeDataProvider"; -export async function copyRemoteImageDigest(context: IActionContext, node?: DockerV2TagTreeItem | AzureTaskRunTreeItem): Promise { +export async function copyRemoteImageDigest(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.dockerV2.tag, { - ...context, - noItemFoundErrorMessage: vscode.l10n.t('No remote images are available to copy the digest') - }); + node = await contextValueExperience(context, ext.registriesTree, { include: ['registryV2Tag'] }); } - let digest: string; - if (node instanceof AzureTaskRunTreeItem) { - if (node.outputImage) { - digest = nonNullProp(node.outputImage, 'digest'); - } else { - throw new Error(vscode.l10n.t('Failed to find output image for this task run.')); - } - } else { - await node.runWithTemporaryDescription(context, vscode.l10n.t('Getting digest...'), async () => { - digest = await (node).getDigest(); - }); - } + const v2DataProvider = node.provider as unknown as RegistryV2DataProvider; + const digest = await v2DataProvider.getImageDigest(node.wrappedItem as V2Tag); /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ vscode.env.clipboard.writeText(digest); diff --git a/src/commands/registries/deleteRemoteImage.ts b/src/commands/registries/deleteRemoteImage.ts index ec938b1f68..0495177910 100644 --- a/src/commands/registries/deleteRemoteImage.ts +++ b/src/commands/registries/deleteRemoteImage.ts @@ -1,37 +1,37 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * Copyright (c) Microsoft Corporation. All rights reserved. +// * Licensed under the MIT License. See LICENSE.md in the project root for license information. +// *--------------------------------------------------------------------------------------------*/ -import { DialogResponses, IActionContext } from '@microsoft/vscode-azext-utils'; -import { l10n, ProgressLocation, window } from 'vscode'; -import { ext } from '../../extensionVariables'; -import { DockerV2TagTreeItem } from '../../tree/registries/dockerV2/DockerV2TagTreeItem'; -import { registryExpectedContextValues } from '../../tree/registries/registryContextValues'; +// import { DialogResponses, IActionContext } from '@microsoft/vscode-azext-utils'; +// import { l10n, ProgressLocation, window } from 'vscode'; +// import { ext } from '../../extensionVariables'; +// import { DockerV2TagTreeItem } from '../../tree/registries/dockerV2/DockerV2TagTreeItem'; +// import { registryExpectedContextValues } from '../../tree/registries/registryContextValues'; -export async function deleteRemoteImage(context: IActionContext, node?: DockerV2TagTreeItem): Promise { - if (!node) { - node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.dockerV2.tag, { - ...context, - suppressCreatePick: true, - noItemFoundErrorMessage: l10n.t('No remote images are available to delete') - }); - } +// export async function deleteRemoteImage(context: IActionContext, node?: DockerV2TagTreeItem): Promise { +// if (!node) { +// node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.dockerV2.tag, { +// ...context, +// suppressCreatePick: true, +// noItemFoundErrorMessage: l10n.t('No remote images are available to delete') +// }); +// } - const confirmDelete = l10n.t('Are you sure you want to delete image "{0}"? This will delete all images that have the same digest.', node.repoNameAndTag); - // no need to check result - cancel will throw a UserCancelledError - await context.ui.showWarningMessage(confirmDelete, { modal: true }, DialogResponses.deleteResponse); +// const confirmDelete = l10n.t('Are you sure you want to delete image "{0}"? This will delete all images that have the same digest.', node.repoNameAndTag); +// // no need to check result - cancel will throw a UserCancelledError +// await context.ui.showWarningMessage(confirmDelete, { modal: true }, DialogResponses.deleteResponse); - const repoTI = node.parent; - const deleting = l10n.t('Deleting image "{0}"...', node.repoNameAndTag); - await window.withProgress({ location: ProgressLocation.Notification, title: deleting }, async () => { - await node.deleteTreeItem(context); - }); +// const repoTI = node.parent; +// const deleting = l10n.t('Deleting image "{0}"...', node.repoNameAndTag); +// await window.withProgress({ location: ProgressLocation.Notification, title: deleting }, async () => { +// await node.deleteTreeItem(context); +// }); - // Other tags that also matched the image may have been deleted, so refresh the whole repository - await repoTI.refresh(context); - const message = l10n.t('Successfully deleted image "{0}".', node.repoNameAndTag); - // don't wait - /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - window.showInformationMessage(message); -} +// // Other tags that also matched the image may have been deleted, so refresh the whole repository +// await repoTI.refresh(context); +// const message = l10n.t('Successfully deleted image "{0}".', node.repoNameAndTag); +// // don't wait +// /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ +// window.showInformationMessage(message); +// } diff --git a/src/commands/registries/disconnectRegistry.ts b/src/commands/registries/disconnectRegistry.ts index 8416735ef9..e158c6b49d 100644 --- a/src/commands/registries/disconnectRegistry.ts +++ b/src/commands/registries/disconnectRegistry.ts @@ -3,17 +3,14 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, InvalidTreeItem } from "@microsoft/vscode-azext-utils"; +import { IActionContext, contextValueExperience } from "@microsoft/vscode-azext-utils"; import { ext } from "../../extensionVariables"; -import { ICachedRegistryProvider } from "../../tree/registries/ICachedRegistryProvider"; -import { IRegistryProviderTreeItem } from "../../tree/registries/IRegistryProviderTreeItem"; +import { UnifiedRegistryItem } from "../../tree/registries/UnifiedRegistryTreeDataProvider"; -export async function disconnectRegistry(context: IActionContext, node?: InvalidTreeItem | IRegistryProviderTreeItem): Promise { - let cachedProvider: ICachedRegistryProvider | undefined; - if (node instanceof InvalidTreeItem) { - cachedProvider = node.data; - } else if (node) { - cachedProvider = node.cachedProvider; +export async function disconnectRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { + if (!node) { + node = await contextValueExperience(context, ext.registriesTree, { include: 'commonregistryroot', exclude: 'genericRegistryV2Root' }); } - await ext.registriesRoot.disconnectRegistry(context, cachedProvider); + + await ext.registriesTree.disconnectRegistryProvider(node); } diff --git a/src/commands/registries/dockerHub/openDockerHubInBrowser.ts b/src/commands/registries/dockerHub/openDockerHubInBrowser.ts index 303d61f9ce..f2ab582bc5 100644 --- a/src/commands/registries/dockerHub/openDockerHubInBrowser.ts +++ b/src/commands/registries/dockerHub/openDockerHubInBrowser.ts @@ -1,34 +1,30 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * 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 "@microsoft/vscode-azext-utils"; -import * as vscode from "vscode"; -import { dockerHubUrl } from "../../../constants"; -import { ext } from "../../../extensionVariables"; -import { DockerHubNamespaceTreeItem } from "../../../tree/registries/dockerHub/DockerHubNamespaceTreeItem"; -import { DockerHubRepositoryTreeItem } from "../../../tree/registries/dockerHub/DockerHubRepositoryTreeItem"; -import { registryExpectedContextValues } from "../../../tree/registries/registryContextValues"; -import { RemoteTagTreeItem } from "../../../tree/registries/RemoteTagTreeItem"; +// import { IActionContext, contextValueExperience } from "@microsoft/vscode-azext-utils"; +// import * as vscode from "vscode"; +// import { ext } from "../../../extensionVariables"; +// import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; -export async function openDockerHubInBrowser(context: IActionContext, node?: DockerHubNamespaceTreeItem | DockerHubRepositoryTreeItem | RemoteTagTreeItem): Promise { - if (!node) { - node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.dockerHub.registry, { - ...context, - noItemFoundErrorMessage: vscode.l10n.t('No Docker Hub registries available to browse') - }); - } +// export async function openDockerHubInBrowser(context: IActionContext, node?: UnifiedRegistryItem): Promise { +// if (!node) { +// node = await contextValueExperience(context, ext.registriesRoot, { include: 'dockerhubregistry' }); +// } - let url = dockerHubUrl; - if (node instanceof DockerHubNamespaceTreeItem) { - url += `u/${node.namespace}`; - } else if (node instanceof DockerHubRepositoryTreeItem) { - url += `r/${node.parent.namespace}/${node.repoName}`; - } else { - const repoTI = node.parent; - url += `r/${repoTI.parent.namespace}/${repoTI.repoName}/tags`; - } +// let url = dockerHubUrl; +// if (node instanceof DockerHubNamespaceTreeItem) { +// url += `u/${node.namespace}`; +// } else if (node instanceof DockerHubRepositoryTreeItem) { +// url += `r/${node.parent.namespace}/${node.repoName}`; +// } else { +// const repoTI = node.parent; +// url += `r/${repoTI.parent.namespace}/${repoTI.repoName}/tags`; +// } - await vscode.env.openExternal(vscode.Uri.parse(url)); -} +// const url = ''; +// // TODO: review this later + +// await vscode.env.openExternal(vscode.Uri.parse(url)); +// } diff --git a/src/commands/registries/logInToDockerCli.ts b/src/commands/registries/logInToDockerCli.ts index b4c367e9e3..2b7fd92b20 100644 --- a/src/commands/registries/logInToDockerCli.ts +++ b/src/commands/registries/logInToDockerCli.ts @@ -3,33 +3,24 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, parseError } from '@microsoft/vscode-azext-utils'; +import { IActionContext, contextValueExperience, parseError } from '@microsoft/vscode-azext-utils'; +import { CommonRegistry } from '@microsoft/vscode-docker-registries/lib/clients/Common/models'; // TODO: update this import * as stream from 'stream'; import * as vscode from 'vscode'; -import { NULL_GUID } from '../../constants'; import { ext } from '../../extensionVariables'; -import { registryExpectedContextValues } from '../../tree/registries/registryContextValues'; -import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase'; +import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; -export async function logInToDockerCli(context: IActionContext, node?: RegistryTreeItemBase): Promise { +export async function logInToDockerCli(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.all.registry, context); + node = await contextValueExperience(context, ext.registriesRoot, { include: 'commonroot' }); } - const creds = await node.getDockerCliCredentials(); - const auth: { username?: string, password?: string, token?: string } = creds.auth || {}; - let username: string | undefined; - let password: string | undefined; - if (auth.token) { - username = NULL_GUID; - password = auth.token; - } else if (auth.password) { - username = auth.username; - password = auth.password; - } + const creds = await node.provider?.getLoginInformation?.(node.wrappedItem); + const username = creds?.username; + const secret = creds?.secret; - if (!username || !password) { - ext.outputChannel.warn(vscode.l10n.t('Skipping login for "{0}" because it does not require authentication.', creds.registryPath)); + if (!username || !secret) { + ext.outputChannel.warn(vscode.l10n.t('Skipping login for "{0}" because it does not require authentication.', node.provider.label)); } else { const progressOptions: vscode.ProgressOptions = { location: vscode.ProgressLocation.Notification, @@ -42,10 +33,10 @@ export async function logInToDockerCli(context: IActionContext, node?: RegistryT client => client.login({ username: username, passwordStdIn: true, - registry: creds.registryPath, + registry: node.wrappedItem.baseUrl?.toString() ?? '' }), { - stdInPipe: stream.Readable.from(password), + stdInPipe: stream.Readable.from(secret), } ); ext.outputChannel.info('Login succeeded.'); diff --git a/src/commands/registries/logOutOfDockerCli.ts b/src/commands/registries/logOutOfDockerCli.ts index 4e828f109e..ccf26ec220 100644 --- a/src/commands/registries/logOutOfDockerCli.ts +++ b/src/commands/registries/logOutOfDockerCli.ts @@ -1,29 +1,29 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * 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 '@microsoft/vscode-azext-utils'; -import { ext } from '../../extensionVariables'; -import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; -import { registryExpectedContextValues } from '../../tree/registries/registryContextValues'; -import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase'; +// import { IActionContext } from '@microsoft/vscode-azext-utils'; +// import { ext } from '../../extensionVariables'; +// import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; +// import { registryExpectedContextValues } from '../../tree/registries/registryContextValues'; +// import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase'; -export async function logOutOfDockerCli(context: IActionContext, node?: RegistryTreeItemBase): Promise { - if (!node) { - node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.all.registry, context); - } +// export async function logOutOfDockerCli(context: IActionContext, node?: RegistryTreeItemBase): Promise { +// if (!node) { +// node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.all.registry, context); +// } - const creds = await node.getDockerCliCredentials(); +// const creds = await node.getDockerCliCredentials(); - const client = await ext.runtimeManager.getClient(); - const taskCRF = new TaskCommandRunnerFactory( - { - taskName: 'Docker' - } - ); +// const client = await ext.runtimeManager.getClient(); +// const taskCRF = new TaskCommandRunnerFactory( +// { +// taskName: 'Docker' +// } +// ); - await taskCRF.getCommandRunner()( - client.logout({ registry: creds.registryPath }) - ); -} +// await taskCRF.getCommandRunner()( +// client.logout({ registry: creds.registryPath }) +// ); +// } diff --git a/src/commands/registries/pullImages.ts b/src/commands/registries/pullImages.ts index 47dd2eda37..d77620b4c5 100644 --- a/src/commands/registries/pullImages.ts +++ b/src/commands/registries/pullImages.ts @@ -3,33 +3,33 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext } from '@microsoft/vscode-azext-utils'; +import { IActionContext, contextValueExperience } from '@microsoft/vscode-azext-utils'; +import { CommonRegistry, CommonRepository, CommonTag } from '@microsoft/vscode-docker-registries/lib/clients/Common/models'; import { ext } from '../../extensionVariables'; import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; -import { registryExpectedContextValues } from '../../tree/registries/registryContextValues'; -import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase'; -import { RemoteRepositoryTreeItemBase } from '../../tree/registries/RemoteRepositoryTreeItemBase'; -import { RemoteTagTreeItem } from '../../tree/registries/RemoteTagTreeItem'; +import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { getImageNameFromRegistryItem } from '../../tree/registries/getInformationFromRegistryItem'; import { logInToDockerCli } from './logInToDockerCli'; -export async function pullRepository(context: IActionContext, node?: RemoteRepositoryTreeItemBase): Promise { +export async function pullRepository(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.all.repository, context); + node = await contextValueExperience(context, ext.registriesTree, { include: 'commonrepository' }); } - await pullImages(context, node.parent, node.repoName, true); + await pullImages(context, node.parent, node.wrappedItem.label, true); } -export async function pullImageFromRepository(context: IActionContext, node?: RemoteTagTreeItem): Promise { +export async function pullImageFromRepository(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.all.tag, context); + node = await contextValueExperience(context, ext.registriesTree, { include: 'commontag' }); } - await pullImages(context, node.parent.parent, node.repoNameAndTag, false); + await pullImages(context, node.parent.parent, getImageNameFromRegistryItem(node), false); } -async function pullImages(context: IActionContext, node: RegistryTreeItemBase, imageRequest: string, allTags: boolean): Promise { - await logInToDockerCli(context, node); +async function pullImages(context: IActionContext, node: UnifiedRegistryItem, imageRequest: string, allTags: boolean): Promise { + const registryNode = node as UnifiedRegistryItem; + await logInToDockerCli(context, registryNode); const client = await ext.runtimeManager.getClient(); const taskCRF = new TaskCommandRunnerFactory({ @@ -39,7 +39,7 @@ async function pullImages(context: IActionContext, node: RegistryTreeItemBase, i await taskCRF.getCommandRunner()( client.pullImage( { - imageRef: `${node.baseImagePath}/${imageRequest}`, + imageRef: `${registryNode.wrappedItem.label}/${imageRequest}`, allTags: allTags, } ) diff --git a/src/commands/registries/reconnectRegistry.ts b/src/commands/registries/reconnectRegistry.ts index 31e0c79be7..0537f79960 100644 --- a/src/commands/registries/reconnectRegistry.ts +++ b/src/commands/registries/reconnectRegistry.ts @@ -1,19 +1,19 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +// /*--------------------------------------------------------------------------------------------- +// * 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 "@microsoft/vscode-azext-utils"; -import { l10n } from 'vscode'; -import { ext } from "../../extensionVariables"; -import { RegistryConnectErrorTreeItem } from "../../tree/registries/RegistryConnectErrorTreeItem"; +// import { IActionContext } from "@microsoft/vscode-azext-utils"; +// import { l10n } from 'vscode'; +// import { ext } from "../../extensionVariables"; +// import { RegistryConnectErrorTreeItem } from "../../tree/registries/RegistryConnectErrorTreeItem"; -export async function reconnectRegistry(context: IActionContext, node?: RegistryConnectErrorTreeItem): Promise { - if (!node?.cachedProvider || !node?.provider) { - // This is not expected ever, so we'll throw an error which can be bubbled up to a Report Issue if it does - throw new Error(l10n.t('Unable to determine provider to re-enter credentials. Please disconnect and connect again.')); - } +// export async function reconnectRegistry(context: IActionContext, node?: RegistryConnectErrorTreeItem): Promise { +// if (!node?.cachedProvider || !node?.provider) { +// // This is not expected ever, so we'll throw an error which can be bubbled up to a Report Issue if it does +// throw new Error(l10n.t('Unable to determine provider to re-enter credentials. Please disconnect and connect again.')); +// } - await ext.registriesRoot.disconnectRegistry(context, node.cachedProvider); - await ext.registriesRoot.connectRegistry(context, node.provider, node.url); -} +// await ext.registriesRoot.disconnectRegistry(context, node.cachedProvider); +// await ext.registriesRoot.connectRegistry(context, node.provider, node.url); +// } diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index e3c403caea..93c3fad0db 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { AzExtTreeDataProvider, AzExtTreeItem, IExperimentationServiceAdapter } from '@microsoft/vscode-azext-utils'; +import { GenericRegistryV2DataProvider } from '@microsoft/vscode-docker-registries'; import { ExtensionContext, StatusBarItem, TreeView } from 'vscode'; import { ContainerRuntimeManager } from './runtimes/ContainerRuntimeManager'; import { OrchestratorRuntimeManager } from './runtimes/OrchestratorRuntimeManager'; @@ -13,7 +14,7 @@ import { ContainersTreeItem } from './tree/containers/ContainersTreeItem'; import { ContextsTreeItem } from './tree/contexts/ContextsTreeItem'; import { ImagesTreeItem } from './tree/images/ImagesTreeItem'; import { NetworksTreeItem } from './tree/networks/NetworksTreeItem'; -import { RegistriesTreeItem } from './tree/registries/RegistriesTreeItem'; +import { UnifiedRegistryItem, UnifiedRegistryTreeDataProvider } from './tree/registries/UnifiedRegistryTreeDataProvider'; import { VolumesTreeItem } from './tree/volumes/VolumesTreeItem'; import { AzExtLogOutputChannelWrapper } from './utils/AzExtLogOutputChannelWrapper'; @@ -45,9 +46,10 @@ export namespace ext { export const prefix: string = 'docker'; - export let registriesTree: AzExtTreeDataProvider; - export let registriesTreeView: TreeView; - export let registriesRoot: RegistriesTreeItem; + export let registriesTree: UnifiedRegistryTreeDataProvider; + export let registriesTreeView: TreeView>; + export let registriesRoot: UnifiedRegistryTreeDataProvider; + export let genericRegistryV2DataProvider: GenericRegistryV2DataProvider; export let volumesTree: AzExtTreeDataProvider; export let volumesTreeView: TreeView; diff --git a/src/tree/RefreshManager.ts b/src/tree/RefreshManager.ts index f3ab4226cd..502ac49604 100644 --- a/src/tree/RefreshManager.ts +++ b/src/tree/RefreshManager.ts @@ -301,7 +301,7 @@ export class RefreshManager extends vscode.Disposable { callback = () => ext.networksRoot.refresh(context); break; case 'registries': - callback = () => ext.registriesRoot.refresh(context); + callback = () => ext.registriesRoot.refresh(); break; case 'volumes': callback = () => ext.volumesRoot.refresh(context); diff --git a/src/tree/registerTrees.ts b/src/tree/registerTrees.ts index c9f53f51a3..e55991df7f 100644 --- a/src/tree/registerTrees.ts +++ b/src/tree/registerTrees.ts @@ -3,18 +3,20 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; import { AzExtTreeDataProvider, AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils"; +import { DockerHubRegistryDataProvider, GenericRegistryV2DataProvider, GitHubRegistryDataProvider } from "@microsoft/vscode-docker-registries"; +import * as vscode from 'vscode'; import { registerCommand } from '../commands/registerCommands'; import { ext } from '../extensionVariables'; +import { OpenUrlTreeItem } from './OpenUrlTreeItem'; +import { RefreshManager } from './RefreshManager'; import { ContainersTreeItem } from './containers/ContainersTreeItem'; import { ContextsTreeItem } from './contexts/ContextsTreeItem'; import { HelpsTreeItem } from './help/HelpsTreeItem'; import { ImagesTreeItem } from "./images/ImagesTreeItem"; import { NetworksTreeItem } from "./networks/NetworksTreeItem"; -import { OpenUrlTreeItem } from './OpenUrlTreeItem'; -import { RefreshManager } from './RefreshManager'; -import { RegistriesTreeItem } from "./registries/RegistriesTreeItem"; +import { AzureRegistryDataProvider } from "./registries/Azure/AzureRegistryDataProvider"; +import { UnifiedRegistryTreeDataProvider } from "./registries/UnifiedRegistryTreeDataProvider"; import { VolumesTreeItem } from "./volumes/VolumesTreeItem"; export function registerTrees(): void { @@ -42,13 +44,16 @@ export function registerTrees(): void { /* eslint-disable-next-line @typescript-eslint/promise-function-async */ registerCommand(imagesLoadMore, (context: IActionContext, node: AzExtTreeItem) => ext.imagesTree.loadMore(node, context)); - ext.registriesRoot = new RegistriesTreeItem(); - const registriesLoadMore = 'vscode-docker.registries.loadMore'; - ext.registriesTree = new AzExtTreeDataProvider(ext.registriesRoot, registriesLoadMore); - ext.registriesTreeView = vscode.window.createTreeView('dockerRegistries', { treeDataProvider: ext.registriesTree, showCollapseAll: true, canSelectMany: false }); - ext.context.subscriptions.push(ext.registriesTreeView); - /* eslint-disable-next-line @typescript-eslint/promise-function-async */ - registerCommand(registriesLoadMore, (context: IActionContext, node: AzExtTreeItem) => ext.registriesTree.loadMore(node, context)); + const urtdp = new UnifiedRegistryTreeDataProvider(ext.context.globalState); + const genericRegistryV2DataProvider = new GenericRegistryV2DataProvider(ext.context); + urtdp.registerProvider(new GitHubRegistryDataProvider(ext.context)); + urtdp.registerProvider(new DockerHubRegistryDataProvider(ext.context)); + urtdp.registerProvider(new AzureRegistryDataProvider(ext.context)); + urtdp.registerProvider(genericRegistryV2DataProvider); + ext.registriesRoot = urtdp; + ext.registriesTreeView = vscode.window.createTreeView('dockerRegistries', { treeDataProvider: urtdp }); + ext.registriesTree = urtdp; + ext.genericRegistryV2DataProvider = genericRegistryV2DataProvider; ext.volumesRoot = new VolumesTreeItem(undefined); const volumesLoadMore = 'vscode-docker.volumes.loadMore'; diff --git a/src/tree/registries/Azure/ACROAuthProvider.ts b/src/tree/registries/Azure/ACROAuthProvider.ts new file mode 100644 index 0000000000..887811a8ac --- /dev/null +++ b/src/tree/registries/Azure/ACROAuthProvider.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureSubscription } from '@microsoft/vscode-azext-azureauth'; +import { httpRequest } from '@microsoft/vscode-docker-registries'; +import { AuthenticationProvider } from "@microsoft/vscode-docker-registries/"; +import * as vscode from 'vscode'; + +// export interface ACROAuthOptions extends BasicOAuthOptions { +// readonly subscription: AzureSubscription; +// } + +export class ACROAuthProvider implements AuthenticationProvider { + private refreshTokenCache = new Map(); + + public constructor(private readonly registryUri: vscode.Uri, private readonly subscription: AzureSubscription) { } + + public async getSession(scopes: string[], options?: vscode.AuthenticationGetSessionOptions): Promise { + const accessToken = await this.getAccessToken(this.subscription); + const registryString = this.registryUri.toString(); + + let refreshToken: string; + if (!options?.forceNewSession && this.refreshTokenCache.has(registryString)) { + refreshToken = this.refreshTokenCache.get(registryString)!; + } else { + refreshToken = await this.getRefreshTokenFromAccessToken(accessToken, this.registryUri, this.subscription); + this.refreshTokenCache.set(registryString, refreshToken); + } + + const oauthToken = await this.getOAuthTokenFromRefreshToken(refreshToken, this.registryUri, scopes.join(' '), this.subscription); + const { sub, jti } = this.parseToken(oauthToken); + + return { + id: jti, + type: 'Bearer', + accessToken: oauthToken, + account: { + label: sub, + id: sub, + }, + scopes: scopes, + }; + } + + private parseToken(accessToken: string): { sub: string, jti: string } { + const tokenParts = accessToken.split('.'); + const tokenBody = JSON.parse(Buffer.from(tokenParts[1], 'base64').toString('utf8')); + return { + sub: tokenBody.sub, + jti: tokenBody.jti, + }; + } + + private async getOAuthTokenFromRefreshToken(refreshToken: string, registryUri: vscode.Uri, scopes: string, subscription: AzureSubscription): Promise { + const requestUrl = registryUri.with({ path: '/oauth2/token' }); + + const requestBody = new URLSearchParams({ + /* eslint-disable @typescript-eslint/naming-convention */ + grant_type: 'refresh_token', + refresh_token: refreshToken, + service: registryUri.authority, + scope: scopes, + }); + + const response = await httpRequest<{ access_token: string }>(requestUrl.toString(), { + method: 'POST', + headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'content-type': 'application/x-www-form-urlencoded' + }, + body: requestBody, + }); + + return (await response.json()).access_token; + } + + private async getRefreshTokenFromAccessToken(accessToken: string, registryUri: vscode.Uri, subscription: AzureSubscription): Promise { + const requestUrl = registryUri.with({ path: '/oauth2/exchange' }); + + const requestBody = new URLSearchParams({ + /* eslint-disable @typescript-eslint/naming-convention */ + grant_type: 'access_token', + access_token: accessToken, + service: registryUri.authority, + tenant: subscription.tenantId, + /* eslint-enable @typescript-eslint/naming-convention */ + }); + + // eslint-disable-next-line @typescript-eslint/naming-convention + const response = await httpRequest<{ refresh_token: string }>(requestUrl.toString(), { + method: 'POST', + headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'content-type': 'application/x-www-form-urlencoded' + }, + body: requestBody, + }); + + return (await response.json()).refresh_token; + } + + private async getAccessToken(subscription: AzureSubscription): Promise { + // Registry scopes, i.e. those passed to `getSession()`, are not valid for acquiring this + // access token--instead, those only need to be passed to `getOAuthTokenFromRefreshToken()` + const token = await subscription.credential.getToken([]); + return token!.token; + } +} diff --git a/src/tree/registries/Azure/AzureRegistryDataProvider.ts b/src/tree/registries/Azure/AzureRegistryDataProvider.ts new file mode 100644 index 0000000000..3ec224bc34 --- /dev/null +++ b/src/tree/registries/Azure/AzureRegistryDataProvider.ts @@ -0,0 +1,126 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { Registry as AcrRegistry } from '@azure/arm-containerregistry'; +import { AzureSubscription, VSCodeAzureSubscriptionProvider } from '@microsoft/vscode-azext-azureauth'; +import { RegistryV2DataProvider, V2Registry, V2RegistryItem } from '@microsoft/vscode-docker-registries'; +import { CommonRegistryItem, isRegistryRoot } from '@microsoft/vscode-docker-registries/lib/clients/Common/models'; +import * as vscode from 'vscode'; +import { ACROAuthProvider } from './ACROAuthProvider'; + +interface AzureRegistryItem extends V2RegistryItem { + readonly subscription: AzureSubscription; +} + +interface AzureSubscriptionRegistryItem extends CommonRegistryItem { + readonly subscription: AzureSubscription; + readonly type: 'azuresubscription'; +} + +function isAzureSubscriptionRegistryItem(item: unknown): item is AzureSubscriptionRegistryItem { + return !!item && typeof item === 'object' && (item as AzureSubscriptionRegistryItem).type === 'azuresubscription'; +} + +type AzureRegistry = V2Registry & AzureRegistryItem; + +export class AzureRegistryDataProvider extends RegistryV2DataProvider implements vscode.Disposable { + public readonly id = 'vscode-docker.azureContainerRegistry'; + public readonly label = vscode.l10n.t('Azure'); + public readonly iconPath = new vscode.ThemeIcon('azure'); + public readonly description = vscode.l10n.t('Azure Container Registry'); + + private readonly subscriptionProvider = new VSCodeAzureSubscriptionProvider(); + private readonly authenticationProviders = new Map(); // The tree items are too short-lived to store the associated auth provider so keep a cache + + public constructor(private readonly extensionContext: vscode.ExtensionContext) { + super(); + } + + public override async getChildren(element?: CommonRegistryItem | undefined): Promise { + if (isRegistryRoot(element)) { + if (!await this.subscriptionProvider.isSignedIn()) { + // TODO: show a node for sign in + await this.subscriptionProvider.signIn(); + this.onDidChangeTreeDataEmitter.fire(element); // TODO: this fires too fast, need a "fire soon" analogue + return []; + } + + const subscriptions = await this.subscriptionProvider.getSubscriptions(); + + return subscriptions.map(sub => { + return { + parent: element, + label: sub.name, + type: 'azuresubscription', + subscription: sub, + additionalContextValues: ['azuresubscription'] + } as AzureSubscriptionRegistryItem; + }); + } else if (isAzureSubscriptionRegistryItem(element)) { + return await this.getRegistries(element); + } else { + const children = await super.getChildren(element); + + if ((element as AzureRegistryItem)?.subscription) { + children.forEach(e => { + e.subscription = (element as AzureRegistryItem).subscription; + }); + } + + return children; + } + } + + public dispose(): void { + this.subscriptionProvider.dispose(); + } + + public async getRegistries(subscriptionItem: CommonRegistryItem): Promise { + subscriptionItem = subscriptionItem as AzureSubscriptionRegistryItem; + // TODO: replace this with `createAzureClient` + const acrClient = new (await import('@azure/arm-containerregistry')).ContainerRegistryManagementClient(subscriptionItem.subscription.credential, subscriptionItem.subscription.subscriptionId); + + const registries: AcrRegistry[] = []; + + for await (const registry of acrClient.registries.list()) { + registries.push(registry); + } + + return registries.map(registry => { + return { + parent: subscriptionItem, + type: 'commonregistry', + baseUrl: vscode.Uri.parse(`https://${registry.loginServer}`), + label: registry.name!, + iconPath: vscode.Uri.joinPath(this.extensionContext.extensionUri, 'resources', 'azureRegistry.svg'), + subscription: subscriptionItem.subscription, + }; + }); + } + + public override getTreeItem(element: CommonRegistryItem): Promise { + if (isAzureSubscriptionRegistryItem(element)) { + return Promise.resolve({ + label: element.label, + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + contextValue: 'azuresubscription', + iconPath: vscode.Uri.joinPath(this.extensionContext.extensionUri, 'resources', 'azureSubscription.svg'), + }); + } else { + return super.getTreeItem(element); + } + } + + protected override getAuthenticationProvider(item: AzureRegistryItem): ACROAuthProvider { + const registryString = item.baseUrl.toString(); + + if (!this.authenticationProviders.has(registryString)) { + const provider = new ACROAuthProvider(item.baseUrl, item.subscription); + this.authenticationProviders.set(registryString, provider); + } + + return this.authenticationProviders.get(registryString)!; + } +} diff --git a/src/tree/registries/ConnectedRegistriesTreeItem.ts b/src/tree/registries/ConnectedRegistriesTreeItem.ts deleted file mode 100644 index 1a8a097bf7..0000000000 --- a/src/tree/registries/ConnectedRegistriesTreeItem.ts +++ /dev/null @@ -1,31 +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 { AzExtParentTreeItem, AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils"; -import { l10n, ThemeIcon } from "vscode"; - -export class ConnectedRegistriesTreeItem extends AzExtParentTreeItem { - public contextValue: string = 'connectedRegistries'; - public childTypeLabel: string = 'registry'; - public label: string = l10n.t('Connected Registries'); - public children: AzExtTreeItem[] = []; - - public constructor(parent: AzExtParentTreeItem | undefined) { - super(parent); - this.iconPath = new ThemeIcon('link'); - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - return this.children; - } - - public hasMoreChildrenImpl(): boolean { - return false; - } - - public isAncestorOfImpl(expectedContextValue: string | RegExp): boolean { - return this.children.some(c => c.isAncestorOfImpl && c.isAncestorOfImpl(expectedContextValue)); - } -} diff --git a/src/tree/registries/ICachedRegistryProvider.ts b/src/tree/registries/ICachedRegistryProvider.ts deleted file mode 100644 index aac276b037..0000000000 --- a/src/tree/registries/ICachedRegistryProvider.ts +++ /dev/null @@ -1,16 +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 { RegistryApi } from "./all/RegistryApi"; - -/** - * Basic _non-sensitive_ information that will be cached across sessions - */ -export interface ICachedRegistryProvider { - id: string; - api: RegistryApi; - url?: string; - username?: string; -} diff --git a/src/tree/registries/IRegistryProvider.ts b/src/tree/registries/IRegistryProvider.ts deleted file mode 100644 index bf1100dc81..0000000000 --- a/src/tree/registries/IRegistryProvider.ts +++ /dev/null @@ -1,68 +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 { AzExtParentTreeItem } from "@microsoft/vscode-azext-utils"; -import { RegistryApi } from "./all/RegistryApi"; -import { IConnectRegistryWizardOptions } from "./connectWizard/IConnectRegistryWizardOptions"; -import { ICachedRegistryProvider } from "./ICachedRegistryProvider"; -import { IRegistryProviderTreeItem } from "./IRegistryProviderTreeItem"; - -export interface IRegistryProvider { - /** - * A unique id for this registry provider - */ - id: string; - - /** - * The api used by this provider - */ - api: RegistryApi; - - /** - * Primary value to display when prompting a user to connect a provider - */ - label: string; - - /** - * Optional secondary value to display when prompting a user to connect a provider - */ - description?: string; - - /** - * Optional tertiary value to display when prompting a user to connect a provider - */ - detail?: string; - - /** - * Set to true if this provider maps to a single registry as opposed to multiple registries - * If it maps to a single registry, it will be grouped under a "Connected Registries" node in the tree. - */ - isSingleRegistry?: boolean; - - /** - * Set to true if only a single instance of this provider can be connected at a time - */ - onlyOneAllowed?: boolean; - - /** - * Describes the wizard to be used when connecting this provider - */ - connectWizardOptions?: IConnectRegistryWizardOptions; - - /** - * The factory method for creating the root tree item - */ - treeItemFactory(parent: AzExtParentTreeItem, cachedProvider: ICachedRegistryProvider): (AzExtParentTreeItem & IRegistryProviderTreeItem) | Promise; - - /** - * Method to call for persisting auth secrets - */ - persistAuth?(cachedProvider: ICachedRegistryProvider, secret: string): Promise; - - /** - * Method to call to remove auth secrets - */ - removeAuth?(cachedProvider: ICachedRegistryProvider): Promise; -} diff --git a/src/tree/registries/IRegistryProviderTreeItem.ts b/src/tree/registries/IRegistryProviderTreeItem.ts deleted file mode 100644 index 5bac896097..0000000000 --- a/src/tree/registries/IRegistryProviderTreeItem.ts +++ /dev/null @@ -1,10 +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 { ICachedRegistryProvider } from "./ICachedRegistryProvider"; - -export interface IRegistryProviderTreeItem { - cachedProvider: ICachedRegistryProvider; -} diff --git a/src/tree/registries/RegistriesTreeItem.ts b/src/tree/registries/RegistriesTreeItem.ts deleted file mode 100644 index b20ff0b8c4..0000000000 --- a/src/tree/registries/RegistriesTreeItem.ts +++ /dev/null @@ -1,233 +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 { AzExtParentTreeItem, AzExtTreeItem, AzureWizard, GenericTreeItem, IActionContext, IAzureQuickPickItem, parseError, UserCancelledError } from "@microsoft/vscode-azext-utils"; -import { l10n, ThemeIcon } from "vscode"; -import { ext } from "../../extensionVariables"; -import { TreePrefix } from "../TreePrefix"; -import { getRegistryProviders } from "./all/getRegistryProviders"; -import { ConnectedRegistriesTreeItem } from "./ConnectedRegistriesTreeItem"; -import { IConnectRegistryWizardContext } from "./connectWizard/IConnectRegistryWizardContext"; -import { RegistryPasswordStep } from "./connectWizard/RegistryPasswordStep"; -import { RegistryUrlStep } from "./connectWizard/RegistryUrlStep"; -import { RegistryUsernameStep } from "./connectWizard/RegistryUsernameStep"; -import { ICachedRegistryProvider } from "./ICachedRegistryProvider"; -import { IRegistryProvider } from "./IRegistryProvider"; -import { IRegistryProviderTreeItem } from "./IRegistryProviderTreeItem"; -import { anyContextValuePart, contextValueSeparator } from "./registryContextValues"; -import { RegistryTreeItemBase } from "./RegistryTreeItemBase"; - -const providersKey = 'docker.registryProviders'; - -export class RegistriesTreeItem extends AzExtParentTreeItem { - public treePrefix: TreePrefix = 'registries'; - public static contextValue: string = 'registries'; - public contextValue: string = RegistriesTreeItem.contextValue; - public label: string = l10n.t('Registries'); - public childTypeLabel: string = 'registry provider'; - public autoSelectInTreeItemPicker: boolean = true; - - private _connectedRegistriesTreeItem: ConnectedRegistriesTreeItem; - private _cachedProviders: ICachedRegistryProvider[]; - - public constructor() { - super(undefined); - this._connectedRegistriesTreeItem = new ConnectedRegistriesTreeItem(this); - this._cachedProviders = ext.context.globalState.get(providersKey, []); - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - if (this._cachedProviders.length === 0) { - return [new GenericTreeItem(this, { - label: l10n.t('Connect Registry...'), - contextValue: 'connectRegistry', - iconPath: new ThemeIcon('plug'), - includeInTreeItemPicker: true, - commandId: 'vscode-docker.registries.connectRegistry' - })]; - } else { - this._connectedRegistriesTreeItem.children = []; - const children: AzExtTreeItem[] = await this.createTreeItemsWithErrorHandling( - this._cachedProviders, - 'invalidRegistryProvider', - async cachedProvider => { - const provider = getRegistryProviders().find(rp => rp.id === cachedProvider.id); - if (!provider) { - throw new Error(l10n.t('Failed to find registry provider with id "{0}".', cachedProvider.id)); - } - - const parent = provider.isSingleRegistry ? this._connectedRegistriesTreeItem : this; - return this.initTreeItem(await Promise.resolve(provider.treeItemFactory(parent, cachedProvider))); - }, - cachedInfo => cachedInfo.id - ); - - this._connectedRegistriesTreeItem.children = children.filter(c => c.parent === this._connectedRegistriesTreeItem); - if (this._connectedRegistriesTreeItem.children.length > 0) { - children.push(this._connectedRegistriesTreeItem); - } - - return children.filter(c => c.parent !== this._connectedRegistriesTreeItem); - } - } - - public hasMoreChildrenImpl(): boolean { - return false; - } - - public async connectRegistry(context: IActionContext, provider?: IRegistryProvider, url?: string): Promise { - let picks: IAzureQuickPickItem[] = getRegistryProviders().map(rp => { - return { - label: rp.label, - description: rp.description, - detail: rp.detail, - data: rp - }; - }); - picks = picks.sort((p1, p2) => p1.label.localeCompare(p2.label)); - - const placeHolder: string = l10n.t('Select the provider for your registry'); - provider = provider ?? (await context.ui.showQuickPick(picks, { placeHolder, suppressPersistence: true })).data; - if (!provider) { - throw new UserCancelledError(); - } else if (provider.onlyOneAllowed && this._cachedProviders.find(c => c.id === provider.id)) { - // Don't wait, no input to wait for anyway - void context.ui.showWarningMessage(l10n.t('The "{0}" registry provider is already connected.', provider.label)); - throw new UserCancelledError('registryProviderAlreadyAdded'); - } - - context.telemetry.properties.providerId = provider.id; - context.telemetry.properties.providerApi = provider.api; - - const cachedProvider: ICachedRegistryProvider = { - id: provider.id, - api: provider.api, - }; - - if (provider.connectWizardOptions) { - const existingProviders: ICachedRegistryProvider[] = this._cachedProviders.filter(rp => rp.id === provider.id); - const wizardContext: IConnectRegistryWizardContext = { ...context, ...provider.connectWizardOptions, url, existingProviders }; - const wizard = new AzureWizard(wizardContext, { - title: provider.connectWizardOptions.wizardTitle, - promptSteps: [ - new RegistryUrlStep(), - new RegistryUsernameStep(), - new RegistryPasswordStep() - ] - }); - - await wizard.prompt(); - await wizard.execute(); - - cachedProvider.url = wizardContext.url; - cachedProvider.username = wizardContext.username; - - if (wizardContext.secret && provider.persistAuth) { - await provider.persistAuth(cachedProvider, wizardContext.secret); - } - } - - this._cachedProviders.push(cachedProvider); - await this.saveCachedProviders(context); - } - - public async disconnectRegistry(context: IActionContext, cachedProvider: ICachedRegistryProvider | undefined): Promise { - if (!cachedProvider) { - const picks = this._cachedProviders.map(crp => { - const provider = getRegistryProviders().find(rp => rp.id === crp.id); - const label: string = (provider && provider.label) || crp.id; - const descriptions: string[] = []; - if (crp.username) { - descriptions.push(l10n.t('Username: "{0}"', crp.username)); - } - if (crp.url) { - descriptions.push(l10n.t('URL: "{0}"', crp.url)); - } - return { - label, - description: descriptions[0], - detail: descriptions[1], - data: crp - }; - }); - const placeHolder: string = l10n.t('Select the registry to disconnect'); - cachedProvider = (await context.ui.showQuickPick(picks, { placeHolder, suppressPersistence: true })).data; - } - - context.telemetry.properties.providerId = cachedProvider.id; - context.telemetry.properties.providerApi = cachedProvider.api; - - // NOTE: Do not let failure prevent removal of the tree item. - - try { - const provider = getRegistryProviders().find(rp => rp.id === cachedProvider.id); - if (provider?.removeAuth) { - await provider.removeAuth(cachedProvider); - } - } catch (err) { - // Don't wait, no input to wait for anyway - void context.ui.showWarningMessage(l10n.t('The registry password could not be removed from the cache: {0}', parseError(err).message)); - } - - const index = this._cachedProviders.findIndex(n => n === cachedProvider); - if (index !== -1) { - this._cachedProviders.splice(index, 1); - } - - await this.saveCachedProviders(context); - } - - public hasMultiplesOfProvider(cachedProvider: ICachedRegistryProvider): boolean { - return this._cachedProviders.filter(c => c.id === cachedProvider.id).length > 1; - } - - public async getAllConnectedRegistries(context: IActionContext): Promise { - return await recursiveGetAllConnectedRegistries(context, ext.registriesRoot); - } - - private async saveCachedProviders(context: IActionContext): Promise { - await ext.context.globalState.update(providersKey, this._cachedProviders); - await this.refresh(context); - } - - private initTreeItem(node: AzExtParentTreeItem & IRegistryProviderTreeItem): AzExtParentTreeItem & IRegistryProviderTreeItem { - // Forcing all registry providers to have the same `isAncestorOfImpl` so that a provider doesn't show up for another provider's commands - node.isAncestorOfImpl = (expectedContextValue: string | RegExp) => { - expectedContextValue = expectedContextValue instanceof RegExp ? expectedContextValue.source.toString() : expectedContextValue; - - if (!expectedContextValue.includes(contextValueSeparator)) { - // If the expected context value has a non-standard format, just check against the id - // For example 'azureTask' is non-standard since it is unique to azure - return expectedContextValue.startsWith(node.cachedProvider.id); - } else { - const parts = expectedContextValue.split(contextValueSeparator); - if (parts[0] !== anyContextValuePart) { - return parts[0] === node.cachedProvider.id; - } else if (parts[1] !== anyContextValuePart) { - return parts[1] === node.cachedProvider.api; - } else { - // expectedContextValue must not have specificied any particular id or api, so return true - return true; - } - } - }; - - return node; - } -} - -async function recursiveGetAllConnectedRegistries(context: IActionContext, node: AzExtParentTreeItem): Promise { - let results: RegistryTreeItemBase[] = []; - - for (const child of await node.getCachedChildren(context)) { - if (child instanceof RegistryTreeItemBase) { - results.push(child); - } else if (child instanceof AzExtParentTreeItem) { - results = results.concat(await recursiveGetAllConnectedRegistries(context, child)); - } - } - - return results; -} diff --git a/src/tree/registries/RegistryConnectErrorTreeItem.ts b/src/tree/registries/RegistryConnectErrorTreeItem.ts deleted file mode 100644 index f67ef20a92..0000000000 --- a/src/tree/registries/RegistryConnectErrorTreeItem.ts +++ /dev/null @@ -1,24 +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 { AzExtParentTreeItem, GenericTreeItem, parseError } from "@microsoft/vscode-azext-utils"; -import { ThemeColor, ThemeIcon } from "vscode"; -import { getRegistryProviders } from "./all/getRegistryProviders"; -import { ICachedRegistryProvider } from "./ICachedRegistryProvider"; -import { IRegistryProvider } from "./IRegistryProvider"; - -export class RegistryConnectErrorTreeItem extends GenericTreeItem { - public constructor(parent: AzExtParentTreeItem, err: unknown, public readonly cachedProvider: ICachedRegistryProvider, public readonly url?: string) { - super(parent, { - label: parseError(err).message, - contextValue: 'registryConnectError', - iconPath: new ThemeIcon('warning', new ThemeColor('problemsWarningIcon.foreground')), - }); - - this.provider = getRegistryProviders().find(rp => rp.id === this.cachedProvider.id); - } - - public readonly provider: IRegistryProvider; -} diff --git a/src/tree/registries/RegistryTreeItemBase.ts b/src/tree/registries/RegistryTreeItemBase.ts deleted file mode 100644 index 5e482e6ec5..0000000000 --- a/src/tree/registries/RegistryTreeItemBase.ts +++ /dev/null @@ -1,66 +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 { AzExtParentTreeItem } from "@microsoft/vscode-azext-utils"; -import { ThemeIcon } from "vscode"; -import { RequestLike } from "../../utils/httpRequest"; -import { IRegistryAuthTreeItem } from "../../utils/registryRequestUtils"; -import { getRegistryContextValue, registrySuffix } from "./registryContextValues"; - -/** - * Base class for all registries - * NOTE: A registry is loosely defined as anything that contains repositories (e.g. a private registry or a Docker Hub namespace) - */ -export abstract class RegistryTreeItemBase extends AzExtParentTreeItem implements IRegistryAuthTreeItem { - public childTypeLabel: string = 'repository'; - - public constructor(parent: AzExtParentTreeItem | undefined) { - super(parent); - this.iconPath = new ThemeIcon('briefcase'); - } - - public get contextValue(): string { - return getRegistryContextValue(this, registrySuffix); - } - - /** - * Used for an image's full tag - * For example, if the full tag is "example.azurecr.io/hello-world:latest", this would return "example.azurecr.io" - * NOTE: This usually would _not_ include the protocol part of a url - */ - public abstract baseImagePath: string; - - /** - * Used for registry requests - * NOTE: This _should_ include the protocol part of a url - */ - public abstract baseUrl: string; - - /** - * This will be called before each registry request to add authentication - */ - public abstract signRequest(request: RequestLike): Promise; - - /** - * Describes credentials used to log in to the docker cli before pushing or pulling an image - */ - public abstract getDockerCliCredentials(): Promise; -} - -export interface IDockerCliCredentials { - /** - * Return either username/password or token credentials - * Return undefined if this registry doesn't require logging in to the docker cli - * NOTE: This may or may not be the same credentials used for registry requests - */ - auth?: { token: string } | { username: string; password: string }; - - /** - * The registry to log in to - * For central registries, this will usually be at an account level (aka empty for all of Docker Hub) - * For private registries, this will usually be at the registry level (aka the registry url) - */ - registryPath: string; -} diff --git a/src/tree/registries/RemoteRepositoryTreeItemBase.ts b/src/tree/registries/RemoteRepositoryTreeItemBase.ts deleted file mode 100644 index 1750bdf584..0000000000 --- a/src/tree/registries/RemoteRepositoryTreeItemBase.ts +++ /dev/null @@ -1,49 +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 { AzExtParentTreeItem, AzExtTreeItem } from "@microsoft/vscode-azext-utils"; -import { ThemeIcon } from "vscode"; -import { RequestLike } from "../../utils/httpRequest"; -import { IRepositoryAuthTreeItem } from "../../utils/registryRequestUtils"; -import { getRegistryContextValue, repositorySuffix } from "./registryContextValues"; -import { RegistryTreeItemBase } from "./RegistryTreeItemBase"; -import { RemoteTagTreeItem } from "./RemoteTagTreeItem"; - -/** - * Base class for all repositories - */ -export abstract class RemoteRepositoryTreeItemBase extends AzExtParentTreeItem implements IRepositoryAuthTreeItem { - public childTypeLabel: string = 'tag'; - public parent: RegistryTreeItemBase; - public repoName: string; - - public constructor(parent: RegistryTreeItemBase, repoName: string) { - super(parent); - this.repoName = repoName; - this.iconPath = new ThemeIcon('repo'); - } - - public get label(): string { - return this.repoName; - } - - public get contextValue(): string { - return getRegistryContextValue(this, repositorySuffix); - } - - /** - * Optional method to implement if repo-level requests should have different authentication than registry-level requests - * For example, if the registry supports OAuth you might get a token that has just repo-level permissions instead of registry-level permissions - */ - public signRequest?(request: RequestLike): Promise; - - public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number { - if (ti1 instanceof RemoteTagTreeItem && ti2 instanceof RemoteTagTreeItem) { - return ti2.time.valueOf() - ti1.time.valueOf(); - } else { - return super.compareChildrenImpl(ti1, ti2); - } - } -} diff --git a/src/tree/registries/RemoteTagTreeItem.ts b/src/tree/registries/RemoteTagTreeItem.ts deleted file mode 100644 index be6230349f..0000000000 --- a/src/tree/registries/RemoteTagTreeItem.ts +++ /dev/null @@ -1,52 +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 dayjs from 'dayjs'; -import * as relativeTime from 'dayjs/plugin/relativeTime'; -import { AzExtTreeItem } from '@microsoft/vscode-azext-utils'; -import { ThemeIcon } from 'vscode'; -import { getRegistryContextValue, tagSuffix } from './registryContextValues'; -import { RemoteRepositoryTreeItemBase } from './RemoteRepositoryTreeItemBase'; - -dayjs.extend(relativeTime); - -export class RemoteTagTreeItem extends AzExtTreeItem { - public parent: RemoteRepositoryTreeItemBase; - public tag: string; - public time: Date; - - public constructor(parent: RemoteRepositoryTreeItemBase, tag: string, time: string) { - super(parent); - this.tag = tag; - this.time = new Date(time); - } - - public get label(): string { - return this.tag; - } - - public get contextValue(): string { - return getRegistryContextValue(this, tagSuffix); - } - - /** - * The fullTag minus the registry part - */ - public get repoNameAndTag(): string { - return this.parent.repoName + ':' + this.tag; - } - - public get fullTag(): string { - return `${this.parent.parent.baseImagePath}/${this.repoNameAndTag}`; - } - - public get description(): string { - return dayjs(this.time).fromNow(); - } - - public get iconPath(): ThemeIcon { - return new ThemeIcon('bookmark'); - } -} diff --git a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts new file mode 100644 index 0000000000..9df806fd6d --- /dev/null +++ b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts @@ -0,0 +1,131 @@ +import { RegistryDataProvider } from '@microsoft/vscode-docker-registries'; +import * as vscode from 'vscode'; + +export interface UnifiedRegistryItem { + provider: RegistryDataProvider; + wrappedItem: T; + parent: UnifiedRegistryItem | undefined; +} + +const ConnectedRegistryProvidersKey = 'ConnectedRegistryProviders'; + +export class UnifiedRegistryTreeDataProvider implements vscode.TreeDataProvider> { + private readonly onDidChangeTreeDataEmitter = new vscode.EventEmitter | UnifiedRegistryItem[] | undefined>(); + public readonly onDidChangeTreeData = this.onDidChangeTreeDataEmitter.event; + + private readonly providers = new Map>(); + + public constructor(private readonly storageMemento: vscode.Memento) { + + } + + public getTreeItem(element: UnifiedRegistryItem): vscode.TreeItem | Thenable { + return element.provider.getTreeItem(element.wrappedItem); + } + + public async getChildren(element?: UnifiedRegistryItem | undefined): Promise[]> { + if (element) { + const elements = await element.provider.getChildren(element.wrappedItem); + + if (!elements) { + return []; + } + + return elements.map(e => { + return { + provider: element.provider, + wrappedItem: e, + parent: element + }; + }); + } else { + const unifiedRoots: UnifiedRegistryItem[] = []; + + const connectedProviderIds = this.storageMemento.get(ConnectedRegistryProvidersKey, []); + + for (const provider of this.providers.values()) { + if (!connectedProviderIds.includes(provider.id)) { + continue; + } + + const roots = await provider.getChildren(undefined); + if (!roots) { + continue; + } + + unifiedRoots.push(...roots.map(r => { + return { + provider, + wrappedItem: r, + parent: undefined + }; + })); + } + + return unifiedRoots; + } + } + + public getParent(element: UnifiedRegistryItem): UnifiedRegistryItem | undefined { + return element.parent; + } + + public registerProvider(provider: RegistryDataProvider): vscode.Disposable { + this.providers.set(provider.id, provider); + + return { + dispose: () => { + this.providers.delete(provider.id); + } + }; + } + + public async refresh(): Promise { + this.onDidChangeTreeDataEmitter.fire(undefined); + } + + public async connectRegistryProvider(provider: RegistryDataProvider | undefined = undefined): Promise { + const connectedProviderIds = this.storageMemento.get(ConnectedRegistryProvidersKey, []); + + if (!provider) { + const picks: (vscode.QuickPickItem & { provider: RegistryDataProvider })[] = []; + + for (const currentProvider of this.providers.values()) { + if (connectedProviderIds.includes(currentProvider.id)) { + continue; + } + + picks.push({ + label: currentProvider.label, + description: currentProvider.description, + provider: currentProvider + }); + } + + const picked = await vscode.window.showQuickPick(picks, { placeHolder: vscode.l10n.t('Select a registry provider to use') }); + if (!picked) { + return; + } + + provider = picked.provider; + } + + if (!connectedProviderIds.includes(provider.id)) { + await provider?.onConnect?.(); + const connectedProviderIdsSet: Set = new Set(connectedProviderIds); + connectedProviderIdsSet.add(provider.id); + await this.storageMemento.update(ConnectedRegistryProvidersKey, Array.from(connectedProviderIdsSet)); + } + + this.refresh(); + } + + public async disconnectRegistryProvider(item: UnifiedRegistryItem): Promise { + await item.provider?.onDisconnect?.(); + const newConnectedProviderIds = this.storageMemento + .get(ConnectedRegistryProvidersKey, []) + .filter(cpi => cpi !== item.provider.id); + await this.storageMemento.update(ConnectedRegistryProvidersKey, newConnectedProviderIds); + this.refresh(); + } +} diff --git a/src/tree/registries/all/RegistryApi.ts b/src/tree/registries/all/RegistryApi.ts deleted file mode 100644 index b52cb17815..0000000000 --- a/src/tree/registries/all/RegistryApi.ts +++ /dev/null @@ -1,22 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export enum RegistryApi { - /** - * https://docs.docker.com/registry/spec/api/ - */ - DockerV2 = 'DockerV2', - - /** - * https://docs.gitlab.com/ee/api/README.html - * https://docs.gitlab.com/ee/api/container_registry.html - */ - GitLabV4 = 'GitLabV4', - - /** - * No public docs found - */ - DockerHubV2 = 'DockerHubV2' -} diff --git a/src/tree/registries/all/getRegistryProviders.ts b/src/tree/registries/all/getRegistryProviders.ts deleted file mode 100644 index 5ad27a3133..0000000000 --- a/src/tree/registries/all/getRegistryProviders.ts +++ /dev/null @@ -1,19 +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 { azureRegistryProvider } from "../azure/azureRegistryProvider"; -import { dockerHubRegistryProvider } from "../dockerHub/dockerHubRegistryProvider"; -import { genericDockerV2RegistryProvider } from "../dockerV2/genericDockerV2RegistryProvider"; -import { gitLabRegistryProvider } from "../gitLab/gitLabRegistryProvider"; -import { IRegistryProvider } from "../IRegistryProvider"; - -export function getRegistryProviders(): IRegistryProvider[] { - return [ - azureRegistryProvider, - dockerHubRegistryProvider, - gitLabRegistryProvider, - genericDockerV2RegistryProvider - ]; -} diff --git a/src/tree/registries/auth/AzureOAuthProvider.ts b/src/tree/registries/auth/AzureOAuthProvider.ts deleted file mode 100644 index f65ead5c1b..0000000000 --- a/src/tree/registries/auth/AzureOAuthProvider.ts +++ /dev/null @@ -1,34 +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 { ISubscriptionContext } from '@microsoft/vscode-azext-utils'; -import { acquireAcrAccessToken, acquireAcrRefreshToken } from '../../../utils/azureUtils'; -import { IOAuthContext, RequestLike, bearerAuthHeader } from '../../../utils/httpRequest'; -import { ICachedRegistryProvider } from '../ICachedRegistryProvider'; -import { IDockerCliCredentials } from '../RegistryTreeItemBase'; -import { IAuthProvider } from './IAuthProvider'; - -export interface IAzureOAuthContext extends IOAuthContext { - subscriptionContext: ISubscriptionContext -} - -class AzureOAuthProvider implements IAuthProvider { - - public async signRequest(cachedProvider: ICachedRegistryProvider, request: RequestLike, authContext: IAzureOAuthContext): Promise { - request.headers.set('Authorization', bearerAuthHeader(await acquireAcrAccessToken(authContext.realm.host, authContext.subscriptionContext, authContext.scope))); - return request; - } - - public async getDockerCliCredentials(cachedProvider: ICachedRegistryProvider, authContext?: IAzureOAuthContext): Promise { - return { - registryPath: `https://${authContext.service}`, - auth: { - token: await acquireAcrRefreshToken(authContext.realm.host, authContext.subscriptionContext), - }, - }; - } -} - -export const azureOAuthProvider: IAuthProvider = new AzureOAuthProvider(); diff --git a/src/tree/registries/auth/BasicOAuthProvider.ts b/src/tree/registries/auth/BasicOAuthProvider.ts deleted file mode 100644 index 73aaf5280b..0000000000 --- a/src/tree/registries/auth/BasicOAuthProvider.ts +++ /dev/null @@ -1,65 +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 { HttpResponse, IOAuthContext, RequestLike, RequestOptionsLike, basicAuthHeader, bearerAuthHeader, httpRequest } from '../../../utils/httpRequest'; -import { ICachedRegistryProvider } from '../ICachedRegistryProvider'; -import { getRegistryPassword } from '../registryPasswords'; -import { IDockerCliCredentials } from '../RegistryTreeItemBase'; -import { IAuthProvider } from './IAuthProvider'; - -/** - * Performs basic auth and password-grant-type OAuth - */ -class BasicOAuthProvider implements IAuthProvider { - - public async signRequest(cachedProvider: ICachedRegistryProvider, request: RequestLike, authContext?: IOAuthContext): Promise { - if (!authContext) { - request.headers.set('Authorization', basicAuthHeader(cachedProvider.username, await getRegistryPassword(cachedProvider))); - return request; - } - - const options: RequestOptionsLike = { - form: { - // eslint-disable-next-line @typescript-eslint/naming-convention - 'grant_type': 'password', - 'service': authContext.service, - 'scope': authContext.scope - }, - headers: { - Authorization: basicAuthHeader(cachedProvider.username, await getRegistryPassword(cachedProvider)), - }, - }; - - let tokenResponse: HttpResponse<{ token: string }>; - try { - // First try with POST - tokenResponse = await httpRequest(authContext.realm.toString(), { method: 'POST', ...options }); - } catch { - // If that fails, try falling back to GET - // (If that fails we'll just throw) - tokenResponse = await httpRequest(authContext.realm.toString(), { method: 'GET', ...options }); - } - - request.headers.set('Authorization', bearerAuthHeader((await tokenResponse.json()).token)); - return request; - } - - public async getDockerCliCredentials(cachedProvider: ICachedRegistryProvider, authContext?: IOAuthContext): Promise { - const creds: IDockerCliCredentials = { - registryPath: cachedProvider.url - }; - - if (cachedProvider.username) { - creds.auth = { - username: cachedProvider.username, - password: await getRegistryPassword(cachedProvider), - }; - } - - return creds; - } -} - -export const basicOAuthProvider: IAuthProvider = new BasicOAuthProvider(); diff --git a/src/tree/registries/auth/IAuthProvider.ts b/src/tree/registries/auth/IAuthProvider.ts deleted file mode 100644 index 6fa984c305..0000000000 --- a/src/tree/registries/auth/IAuthProvider.ts +++ /dev/null @@ -1,13 +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 { IOAuthContext, RequestLike } from '../../../utils/httpRequest'; -import { ICachedRegistryProvider } from '../ICachedRegistryProvider'; -import { IDockerCliCredentials } from '../RegistryTreeItemBase'; - -export interface IAuthProvider { - signRequest(cachedProvider: ICachedRegistryProvider, request: RequestLike, authContext?: IOAuthContext): Promise; - getDockerCliCredentials(cachedProvider: ICachedRegistryProvider, authContext?: IOAuthContext): Promise; -} diff --git a/src/tree/registries/azure/AzureAccountTreeItem.ts b/src/tree/registries/azure/AzureAccountTreeItem.ts deleted file mode 100644 index 939b0f4d0d..0000000000 --- a/src/tree/registries/azure/AzureAccountTreeItem.ts +++ /dev/null @@ -1,40 +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 { AzureAccountTreeItemBase } from "@microsoft/vscode-azext-azureutils"; // This can't be made lazy, so users of this class must be lazy -import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, ISubscriptionContext } from "@microsoft/vscode-azext-utils"; -import { Disposable } from "vscode"; -import { AzureAccountExtensionListener } from "../../../utils/AzureAccountExtensionListener"; -import { getAzSubTreeItem } from "../../../utils/lazyPackages"; -import { ICachedRegistryProvider } from "../ICachedRegistryProvider"; -import { IRegistryProviderTreeItem } from "../IRegistryProviderTreeItem"; -import { getRegistryContextValue, registryProviderSuffix } from "../registryContextValues"; -import type { SubscriptionTreeItem } from "./SubscriptionTreeItem"; - -export class AzureAccountTreeItem extends AzureAccountTreeItemBase implements IRegistryProviderTreeItem { - public constructor(parent: AzExtParentTreeItem, public readonly cachedProvider: ICachedRegistryProvider) { - super(parent); - this.contextValue = getRegistryContextValue(this, registryProviderSuffix); - } - - public async createSubscriptionTreeItem(subContext: ISubscriptionContext): Promise { - const azSubTreeItem = await getAzSubTreeItem(); - return new azSubTreeItem.SubscriptionTreeItem(this, subContext, this.cachedProvider); - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - const treeItems: AzExtTreeItem[] = await super.loadMoreChildrenImpl(clearCache, context); - if (treeItems.length === 1 && treeItems[0].commandId === 'extension.open') { - const extensionInstallEventDisposable: Disposable = AzureAccountExtensionListener.onExtensionInstalled(() => { - extensionInstallEventDisposable.dispose(); - - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.refresh(context); - }); - } - - return treeItems; - } -} diff --git a/src/tree/registries/azure/AzureRegistryTreeItem.ts b/src/tree/registries/azure/AzureRegistryTreeItem.ts deleted file mode 100644 index d35fe97458..0000000000 --- a/src/tree/registries/azure/AzureRegistryTreeItem.ts +++ /dev/null @@ -1,115 +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 type { ContainerRegistryManagementClient, Registry, RegistryListCredentialsResult } from "@azure/arm-containerregistry"; // These are only dev-time imports so don't need to be lazy -import { AzExtTreeItem, IActionContext, nonNullProp } from "@microsoft/vscode-azext-utils"; -import { URL } from "url"; -import { getResourceGroupFromId } from "../../../utils/azureUtils"; -import { getArmContainerRegistry, getAzExtAzureUtils } from "../../../utils/lazyPackages"; -import { getIconPath } from "../../getThemedIconPath"; -import { IAzureOAuthContext, azureOAuthProvider } from "../auth/AzureOAuthProvider"; -import { DockerV2RegistryTreeItemBase } from "../dockerV2/DockerV2RegistryTreeItemBase"; -import { ICachedRegistryProvider } from "../ICachedRegistryProvider"; -import { AzureRepositoryTreeItem } from "./AzureRepositoryTreeItem"; -import { AzureTasksTreeItem } from "./AzureTasksTreeItem"; -import type { SubscriptionTreeItem } from "./SubscriptionTreeItem"; // These are only dev-time imports so don't need to be lazy - -export class AzureRegistryTreeItem extends DockerV2RegistryTreeItemBase { - public parent: SubscriptionTreeItem; - - protected authContext?: IAzureOAuthContext; - - private _tasksTreeItem: AzureTasksTreeItem; - - public constructor(parent: SubscriptionTreeItem, cachedProvider: ICachedRegistryProvider, private readonly registry: Registry) { - super(parent, cachedProvider, azureOAuthProvider); - this._tasksTreeItem = new AzureTasksTreeItem(this); - this.authContext = { - realm: new URL(`${this.baseUrl}/oauth2/token`), - service: this.host, - subscriptionContext: this.parent.subscription, - scope: 'registry:catalog:*', - }; - - this.id = this.registryId; - this.iconPath = getIconPath('azureRegistry'); - } - - public get registryName(): string { - return nonNullProp(this.registry, 'name'); - } - - public get registryId(): string { - return nonNullProp(this.registry, 'id'); - } - - public get resourceGroup(): string { - return getResourceGroupFromId(this.registryId); - } - - public get registryLocation(): string { - return this.registry.location; - } - - public async getClient(context: IActionContext): Promise { - const azExtAzureUtils = await getAzExtAzureUtils(); - const armContainerRegistry = await getArmContainerRegistry(); - return azExtAzureUtils.createAzureClient({ ...context, ...this.subscription }, armContainerRegistry.ContainerRegistryManagementClient); - } - - public get label(): string { - return this.registryName; - } - - public get properties(): unknown { - return this.registry; - } - - public get baseUrl(): string { - return `https://${nonNullProp(this.registry, 'loginServer')}`; - } - - public createRepositoryTreeItem(name: string): AzureRepositoryTreeItem { - return new AzureRepositoryTreeItem(this, name, this.cachedProvider, this.authHelper, this.authContext); - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - const children: AzExtTreeItem[] = await super.loadMoreChildrenImpl(clearCache, context); - if (clearCache) { - children.push(this._tasksTreeItem); - } - return children; - } - - public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number { - if (ti1 instanceof AzureTasksTreeItem) { - return -1; - } else if (ti2 instanceof AzureTasksTreeItem) { - return 1; - } else { - return super.compareChildrenImpl(ti1, ti2); - } - } - - public async pickTreeItemImpl(expectedContextValues: (string | RegExp)[]): Promise { - if (expectedContextValues.some(v => this._tasksTreeItem.isAncestorOfImpl(v))) { - return this._tasksTreeItem; - } else { - return undefined; - } - } - - public async deleteTreeItemImpl(context: IActionContext): Promise { - await (await this.getClient(context)).registries.beginDeleteAndWait(this.resourceGroup, this.registryName); - } - - public async tryGetAdminCredentials(context: IActionContext): Promise { - if (this.registry.adminUserEnabled) { - return await (await this.getClient(context)).registries.listCredentials(this.resourceGroup, this.registryName); - } else { - return undefined; - } - } -} diff --git a/src/tree/registries/azure/AzureRepositoryTreeItem.ts b/src/tree/registries/azure/AzureRepositoryTreeItem.ts deleted file mode 100644 index 03893ffb78..0000000000 --- a/src/tree/registries/azure/AzureRepositoryTreeItem.ts +++ /dev/null @@ -1,19 +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 { registryRequest } from "../../../utils/registryRequestUtils"; -import { IAzureOAuthContext } from "../auth/AzureOAuthProvider"; -import { DockerV2RepositoryTreeItem } from "../dockerV2/DockerV2RepositoryTreeItem"; -import { AzureRegistryTreeItem } from "./AzureRegistryTreeItem"; - -export class AzureRepositoryTreeItem extends DockerV2RepositoryTreeItem { - public parent: AzureRegistryTreeItem; - - protected authContext?: IAzureOAuthContext; - - public async deleteTreeItemImpl(): Promise { - await registryRequest(this, 'DELETE', `v2/_acr/${this.repoName}/repository`); - } -} diff --git a/src/tree/registries/azure/AzureTaskRunTreeItem.ts b/src/tree/registries/azure/AzureTaskRunTreeItem.ts deleted file mode 100644 index c3d082d0eb..0000000000 --- a/src/tree/registries/azure/AzureTaskRunTreeItem.ts +++ /dev/null @@ -1,80 +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 dayjs from 'dayjs'; -import * as relativeTime from 'dayjs/plugin/relativeTime'; -import type { Run as AcrRun, ImageDescriptor } from "@azure/arm-containerregistry"; // These are only dev-time imports so don't need to be lazy -import { AzExtTreeItem, nonNullProp } from "@microsoft/vscode-azext-utils"; -import { ThemeColor, ThemeIcon } from "vscode"; -import { AzureTaskTreeItem } from "./AzureTaskTreeItem"; - -dayjs.extend(relativeTime); - -export class AzureTaskRunTreeItem extends AzExtTreeItem { - public static contextValue: string = 'azureTaskRun'; - public contextValue: string = AzureTaskRunTreeItem.contextValue; - public parent: AzureTaskTreeItem; - - private _run: AcrRun; - - public constructor(parent: AzureTaskTreeItem, run: AcrRun) { - super(parent); - this._run = run; - } - - public get runName(): string { - return nonNullProp(this._run, 'name'); - } - - public get runId(): string { - return nonNullProp(this._run, 'runId'); - } - - public get label(): string { - return this.runName; - } - - public get id(): string { - return this.runId; - } - - public get createTime(): Date | undefined { - return this._run.createTime; - } - - public get outputImage(): ImageDescriptor | undefined { - return this._run.outputImages && this._run.outputImages[0]; - } - - public get iconPath(): ThemeIcon { - switch (this._run.status) { - case 'Succeeded': - return new ThemeIcon('check', new ThemeColor('debugIcon.startForeground')); - case 'Failed': - return new ThemeIcon('error', new ThemeColor('problemsErrorIcon.foreground')); - case 'Running': - return new ThemeIcon('debug-start', new ThemeColor('debugIcon.startForeground')); - default: - return new ThemeIcon('warning', new ThemeColor('problemsWarningIcon.foreground')); - } - } - - public get properties(): unknown { - return this._run; - } - - public get description(): string { - const parts: string[] = []; - if (this.createTime) { - parts.push(dayjs(this.createTime).fromNow()); - } - - if (this._run.status && this._run.status !== 'Succeeded') { - parts.push(this._run.status); - } - - return parts.join(' - '); - } -} diff --git a/src/tree/registries/azure/AzureTaskTreeItem.ts b/src/tree/registries/azure/AzureTaskTreeItem.ts deleted file mode 100644 index 6dfa4aa369..0000000000 --- a/src/tree/registries/azure/AzureTaskTreeItem.ts +++ /dev/null @@ -1,90 +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 type { Task as AcrTask, TaskRun as AcrTaskRun } from "@azure/arm-containerregistry"; // These are only dev-time imports so don't need to be lazy -import { AzExtParentTreeItem, AzExtTreeItem, GenericTreeItem, IActionContext, nonNullValue, nonNullValueAndProp } from "@microsoft/vscode-azext-utils"; -import { l10n, ThemeIcon } from "vscode"; -import { getAzExtAzureUtils } from "../../../utils/lazyPackages"; -import { AzureRegistryTreeItem } from "./AzureRegistryTreeItem"; -import { AzureTaskRunTreeItem } from "./AzureTaskRunTreeItem"; -import { AzureTasksTreeItem } from "./AzureTasksTreeItem"; - -export class AzureTaskTreeItem extends AzExtParentTreeItem { - public static contextValue: string = 'azureTask'; - private static _noTaskFilter: string = 'TaskName eq null'; - public childTypeLabel: string = 'task run'; - public parent: AzureTasksTreeItem; - - private _task: AcrTask | undefined; - - public constructor(parent: AzureTasksTreeItem, task: AcrTask | undefined) { - super(parent); - this._task = task; - this.iconPath = new ThemeIcon('tasklist'); - this.id = this._task ? this._task.id : undefined; - } - - public get contextValue(): string { - return this._task ? AzureTaskTreeItem.contextValue : 'azureRunsWithoutTask'; - } - - public get label(): string { - return this._task ? this.taskName : l10n.t('Runs without a task'); - } - - public get taskName(): string { - return nonNullValueAndProp(this._task, 'name'); - } - - public hasMoreChildrenImpl(): boolean { - return false; - } - - public get properties(): unknown { - return nonNullValue(this._task, '_task'); - } - - public static async hasRunsWithoutTask(context: IActionContext, registryTI: AzureRegistryTreeItem): Promise { - const runListResult = await AzureTaskTreeItem.getTaskRuns(context, registryTI, AzureTaskTreeItem._noTaskFilter); - return runListResult.length > 0; - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - const filter = this._task ? `TaskName eq '${this.taskName}'` : AzureTaskTreeItem._noTaskFilter; - const runListResult = await AzureTaskTreeItem.getTaskRuns(context, this.parent.parent, filter); - - if (clearCache && runListResult.length === 0 && this._task) { - const ti = new GenericTreeItem(this, { - label: l10n.t('Run Task...'), - commandId: 'vscode-docker.registries.azure.runTask', - contextValue: 'runTask' - }); - ti.commandArgs = [this]; - return [ti]; - } else { - return await this.createTreeItemsWithErrorHandling( - runListResult, - 'invalidAzureTaskRun', - async r => new AzureTaskRunTreeItem(this, r), - r => r.name - ); - } - } - - public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number { - if (ti1 instanceof AzureTaskRunTreeItem && ti2 instanceof AzureTaskRunTreeItem && ti1.createTime && ti2.createTime) { - return ti2.createTime.valueOf() - ti1.createTime.valueOf(); - } else { - return super.compareChildrenImpl(ti1, ti2); - } - } - - private static async getTaskRuns(context: IActionContext, registryTI: AzureRegistryTreeItem, filter: string): Promise { - const azExtAzureUtils = await getAzExtAzureUtils(); - const registryClient = await registryTI.getClient(context); - - return await azExtAzureUtils.uiUtils.listAllIterator(registryClient.runs.list(registryTI.resourceGroup, registryTI.registryName, { filter })); - } -} diff --git a/src/tree/registries/azure/AzureTasksTreeItem.ts b/src/tree/registries/azure/AzureTasksTreeItem.ts deleted file mode 100644 index 82f3417de6..0000000000 --- a/src/tree/registries/azure/AzureTasksTreeItem.ts +++ /dev/null @@ -1,72 +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 type { Task } from "@azure/arm-containerregistry"; // These are only dev-time imports so don't need to be lazy -import { AzExtParentTreeItem, AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils"; -import { l10n, ThemeIcon } from "vscode"; -import { getAzExtAzureUtils } from "../../../utils/lazyPackages"; -import { OpenUrlTreeItem } from "../../OpenUrlTreeItem"; -import { AzureRegistryTreeItem } from "./AzureRegistryTreeItem"; -import { AzureTaskTreeItem } from "./AzureTaskTreeItem"; - -export class AzureTasksTreeItem extends AzExtParentTreeItem { - public static contextValue: string = 'azureTasks'; - public contextValue: string = AzureTasksTreeItem.contextValue; - public label: string = 'Tasks'; - public childTypeLabel: string = 'task'; - public parent: AzureRegistryTreeItem; - - private _nextLink: string | undefined; - - public constructor(parent: AzureRegistryTreeItem) { - super(parent); - this.iconPath = new ThemeIcon('checklist'); - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - if (clearCache) { - this._nextLink = undefined; - } - - const registryTI = this.parent; - - const azExtAzureUtils = await getAzExtAzureUtils(); - const registryClient = await registryTI.getClient(context); - - const taskListResult: Task[] = await azExtAzureUtils.uiUtils.listAllIterator(registryClient.tasks.list(registryTI.resourceGroup, registryTI.registryName)); - - if (clearCache && taskListResult.length === 0) { - return [new OpenUrlTreeItem(this, l10n.t('Learn how to create a build task...'), 'https://aka.ms/acr/task')]; - } else { - const result: AzExtTreeItem[] = await this.createTreeItemsWithErrorHandling( - taskListResult, - 'invalidAzureTask', - async t => new AzureTaskTreeItem(this, t), - t => t.name - ); - - if (clearCache) { - // If there are any runs _not_ associated with a task (e.g. the user ran a task from a local Dockerfile) add a tree item to display those runs - if (await AzureTaskTreeItem.hasRunsWithoutTask(context, this.parent)) { - result.push(new AzureTaskTreeItem(this, undefined)); - } - } - - return result; - } - } - - public hasMoreChildrenImpl(): boolean { - return !!this._nextLink; - } - - public isAncestorOfImpl(expectedContextValue: string | RegExp): boolean { - if (expectedContextValue instanceof RegExp) { - expectedContextValue = expectedContextValue.source.toString(); - } - - return expectedContextValue.toLowerCase().includes('task'); - } -} diff --git a/src/tree/registries/azure/SubscriptionTreeItem.ts b/src/tree/registries/azure/SubscriptionTreeItem.ts deleted file mode 100644 index 77769f7ba6..0000000000 --- a/src/tree/registries/azure/SubscriptionTreeItem.ts +++ /dev/null @@ -1,75 +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 type { ContainerRegistryManagementClient, Registry as AcrRegistry } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy -import { SubscriptionTreeItemBase } from '@microsoft/vscode-azext-azureutils'; // This can't be made lazy, so users of this class must be lazy -import { AzExtParentTreeItem, AzExtTreeItem, AzureWizard, IActionContext, ICreateChildImplContext, ISubscriptionContext, nonNullProp } from "@microsoft/vscode-azext-utils"; -import { l10n, window } from 'vscode'; -import { getArmContainerRegistry, getAzExtAzureUtils } from '../../../utils/lazyPackages'; -import { ICachedRegistryProvider } from "../ICachedRegistryProvider"; -import { IRegistryProviderTreeItem } from "../IRegistryProviderTreeItem"; -import type { AzureAccountTreeItem } from './AzureAccountTreeItem'; // These are only dev-time imports so don't need to be lazy -import { AzureRegistryTreeItem } from './AzureRegistryTreeItem'; -import { AzureRegistryCreateStep } from './createWizard/AzureRegistryCreateStep'; -import { AzureRegistryNameStep } from './createWizard/AzureRegistryNameStep'; -import { AzureRegistrySkuStep } from './createWizard/AzureRegistrySkuStep'; -import { IAzureRegistryWizardContext } from './createWizard/IAzureRegistryWizardContext'; - -export class SubscriptionTreeItem extends SubscriptionTreeItemBase implements IRegistryProviderTreeItem { - public childTypeLabel: string = 'registry'; - public parent: AzureAccountTreeItem; - - public constructor(parent: AzExtParentTreeItem, root: ISubscriptionContext, public readonly cachedProvider: ICachedRegistryProvider) { - super(parent, root); - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - const armContainerRegistry = await getArmContainerRegistry(); - const azExtAzureUtils = await getAzExtAzureUtils(); - const client: ContainerRegistryManagementClient = azExtAzureUtils.createAzureClient({ ...context, ...this.subscription }, armContainerRegistry.ContainerRegistryManagementClient); - const registryListResult: AcrRegistry[] = await azExtAzureUtils.uiUtils.listAllIterator(client.registries.list()); - - return await this.createTreeItemsWithErrorHandling( - registryListResult, - 'invalidAzureRegistry', - async r => new AzureRegistryTreeItem(this, this.cachedProvider, r), - r => r.name - ); - } - - public hasMoreChildrenImpl(): boolean { - return false; - } - - public async createChildImpl(context: ICreateChildImplContext): Promise { - const wizardContext: IAzureRegistryWizardContext = { ...context, ...this.subscription }; - const azExtAzureUtils = await getAzExtAzureUtils(); - - const promptSteps = [ - new AzureRegistryNameStep(), - new AzureRegistrySkuStep(), - new azExtAzureUtils.ResourceGroupListStep(), - ]; - azExtAzureUtils.LocationListStep.addStep(wizardContext, promptSteps); - - const wizard = new AzureWizard(wizardContext, { - promptSteps, - executeSteps: [ - new AzureRegistryCreateStep() - ], - title: l10n.t('Create new Azure Container Registry') - }); - - await wizard.prompt(); - const newRegistryName: string = nonNullProp(wizardContext, 'newRegistryName'); - context.showCreatingTreeItem(newRegistryName); - await wizard.execute(); - - // don't wait - /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - window.showInformationMessage(`Successfully created registry "${newRegistryName}".`); - return new AzureRegistryTreeItem(this, this.cachedProvider, nonNullProp(wizardContext, 'registry')); - } -} diff --git a/src/tree/registries/azure/azureRegistryProvider.ts b/src/tree/registries/azure/azureRegistryProvider.ts deleted file mode 100644 index 3724f25503..0000000000 --- a/src/tree/registries/azure/azureRegistryProvider.ts +++ /dev/null @@ -1,27 +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 { AzExtParentTreeItem } from "@microsoft/vscode-azext-utils"; -import { getAzActTreeItem } from "../../../utils/lazyPackages"; -import { RegistryApi } from "../all/RegistryApi"; -import { ICachedRegistryProvider } from "../ICachedRegistryProvider"; -import { IRegistryProvider } from "../IRegistryProvider"; -import type { AzureAccountTreeItem } from "./AzureAccountTreeItem"; // These are only dev-time imports so don't need to be lazy - -export const azureRegistryProviderId: string = 'azure'; - -export const azureRegistryProvider: IRegistryProvider = { - label: "Azure", - id: azureRegistryProviderId, - api: RegistryApi.DockerV2, - onlyOneAllowed: true, - connectWizardOptions: undefined, - treeItemFactory: async (parent: AzExtParentTreeItem, cachedProvider: ICachedRegistryProvider): Promise => { - const azActTreeItem = await getAzActTreeItem(); - return new azActTreeItem.AzureAccountTreeItem(parent, cachedProvider); - }, - persistAuth: undefined, - removeAuth: undefined, -}; diff --git a/src/tree/registries/azure/createWizard/AzureRegistryCreateStep.ts b/src/tree/registries/azure/createWizard/AzureRegistryCreateStep.ts deleted file mode 100644 index 077bb1aa84..0000000000 --- a/src/tree/registries/azure/createWizard/AzureRegistryCreateStep.ts +++ /dev/null @@ -1,57 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import type { AzExtLocation } from '@microsoft/vscode-azext-azureutils'; -import { AzureWizardExecuteStep, nonNullProp, parseError } from '@microsoft/vscode-azext-utils'; -import { l10n, Progress } from 'vscode'; -import { ext } from '../../../../extensionVariables'; -import { getArmContainerRegistry, getAzExtAzureUtils } from '../../../../utils/lazyPackages'; -import { IAzureRegistryWizardContext } from './IAzureRegistryWizardContext'; - -export class AzureRegistryCreateStep extends AzureWizardExecuteStep { - public priority: number = 130; - - public async execute(context: IAzureRegistryWizardContext, progress: Progress<{ message?: string; increment?: number }>): Promise { - const newRegistryName = nonNullProp(context, 'newRegistryName'); - - const azExtAzureUtils = await getAzExtAzureUtils(); - const armContainerRegistry = await getArmContainerRegistry(); - const client = azExtAzureUtils.createAzureClient(context, armContainerRegistry.ContainerRegistryManagementClient); - const creating: string = l10n.t('Creating registry "{0}"...', newRegistryName); - ext.outputChannel.info(creating); - progress.report({ message: creating }); - - const location: AzExtLocation = await azExtAzureUtils.LocationListStep.getLocation(context); - const locationName: string = nonNullProp(location, 'name'); - const resourceGroup = nonNullProp(context, 'resourceGroup'); - try { - context.registry = await client.registries.beginCreateAndWait( - nonNullProp(resourceGroup, 'name'), - newRegistryName, - { - sku: { - name: nonNullProp(context, 'newRegistrySku') - }, - location: locationName - } - ); - } - catch (err) { - const parsedError = parseError(err); - if (parsedError.errorType === 'MissingSubscriptionRegistration') { - context.errorHandling.suppressReportIssue = true; - } - - throw err; - } - - const created = l10n.t('Successfully created registry "{0}".', newRegistryName); - ext.outputChannel.info(created); - } - - public shouldExecute(context: IAzureRegistryWizardContext): boolean { - return !context.registry; - } -} diff --git a/src/tree/registries/azure/createWizard/AzureRegistryNameStep.ts b/src/tree/registries/azure/createWizard/AzureRegistryNameStep.ts deleted file mode 100644 index 14843f6b4b..0000000000 --- a/src/tree/registries/azure/createWizard/AzureRegistryNameStep.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import type { ContainerRegistryManagementClient } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy -import { AzureNameStep } from '@microsoft/vscode-azext-utils'; -import { l10n } from 'vscode'; -import { getArmContainerRegistry, getAzExtAzureUtils } from '../../../../utils/lazyPackages'; -import { IAzureRegistryWizardContext } from './IAzureRegistryWizardContext'; - -export class AzureRegistryNameStep extends AzureNameStep { - protected async isRelatedNameAvailable(context: IAzureRegistryWizardContext, name: string): Promise { - const azExtAzureUtils = await getAzExtAzureUtils(); - return await azExtAzureUtils.ResourceGroupListStep.isNameAvailable(context, name); - } - - public async prompt(context: IAzureRegistryWizardContext): Promise { - const azExtAzureUtils = await getAzExtAzureUtils(); - const armContainerRegistry = await getArmContainerRegistry(); - const client = azExtAzureUtils.createAzureClient(context, armContainerRegistry.ContainerRegistryManagementClient); - context.newRegistryName = (await context.ui.showInputBox({ - placeHolder: l10n.t('Registry name'), - prompt: l10n.t('Provide a registry name'), - /* eslint-disable-next-line @typescript-eslint/promise-function-async */ - validateInput: (name: string) => validateRegistryName(name, client) - })).trim(); - - context.relatedNameTask = this.generateRelatedName(context, context.newRegistryName, azExtAzureUtils.resourceGroupNamingRules); - } - - public shouldPrompt(context: IAzureRegistryWizardContext): boolean { - return !context.newRegistryName; - } -} - -async function validateRegistryName(name: string, client: ContainerRegistryManagementClient): Promise { - name = name ? name.trim() : ''; - - const min = 5; - const max = 50; - if (name.length < min || name.length > max) { - return l10n.t('The name must be between {0} and {1} characters.', min, max); - } else if (name.match(/[^a-z0-9]/i)) { - return l10n.t('The name can only contain alphanumeric characters.'); - } else { - const nameStatus = await client.registries.checkNameAvailability({ name, type: 'Microsoft.ContainerRegistry/registries' }); - return nameStatus.message; - } -} diff --git a/src/tree/registries/azure/createWizard/AzureRegistrySkuStep.ts b/src/tree/registries/azure/createWizard/AzureRegistrySkuStep.ts deleted file mode 100644 index bbfc3684c1..0000000000 --- a/src/tree/registries/azure/createWizard/AzureRegistrySkuStep.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import type { SkuName as AcrSkuName } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy -import { AzureWizardPromptStep, IAzureQuickPickItem } from '@microsoft/vscode-azext-utils'; -import { l10n } from 'vscode'; -import { IAzureRegistryWizardContext } from './IAzureRegistryWizardContext'; - -export class AzureRegistrySkuStep extends AzureWizardPromptStep { - public async prompt(context: IAzureRegistryWizardContext): Promise { - const skus: AcrSkuName[] = ["Basic", "Standard", "Premium"]; - const picks: IAzureQuickPickItem[] = skus.map(s => { return { label: s, data: s }; }); - - const placeHolder: string = l10n.t('Select a SKU'); - context.newRegistrySku = (await context.ui.showQuickPick(picks, { placeHolder })).data; - } - - public shouldPrompt(context: IAzureRegistryWizardContext): boolean { - return !context.newRegistrySku; - } -} diff --git a/src/tree/registries/azure/createWizard/IAzureRegistryWizardContext.ts b/src/tree/registries/azure/createWizard/IAzureRegistryWizardContext.ts deleted file mode 100644 index 7d0109f2b0..0000000000 --- a/src/tree/registries/azure/createWizard/IAzureRegistryWizardContext.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import type { Registry as AcrRegistry, SkuName as AcrSkuName } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy -import type { IResourceGroupWizardContext } from '@microsoft/vscode-azext-azureutils'; // These are only dev-time imports so don't need to be lazy - -export interface IAzureRegistryWizardContext extends IResourceGroupWizardContext { - newRegistryName?: string; - newRegistrySku?: AcrSkuName; - registry?: AcrRegistry; -} diff --git a/src/tree/registries/connectWizard/IConnectRegistryWizardContext.ts b/src/tree/registries/connectWizard/IConnectRegistryWizardContext.ts deleted file mode 100644 index 360441afd8..0000000000 --- a/src/tree/registries/connectWizard/IConnectRegistryWizardContext.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IActionContext } from '@microsoft/vscode-azext-utils'; -import { ICachedRegistryProvider } from "../ICachedRegistryProvider"; -import { IConnectRegistryWizardOptions } from './IConnectRegistryWizardOptions'; - -export interface IConnectRegistryWizardContext extends IActionContext, IConnectRegistryWizardOptions { - existingProviders: ICachedRegistryProvider[]; - - username?: string; - secret?: string; - url?: string; -} diff --git a/src/tree/registries/connectWizard/IConnectRegistryWizardOptions.ts b/src/tree/registries/connectWizard/IConnectRegistryWizardOptions.ts deleted file mode 100644 index 2ca525ac0c..0000000000 --- a/src/tree/registries/connectWizard/IConnectRegistryWizardOptions.ts +++ /dev/null @@ -1,51 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export interface IConnectRegistryWizardOptions { - /** - * The title for the wizard (e.g. "Sign In To Docker Hub") - */ - wizardTitle: string; - - /** - * Set to true to prompt for a url - */ - includeUrl?: boolean; - - /** - * Optional value to overwrite the default text displayed underneath the url input box - */ - urlPrompt?: string; - - /** - * Set to true to prompt for a username - */ - includeUsername?: boolean; - - /** - * Optional value to overwrite the default prompt text displayed underneath the username input box - */ - usernamePrompt?: string; - - /** - * Optional value to overwrite the default "ghost" text displayed within the username input box - */ - usernamePlaceholder?: string; - - /** - * Set to true if the username is optional - */ - isUsernameOptional?: boolean; - - /** - * Set to true to prompt for a password - */ - includePassword?: boolean; - - /** - * Optional value to overwrite the default text displayed underneath the password input box - */ - passwordPrompt?: string; -} diff --git a/src/tree/registries/connectWizard/RegistryPasswordStep.ts b/src/tree/registries/connectWizard/RegistryPasswordStep.ts deleted file mode 100644 index a70e6e76ce..0000000000 --- a/src/tree/registries/connectWizard/RegistryPasswordStep.ts +++ /dev/null @@ -1,27 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { AzureWizardPromptStep } from '@microsoft/vscode-azext-utils'; -import { l10n } from 'vscode'; -import { IConnectRegistryWizardContext } from './IConnectRegistryWizardContext'; - -export class RegistryPasswordStep extends AzureWizardPromptStep { - public async prompt(context: IConnectRegistryWizardContext): Promise { - const prompt: string = context.passwordPrompt || l10n.t('Enter your password'); - context.secret = await context.ui.showInputBox({ prompt, validateInput, password: true }); - } - - public shouldPrompt(context: IConnectRegistryWizardContext): boolean { - return !!context.includePassword && !context.secret; - } -} - -function validateInput(value: string | undefined): string | undefined { - if (!value) { - return l10n.t('Password cannot be empty.'); - } else { - return undefined; - } -} diff --git a/src/tree/registries/connectWizard/RegistryUrlStep.ts b/src/tree/registries/connectWizard/RegistryUrlStep.ts deleted file mode 100644 index b576bf70e3..0000000000 --- a/src/tree/registries/connectWizard/RegistryUrlStep.ts +++ /dev/null @@ -1,49 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { AzureWizardPromptStep } from '@microsoft/vscode-azext-utils'; -import { URL } from 'url'; -import { l10n } from 'vscode'; -import { IConnectRegistryWizardContext } from './IConnectRegistryWizardContext'; - -export class RegistryUrlStep extends AzureWizardPromptStep { - public async prompt(context: IConnectRegistryWizardContext): Promise { - const prompt: string = context.urlPrompt || l10n.t('Enter the URL for the registry provider'); - const placeHolder: string = l10n.t('Example: http://localhost:5000'); - context.url = (await context.ui.showInputBox({ - prompt, - placeHolder, - validateInput: v => this.validateUrl(context, v) - })); - } - - public shouldPrompt(context: IConnectRegistryWizardContext): boolean { - return !!context.includeUrl && !context.url; - } - - private validateUrl(context: IConnectRegistryWizardContext, value: string): string | undefined { - if (!value) { - return l10n.t('URL cannot be empty.'); - } else { - let protocol: string | undefined; - let host: string | undefined; - try { - const uri = new URL(value); - protocol = uri.protocol; - host = uri.host; - } catch { - // ignore - } - - if (!protocol || !host) { - return l10n.t('Please enter a valid URL'); - } else if (context.existingProviders.find(rp => rp.url === value)) { - return l10n.t('URL "{0}" is already connected.', value); - } else { - return undefined; - } - } - } -} diff --git a/src/tree/registries/connectWizard/RegistryUsernameStep.ts b/src/tree/registries/connectWizard/RegistryUsernameStep.ts deleted file mode 100644 index 846db40e0d..0000000000 --- a/src/tree/registries/connectWizard/RegistryUsernameStep.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { AzureWizardPromptStep } from '@microsoft/vscode-azext-utils'; -import { InputBoxOptions, l10n } from 'vscode'; -import { IConnectRegistryWizardContext } from './IConnectRegistryWizardContext'; - -export class RegistryUsernameStep extends AzureWizardPromptStep { - public async prompt(context: IConnectRegistryWizardContext): Promise { - const prompt: string = context.usernamePrompt || (context.isUsernameOptional ? l10n.t('Enter your username, or press \'Enter\' for none') : l10n.t('Enter your username')); - const options: InputBoxOptions = { - prompt, - placeHolder: context.usernamePlaceholder, - validateInput: (value: string | undefined) => this.validateInput(context, value) - }; - - context.username = await context.ui.showInputBox(options); - - if (!context.username) { - context.includePassword = false; - } - } - - public shouldPrompt(context: IConnectRegistryWizardContext): boolean { - return !!context.includeUsername && !context.username; - } - - private validateInput(context: IConnectRegistryWizardContext, value: string | undefined): string | undefined { - if (!context.isUsernameOptional && !value) { - return l10n.t('Username cannot be empty.'); - } else if (context.existingProviders.find(rp => rp.url === context.url && rp.username === value)) { - return l10n.t('Username "{0}" is already connected.', value); - } else { - return undefined; - } - } -} diff --git a/src/tree/registries/dockerHub/DockerHubAccountTreeItem.ts b/src/tree/registries/dockerHub/DockerHubAccountTreeItem.ts deleted file mode 100644 index 663455ae19..0000000000 --- a/src/tree/registries/dockerHub/DockerHubAccountTreeItem.ts +++ /dev/null @@ -1,125 +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 { AzExtParentTreeItem, AzExtTreeItem, IActionContext, nonNullProp } from "@microsoft/vscode-azext-utils"; -import { dockerHubUrl } from "../../../constants"; -import { ext } from "../../../extensionVariables"; -import { RequestLike, bearerAuthHeader } from "../../../utils/httpRequest"; -import { registryRequest } from "../../../utils/registryRequestUtils"; -import { getThemedIconPath } from "../../getThemedIconPath"; -import { ICachedRegistryProvider } from "../ICachedRegistryProvider"; -import { IRegistryProviderTreeItem } from "../IRegistryProviderTreeItem"; -import { RegistryConnectErrorTreeItem } from "../RegistryConnectErrorTreeItem"; -import { getRegistryContextValue, registryProviderSuffix } from "../registryContextValues"; -import { getRegistryPassword } from "../registryPasswords"; -import { DockerHubNamespaceTreeItem } from "./DockerHubNamespaceTreeItem"; - -export class DockerHubAccountTreeItem extends AzExtParentTreeItem implements IRegistryProviderTreeItem { - public label: string = 'Docker Hub'; - public childTypeLabel: string = 'namespace'; - public baseUrl: string = dockerHubUrl; - public cachedProvider: ICachedRegistryProvider; - - private _token?: string; - - public constructor(parent: AzExtParentTreeItem, cachedProvider: ICachedRegistryProvider) { - super(parent); - this.cachedProvider = cachedProvider; - this.id = this.cachedProvider.id + this.username; - this.iconPath = getThemedIconPath('docker'); - this.description = ext.registriesRoot.hasMultiplesOfProvider(this.cachedProvider) ? this.username : undefined; - } - - public get contextValue(): string { - return getRegistryContextValue(this, registryProviderSuffix); - } - - public get username(): string { - return nonNullProp(this.cachedProvider, 'username'); - } - - public async getPassword(): Promise { - return await getRegistryPassword(this.cachedProvider); - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - if (clearCache) { - try { - await this.refreshToken(); - } catch (err) { - // If creds are invalid, the above refreshToken will fail - return [new RegistryConnectErrorTreeItem(this, err, this.cachedProvider)]; - } - } - - const orgsAndNamespaces = new Set(); - - for (const orgs of await this.getOrganizations()) { - orgsAndNamespaces.add(orgs); - } - - for (const namespaces of await this.getNamespaces()) { - orgsAndNamespaces.add(namespaces); - } - - return this.createTreeItemsWithErrorHandling( - Array.from(orgsAndNamespaces), - 'invalidDockerHubNamespace', - n => new DockerHubNamespaceTreeItem(this, n.toLowerCase()), - n => n - ); - } - - public hasMoreChildrenImpl(): boolean { - return false; - } - - public async signRequest(request: RequestLike): Promise { - if (this._token) { - request.headers.set('Authorization', bearerAuthHeader(this._token)); - } - - return request; - } - - private async refreshToken(): Promise { - this._token = undefined; - const url = 'v2/users/login'; - const body = { username: this.username, password: await this.getPassword() }; - // eslint-disable-next-line @typescript-eslint/naming-convention - const response = await registryRequest(this, 'POST', url, { body: JSON.stringify(body), headers: { 'Content-Type': 'application/json' } }); - this._token = response.body.token; - } - - private async getNamespaces(): Promise { - const url: string = `v2/repositories/namespaces`; - const response = await registryRequest(this, 'GET', url); - return response.body.namespaces; - } - - private async getOrganizations(): Promise { - const url: string = `v2/user/orgs`; - const response = await registryRequest(this, 'GET', url); - return response.body.results?.map(o => o.orgname) ?? []; - } -} - -interface IToken { - token: string -} - -interface INamespaces { - namespaces: string[]; - next?: string; -} - -interface IOrganizations { - results: [ - { - orgname: string - } - ], - next?: string; -} diff --git a/src/tree/registries/dockerHub/DockerHubNamespaceTreeItem.ts b/src/tree/registries/dockerHub/DockerHubNamespaceTreeItem.ts deleted file mode 100644 index a28f2c45c8..0000000000 --- a/src/tree/registries/dockerHub/DockerHubNamespaceTreeItem.ts +++ /dev/null @@ -1,76 +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 { AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils"; -import { PAGE_SIZE, dockerHubUrl } from "../../../constants"; -import { RequestLike } from "../../../utils/httpRequest"; -import { registryRequest } from "../../../utils/registryRequestUtils"; -import { IDockerCliCredentials, RegistryTreeItemBase } from "../RegistryTreeItemBase"; -import { DockerHubAccountTreeItem } from "./DockerHubAccountTreeItem"; -import { DockerHubRepositoryTreeItem } from "./DockerHubRepositoryTreeItem"; - -export class DockerHubNamespaceTreeItem extends RegistryTreeItemBase { - public parent: DockerHubAccountTreeItem; - public baseUrl: string = dockerHubUrl; - public namespace: string; - - private _nextLink: string | undefined; - - public constructor(parent: DockerHubAccountTreeItem, namespace: string) { - super(parent); - this.namespace = namespace; - } - - public get label(): string { - return this.namespace; - } - - public get baseImagePath(): string { - return this.namespace; - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - if (clearCache) { - this._nextLink = undefined; - } - - const url = this._nextLink || `v2/repositories/${this.namespace}?page_size=${PAGE_SIZE}`; - const response = await registryRequest(this, 'GET', url); - this._nextLink = response.body.next; - return await this.createTreeItemsWithErrorHandling( - response.body.results, - 'invalidRepository', - r => new DockerHubRepositoryTreeItem(this, r.name), - r => r.name - ); - } - - public hasMoreChildrenImpl(): boolean { - return !!this._nextLink; - } - - public async signRequest(request: RequestLike): Promise { - return this.parent.signRequest(request); - } - - public async getDockerCliCredentials(): Promise { - return { - registryPath: '', - auth: { - username: this.parent.username, - password: await this.parent.getPassword() - } - }; - } -} - -interface IRepositories { - results: IRepository[]; - next?: string; -} - -interface IRepository { - name: string; -} diff --git a/src/tree/registries/dockerHub/DockerHubRepositoryTreeItem.ts b/src/tree/registries/dockerHub/DockerHubRepositoryTreeItem.ts deleted file mode 100644 index 3040ac7287..0000000000 --- a/src/tree/registries/dockerHub/DockerHubRepositoryTreeItem.ts +++ /dev/null @@ -1,52 +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 { AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils"; -import { PAGE_SIZE } from "../../../constants"; -import { registryRequest } from "../../../utils/registryRequestUtils"; -import { RemoteRepositoryTreeItemBase } from "../RemoteRepositoryTreeItemBase"; -import { RemoteTagTreeItem } from "../RemoteTagTreeItem"; -import { DockerHubNamespaceTreeItem } from "./DockerHubNamespaceTreeItem"; - -export class DockerHubRepositoryTreeItem extends RemoteRepositoryTreeItemBase { - public parent: DockerHubNamespaceTreeItem; - - private _nextLink: string | undefined; - - public constructor(parent: DockerHubNamespaceTreeItem, name: string) { - super(parent, name); - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - if (clearCache) { - this._nextLink = undefined; - } - - const url = this._nextLink || `v2/repositories/${this.parent.namespace}/${this.repoName}/tags?page_size=${PAGE_SIZE}`; - const response = await registryRequest(this, 'GET', url); - this._nextLink = response.body.next; - return await this.createTreeItemsWithErrorHandling( - response.body.results, - 'invalidTag', - async t => new RemoteTagTreeItem(this, t.name, t.last_updated), - t => t.name - ); - } - - public hasMoreChildrenImpl(): boolean { - return !!this._nextLink; - } -} - -interface ITags { - next?: string; - results: ITag[]; -} - -interface ITag { - name: string; - // eslint-disable-next-line @typescript-eslint/naming-convention - last_updated: string; -} diff --git a/src/tree/registries/dockerHub/dockerHubRegistryProvider.ts b/src/tree/registries/dockerHub/dockerHubRegistryProvider.ts deleted file mode 100644 index 15cf22dcb0..0000000000 --- a/src/tree/registries/dockerHub/dockerHubRegistryProvider.ts +++ /dev/null @@ -1,29 +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 { l10n } from 'vscode'; -import { RegistryApi } from "../all/RegistryApi"; -import { IRegistryProvider } from "../IRegistryProvider"; -import { deleteRegistryPassword, setRegistryPassword } from '../registryPasswords'; -import { DockerHubAccountTreeItem } from "./DockerHubAccountTreeItem"; - -export const dockerHubRegistryProviderId: string = 'dockerHub'; - -export const dockerHubRegistryProvider: IRegistryProvider = { - label: "Docker Hub", - id: dockerHubRegistryProviderId, - api: RegistryApi.DockerHubV2, - connectWizardOptions: { - wizardTitle: l10n.t('Sign in to Docker Hub'), - includeUsername: true, - usernamePrompt: l10n.t('Visit hub.docker.com to sign up for a Docker ID'), - usernamePlaceholder: l10n.t('Enter your Docker ID'), - passwordPrompt: l10n.t('Enter your password or personal access token'), - includePassword: true, - }, - treeItemFactory: (parent, cachedProvider) => new DockerHubAccountTreeItem(parent, cachedProvider), - persistAuth: async (cachedProvider, secret) => await setRegistryPassword(cachedProvider, secret), - removeAuth: async (cachedProvider) => await deleteRegistryPassword(cachedProvider), -}; diff --git a/src/tree/registries/dockerV2/DockerV2RegistryTreeItemBase.ts b/src/tree/registries/dockerV2/DockerV2RegistryTreeItemBase.ts deleted file mode 100644 index 0266d53c99..0000000000 --- a/src/tree/registries/dockerV2/DockerV2RegistryTreeItemBase.ts +++ /dev/null @@ -1,75 +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 { AzExtParentTreeItem, AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils"; -import { URL } from "url"; -import { PAGE_SIZE } from "../../../constants"; -import { IOAuthContext, RequestLike } from "../../../utils/httpRequest"; -import { getNextLinkFromHeaders, registryRequest } from "../../../utils/registryRequestUtils"; -import { IAuthProvider } from "../auth/IAuthProvider"; -import { ICachedRegistryProvider } from "../ICachedRegistryProvider"; -import { IRegistryProviderTreeItem } from "../IRegistryProviderTreeItem"; -import { IDockerCliCredentials, RegistryTreeItemBase } from "../RegistryTreeItemBase"; -import { RemoteRepositoryTreeItemBase } from "../RemoteRepositoryTreeItemBase"; - -export abstract class DockerV2RegistryTreeItemBase extends RegistryTreeItemBase implements IRegistryProviderTreeItem { - protected authContext?: IOAuthContext; - - private _nextLink: string | undefined; - - public constructor(parent: AzExtParentTreeItem, public readonly cachedProvider: ICachedRegistryProvider, protected readonly authHelper: IAuthProvider) { - super(parent); - } - - public get baseImagePath(): string { - return this.host.toLowerCase(); - } - - public get host(): string { - return new URL(this.baseUrl).host; - } - - public abstract createRepositoryTreeItem(name: string): RemoteRepositoryTreeItemBase; - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - if (clearCache) { - this._nextLink = undefined; - } - - const url = this._nextLink || `v2/_catalog?n=${PAGE_SIZE}`; - const response = await registryRequest(this, 'GET', url); - this._nextLink = getNextLinkFromHeaders(response); - return await this.createTreeItemsWithErrorHandling( - response.body.repositories, - 'invalidRepository', - r => this.createRepositoryTreeItem(r), - r => r - ); - } - - public hasMoreChildrenImpl(): boolean { - return !!this._nextLink; - } - - public async signRequest(request: RequestLike): Promise { - if (this.authHelper) { - return this.authHelper.signRequest(this.cachedProvider, request, this.authContext); - } - - return request; - } - - public async getDockerCliCredentials(): Promise { - if (this.authHelper) { - return await this.authHelper.getDockerCliCredentials(this.cachedProvider, this.authContext); - } - - return undefined; - } -} - -interface IRepositories { - repositories: string[]; -} diff --git a/src/tree/registries/dockerV2/DockerV2RepositoryTreeItem.ts b/src/tree/registries/dockerV2/DockerV2RepositoryTreeItem.ts deleted file mode 100644 index 63dd0448a6..0000000000 --- a/src/tree/registries/dockerV2/DockerV2RepositoryTreeItem.ts +++ /dev/null @@ -1,89 +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 { AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils"; -import { PAGE_SIZE } from "../../../constants"; -import { ErrorHandling, HttpErrorResponse, HttpStatusCode, IOAuthContext, RequestLike } from "../../../utils/httpRequest"; -import { getNextLinkFromHeaders, registryRequest } from "../../../utils/registryRequestUtils"; -import { IAuthProvider } from "../auth/IAuthProvider"; -import { ICachedRegistryProvider } from "../ICachedRegistryProvider"; -import { IRegistryProviderTreeItem } from "../IRegistryProviderTreeItem"; -import { RemoteRepositoryTreeItemBase } from "../RemoteRepositoryTreeItemBase"; -import { DockerV2RegistryTreeItemBase } from "./DockerV2RegistryTreeItemBase"; -import { DockerV2TagTreeItem } from "./DockerV2TagTreeItem"; - -export class DockerV2RepositoryTreeItem extends RemoteRepositoryTreeItemBase implements IRegistryProviderTreeItem { - public parent: DockerV2RegistryTreeItemBase; - - private _nextLink: string | undefined; - - public constructor(parent: DockerV2RegistryTreeItemBase, repoName: string, public readonly cachedProvider: ICachedRegistryProvider, protected readonly authHelper: IAuthProvider, protected readonly authContext?: IOAuthContext) { - super(parent, repoName); - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - if (clearCache) { - this._nextLink = undefined; - } - - const url = this._nextLink || `v2/${this.repoName}/tags/list?n=${PAGE_SIZE}`; - const response = await registryRequest(this, 'GET', url, undefined, ErrorHandling.ReturnErrorResponse); - if (response.status === HttpStatusCode.NotFound) { - // Some registries return 404 when all tags have been removed and the repository becomes effectively unavailable. - void this.deleteTreeItem(context); - return []; - } - else if (!response.ok) { - throw new HttpErrorResponse(response); - } - - this._nextLink = getNextLinkFromHeaders(response); - return await this.createTreeItemsWithErrorHandling( - response.body.tags, - 'invalidTag', - async t => { - const time = await this.getTagTime(t); - return new DockerV2TagTreeItem(this, t, time); - }, - t => t - ); - } - - public async signRequest(request: RequestLike): Promise { - if (this.authHelper) { - const authContext: IOAuthContext | undefined = this.authContext ? { ...this.authContext, scope: `repository:${this.repoName}:${request.method === 'DELETE' ? '*' : 'pull'}` } : undefined; - return this.authHelper.signRequest(this.cachedProvider, request, authContext); - } - - return request; - } - - public hasMoreChildrenImpl(): boolean { - return !!this._nextLink; - } - - private async getTagTime(tag: string): Promise { - const manifestUrl: string = `v2/${this.repoName}/manifests/${tag}`; - const manifestResponse = await registryRequest(this, 'GET', manifestUrl); - const history = JSON.parse(manifestResponse.body.history[0].v1Compatibility); - return history.created; - } -} - -interface ITags { - tags: string[]; -} - -interface IManifestHistory { - v1Compatibility: string; // stringified ManifestHistoryV1Compatibility -} - -interface IManifestHistoryV1Compatibility { - created: string; -} - -interface IManifest { - history: IManifestHistory[]; -} diff --git a/src/tree/registries/dockerV2/DockerV2TagTreeItem.ts b/src/tree/registries/dockerV2/DockerV2TagTreeItem.ts deleted file mode 100644 index 1ee3516e62..0000000000 --- a/src/tree/registries/dockerV2/DockerV2TagTreeItem.ts +++ /dev/null @@ -1,44 +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 { IActionContext, UserCancelledError, parseError } from '@microsoft/vscode-azext-utils'; -import { registryRequest } from '../../../utils/registryRequestUtils'; -import { RemoteTagTreeItem } from '../RemoteTagTreeItem'; - -export class DockerV2TagTreeItem extends RemoteTagTreeItem { - public async getDigest(): Promise { - const digestOptions = { - headers: { - // According to https://docs.docker.com/registry/spec/api/ - // When deleting a manifest from a registry version 2.3 or later, the following header must be used when HEAD or GET-ing the manifest to obtain the correct digest to delete - accept: 'application/vnd.docker.distribution.manifest.v2+json' - } - }; - - const url = `v2/${this.parent.repoName}/manifests/${this.tag}`; - const response = await registryRequest(this.parent, 'GET', url, digestOptions); - const digest = response.headers.get('docker-content-digest') as string; - return digest; - } - - public async deleteTreeItemImpl(context: IActionContext): Promise { - const digest = await this.getDigest(); - const url = `v2/${this.parent.repoName}/manifests/${digest}`; - - try { - await registryRequest(this.parent, 'DELETE', url); - } catch (error) { - const errorType: string = parseError(error).errorType.toLowerCase(); - if (errorType === '405' || errorType === 'unsupported') { - // Don't wait - // eslint-disable-next-line @typescript-eslint/no-floating-promises - context.ui.showWarningMessage('Deleting remote images is not supported on this registry. It may need to be enabled.', { learnMoreLink: 'https://aka.ms/AA7jsql' }); - throw new UserCancelledError(); - } else { - throw error; - } - } - } -} diff --git a/src/tree/registries/dockerV2/GenericDockerV2RegistryTreeItem.ts b/src/tree/registries/dockerV2/GenericDockerV2RegistryTreeItem.ts deleted file mode 100644 index 9b6336ee37..0000000000 --- a/src/tree/registries/dockerV2/GenericDockerV2RegistryTreeItem.ts +++ /dev/null @@ -1,56 +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 { AzExtParentTreeItem, AzExtTreeItem, IActionContext, nonNullProp } from "@microsoft/vscode-azext-utils"; -import { HttpErrorResponse, getWwwAuthenticateContext } from "../../../utils/httpRequest"; -import { registryRequest } from "../../../utils/registryRequestUtils"; -import { IAuthProvider } from "../auth/IAuthProvider"; -import { ICachedRegistryProvider } from "../ICachedRegistryProvider"; -import { getRegistryContextValue, registryProviderSuffix, registrySuffix } from "../registryContextValues"; -import { DockerV2RegistryTreeItemBase } from "./DockerV2RegistryTreeItemBase"; -import { DockerV2RepositoryTreeItem } from "./DockerV2RepositoryTreeItem"; - -export class GenericDockerV2RegistryTreeItem extends DockerV2RegistryTreeItemBase { - public constructor(parent: AzExtParentTreeItem, cachedProvider: ICachedRegistryProvider, authHelper: IAuthProvider) { - super(parent, cachedProvider, authHelper); - this.id = this.baseUrl; - } - - public get contextValue(): string { - return getRegistryContextValue(this, registrySuffix, registryProviderSuffix); - } - - public get label(): string { - return this.host; - } - - public get baseUrl(): string { - return nonNullProp(this.cachedProvider, 'url'); - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - if (clearCache) { - try { - // If the call succeeds, it's a V2 registry (https://docs.docker.com/registry/spec/api/#api-version-check) - // NOTE: Trailing slash is necessary (https://github.com/microsoft/vscode-docker/issues/1142) - await registryRequest(this, 'GET', 'v2/'); - } catch (error) { - if (error instanceof HttpErrorResponse && - (this.authContext = getWwwAuthenticateContext(error))) { - // We got authentication context successfully--set scope and move on to requesting the items - this.authContext.scope = 'registry:catalog:*'; - } else { - throw error; - } - } - } - - return super.loadMoreChildrenImpl(clearCache, context); - } - - public createRepositoryTreeItem(name: string): DockerV2RepositoryTreeItem { - return new DockerV2RepositoryTreeItem(this, name, this.cachedProvider, this.authHelper, this.authContext); - } -} diff --git a/src/tree/registries/dockerV2/genericDockerV2RegistryProvider.ts b/src/tree/registries/dockerV2/genericDockerV2RegistryProvider.ts deleted file mode 100644 index 9054b212c7..0000000000 --- a/src/tree/registries/dockerV2/genericDockerV2RegistryProvider.ts +++ /dev/null @@ -1,31 +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 { l10n } from 'vscode'; -import { RegistryApi } from "../all/RegistryApi"; -import { basicOAuthProvider } from '../auth/BasicOAuthProvider'; -import { IRegistryProvider } from "../IRegistryProvider"; -import { deleteRegistryPassword, setRegistryPassword } from '../registryPasswords'; -import { GenericDockerV2RegistryTreeItem } from "./GenericDockerV2RegistryTreeItem"; - -export const genericDockerV2RegistryProvider: IRegistryProvider = { - label: l10n.t('Generic Docker Registry'), - description: l10n.t('(Preview)'), - detail: l10n.t('Connect any generic private registry that supports the "Docker V2" api.'), - id: 'genericDockerV2', - api: RegistryApi.DockerV2, - isSingleRegistry: true, - connectWizardOptions: { - wizardTitle: l10n.t('Connect Docker Registry'), - includeUrl: true, - urlPrompt: l10n.t('Enter the URL for the registry'), - includeUsername: true, - isUsernameOptional: true, - includePassword: true, - }, - treeItemFactory: (parent, cachedProvider) => new GenericDockerV2RegistryTreeItem(parent, cachedProvider, basicOAuthProvider), - persistAuth: async (cachedProvider, secret) => await setRegistryPassword(cachedProvider, secret), - removeAuth: async (cachedProvider) => await deleteRegistryPassword(cachedProvider), -}; diff --git a/src/tree/registries/getInformationFromRegistryItem.ts b/src/tree/registries/getInformationFromRegistryItem.ts new file mode 100644 index 0000000000..eccab72abf --- /dev/null +++ b/src/tree/registries/getInformationFromRegistryItem.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CommonRegistry, CommonRepository, CommonTag, isRegistry, isRepository, isTag } from "@microsoft/vscode-docker-registries"; +import { UnifiedRegistryItem } from "./UnifiedRegistryTreeDataProvider"; + +export function getImageNameFromRegistryItem(node: UnifiedRegistryItem): string { + if (!isTag(node.wrappedItem) || !isRepository(node.parent.wrappedItem)) { + throw new Error('Unable to get image name'); + } + + const repository = node.parent.wrappedItem as CommonRepository; + + return `${repository.label}:${node.wrappedItem.label}`; +} + +export function getFullImageNameFromRegistryItem(node: UnifiedRegistryItem): string { + const imageName = getImageNameFromRegistryItem(node); + if (!isRegistry(node.parent.parent?.wrappedItem)) { + throw new Error('Unable to get full image name'); + } + + const registry = node.parent.parent.wrappedItem as CommonRegistry; + + switch (node.wrappedItem.additionalContextValues?.[0] ?? '') { + case 'registryV2Tag': { + const authority = registry.baseUrl.authority; + return `${authority.toLowerCase()}/${imageName}`; + } + case 'dockerHubTag': + default: + return `${registry.label}/${imageName}`; + } +} diff --git a/src/tree/registries/gitLab/GitLabAccountTreeItem.ts b/src/tree/registries/gitLab/GitLabAccountTreeItem.ts deleted file mode 100644 index 4db3a4a873..0000000000 --- a/src/tree/registries/gitLab/GitLabAccountTreeItem.ts +++ /dev/null @@ -1,86 +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 { AzExtParentTreeItem, AzExtTreeItem, IActionContext, nonNullProp, parseError } from "@microsoft/vscode-azext-utils"; -import { PAGE_SIZE } from "../../../constants"; -import { ext } from "../../../extensionVariables"; -import { RequestLike } from "../../../utils/httpRequest"; -import { getNextLinkFromHeaders, registryRequest } from "../../../utils/registryRequestUtils"; -import { getIconPath } from "../../getThemedIconPath"; -import { ICachedRegistryProvider } from "../ICachedRegistryProvider"; -import { IRegistryProviderTreeItem } from "../IRegistryProviderTreeItem"; -import { RegistryConnectErrorTreeItem } from "../RegistryConnectErrorTreeItem"; -import { getRegistryContextValue, registryProviderSuffix } from "../registryContextValues"; -import { getRegistryPassword } from "../registryPasswords"; -import { GitLabProjectTreeItem } from "./GitLabProjectTreeItem"; - -export class GitLabAccountTreeItem extends AzExtParentTreeItem implements IRegistryProviderTreeItem { - public label: string = 'GitLab'; - public childTypeLabel: string = 'project'; - public baseUrl: string = 'https://gitlab.com/'; - public cachedProvider: ICachedRegistryProvider; - - private _nextLink?: string; - - public constructor(parent: AzExtParentTreeItem, provider: ICachedRegistryProvider) { - super(parent); - this.cachedProvider = provider; - this.id = this.cachedProvider.id + this.username; - this.iconPath = getIconPath('gitlab'); - this.description = ext.registriesRoot.hasMultiplesOfProvider(this.cachedProvider) ? this.username : undefined; - } - - public get contextValue(): string { - return getRegistryContextValue(this, registryProviderSuffix); - } - - public get username(): string { - return nonNullProp(this.cachedProvider, 'username'); - } - - public async getPassword(): Promise { - return await getRegistryPassword(this.cachedProvider); - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - if (clearCache) { - this._nextLink = undefined; - } - - try { - const url: string = this._nextLink || `api/v4/projects?per_page=${PAGE_SIZE}&simple=true&membership=true`; - const response = await registryRequest(this, 'GET', url); - this._nextLink = getNextLinkFromHeaders(response); - return this.createTreeItemsWithErrorHandling( - response.body, - 'invalidGitLabProject', - n => new GitLabProjectTreeItem(this, n.id.toString(), n.path_with_namespace.toLowerCase()), - n => n.path_with_namespace - ); - } catch (err) { - const errorType: string = parseError(err).errorType.toLowerCase(); - if (errorType === '401' || errorType === 'unauthorized') { - return [new RegistryConnectErrorTreeItem(this, err, this.cachedProvider)]; - } else { - throw err; - } - } - } - - public hasMoreChildrenImpl(): boolean { - return !!this._nextLink; - } - - public async signRequest(request: RequestLike): Promise { - request.headers.set('PRIVATE-TOKEN', await this.getPassword()); - return request; - } -} - -interface IProject { - id: number; - // eslint-disable-next-line @typescript-eslint/naming-convention - path_with_namespace: string; -} diff --git a/src/tree/registries/gitLab/GitLabProjectTreeItem.ts b/src/tree/registries/gitLab/GitLabProjectTreeItem.ts deleted file mode 100644 index dfb917fe54..0000000000 --- a/src/tree/registries/gitLab/GitLabProjectTreeItem.ts +++ /dev/null @@ -1,80 +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 { AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils"; -import { PAGE_SIZE } from "../../../constants"; -import { RequestLike } from "../../../utils/httpRequest"; -import { getNextLinkFromHeaders, registryRequest } from "../../../utils/registryRequestUtils"; -import { IDockerCliCredentials, RegistryTreeItemBase } from "../RegistryTreeItemBase"; -import { GitLabAccountTreeItem } from "./GitLabAccountTreeItem"; -import { GitLabRepositoryTreeItem } from "./GitLabRepositoryTreeItem"; - -const gitLabRegistryUrl: string = 'registry.gitlab.com'; - -export class GitLabProjectTreeItem extends RegistryTreeItemBase { - public parent: GitLabAccountTreeItem; - public readonly projectId: string; - public pathWithNamespace: string; - - private _nextLink?: string; - - public constructor(parent: GitLabAccountTreeItem, id: string, pathWithNamespace: string) { - super(parent); - this.projectId = id; - this.pathWithNamespace = pathWithNamespace; - this.id = this.projectId; - } - - public get baseUrl(): string { - return this.parent.baseUrl; - } - - public get label(): string { - return this.pathWithNamespace; - } - - public get baseImagePath(): string { - return gitLabRegistryUrl + '/' + this.pathWithNamespace; - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - if (clearCache) { - this._nextLink = undefined; - } - - const url = this._nextLink || `api/v4/projects/${this.projectId}/registry/repositories?per_page=${PAGE_SIZE}`; - const response = await registryRequest(this, 'GET', url); - this._nextLink = getNextLinkFromHeaders(response); - return await this.createTreeItemsWithErrorHandling( - response.body, - 'invalidRepository', - r => new GitLabRepositoryTreeItem(this, r.id.toString(), r.name), - r => r.name - ); - } - - public hasMoreChildrenImpl(): boolean { - return !!this._nextLink; - } - - public async signRequest(request: RequestLike): Promise { - return this.parent.signRequest(request); - } - - public async getDockerCliCredentials(): Promise { - return { - registryPath: gitLabRegistryUrl, - auth: { - username: this.parent.username, - password: await this.parent.getPassword() - } - }; - } -} - -interface IRepository { - name: string; - id: number; -} diff --git a/src/tree/registries/gitLab/GitLabRepositoryTreeItem.ts b/src/tree/registries/gitLab/GitLabRepositoryTreeItem.ts deleted file mode 100644 index b0fe1ced00..0000000000 --- a/src/tree/registries/gitLab/GitLabRepositoryTreeItem.ts +++ /dev/null @@ -1,64 +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 { AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils"; -import { PAGE_SIZE } from "../../../constants"; -import { getNextLinkFromHeaders, registryRequest } from "../../../utils/registryRequestUtils"; -import { RemoteRepositoryTreeItemBase } from "../RemoteRepositoryTreeItemBase"; -import { RemoteTagTreeItem } from "../RemoteTagTreeItem"; -import { GitLabProjectTreeItem } from "./GitLabProjectTreeItem"; - -export class GitLabRepositoryTreeItem extends RemoteRepositoryTreeItemBase { - public parent: GitLabProjectTreeItem; - public readonly repoId: string; - - private _nextLink?: string; - - public constructor(parent: GitLabProjectTreeItem, id: string, name: string) { - // GitLab returns an empty repository name, - // if the project's namespace is the same as the repository - super(parent, name || parent.label); - this.repoId = id; - this.id = this.repoId; - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - if (clearCache) { - this._nextLink = undefined; - } - - const url = this._nextLink || `api/v4/projects/${this.parent.projectId}/registry/repositories/${this.repoId}/tags?per_page=${PAGE_SIZE}`; - const response = await registryRequest(this, 'GET', url); - this._nextLink = getNextLinkFromHeaders(response); - return await this.createTreeItemsWithErrorHandling( - response.body, - 'invalidTag', - async t => { - const details = await this.getTagDetails(t.name); - return new RemoteTagTreeItem(this, t.name, details.created_at); - }, - t => t.name - ); - } - - public hasMoreChildrenImpl(): boolean { - return !!this._nextLink; - } - - private async getTagDetails(tag: string): Promise { - const url = `api/v4/projects/${this.parent.projectId}/registry/repositories/${this.repoId}/tags/${tag}`; - const response = await registryRequest(this, 'GET', url); - return response.body; - } -} - -interface ITag { - name: string; -} - -interface ITagDetails { - // eslint-disable-next-line @typescript-eslint/naming-convention - created_at: string; -} diff --git a/src/tree/registries/gitLab/gitLabRegistryProvider.ts b/src/tree/registries/gitLab/gitLabRegistryProvider.ts deleted file mode 100644 index 827ef762b7..0000000000 --- a/src/tree/registries/gitLab/gitLabRegistryProvider.ts +++ /dev/null @@ -1,25 +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 { l10n } from 'vscode'; -import { RegistryApi } from "../all/RegistryApi"; -import { IRegistryProvider } from "../IRegistryProvider"; -import { deleteRegistryPassword, setRegistryPassword } from '../registryPasswords'; -import { GitLabAccountTreeItem } from "./GitLabAccountTreeItem"; - -export const gitLabRegistryProvider: IRegistryProvider = { - label: "GitLab", - id: 'gitLab', - api: RegistryApi.GitLabV4, - connectWizardOptions: { - wizardTitle: l10n.t('Sign in to GitLab'), - includeUsername: true, - includePassword: true, - passwordPrompt: l10n.t('GitLab Personal Access Token (requires `api` or `read_api` scope)'), - }, - treeItemFactory: (parent, cachedProvider) => new GitLabAccountTreeItem(parent, cachedProvider), - persistAuth: async (cachedProvider, secret) => await setRegistryPassword(cachedProvider, secret), - removeAuth: async (cachedProvider) => await deleteRegistryPassword(cachedProvider), -}; diff --git a/src/tree/registries/registryContextValues.ts b/src/tree/registries/registryContextValues.ts deleted file mode 100644 index 57f10dcdc4..0000000000 --- a/src/tree/registries/registryContextValues.ts +++ /dev/null @@ -1,64 +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 { AzExtTreeItem } from "@microsoft/vscode-azext-utils"; -import { l10n } from 'vscode'; -import { RegistryApi } from "./all/RegistryApi"; -import { azureRegistryProviderId } from "./azure/azureRegistryProvider"; -import { dockerHubRegistryProviderId } from "./dockerHub/dockerHubRegistryProvider"; -import { ICachedRegistryProvider } from "./ICachedRegistryProvider"; -import { IRegistryProviderTreeItem } from "./IRegistryProviderTreeItem"; - -export const registryProviderSuffix = 'RegistryProvider'; -export const registrySuffix = 'Registry'; -export const repositorySuffix = 'Repository'; -export const tagSuffix = 'Tag'; - -export const contextValueSeparator = ';'; -export const anyContextValuePart = '.*'; - -export function getRegistryContextValue(node: AzExtTreeItem & Partial, ...suffixes: string[]): string { - const cachedProvider = getCachedProvider(node); - const parts = [cachedProvider.id, cachedProvider.api, ...suffixes]; - return parts.join(contextValueSeparator) + contextValueSeparator; -} - -/** - * Regular expressions used for the Tree Item Picker (which is used when a command is called from the command palette) - * Registry providers only need to add an entry here if they support commands unique to their provider - */ -export const registryExpectedContextValues = { - all: getRegistryExpectedContextValues({}), - azure: getRegistryExpectedContextValues({ id: azureRegistryProviderId }), - dockerHub: getRegistryExpectedContextValues({ id: dockerHubRegistryProviderId }), - dockerV2: getRegistryExpectedContextValues({ api: RegistryApi.DockerV2 }), -}; - -function getRegistryExpectedContextValues(provider: Partial): { registryProvider: RegExp, registry: RegExp, repository: RegExp, tag: RegExp } { - return { - registryProvider: convertToRegExp(provider, registryProviderSuffix), - registry: convertToRegExp(provider, registrySuffix), - repository: convertToRegExp(provider, repositorySuffix), - tag: convertToRegExp(provider, tagSuffix) - }; -} - -function convertToRegExp(provider: Partial, suffix: string): RegExp { - const parts = [provider.id, provider.api, suffix].map(p => p || anyContextValuePart); - const value = parts.join(contextValueSeparator) + contextValueSeparator; - return new RegExp(value.replace(/undefined/g, anyContextValuePart), 'i'); -} - -function getCachedProvider(node: AzExtTreeItem & Partial): ICachedRegistryProvider { - while (!node.cachedProvider) { - if (!node.parent) { - throw new Error(l10n.t('Failed to find cachedProvider')); - } else { - node = node.parent; - } - } - - return node.cachedProvider; -} diff --git a/src/tree/registries/registryPasswords.ts b/src/tree/registries/registryPasswords.ts deleted file mode 100644 index d02f3b279c..0000000000 --- a/src/tree/registries/registryPasswords.ts +++ /dev/null @@ -1,28 +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 crypto from 'crypto'; -import { ext } from '../../extensionVariables'; -import { ICachedRegistryProvider } from "./ICachedRegistryProvider"; - -export async function getRegistryPassword(cached: ICachedRegistryProvider): Promise { - return ext.context.secrets.get(getRegistryPasswordKey(cached)); -} - -export async function setRegistryPassword(cached: ICachedRegistryProvider, password: string): Promise { - return ext.context.secrets.store(getRegistryPasswordKey(cached), password); -} - -export async function deleteRegistryPassword(cached: ICachedRegistryProvider): Promise { - return ext.context.secrets.delete(getRegistryPasswordKey(cached)); -} - -function getRegistryPasswordKey(cached: ICachedRegistryProvider): string { - return getPseudononymousStringHash(cached.id + cached.api + (cached.url || '') + (cached.username || '')); -} - -function getPseudononymousStringHash(s: string): string { - return crypto.createHash('sha256').update(s).digest('hex'); -} diff --git a/src/utils/lazyPackages.ts b/src/utils/lazyPackages.ts index ab02bb6912..11edd3fe73 100644 --- a/src/utils/lazyPackages.ts +++ b/src/utils/lazyPackages.ts @@ -34,13 +34,3 @@ export async function getAzExtAppService(): Promise { - return await import('../tree/registries/azure/AzureAccountTreeItem'); -} - -export async function getAzSubTreeItem(): Promise { - return await import('../tree/registries/azure/SubscriptionTreeItem'); -} From 008e9bf7ab87396a2e574887404e71285c73e15a Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:04:41 -0400 Subject: [PATCH 02/43] Made Some ACR Commands Work (#4027) * adjusted scheduleRunReuqest to work with new registries tree * delete azure repository command implementation * actually added back delete azure repository * added back delete Azure Registry command * changed registry tree util file name to be more general * added openInAzurePortalCommand --- package.json | 6 +- src/commands/images/buildImage.ts | 160 +++---- src/commands/images/tagImage.ts | 194 ++++---- src/commands/registerCommands.ts | 9 +- .../registries/azure/deleteAzureRegistry.ts | 55 ++- .../registries/azure/deleteAzureRepository.ts | 53 ++- .../registries/azure/openInAzurePortal.ts | 34 +- .../azure/tasks/buildImageInAzure.ts | 59 +-- .../azure/tasks/scheduleRunRequest.ts | 439 +++++++++--------- src/commands/registries/copyRemoteFullTag.ts | 2 +- src/commands/registries/pullImages.ts | 2 +- .../Azure/AzureRegistryDataProvider.ts | 72 ++- ...omRegistryItem.ts => registryTreeUtils.ts} | 6 + 13 files changed, 590 insertions(+), 501 deletions(-) rename src/tree/registries/{getInformationFromRegistryItem.ts => registryTreeUtils.ts} (78%) diff --git a/package.json b/package.json index 4490647747..659e06e9bf 100644 --- a/package.json +++ b/package.json @@ -486,7 +486,7 @@ }, { "command": "vscode-docker.registries.azure.deleteRegistry", - "when": "view == dockerRegistries && viewItem == azure;DockerV2;Registry;", + "when": "view == dockerRegistries && viewItem =~ /azureContainerRegistry/i", "group": "regs_reg_2_destructive@1" }, { @@ -496,7 +496,7 @@ }, { "command": "vscode-docker.registries.azure.deleteRepository", - "when": "view == dockerRegistries && viewItem == azure;DockerV2;Repository;", + "when": "view == dockerRegistries && viewItem =~ /azureContainerRepository/i", "group": "regs_repo_2_destructive@1" }, { @@ -561,7 +561,7 @@ }, { "command": "vscode-docker.registries.azure.openInPortal", - "when": "view == dockerRegistries && viewItem =~ /azure(Subscription|;DockerV2;Registry;)/", + "when": "view == dockerRegistries && viewItem =~ /(azuresubscription|azureContainerRegistry)/i", "group": "regs_zzz_common@1" }, { diff --git a/src/commands/images/buildImage.ts b/src/commands/images/buildImage.ts index a51eb7a44b..db6660c458 100644 --- a/src/commands/images/buildImage.ts +++ b/src/commands/images/buildImage.ts @@ -1,80 +1,80 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ - -// import { IActionContext, UserCancelledError } from "@microsoft/vscode-azext-utils"; -// import * as path from "path"; -// import * as vscode from "vscode"; -// import { ext } from "../../extensionVariables"; -// import { TaskCommandRunnerFactory } from "../../runtimes/runners/TaskCommandRunnerFactory"; -// import { getOfficialBuildTaskForDockerfile } from "../../tasks/TaskHelper"; -// import { getValidImageNameFromPath } from "../../utils/getValidImageName"; -// import { delay } from "../../utils/promiseUtils"; -// import { quickPickDockerFileItem } from "../../utils/quickPickFile"; -// import { quickPickWorkspaceFolder } from "../../utils/quickPickWorkspaceFolder"; -// import { selectBuildCommand } from "../selectCommandTemplate"; -// import { addImageTaggingTelemetry, getTagFromUserInput } from "./tagImage"; - -// const tagRegex: RegExp = /\$\{tag\}/i; - -// export async function buildImage(context: IActionContext, dockerFileUri: vscode.Uri | undefined): Promise { -// if (!vscode.workspace.isTrusted) { -// throw new UserCancelledError('enforceTrust'); -// } - -// const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('docker'); -// const defaultContextPath = configOptions.get('imageBuildContextPath', ''); - -// let rootFolder: vscode.WorkspaceFolder; -// if (dockerFileUri) { -// rootFolder = vscode.workspace.getWorkspaceFolder(dockerFileUri); -// } - -// rootFolder = rootFolder || await quickPickWorkspaceFolder(context, vscode.l10n.t('To build Docker files you must first open a folder or workspace in VS Code.')); - -// const dockerFileItem = await quickPickDockerFileItem(context, dockerFileUri, rootFolder); -// const task = await getOfficialBuildTaskForDockerfile(context, dockerFileItem.absoluteFilePath, rootFolder); - -// if (task) { -// await vscode.tasks.executeTask(task); -// } else { -// const contextPath: string = defaultContextPath || dockerFileItem.relativeFolderPath; - -// const terminalCommand = await selectBuildCommand( -// context, -// rootFolder, -// dockerFileItem.relativeFilePath, -// contextPath -// ); - -// // Replace '${tag}' if needed. Tag is a special case because we don't want to prompt unless necessary, so must manually replace it. -// if (tagRegex.test(terminalCommand.command)) { -// const absFilePath: string = path.join(rootFolder.uri.fsPath, dockerFileItem.relativeFilePath); -// const dockerFileKey = `buildTag_${absFilePath}`; -// const prevImageName: string | undefined = ext.context.workspaceState.get(dockerFileKey); - -// // Get imageName based previous entries, else on name of subfolder containing the Dockerfile -// const suggestedImageName = prevImageName || getValidImageNameFromPath(dockerFileItem.absoluteFolderPath, 'latest'); - -// // Temporary work-around for vscode bug where valueSelection can be messed up if a quick pick is followed by a showInputBox -// await delay(500); - -// addImageTaggingTelemetry(context, suggestedImageName, '.before'); -// const imageName: string = await getTagFromUserInput(context, suggestedImageName); -// addImageTaggingTelemetry(context, imageName, '.after'); - -// await ext.context.workspaceState.update(dockerFileKey, imageName); -// terminalCommand.command = terminalCommand.command.replace(tagRegex, imageName); -// } - -// const client = await ext.runtimeManager.getClient(); -// const taskCRF = new TaskCommandRunnerFactory({ -// taskName: client.displayName, -// workspaceFolder: rootFolder, -// focus: true, -// }); - -// await taskCRF.getCommandRunner()(terminalCommand); -// } -// } +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IActionContext, UserCancelledError } from "@microsoft/vscode-azext-utils"; +import * as path from "path"; +import * as vscode from "vscode"; +import { ext } from "../../extensionVariables"; +import { TaskCommandRunnerFactory } from "../../runtimes/runners/TaskCommandRunnerFactory"; +import { getOfficialBuildTaskForDockerfile } from "../../tasks/TaskHelper"; +import { getValidImageNameFromPath } from "../../utils/getValidImageName"; +import { delay } from "../../utils/promiseUtils"; +import { quickPickDockerFileItem } from "../../utils/quickPickFile"; +import { quickPickWorkspaceFolder } from "../../utils/quickPickWorkspaceFolder"; +import { selectBuildCommand } from "../selectCommandTemplate"; +import { addImageTaggingTelemetry, getTagFromUserInput } from "./tagImage"; + +const tagRegex: RegExp = /\$\{tag\}/i; + +export async function buildImage(context: IActionContext, dockerFileUri: vscode.Uri | undefined): Promise { + if (!vscode.workspace.isTrusted) { + throw new UserCancelledError('enforceTrust'); + } + + const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('docker'); + const defaultContextPath = configOptions.get('imageBuildContextPath', ''); + + let rootFolder: vscode.WorkspaceFolder; + if (dockerFileUri) { + rootFolder = vscode.workspace.getWorkspaceFolder(dockerFileUri); + } + + rootFolder = rootFolder || await quickPickWorkspaceFolder(context, vscode.l10n.t('To build Docker files you must first open a folder or workspace in VS Code.')); + + const dockerFileItem = await quickPickDockerFileItem(context, dockerFileUri, rootFolder); + const task = await getOfficialBuildTaskForDockerfile(context, dockerFileItem.absoluteFilePath, rootFolder); + + if (task) { + await vscode.tasks.executeTask(task); + } else { + const contextPath: string = defaultContextPath || dockerFileItem.relativeFolderPath; + + const terminalCommand = await selectBuildCommand( + context, + rootFolder, + dockerFileItem.relativeFilePath, + contextPath + ); + + // Replace '${tag}' if needed. Tag is a special case because we don't want to prompt unless necessary, so must manually replace it. + if (tagRegex.test(terminalCommand.command)) { + const absFilePath: string = path.join(rootFolder.uri.fsPath, dockerFileItem.relativeFilePath); + const dockerFileKey = `buildTag_${absFilePath}`; + const prevImageName: string | undefined = ext.context.workspaceState.get(dockerFileKey); + + // Get imageName based previous entries, else on name of subfolder containing the Dockerfile + const suggestedImageName = prevImageName || getValidImageNameFromPath(dockerFileItem.absoluteFolderPath, 'latest'); + + // Temporary work-around for vscode bug where valueSelection can be messed up if a quick pick is followed by a showInputBox + await delay(500); + + addImageTaggingTelemetry(context, suggestedImageName, '.before'); + const imageName: string = await getTagFromUserInput(context, suggestedImageName); + addImageTaggingTelemetry(context, imageName, '.after'); + + await ext.context.workspaceState.update(dockerFileKey, imageName); + terminalCommand.command = terminalCommand.command.replace(tagRegex, imageName); + } + + const client = await ext.runtimeManager.getClient(); + const taskCRF = new TaskCommandRunnerFactory({ + taskName: client.displayName, + workspaceFolder: rootFolder, + focus: true, + }); + + await taskCRF.getCommandRunner()(terminalCommand); + } +} diff --git a/src/commands/images/tagImage.ts b/src/commands/images/tagImage.ts index dda54f7e1c..aff407e5bd 100644 --- a/src/commands/images/tagImage.ts +++ b/src/commands/images/tagImage.ts @@ -1,97 +1,97 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ - -// import { IActionContext, TelemetryProperties } from '@microsoft/vscode-azext-utils'; -// import * as vscode from 'vscode'; -// import { ext } from '../../extensionVariables'; -// import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; -// import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; - -// export async function tagImage(context: IActionContext, node?: ImageTreeItem, registry?: UnifiedRegistryItem): Promise { -// if (!node) { -// await ext.imagesTree.refresh(context); -// node = await ext.imagesTree.showTreeItemPicker(ImageTreeItem.contextValue, { -// ...context, -// noItemFoundErrorMessage: vscode.l10n.t('No images are available to tag') -// }); -// } - -// addImageTaggingTelemetry(context, node.fullTag, '.before'); -// const newTaggedName: string = await getTagFromUserInput(context, node.fullTag, ''); // TODO: review this later -// addImageTaggingTelemetry(context, newTaggedName, '.after'); - -// await ext.runWithDefaults(client => -// client.tagImage({ fromImageRef: node.imageId, toImageRef: newTaggedName }) -// ); - -// return newTaggedName; -// } - -// export async function getTagFromUserInput(context: IActionContext, fullTag: string, baseImagePath?: string): Promise { -// const opt: vscode.InputBoxOptions = { -// ignoreFocusOut: true, -// prompt: vscode.l10n.t('Tag image as...'), -// }; - -// if (fullTag.includes('/')) { -// opt.valueSelection = [0, fullTag.lastIndexOf('/')]; -// } else if (baseImagePath) { -// fullTag = `${baseImagePath}/${fullTag}`; -// opt.valueSelection = [0, fullTag.lastIndexOf('/')]; -// } - -// opt.value = fullTag; - -// return await context.ui.showInputBox(opt); -// } - -// const KnownRegistries: { type: string, regex: RegExp }[] = [ -// // Like username/path -// { type: 'dockerhub-namespace', regex: /^[^.:]+\/[^.:]+$/ }, - -// { type: 'dockerhub-dockerio', regex: /^docker.io.*\// }, -// { type: 'github', regex: /ghcr\.io.*\// }, -// { type: 'gitlab', regex: /gitlab.*\// }, -// { type: 'ACR', regex: /azurecr\.io.*\// }, -// { type: 'GCR', regex: /gcr\.io.*\// }, -// { type: 'ECR', regex: /\.ecr\..*\// }, -// { type: 'localhost', regex: /localhost:.*\// }, - -// // Has a port, probably a private registry -// { type: 'privateWithPort', regex: /:[0-9]+\// }, - -// // Match anything remaining -// { type: 'other', regex: /\// }, // has a slash -// { type: 'none', regex: /./ } // no slash -// ]; - -// export function addImageTaggingTelemetry(context: IActionContext, fullImageName: string, propertyPostfix: '.before' | '.after' | ''): void { -// try { -// const properties: TelemetryProperties = {}; - -// const [, repository, tag] = /^(.*):(.*)$/.exec(fullImageName) ?? [undefined, fullImageName, '']; - -// if (!!tag.match(/^[0-9.-]*(|alpha|beta|latest|edge|v|version)?[0-9.-]*$/)) { -// properties.safeTag = tag; -// } -// properties.hasTag = String(!!tag); -// properties.numSlashes = String(numberMatches(repository.match(/\//g))); - -// const knownRegistry = KnownRegistries.find(kr => !!repository.match(kr.regex)); -// if (knownRegistry) { -// properties.registryType = knownRegistry.type; -// } - -// for (const propertyName of Object.keys(properties)) { -// context.telemetry.properties[propertyName + propertyPostfix] = properties[propertyName]; -// } -// } catch (error) { -// console.error(error); -// } -// } - -// function numberMatches(matches: RegExpMatchArray | null): number { -// return matches ? matches.length : 0; -// } +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IActionContext, TelemetryProperties } from '@microsoft/vscode-azext-utils'; +import * as vscode from 'vscode'; +import { ext } from '../../extensionVariables'; +import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; +import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; + +export async function tagImage(context: IActionContext, node?: ImageTreeItem, registry?: UnifiedRegistryItem): Promise { + if (!node) { + await ext.imagesTree.refresh(context); + node = await ext.imagesTree.showTreeItemPicker(ImageTreeItem.contextValue, { + ...context, + noItemFoundErrorMessage: vscode.l10n.t('No images are available to tag') + }); + } + + addImageTaggingTelemetry(context, node.fullTag, '.before'); + const newTaggedName: string = await getTagFromUserInput(context, node.fullTag, ''); // TODO: review this later + addImageTaggingTelemetry(context, newTaggedName, '.after'); + + await ext.runWithDefaults(client => + client.tagImage({ fromImageRef: node.imageId, toImageRef: newTaggedName }) + ); + + return newTaggedName; +} + +export async function getTagFromUserInput(context: IActionContext, fullTag: string, baseImagePath?: string): Promise { + const opt: vscode.InputBoxOptions = { + ignoreFocusOut: true, + prompt: vscode.l10n.t('Tag image as...'), + }; + + if (fullTag.includes('/')) { + opt.valueSelection = [0, fullTag.lastIndexOf('/')]; + } else if (baseImagePath) { + fullTag = `${baseImagePath}/${fullTag}`; + opt.valueSelection = [0, fullTag.lastIndexOf('/')]; + } + + opt.value = fullTag; + + return await context.ui.showInputBox(opt); +} + +const KnownRegistries: { type: string, regex: RegExp }[] = [ + // Like username/path + { type: 'dockerhub-namespace', regex: /^[^.:]+\/[^.:]+$/ }, + + { type: 'dockerhub-dockerio', regex: /^docker.io.*\// }, + { type: 'github', regex: /ghcr\.io.*\// }, + { type: 'gitlab', regex: /gitlab.*\// }, + { type: 'ACR', regex: /azurecr\.io.*\// }, + { type: 'GCR', regex: /gcr\.io.*\// }, + { type: 'ECR', regex: /\.ecr\..*\// }, + { type: 'localhost', regex: /localhost:.*\// }, + + // Has a port, probably a private registry + { type: 'privateWithPort', regex: /:[0-9]+\// }, + + // Match anything remaining + { type: 'other', regex: /\// }, // has a slash + { type: 'none', regex: /./ } // no slash +]; + +export function addImageTaggingTelemetry(context: IActionContext, fullImageName: string, propertyPostfix: '.before' | '.after' | ''): void { + try { + const properties: TelemetryProperties = {}; + + const [, repository, tag] = /^(.*):(.*)$/.exec(fullImageName) ?? [undefined, fullImageName, '']; + + if (!!tag.match(/^[0-9.-]*(|alpha|beta|latest|edge|v|version)?[0-9.-]*$/)) { + properties.safeTag = tag; + } + properties.hasTag = String(!!tag); + properties.numSlashes = String(numberMatches(repository.match(/\//g))); + + const knownRegistry = KnownRegistries.find(kr => !!repository.match(kr.regex)); + if (knownRegistry) { + properties.registryType = knownRegistry.type; + } + + for (const propertyName of Object.keys(properties)) { + context.telemetry.properties[propertyName + propertyPostfix] = properties[propertyName]; + } + } catch (error) { + console.error(error); + } +} + +function numberMatches(matches: RegExpMatchArray | null): number { + return matches ? matches.length : 0; +} diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index ee3350847a..15671deed9 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -68,6 +68,9 @@ import { connectRegistry } from "./registries/connectRegistry"; // import { logOutOfDockerCli } from "./registries/logOutOfDockerCli"; // import { pullImageFromRepository, pullRepository } from "./registries/pullImages"; // import { reconnectRegistry } from "./registries/reconnectRegistry"; +import { deleteAzureRegistry } from "./registries/azure/deleteAzureRegistry"; +import { deleteAzureRepository } from "./registries/azure/deleteAzureRepository"; +import { buildImageInAzure } from "./registries/azure/tasks/buildImageInAzure"; import { copyRemoteFullTag } from "./registries/copyRemoteFullTag"; import { copyRemoteImageDigest } from "./registries/copyRemoteImageDigest"; import { disconnectRegistry } from "./registries/disconnectRegistry"; @@ -182,10 +185,10 @@ export function registerCommands(): void { // registerCommand('vscode-docker.registries.dockerHub.openInBrowser', openDockerHubInBrowser); - // registerWorkspaceCommand('vscode-docker.registries.azure.buildImage', buildImageInAzure); + registerWorkspaceCommand('vscode-docker.registries.azure.buildImage', buildImageInAzure); registerCommand('vscode-docker.registries.azure.createRegistry', createAzureRegistry); - // registerCommand('vscode-docker.registries.azure.deleteRegistry', deleteAzureRegistry); - // registerCommand('vscode-docker.registries.azure.deleteRepository', deleteAzureRepository); + registerCommand('vscode-docker.registries.azure.deleteRegistry', deleteAzureRegistry); + registerCommand('vscode-docker.registries.azure.deleteRepository', deleteAzureRepository); registerCommand('vscode-docker.registries.azure.openInPortal', openInAzurePortal); registerCommand('vscode-docker.registries.azure.runTask', runAzureTask); // registerWorkspaceCommand('vscode-docker.registries.azure.runFileAsTask', runFileAsAzureTask); diff --git a/src/commands/registries/azure/deleteAzureRegistry.ts b/src/commands/registries/azure/deleteAzureRegistry.ts index bfac41cc95..febc0e4625 100644 --- a/src/commands/registries/azure/deleteAzureRegistry.ts +++ b/src/commands/registries/azure/deleteAzureRegistry.ts @@ -1,30 +1,35 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ -// import { DialogResponses, IActionContext } from '@microsoft/vscode-azext-utils'; -// import { l10n, ProgressLocation, window } from 'vscode'; -// import { ext } from '../../../extensionVariables'; -// import type { AzureRegistryTreeItem } from '../../../tree/registries/azure/AzureRegistryTreeItem'; -// import { registryExpectedContextValues } from '../../../tree/registries/registryContextValues'; +import { DialogResponses, IActionContext, contextValueExperience } from '@microsoft/vscode-azext-utils'; +import { ProgressLocation, l10n, window } from 'vscode'; +import { ext } from '../../../extensionVariables'; +import { AzureRegistry, AzureRegistryDataProvider } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; +import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; -// export async function deleteAzureRegistry(context: IActionContext, node?: AzureRegistryTreeItem): Promise { -// if (!node) { -// node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.registry, { ...context, suppressCreatePick: true }); -// } +export async function deleteAzureRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { + if (!node) { + node = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerRegistry' }); + } -// const confirmDelete: string = l10n.t('Are you sure you want to delete registry "{0}" and its associated images?', node.registryName); -// // no need to check result - cancel will throw a UserCancelledError -// await context.ui.showWarningMessage(confirmDelete, { modal: true }, DialogResponses.deleteResponse); + const registryName = node.wrappedItem.label; -// const deleting = l10n.t('Deleting registry "{0}"...', node.registryName); -// await window.withProgress({ location: ProgressLocation.Notification, title: deleting }, async () => { -// await node.deleteTreeItem(context); -// }); + const confirmDelete: string = l10n.t('Are you sure you want to delete registry "{0}" and its associated images?', registryName); + // no need to check result - cancel will throw a UserCancelledError + await context.ui.showWarningMessage(confirmDelete, { modal: true }, DialogResponses.deleteResponse); -// const message = l10n.t('Successfully deleted registry "{0}".', node.registryName); -// // don't wait -// /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ -// window.showInformationMessage(message); -// } + const deleting = l10n.t('Deleting registry "{0}"...', registryName); + await window.withProgress({ location: ProgressLocation.Notification, title: deleting }, async () => { + const azureRegistryDataProvider = node.provider as unknown as AzureRegistryDataProvider; + await azureRegistryDataProvider.deleteRegistry(node.wrappedItem); + }); + + ext.registriesTree.refresh(); + + const message = l10n.t('Successfully deleted registry "{0}".', registryName); + // don't wait + /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ + window.showInformationMessage(message); +} diff --git a/src/commands/registries/azure/deleteAzureRepository.ts b/src/commands/registries/azure/deleteAzureRepository.ts index 57e8e4de49..542f5610bb 100644 --- a/src/commands/registries/azure/deleteAzureRepository.ts +++ b/src/commands/registries/azure/deleteAzureRepository.ts @@ -1,30 +1,33 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ -// import { DialogResponses, IActionContext } from '@microsoft/vscode-azext-utils'; -// import { l10n, ProgressLocation, window } from 'vscode'; -// import { ext } from '../../../extensionVariables'; -// import type { AzureRepositoryTreeItem } from '../../../tree/registries/azure/AzureRepositoryTreeItem'; -// import { registryExpectedContextValues } from '../../../tree/registries/registryContextValues'; +import { DialogResponses, IActionContext, contextValueExperience } from '@microsoft/vscode-azext-utils'; +import { ProgressLocation, l10n, window } from 'vscode'; +import { ext } from '../../../extensionVariables'; +import { AzureRegistryDataProvider, AzureRepository } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; +import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; -// export async function deleteAzureRepository(context: IActionContext, node?: AzureRepositoryTreeItem): Promise { -// if (!node) { -// node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.repository, { ...context, suppressCreatePick: true }); -// } +export async function deleteAzureRepository(context: IActionContext, node?: UnifiedRegistryItem): Promise { + if (!node) { + node = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerRepository' }); + } -// const confirmDelete = l10n.t('Are you sure you want to delete repository "{0}" and its associated images?', node.repoName); -// // no need to check result - cancel will throw a UserCancelledError -// await context.ui.showWarningMessage(confirmDelete, { modal: true }, DialogResponses.deleteResponse); + const confirmDelete = l10n.t('Are you sure you want to delete repository "{0}" and its associated images?', node.wrappedItem.label); + // no need to check result - cancel will throw a UserCancelledError + await context.ui.showWarningMessage(confirmDelete, { modal: true }, DialogResponses.deleteResponse); -// const deleting = l10n.t('Deleting repository "{0}"...', node.repoName); -// await window.withProgress({ location: ProgressLocation.Notification, title: deleting }, async () => { -// await node.deleteTreeItem(context); -// }); + const deleting = l10n.t('Deleting repository "{0}"...', node.wrappedItem.label); + await window.withProgress({ location: ProgressLocation.Notification, title: deleting }, async () => { + const azureDataProvider = node.provider as unknown as AzureRegistryDataProvider; + await azureDataProvider.deleteRepository(node.wrappedItem); + }); -// const deleteSucceeded = l10n.t('Successfully deleted repository "{0}".', node.repoName); -// // don't wait -// /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ -// window.showInformationMessage(deleteSucceeded); -// } + ext.registriesTree.refresh(); + + const deleteSucceeded = l10n.t('Successfully deleted repository "{0}".', node.wrappedItem.label); + // don't wait + /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ + window.showInformationMessage(deleteSucceeded); +} diff --git a/src/commands/registries/azure/openInAzurePortal.ts b/src/commands/registries/azure/openInAzurePortal.ts index 71c284f7d3..2dd09988a7 100644 --- a/src/commands/registries/azure/openInAzurePortal.ts +++ b/src/commands/registries/azure/openInAzurePortal.ts @@ -3,24 +3,28 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext } from '@microsoft/vscode-azext-utils'; +import { IActionContext, UserCancelledError, contextValueExperience } from '@microsoft/vscode-azext-utils'; +import * as vscode from 'vscode'; +import { ext } from '../../../extensionVariables'; +import { AzureRegistry, AzureSubscriptionRegistryItem, isAzureRegistryItem, isAzureSubscriptionRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; -export async function openInAzurePortal(context: IActionContext, node?: UnifiedRegistryItem): Promise { - // if (!node) { - // node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.registry, context); - // } +export async function openInAzurePortal(context: IActionContext, node?: UnifiedRegistryItem): Promise { + if (!node) { + node = await contextValueExperience(context, ext.registriesTree, { include: ['azuresubscription', 'azureContainerRegistry'] }); + } - // const azSubTreeItem = await getAzSubTreeItem(); - // const azExtAzureUtils = await getAzExtAzureUtils(); + const azureRegistryItem = node.wrappedItem; + const baseUrl = `${azureRegistryItem.subscription.environment.portalUrl}/#@${azureRegistryItem.subscription.tenantId}/resource`; + let url: string; - // if (node instanceof azSubTreeItem.SubscriptionTreeItem) { - // await azExtAzureUtils.openInPortal(node.subscription, node.subscription.subscriptionId); - // } else if (node instanceof AzureRegistryTreeItem) { - // await azExtAzureUtils.openInPortal(node.parent.subscription, node.registryId); - // } else { - // await azExtAzureUtils.openInPortal(node.parent.parent.subscription, `${node.parent.registryId}/repository`); - // } + if (isAzureSubscriptionRegistryItem(azureRegistryItem)) { + url = `${baseUrl}/subscriptions/${azureRegistryItem.subscription.subscriptionId}`; + } else if (isAzureRegistryItem(azureRegistryItem)) { + url = `${baseUrl}/${azureRegistryItem.id}`; + } else { + throw new UserCancelledError(); + } - // TODO: review this later + await vscode.env.openExternal(vscode.Uri.parse(url)); } diff --git a/src/commands/registries/azure/tasks/buildImageInAzure.ts b/src/commands/registries/azure/tasks/buildImageInAzure.ts index f2d25ba469..7d1cca1471 100644 --- a/src/commands/registries/azure/tasks/buildImageInAzure.ts +++ b/src/commands/registries/azure/tasks/buildImageInAzure.ts @@ -1,36 +1,37 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ -// import type { Run } from '@azure/arm-containerregistry'; -// import { IActionContext, UserCancelledError } from '@microsoft/vscode-azext-utils'; -// import * as vscode from 'vscode'; -// import { getArmContainerRegistry } from '../../../../utils/lazyPackages'; -// import { delay } from '../../../../utils/promiseUtils'; +import type { Run } from '@azure/arm-containerregistry'; +import { IActionContext, UserCancelledError } from '@microsoft/vscode-azext-utils'; +import * as vscode from 'vscode'; +import { getArmContainerRegistry } from '../../../../utils/lazyPackages'; +import { delay } from '../../../../utils/promiseUtils'; +import { RootStrategy, scheduleRunRequest } from './scheduleRunRequest'; -// const WAIT_MS = 5000; +const WAIT_MS = 5000; -// export async function buildImageInAzure(context: IActionContext, uri?: vscode.Uri | undefined, rootStrategy?: RootStrategy | undefined): Promise { -// if (!vscode.workspace.isTrusted) { -// throw new UserCancelledError('enforceTrust'); -// } +export async function buildImageInAzure(context: IActionContext, uri?: vscode.Uri | undefined, rootStrategy?: RootStrategy | undefined): Promise { + if (!vscode.workspace.isTrusted) { + throw new UserCancelledError('enforceTrust'); + } -// const getRun = await scheduleRunRequest(context, "DockerBuildRequest", uri, rootStrategy); + const getRun = await scheduleRunRequest(context, "DockerBuildRequest", uri, rootStrategy); -// let run = await getRun(); -// const { KnownRunStatus } = await getArmContainerRegistry(); -// while ( -// run.status === KnownRunStatus.Started || -// run.status === KnownRunStatus.Queued || -// run.status === KnownRunStatus.Running -// ) { -// await delay(WAIT_MS); -// run = await getRun(); -// } + let run = await getRun(); + const { KnownRunStatus } = await getArmContainerRegistry(); + while ( + run.status === KnownRunStatus.Started || + run.status === KnownRunStatus.Queued || + run.status === KnownRunStatus.Running + ) { + await delay(WAIT_MS); + run = await getRun(); + } -// // we are returning the run so that other extensions can consume this with the vscode.commands.executeCommand -// // currently it is used by the ms-kubernetes-tools.aks-devx-tools extension (https://github.com/Azure/aks-devx-tools) -// return run; -// } + // we are returning the run so that other extensions can consume this with the vscode.commands.executeCommand + // currently it is used by the ms-kubernetes-tools.aks-devx-tools extension (https://github.com/Azure/aks-devx-tools) + return run; +} diff --git a/src/commands/registries/azure/tasks/scheduleRunRequest.ts b/src/commands/registries/azure/tasks/scheduleRunRequest.ts index e47f9d3460..cc6e7c3816 100644 --- a/src/commands/registries/azure/tasks/scheduleRunRequest.ts +++ b/src/commands/registries/azure/tasks/scheduleRunRequest.ts @@ -1,216 +1,223 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ - -// import type { DockerBuildRequest as AcrDockerBuildRequest, FileTaskRunRequest as AcrFileTaskRunRequest, OS as AcrOS, Run as AcrRun, ContainerRegistryManagementClient } from "@azure/arm-containerregistry"; // These are only dev-time imports so don't need to be lazy -// import { IActionContext, IAzureQuickPickItem, nonNullProp } from '@microsoft/vscode-azext-utils'; -// import * as fse from 'fs-extra'; -// import * as os from 'os'; -// import * as path from 'path'; -// import * as readline from 'readline'; -// import * as tar from 'tar'; -// import * as vscode from 'vscode'; -// import { ext } from '../../../../extensionVariables'; -// import { getStorageBlob } from '../../../../utils/lazyPackages'; -// import { delay } from '../../../../utils/promiseUtils'; -// import { Item, quickPickDockerFileItem, quickPickYamlFileItem } from '../../../../utils/quickPickFile'; -// import { quickPickWorkspaceFolder } from '../../../../utils/quickPickWorkspaceFolder'; -// import { addImageTaggingTelemetry, getTagFromUserInput } from '../../../images/tagImage'; - -// const idPrecision = 6; -// const vcsIgnoreList = ['.git', '.gitignore', '.bzr', 'bzrignore', '.hg', '.hgignore', '.svn']; - -// // this is used by the ms-kubernetes-tools.aks-devx-tools extension (https://github.com/Azure/aks-devx-tools) -// export enum RootStrategy { -// Default = 'Default', -// DockerfileFolder = 'DockerfileFolder', -// } - -// export async function scheduleRunRequest(context: IActionContext, requestType: 'DockerBuildRequest' | 'FileTaskRunRequest', uri: vscode.Uri | undefined, rootStrategy?: RootStrategy | undefined): Promise<() => Promise> { -// // Acquire information. -// let rootFolder: vscode.WorkspaceFolder; -// let fileItem: Item; -// let imageName: string; -// if (requestType === 'DockerBuildRequest') { -// rootFolder = await quickPickWorkspaceFolder(context, vscode.l10n.t('To quick build Docker files you must first open a folder or workspace in VS Code.')); -// fileItem = await quickPickDockerFileItem(context, uri, rootFolder); -// imageName = await quickPickImageName(context, rootFolder, fileItem); -// } else if (requestType === 'FileTaskRunRequest') { -// rootFolder = await quickPickWorkspaceFolder(context, vscode.l10n.t('To run a task from a .yaml file you must first open a folder or workspace in VS Code.')); -// fileItem = await quickPickYamlFileItem(context, uri, rootFolder, vscode.l10n.t('To run a task from a .yaml file you must have yaml file in your VS Code workspace.')); -// } else { -// throw new Error(vscode.l10n.t('Run Request Type Currently not supported.')); -// } - -// const node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.registry, context); - -// const osPick = ['Linux', 'Windows'].map(item => >{ label: item, data: item }); -// const osType: AcrOS = (await context.ui.showQuickPick(osPick, { placeHolder: vscode.l10n.t('Select image base OS') })).data; - -// const tarFilePath: string = getTempSourceArchivePath(); - -// try { -// // Prepare to run. -// ext.outputChannel.show(); - -// let rootUri = rootFolder.uri; -// if (rootStrategy === RootStrategy.DockerfileFolder) { -// // changes the root to the folder where the Dockerfile is -// // it is used by the ms-kubernetes-tools.aks-devx-tools extension (https://github.com/Azure/aks-devx-tools) -// rootUri = vscode.Uri.file(path.dirname(fileItem.absoluteFilePath)); -// } - -// const uploadedSourceLocation: string = await uploadSourceCode(await node.getClient(context), node.registryName, node.resourceGroup, rootUri, tarFilePath); -// ext.outputChannel.info(vscode.l10n.t('Uploaded source code from {0}', tarFilePath)); - -// let runRequest: AcrDockerBuildRequest | AcrFileTaskRunRequest; -// if (requestType === 'DockerBuildRequest') { -// runRequest = { -// type: requestType, -// imageNames: [imageName], -// isPushEnabled: true, -// sourceLocation: uploadedSourceLocation, -// platform: { os: osType }, -// dockerFilePath: path.relative(rootUri.fsPath, fileItem.absoluteFilePath) -// }; -// } else { -// runRequest = { -// type: 'FileTaskRunRequest', -// taskFilePath: path.relative(rootUri.fsPath, fileItem.absoluteFilePath), -// sourceLocation: uploadedSourceLocation, -// platform: { os: osType } -// }; -// } - -// // Schedule the run and Clean up. -// ext.outputChannel.info(vscode.l10n.t('Set up run request')); - -// const client = await node.getClient(context); -// const run = await client.registries.beginScheduleRunAndWait(node.resourceGroup, node.registryName, runRequest); -// ext.outputChannel.info(vscode.l10n.t('Scheduled run {0}', run.runId)); - -// void streamLogs(context, node, run); - -// // function returns the AcrRun info -// return async () => client.runs.get(node.resourceGroup, node.registryName, run.runId); -// } finally { -// if (await fse.pathExists(tarFilePath)) { -// await fse.unlink(tarFilePath); -// } -// } -// } - -// async function quickPickImageName(context: IActionContext, rootFolder: vscode.WorkspaceFolder, dockerFileItem: Item | undefined): Promise { -// const absFilePath: string = path.join(rootFolder.uri.fsPath, dockerFileItem.relativeFilePath); -// const dockerFileKey = `ACR_buildTag_${absFilePath}`; -// const prevImageName: string | undefined = ext.context.workspaceState.get(dockerFileKey); -// let suggestedImageName: string; - -// if (!prevImageName) { -// // Get imageName based on name of subfolder containing the Dockerfile, or else workspacefolder -// suggestedImageName = path.basename(dockerFileItem.relativeFolderPath).toLowerCase(); -// if (suggestedImageName === '.') { -// suggestedImageName = path.basename(rootFolder.uri.fsPath).toLowerCase().replace(/\s/g, ''); -// } - -// suggestedImageName += ":{{.Run.ID}}"; -// } else { -// suggestedImageName = prevImageName; -// } - -// // Temporary work-around for vscode bug where valueSelection can be messed up if a quick pick is followed by a showInputBox -// await delay(500); - -// addImageTaggingTelemetry(context, suggestedImageName, '.before'); -// const imageName: string = await getTagFromUserInput(context, suggestedImageName); -// addImageTaggingTelemetry(context, imageName, '.after'); - -// await ext.context.workspaceState.update(dockerFileKey, imageName); -// return imageName; -// } - -// async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, rootFolder: vscode.Uri, tarFilePath: string): Promise { -// ext.outputChannel.info(vscode.l10n.t(' Sending source code to temp file')); -// const source: string = rootFolder.fsPath; -// let items = await fse.readdir(source); -// items = items.filter(i => !(i in vcsIgnoreList)); -// // tslint:disable-next-line:no-unsafe-any -// tar.c({ cwd: source }, items).pipe(fse.createWriteStream(tarFilePath)); - -// ext.outputChannel.info(vscode.l10n.t(' Getting build source upload URL')); -// const sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); -// const uploadUrl: string = sourceUploadLocation.uploadUrl; -// const relativePath: string = sourceUploadLocation.relativePath; - -// const storageBlob = await getStorageBlob(); -// const blobClient = new storageBlob.BlockBlobClient(uploadUrl); -// ext.outputChannel.info(vscode.l10n.t(' Creating block blob')); -// await blobClient.uploadFile(tarFilePath); - -// return relativePath; -// } - -// const blobCheckInterval = 1000; -// const maxBlobChecks = 30; -// async function streamLogs(context: IActionContext, node: AzureRegistryTreeItem, run: AcrRun): Promise { -// const result = await (await node.getClient(context)).runs.getLogSasUrl(node.resourceGroup, node.registryName, run.runId); - -// const storageBlob = await getStorageBlob(); -// const blobClient = new storageBlob.BlobClient(nonNullProp(result, 'logLink')); - -// // Start streaming the response to the output channel -// let byteOffset = 0; -// let totalChecks = 0; -// let exists = false; - -// await new Promise((resolve, reject) => { -// const timer = setInterval( -// async () => { -// try { -// if (!exists && !(exists = await blobClient.exists())) { -// totalChecks++; -// if (totalChecks >= maxBlobChecks) { -// clearInterval(timer); -// reject('Not found'); -// } -// } - -// const properties = await blobClient.getProperties(); -// if (properties.contentLength > byteOffset) { -// // New data available -// const response = await blobClient.download(byteOffset); -// byteOffset += response.contentLength; - -// const lineReader = readline.createInterface(response.readableStreamBody); -// for await (const line of lineReader) { -// const sanitizedLine = line -// // eslint-disable-next-line no-control-regex -// .replace(/[\x00-\x09\x0B-\x0C\x0E-\x1F]/g, ''); // Remove non-printing control characters -// ext.outputChannel.info(sanitizedLine); -// } -// } - -// if (properties.metadata?.complete) { -// clearInterval(timer); -// resolve(); -// } -// } catch (err) { -// clearInterval(timer); -// reject(err); -// } -// }, -// blobCheckInterval -// ); -// }); -// } - -// function getTempSourceArchivePath(): string { -// /* tslint:disable-next-line:insecure-random */ -// const id: number = Math.floor(Math.random() * Math.pow(10, idPrecision)); -// const archive = `sourceArchive${id}.tar.gz`; -// ext.outputChannel.info(vscode.l10n.t('Setting up temp file with \'{0}\'', archive)); -// const tarFilePath: string = path.join(os.tmpdir(), archive); -// return tarFilePath; -// } -// TODO: review this later +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { DockerBuildRequest as AcrDockerBuildRequest, FileTaskRunRequest as AcrFileTaskRunRequest, OS as AcrOS, Run as AcrRun, ContainerRegistryManagementClient } from "@azure/arm-containerregistry"; // These are only dev-time imports so don't need to be lazy +import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; +import { IActionContext, IAzureQuickPickItem, contextValueExperience, nonNullProp } from '@microsoft/vscode-azext-utils'; +import * as fse from 'fs-extra'; +import * as os from 'os'; +import * as path from 'path'; +import * as readline from 'readline'; +import * as tar from 'tar'; +import * as vscode from 'vscode'; +import { ext } from '../../../../extensionVariables'; +import { AzureRegistryItem } from "../../../../tree/registries/Azure/AzureRegistryDataProvider"; +import { UnifiedRegistryItem } from "../../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { createAzureClient } from "../../../../tree/registries/registryTreeUtils"; +import { getStorageBlob } from '../../../../utils/lazyPackages'; +import { delay } from '../../../../utils/promiseUtils'; +import { Item, quickPickDockerFileItem, quickPickYamlFileItem } from '../../../../utils/quickPickFile'; +import { quickPickWorkspaceFolder } from '../../../../utils/quickPickWorkspaceFolder'; +import { addImageTaggingTelemetry, getTagFromUserInput } from '../../../images/tagImage'; + +const idPrecision = 6; +const vcsIgnoreList = ['.git', '.gitignore', '.bzr', 'bzrignore', '.hg', '.hgignore', '.svn']; + +// this is used by the ms-kubernetes-tools.aks-devx-tools extension (https://github.com/Azure/aks-devx-tools) +export enum RootStrategy { + Default = 'Default', + DockerfileFolder = 'DockerfileFolder', +} + +export async function scheduleRunRequest(context: IActionContext, requestType: 'DockerBuildRequest' | 'FileTaskRunRequest', uri: vscode.Uri | undefined, rootStrategy?: RootStrategy | undefined): Promise<() => Promise> { + // Acquire information. + let rootFolder: vscode.WorkspaceFolder; + let fileItem: Item; + let imageName: string; + if (requestType === 'DockerBuildRequest') { + rootFolder = await quickPickWorkspaceFolder(context, vscode.l10n.t('To quick build Docker files you must first open a folder or workspace in VS Code.')); + fileItem = await quickPickDockerFileItem(context, uri, rootFolder); + imageName = await quickPickImageName(context, rootFolder, fileItem); + } else if (requestType === 'FileTaskRunRequest') { + rootFolder = await quickPickWorkspaceFolder(context, vscode.l10n.t('To run a task from a .yaml file you must first open a folder or workspace in VS Code.')); + fileItem = await quickPickYamlFileItem(context, uri, rootFolder, vscode.l10n.t('To run a task from a .yaml file you must have yaml file in your VS Code workspace.')); + } else { + throw new Error(vscode.l10n.t('Run Request Type Currently not supported.')); + } + + const node: UnifiedRegistryItem = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerRegistry' }); + const registry: AzureRegistryItem = node.wrappedItem; + const resourceGroup = getResourceGroupFromId(registry.id); + + const osPick = ['Linux', 'Windows'].map(item => >{ label: item, data: item }); + const osType: AcrOS = (await context.ui.showQuickPick(osPick, { placeHolder: vscode.l10n.t('Select image base OS') })).data; + + const tarFilePath: string = getTempSourceArchivePath(); + + try { + // Prepare to run. + ext.outputChannel.show(); + + let rootUri = rootFolder.uri; + if (rootStrategy === RootStrategy.DockerfileFolder) { + // changes the root to the folder where the Dockerfile is + // it is used by the ms-kubernetes-tools.aks-devx-tools extension (https://github.com/Azure/aks-devx-tools) + rootUri = vscode.Uri.file(path.dirname(fileItem.absoluteFilePath)); + } + + const azureRegistryClient = await createAzureClient(registry.subscription); + const uploadedSourceLocation: string = await uploadSourceCode(azureRegistryClient, registry.subscription.subscriptionId, resourceGroup, rootUri, tarFilePath); + ext.outputChannel.info(vscode.l10n.t('Uploaded source code from {0}', tarFilePath)); + + let runRequest: AcrDockerBuildRequest | AcrFileTaskRunRequest; + if (requestType === 'DockerBuildRequest') { + runRequest = { + type: requestType, + imageNames: [imageName], + isPushEnabled: true, + sourceLocation: uploadedSourceLocation, + platform: { os: osType }, + dockerFilePath: path.relative(rootUri.fsPath, fileItem.absoluteFilePath) + }; + } else { + runRequest = { + type: 'FileTaskRunRequest', + taskFilePath: path.relative(rootUri.fsPath, fileItem.absoluteFilePath), + sourceLocation: uploadedSourceLocation, + platform: { os: osType } + }; + } + + // Schedule the run and Clean up. + ext.outputChannel.info(vscode.l10n.t('Set up run request')); + + const run = await azureRegistryClient.registries.beginScheduleRunAndWait(resourceGroup, registry.label, runRequest); + ext.outputChannel.info(vscode.l10n.t('Scheduled run {0}', run.runId)); + + void streamLogs(context, node, run); + + // function returns the AcrRun info + return async () => azureRegistryClient.runs.get(resourceGroup, registry.label, run.runId); + } finally { + if (await fse.pathExists(tarFilePath)) { + await fse.unlink(tarFilePath); + } + } +} + +async function quickPickImageName(context: IActionContext, rootFolder: vscode.WorkspaceFolder, dockerFileItem: Item | undefined): Promise { + const absFilePath: string = path.join(rootFolder.uri.fsPath, dockerFileItem.relativeFilePath); + const dockerFileKey = `ACR_buildTag_${absFilePath}`; + const prevImageName: string | undefined = ext.context.workspaceState.get(dockerFileKey); + let suggestedImageName: string; + + if (!prevImageName) { + // Get imageName based on name of subfolder containing the Dockerfile, or else workspacefolder + suggestedImageName = path.basename(dockerFileItem.relativeFolderPath).toLowerCase(); + if (suggestedImageName === '.') { + suggestedImageName = path.basename(rootFolder.uri.fsPath).toLowerCase().replace(/\s/g, ''); + } + + suggestedImageName += ":{{.Run.ID}}"; + } else { + suggestedImageName = prevImageName; + } + + // Temporary work-around for vscode bug where valueSelection can be messed up if a quick pick is followed by a showInputBox + await delay(500); + + addImageTaggingTelemetry(context, suggestedImageName, '.before'); + const imageName: string = await getTagFromUserInput(context, suggestedImageName); + addImageTaggingTelemetry(context, imageName, '.after'); + + await ext.context.workspaceState.update(dockerFileKey, imageName); + return imageName; +} + +async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, rootFolder: vscode.Uri, tarFilePath: string): Promise { + ext.outputChannel.info(vscode.l10n.t(' Sending source code to temp file')); + const source: string = rootFolder.fsPath; + let items = await fse.readdir(source); + items = items.filter(i => !(i in vcsIgnoreList)); + // tslint:disable-next-line:no-unsafe-any + tar.c({ cwd: source }, items).pipe(fse.createWriteStream(tarFilePath)); + + ext.outputChannel.info(vscode.l10n.t(' Getting build source upload URL')); + const sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); + const uploadUrl: string = sourceUploadLocation.uploadUrl; + const relativePath: string = sourceUploadLocation.relativePath; + + const storageBlob = await getStorageBlob(); + const blobClient = new storageBlob.BlockBlobClient(uploadUrl); + ext.outputChannel.info(vscode.l10n.t(' Creating block blob')); + await blobClient.uploadFile(tarFilePath); + + return relativePath; +} + +const blobCheckInterval = 1000; +const maxBlobChecks = 30; +async function streamLogs(context: IActionContext, node: UnifiedRegistryItem, run: AcrRun): Promise { + const azureRegistryClient = await createAzureClient(node.wrappedItem.subscription); + const resourceGroup = getResourceGroupFromId(node.wrappedItem.id); + const result = await azureRegistryClient.runs.getLogSasUrl(resourceGroup, node.wrappedItem.label, run.runId); + + const storageBlob = await getStorageBlob(); + const blobClient = new storageBlob.BlobClient(nonNullProp(result, 'logLink')); + + // Start streaming the response to the output channel + let byteOffset = 0; + let totalChecks = 0; + let exists = false; + + await new Promise((resolve, reject) => { + const timer = setInterval( + async () => { + try { + if (!exists && !(exists = await blobClient.exists())) { + totalChecks++; + if (totalChecks >= maxBlobChecks) { + clearInterval(timer); + reject('Not found'); + } + } + + const properties = await blobClient.getProperties(); + if (properties.contentLength > byteOffset) { + // New data available + const response = await blobClient.download(byteOffset); + byteOffset += response.contentLength; + + const lineReader = readline.createInterface(response.readableStreamBody); + for await (const line of lineReader) { + const sanitizedLine = line + // eslint-disable-next-line no-control-regex + .replace(/[\x00-\x09\x0B-\x0C\x0E-\x1F]/g, ''); // Remove non-printing control characters + ext.outputChannel.info(sanitizedLine); + } + } + + if (properties.metadata?.complete) { + clearInterval(timer); + resolve(); + } + } catch (err) { + clearInterval(timer); + reject(err); + } + }, + blobCheckInterval + ); + }); +} + +function getTempSourceArchivePath(): string { + /* tslint:disable-next-line:insecure-random */ + const id: number = Math.floor(Math.random() * Math.pow(10, idPrecision)); + const archive = `sourceArchive${id}.tar.gz`; + ext.outputChannel.info(vscode.l10n.t('Setting up temp file with \'{0}\'', archive)); + const tarFilePath: string = path.join(os.tmpdir(), archive); + return tarFilePath; +} diff --git a/src/commands/registries/copyRemoteFullTag.ts b/src/commands/registries/copyRemoteFullTag.ts index a85d67a329..4cf7ba996c 100644 --- a/src/commands/registries/copyRemoteFullTag.ts +++ b/src/commands/registries/copyRemoteFullTag.ts @@ -8,7 +8,7 @@ import { CommonTag } from '@microsoft/vscode-docker-registries'; import * as vscode from 'vscode'; import { ext } from '../../extensionVariables'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; -import { getFullImageNameFromRegistryItem } from '../../tree/registries/getInformationFromRegistryItem'; +import { getFullImageNameFromRegistryItem } from '../../tree/registries/registryTreeUtils'; export async function copyRemoteFullTag(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { diff --git a/src/commands/registries/pullImages.ts b/src/commands/registries/pullImages.ts index d77620b4c5..738b05b1af 100644 --- a/src/commands/registries/pullImages.ts +++ b/src/commands/registries/pullImages.ts @@ -8,7 +8,7 @@ import { CommonRegistry, CommonRepository, CommonTag } from '@microsoft/vscode-d import { ext } from '../../extensionVariables'; import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; -import { getImageNameFromRegistryItem } from '../../tree/registries/getInformationFromRegistryItem'; +import { getImageNameFromRegistryItem } from '../../tree/registries/registryTreeUtils'; import { logInToDockerCli } from './logInToDockerCli'; export async function pullRepository(context: IActionContext, node?: UnifiedRegistryItem): Promise { diff --git a/src/tree/registries/Azure/AzureRegistryDataProvider.ts b/src/tree/registries/Azure/AzureRegistryDataProvider.ts index 3ec224bc34..16957ca891 100644 --- a/src/tree/registries/Azure/AzureRegistryDataProvider.ts +++ b/src/tree/registries/Azure/AzureRegistryDataProvider.ts @@ -5,25 +5,34 @@ import type { Registry as AcrRegistry } from '@azure/arm-containerregistry'; import { AzureSubscription, VSCodeAzureSubscriptionProvider } from '@microsoft/vscode-azext-azureauth'; -import { RegistryV2DataProvider, V2Registry, V2RegistryItem } from '@microsoft/vscode-docker-registries'; +import { RegistryV2DataProvider, V2Registry, V2RegistryItem, V2Repository, registryV2Request } from '@microsoft/vscode-docker-registries'; import { CommonRegistryItem, isRegistryRoot } from '@microsoft/vscode-docker-registries/lib/clients/Common/models'; import * as vscode from 'vscode'; +import { getResourceGroupFromId } from '../../../utils/azureUtils'; +import { createAzureClient } from '../registryTreeUtils'; import { ACROAuthProvider } from './ACROAuthProvider'; -interface AzureRegistryItem extends V2RegistryItem { +export interface AzureRegistryItem extends V2RegistryItem { readonly subscription: AzureSubscription; + readonly id: string; } -interface AzureSubscriptionRegistryItem extends CommonRegistryItem { +export interface AzureSubscriptionRegistryItem extends CommonRegistryItem { readonly subscription: AzureSubscription; readonly type: 'azuresubscription'; } -function isAzureSubscriptionRegistryItem(item: unknown): item is AzureSubscriptionRegistryItem { +export type AzureRegistry = V2Registry & AzureRegistryItem; + +export type AzureRepository = V2Repository; + +export function isAzureSubscriptionRegistryItem(item: unknown): item is AzureSubscriptionRegistryItem { return !!item && typeof item === 'object' && (item as AzureSubscriptionRegistryItem).type === 'azuresubscription'; } -type AzureRegistry = V2Registry & AzureRegistryItem; +export function isAzureRegistryItem(item: unknown): item is AzureRegistry { + return !!item && typeof item === 'object' && (item as AzureRegistryItem).additionalContextValues?.includes('azureContainerRegistry'); +} export class AzureRegistryDataProvider extends RegistryV2DataProvider implements vscode.Disposable { public readonly id = 'vscode-docker.azureContainerRegistry'; @@ -77,7 +86,7 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements this.subscriptionProvider.dispose(); } - public async getRegistries(subscriptionItem: CommonRegistryItem): Promise { + public override async getRegistries(subscriptionItem: CommonRegistryItem): Promise { subscriptionItem = subscriptionItem as AzureSubscriptionRegistryItem; // TODO: replace this with `createAzureClient` const acrClient = new (await import('@azure/arm-containerregistry')).ContainerRegistryManagementClient(subscriptionItem.subscription.credential, subscriptionItem.subscription.subscriptionId); @@ -93,13 +102,41 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements parent: subscriptionItem, type: 'commonregistry', baseUrl: vscode.Uri.parse(`https://${registry.loginServer}`), + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion label: registry.name!, iconPath: vscode.Uri.joinPath(this.extensionContext.extensionUri, 'resources', 'azureRegistry.svg'), subscription: subscriptionItem.subscription, + additionalContextValues: ['azureContainerRegistry'], + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + id: registry.id! }; }); } + public override async getRepositories(registry: AzureRegistry): Promise { + const catalogResponse = await registryV2Request<{ repositories: string[] }>({ + authenticationProvider: this.getAuthenticationProvider(registry), + method: 'GET', + registryUri: registry.baseUrl, + path: ['v2', '_catalog'], + scopes: ['registry:catalog:*'], + }); + + const results: AzureRepository[] = []; + + for (const repository of catalogResponse.body?.repositories || []) { + results.push({ + parent: registry, + baseUrl: registry.baseUrl, + label: repository, + type: 'commonrepository', + additionalContextValues: ['azureContainerRepository'] + }); + } + + return results; + } + public override getTreeItem(element: CommonRegistryItem): Promise { if (isAzureSubscriptionRegistryItem(element)) { return Promise.resolve({ @@ -113,6 +150,28 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements } } + public async deleteRepository(item: AzureRepository): Promise { + const authenticationProvider = this.getAuthenticationProvider(item.parent as unknown as AzureRegistryItem); + + const reponse = await registryV2Request({ + method: 'DELETE', + registryUri: item.baseUrl, + path: ['v2', '_acr', `${item.label}`, 'repository'], + scopes: [`repository:${item.label}:delete`], + authenticationProvider: authenticationProvider, + }); + + if (!reponse.succeeded) { + throw new Error(`Failed to delete repository: ${reponse.statusText}`); + } + } + + public async deleteRegistry(item: AzureRegistry): Promise { + const client = await createAzureClient(item.subscription); + const resourceGroup = getResourceGroupFromId(item.id); + await client.registries.beginDeleteAndWait(resourceGroup, item.label); + } + protected override getAuthenticationProvider(item: AzureRegistryItem): ACROAuthProvider { const registryString = item.baseUrl.toString(); @@ -121,6 +180,7 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements this.authenticationProviders.set(registryString, provider); } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return this.authenticationProviders.get(registryString)!; } } diff --git a/src/tree/registries/getInformationFromRegistryItem.ts b/src/tree/registries/registryTreeUtils.ts similarity index 78% rename from src/tree/registries/getInformationFromRegistryItem.ts rename to src/tree/registries/registryTreeUtils.ts index eccab72abf..b51236f0ca 100644 --- a/src/tree/registries/getInformationFromRegistryItem.ts +++ b/src/tree/registries/registryTreeUtils.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ContainerRegistryManagementClient } from "@azure/arm-containerregistry"; +import { AzureSubscription } from "@microsoft/vscode-azext-azureauth"; import { CommonRegistry, CommonRepository, CommonTag, isRegistry, isRepository, isTag } from "@microsoft/vscode-docker-registries"; import { UnifiedRegistryItem } from "./UnifiedRegistryTreeDataProvider"; @@ -34,3 +36,7 @@ export function getFullImageNameFromRegistryItem(node: UnifiedRegistryItem { + return new (await import('@azure/arm-containerregistry')).ContainerRegistryManagementClient(subscriptionItem.credential, subscriptionItem.subscriptionId); +} From e090fb409410d31a5aacc7e596269f80e04feb06 Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Thu, 10 Aug 2023 09:42:00 -0400 Subject: [PATCH 03/43] Added Back `createAzureRegistry` command (#4029) * adjusted scheduleRunReuqest to work with new registries tree * delete azure repository command implementation * actually added back delete azure repository * added back delete Azure Registry command * changed registry tree util file name to be more general * added openInAzurePortalCommand * added back create azure registry command * move createAzureClient to azureUtils --- package.json | 2 +- .../registries/azure/createAzureRegistry.ts | 52 ++++++++++++++--- .../registries/azure/deleteAzureRegistry.ts | 2 +- .../registries/azure/deleteAzureRepository.ts | 2 +- .../azure/tasks/scheduleRunRequest.ts | 6 +- .../Azure/AzureRegistryDataProvider.ts | 31 +++------- .../createWizard/AzureRegistryCreateStep.ts | 58 +++++++++++++++++++ .../createWizard/AzureRegistryNameStep.ts | 50 ++++++++++++++++ .../createWizard/AzureRegistrySkuStep.ts | 23 ++++++++ .../IAzureRegistryWizardContext.ts | 15 +++++ src/tree/registries/registryTreeUtils.ts | 6 -- src/utils/azureUtils.ts | 7 +++ 12 files changed, 211 insertions(+), 43 deletions(-) create mode 100644 src/tree/registries/Azure/createWizard/AzureRegistryCreateStep.ts create mode 100644 src/tree/registries/Azure/createWizard/AzureRegistryNameStep.ts create mode 100644 src/tree/registries/Azure/createWizard/AzureRegistrySkuStep.ts create mode 100644 src/tree/registries/Azure/createWizard/IAzureRegistryWizardContext.ts diff --git a/package.json b/package.json index 659e06e9bf..e4aa4d60eb 100644 --- a/package.json +++ b/package.json @@ -481,7 +481,7 @@ }, { "command": "vscode-docker.registries.azure.createRegistry", - "when": "view == dockerRegistries && viewItem == azureextensionui.azureSubscription", + "when": "view == dockerRegistries && viewItem =~ /azuresubscription/i", "group": "regs_1_general@1" }, { diff --git a/src/commands/registries/azure/createAzureRegistry.ts b/src/commands/registries/azure/createAzureRegistry.ts index a569cd5833..30d4efd305 100644 --- a/src/commands/registries/azure/createAzureRegistry.ts +++ b/src/commands/registries/azure/createAzureRegistry.ts @@ -3,18 +3,54 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext } from '@microsoft/vscode-azext-utils'; -// import type { SubscriptionTreeItem } from '../../../tree/registries/azure/SubscriptionTreeItem'; // These are only dev-time imports so don't need to be lazy +import { AzureWizard, IActionContext, contextValueExperience, createSubscriptionContext, nonNullProp } from '@microsoft/vscode-azext-utils'; +import { l10n, window } from 'vscode'; +import { ext } from '../../../extensionVariables'; +import { AzureSubscriptionRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; +import { AzureRegistryCreateStep } from '../../../tree/registries/Azure/createWizard/AzureRegistryCreateStep'; +import { AzureRegistryNameStep } from '../../../tree/registries/Azure/createWizard/AzureRegistryNameStep'; +import { AzureRegistrySkuStep } from '../../../tree/registries/Azure/createWizard/AzureRegistrySkuStep'; +import { IAzureRegistryWizardContext } from '../../../tree/registries/Azure/createWizard/IAzureRegistryWizardContext'; import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; -// import { getAzSubTreeItem } from '../../../utils/lazyPackages'; +import { getAzExtAzureUtils } from '../../../utils/lazyPackages'; -export async function createAzureRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { - // const azSubTreeItem = await getAzSubTreeItem(); +export async function createAzureRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - // node = await ext.registriesTree.showTreeItemPicker(azSubTreeItem.SubscriptionTreeItem.contextValue, context); + node = await contextValueExperience(context, ext.registriesTree, { include: 'azuresubscription' }); } - // await node.createChild(context); - // TODO: review this later + const subscriptionContext = createSubscriptionContext(node.wrappedItem.subscription); + const wizardContext: IAzureRegistryWizardContext = { + ...context, + ...subscriptionContext, + azureSubscription: node.wrappedItem.subscription, + }; + const azExtAzureUtils = await getAzExtAzureUtils(); + + const promptSteps = [ + new AzureRegistryNameStep(), + new AzureRegistrySkuStep(), + new azExtAzureUtils.ResourceGroupListStep(), + ]; + azExtAzureUtils.LocationListStep.addStep(wizardContext, promptSteps); + + const wizard = new AzureWizard( + wizardContext, + { + promptSteps, + executeSteps: [ + new AzureRegistryCreateStep() + ], + title: l10n.t('Create new Azure Container Registry') + } + ); + + await wizard.prompt(); + const newRegistryName: string = nonNullProp(wizardContext, 'newRegistryName'); + await wizard.execute(); + + /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ + window.showInformationMessage(`Successfully created registry "${newRegistryName}".`); + void ext.registriesTree.refresh(); } diff --git a/src/commands/registries/azure/deleteAzureRegistry.ts b/src/commands/registries/azure/deleteAzureRegistry.ts index febc0e4625..e6f059b123 100644 --- a/src/commands/registries/azure/deleteAzureRegistry.ts +++ b/src/commands/registries/azure/deleteAzureRegistry.ts @@ -26,7 +26,7 @@ export async function deleteAzureRegistry(context: IActionContext, node?: Unifie await azureRegistryDataProvider.deleteRegistry(node.wrappedItem); }); - ext.registriesTree.refresh(); + void ext.registriesTree.refresh(); const message = l10n.t('Successfully deleted registry "{0}".', registryName); // don't wait diff --git a/src/commands/registries/azure/deleteAzureRepository.ts b/src/commands/registries/azure/deleteAzureRepository.ts index 542f5610bb..627b347a7d 100644 --- a/src/commands/registries/azure/deleteAzureRepository.ts +++ b/src/commands/registries/azure/deleteAzureRepository.ts @@ -24,7 +24,7 @@ export async function deleteAzureRepository(context: IActionContext, node?: Unif await azureDataProvider.deleteRepository(node.wrappedItem); }); - ext.registriesTree.refresh(); + void ext.registriesTree.refresh(); const deleteSucceeded = l10n.t('Successfully deleted repository "{0}".', node.wrappedItem.label); // don't wait diff --git a/src/commands/registries/azure/tasks/scheduleRunRequest.ts b/src/commands/registries/azure/tasks/scheduleRunRequest.ts index cc6e7c3816..d0cf24e22d 100644 --- a/src/commands/registries/azure/tasks/scheduleRunRequest.ts +++ b/src/commands/registries/azure/tasks/scheduleRunRequest.ts @@ -15,7 +15,7 @@ import * as vscode from 'vscode'; import { ext } from '../../../../extensionVariables'; import { AzureRegistryItem } from "../../../../tree/registries/Azure/AzureRegistryDataProvider"; import { UnifiedRegistryItem } from "../../../../tree/registries/UnifiedRegistryTreeDataProvider"; -import { createAzureClient } from "../../../../tree/registries/registryTreeUtils"; +import { createAzureContainerRegistryClient } from "../../../../utils/azureUtils"; import { getStorageBlob } from '../../../../utils/lazyPackages'; import { delay } from '../../../../utils/promiseUtils'; import { Item, quickPickDockerFileItem, quickPickYamlFileItem } from '../../../../utils/quickPickFile'; @@ -67,7 +67,7 @@ export async function scheduleRunRequest(context: IActionContext, requestType: ' rootUri = vscode.Uri.file(path.dirname(fileItem.absoluteFilePath)); } - const azureRegistryClient = await createAzureClient(registry.subscription); + const azureRegistryClient = await createAzureContainerRegistryClient(registry.subscription); const uploadedSourceLocation: string = await uploadSourceCode(azureRegistryClient, registry.subscription.subscriptionId, resourceGroup, rootUri, tarFilePath); ext.outputChannel.info(vscode.l10n.t('Uploaded source code from {0}', tarFilePath)); @@ -160,7 +160,7 @@ async function uploadSourceCode(client: ContainerRegistryManagementClient, regis const blobCheckInterval = 1000; const maxBlobChecks = 30; async function streamLogs(context: IActionContext, node: UnifiedRegistryItem, run: AcrRun): Promise { - const azureRegistryClient = await createAzureClient(node.wrappedItem.subscription); + const azureRegistryClient = await createAzureContainerRegistryClient(node.wrappedItem.subscription); const resourceGroup = getResourceGroupFromId(node.wrappedItem.id); const result = await azureRegistryClient.runs.getLogSasUrl(resourceGroup, node.wrappedItem.label, run.runId); diff --git a/src/tree/registries/Azure/AzureRegistryDataProvider.ts b/src/tree/registries/Azure/AzureRegistryDataProvider.ts index 16957ca891..89a185cb66 100644 --- a/src/tree/registries/Azure/AzureRegistryDataProvider.ts +++ b/src/tree/registries/Azure/AzureRegistryDataProvider.ts @@ -8,8 +8,7 @@ import { AzureSubscription, VSCodeAzureSubscriptionProvider } from '@microsoft/v import { RegistryV2DataProvider, V2Registry, V2RegistryItem, V2Repository, registryV2Request } from '@microsoft/vscode-docker-registries'; import { CommonRegistryItem, isRegistryRoot } from '@microsoft/vscode-docker-registries/lib/clients/Common/models'; import * as vscode from 'vscode'; -import { getResourceGroupFromId } from '../../../utils/azureUtils'; -import { createAzureClient } from '../registryTreeUtils'; +import { createAzureContainerRegistryClient, getResourceGroupFromId } from '../../../utils/azureUtils'; import { ACROAuthProvider } from './ACROAuthProvider'; export interface AzureRegistryItem extends V2RegistryItem { @@ -114,27 +113,13 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements } public override async getRepositories(registry: AzureRegistry): Promise { - const catalogResponse = await registryV2Request<{ repositories: string[] }>({ - authenticationProvider: this.getAuthenticationProvider(registry), - method: 'GET', - registryUri: registry.baseUrl, - path: ['v2', '_catalog'], - scopes: ['registry:catalog:*'], - }); - - const results: AzureRepository[] = []; - - for (const repository of catalogResponse.body?.repositories || []) { - results.push({ - parent: registry, - baseUrl: registry.baseUrl, - label: repository, - type: 'commonrepository', - additionalContextValues: ['azureContainerRepository'] - }); - } + const repositories = await super.getRepositories(registry); + const repositoriesWithAdditionalContext = repositories.map(repository => ({ + ...repository, + additionalContextValues: ['azureContainerRepository'] + })); - return results; + return repositoriesWithAdditionalContext; } public override getTreeItem(element: CommonRegistryItem): Promise { @@ -167,7 +152,7 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements } public async deleteRegistry(item: AzureRegistry): Promise { - const client = await createAzureClient(item.subscription); + const client = await createAzureContainerRegistryClient(item.subscription); const resourceGroup = getResourceGroupFromId(item.id); await client.registries.beginDeleteAndWait(resourceGroup, item.label); } diff --git a/src/tree/registries/Azure/createWizard/AzureRegistryCreateStep.ts b/src/tree/registries/Azure/createWizard/AzureRegistryCreateStep.ts new file mode 100644 index 0000000000..43a330c51d --- /dev/null +++ b/src/tree/registries/Azure/createWizard/AzureRegistryCreateStep.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { AzExtLocation } from '@microsoft/vscode-azext-azureutils'; +import { AzureWizardExecuteStep, nonNullProp, parseError } from '@microsoft/vscode-azext-utils'; +import { Progress, l10n } from 'vscode'; +import { ext } from '../../../../extensionVariables'; +import { createAzureContainerRegistryClient } from '../../../../utils/azureUtils'; +import { getAzExtAzureUtils } from '../../../../utils/lazyPackages'; +import { IAzureRegistryWizardContext } from './IAzureRegistryWizardContext'; + +export class AzureRegistryCreateStep extends AzureWizardExecuteStep { + public priority: number = 130; + + public async execute(context: IAzureRegistryWizardContext, progress: Progress<{ message?: string; increment?: number }>): Promise { + const newRegistryName = nonNullProp(context, 'newRegistryName'); + + const client = await createAzureContainerRegistryClient(context.azureSubscription); + + const azExtAzureUtils = await getAzExtAzureUtils(); + const creating: string = l10n.t('Creating registry "{0}"...', newRegistryName); + ext.outputChannel.info(creating); + progress.report({ message: creating }); + + const location: AzExtLocation = await azExtAzureUtils.LocationListStep.getLocation(context); + const locationName: string = nonNullProp(location, 'name'); + const resourceGroup = nonNullProp(context, 'resourceGroup'); + try { + context.registry = await client.registries.beginCreateAndWait( + nonNullProp(resourceGroup, 'name'), + newRegistryName, + { + sku: { + name: nonNullProp(context, 'newRegistrySku') + }, + location: locationName + } + ); + } + catch (err) { + const parsedError = parseError(err); + if (parsedError.errorType === 'MissingSubscriptionRegistration') { + context.errorHandling.suppressReportIssue = true; + } + + throw err; + } + + const created = l10n.t('Successfully created registry "{0}".', newRegistryName); + ext.outputChannel.info(created); + } + + public shouldExecute(context: IAzureRegistryWizardContext): boolean { + return !context.registry; + } +} diff --git a/src/tree/registries/Azure/createWizard/AzureRegistryNameStep.ts b/src/tree/registries/Azure/createWizard/AzureRegistryNameStep.ts new file mode 100644 index 0000000000..14843f6b4b --- /dev/null +++ b/src/tree/registries/Azure/createWizard/AzureRegistryNameStep.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { ContainerRegistryManagementClient } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy +import { AzureNameStep } from '@microsoft/vscode-azext-utils'; +import { l10n } from 'vscode'; +import { getArmContainerRegistry, getAzExtAzureUtils } from '../../../../utils/lazyPackages'; +import { IAzureRegistryWizardContext } from './IAzureRegistryWizardContext'; + +export class AzureRegistryNameStep extends AzureNameStep { + protected async isRelatedNameAvailable(context: IAzureRegistryWizardContext, name: string): Promise { + const azExtAzureUtils = await getAzExtAzureUtils(); + return await azExtAzureUtils.ResourceGroupListStep.isNameAvailable(context, name); + } + + public async prompt(context: IAzureRegistryWizardContext): Promise { + const azExtAzureUtils = await getAzExtAzureUtils(); + const armContainerRegistry = await getArmContainerRegistry(); + const client = azExtAzureUtils.createAzureClient(context, armContainerRegistry.ContainerRegistryManagementClient); + context.newRegistryName = (await context.ui.showInputBox({ + placeHolder: l10n.t('Registry name'), + prompt: l10n.t('Provide a registry name'), + /* eslint-disable-next-line @typescript-eslint/promise-function-async */ + validateInput: (name: string) => validateRegistryName(name, client) + })).trim(); + + context.relatedNameTask = this.generateRelatedName(context, context.newRegistryName, azExtAzureUtils.resourceGroupNamingRules); + } + + public shouldPrompt(context: IAzureRegistryWizardContext): boolean { + return !context.newRegistryName; + } +} + +async function validateRegistryName(name: string, client: ContainerRegistryManagementClient): Promise { + name = name ? name.trim() : ''; + + const min = 5; + const max = 50; + if (name.length < min || name.length > max) { + return l10n.t('The name must be between {0} and {1} characters.', min, max); + } else if (name.match(/[^a-z0-9]/i)) { + return l10n.t('The name can only contain alphanumeric characters.'); + } else { + const nameStatus = await client.registries.checkNameAvailability({ name, type: 'Microsoft.ContainerRegistry/registries' }); + return nameStatus.message; + } +} diff --git a/src/tree/registries/Azure/createWizard/AzureRegistrySkuStep.ts b/src/tree/registries/Azure/createWizard/AzureRegistrySkuStep.ts new file mode 100644 index 0000000000..bbfc3684c1 --- /dev/null +++ b/src/tree/registries/Azure/createWizard/AzureRegistrySkuStep.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { SkuName as AcrSkuName } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy +import { AzureWizardPromptStep, IAzureQuickPickItem } from '@microsoft/vscode-azext-utils'; +import { l10n } from 'vscode'; +import { IAzureRegistryWizardContext } from './IAzureRegistryWizardContext'; + +export class AzureRegistrySkuStep extends AzureWizardPromptStep { + public async prompt(context: IAzureRegistryWizardContext): Promise { + const skus: AcrSkuName[] = ["Basic", "Standard", "Premium"]; + const picks: IAzureQuickPickItem[] = skus.map(s => { return { label: s, data: s }; }); + + const placeHolder: string = l10n.t('Select a SKU'); + context.newRegistrySku = (await context.ui.showQuickPick(picks, { placeHolder })).data; + } + + public shouldPrompt(context: IAzureRegistryWizardContext): boolean { + return !context.newRegistrySku; + } +} diff --git a/src/tree/registries/Azure/createWizard/IAzureRegistryWizardContext.ts b/src/tree/registries/Azure/createWizard/IAzureRegistryWizardContext.ts new file mode 100644 index 0000000000..65b42efa4c --- /dev/null +++ b/src/tree/registries/Azure/createWizard/IAzureRegistryWizardContext.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { Registry as AcrRegistry, SkuName as AcrSkuName } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy +import { AzureSubscription } from '@microsoft/vscode-azext-azureauth'; +import { IResourceGroupWizardContext } from '@microsoft/vscode-azext-azureutils'; + +export interface IAzureRegistryWizardContext extends IResourceGroupWizardContext { + newRegistryName?: string; + newRegistrySku?: AcrSkuName; + registry?: AcrRegistry; + readonly azureSubscription: AzureSubscription; +} diff --git a/src/tree/registries/registryTreeUtils.ts b/src/tree/registries/registryTreeUtils.ts index b51236f0ca..eccab72abf 100644 --- a/src/tree/registries/registryTreeUtils.ts +++ b/src/tree/registries/registryTreeUtils.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ContainerRegistryManagementClient } from "@azure/arm-containerregistry"; -import { AzureSubscription } from "@microsoft/vscode-azext-azureauth"; import { CommonRegistry, CommonRepository, CommonTag, isRegistry, isRepository, isTag } from "@microsoft/vscode-docker-registries"; import { UnifiedRegistryItem } from "./UnifiedRegistryTreeDataProvider"; @@ -36,7 +34,3 @@ export function getFullImageNameFromRegistryItem(node: UnifiedRegistryItem { - return new (await import('@azure/arm-containerregistry')).ContainerRegistryManagementClient(subscriptionItem.credential, subscriptionItem.subscriptionId); -} diff --git a/src/utils/azureUtils.ts b/src/utils/azureUtils.ts index 25c9bf7977..dbcc5ae66f 100644 --- a/src/utils/azureUtils.ts +++ b/src/utils/azureUtils.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import type { ContainerRegistryManagementClient } from '@azure/arm-containerregistry'; +import { AzureSubscription } from '@microsoft/vscode-azext-azureauth'; import { ISubscriptionContext } from '@microsoft/vscode-azext-utils'; import { Request } from 'node-fetch'; import { URLSearchParams } from 'url'; @@ -69,4 +71,9 @@ export async function acquireAcrRefreshToken(registryHost: string, subContext: I return (await response.json()).refresh_token; } + +export async function createAzureContainerRegistryClient(subscriptionItem: AzureSubscription): Promise { + return new (await import('@azure/arm-containerregistry')).ContainerRegistryManagementClient(subscriptionItem.credential, subscriptionItem.subscriptionId); +} + /* eslint-enable @typescript-eslint/naming-convention */ From bdac0832785a9f9dbd264c0c8fc99d8bb04cd789 Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Fri, 11 Aug 2023 12:23:49 -0400 Subject: [PATCH 04/43] Added More Commands Back (#4031) * adjusted scheduleRunReuqest to work with new registries tree * delete azure repository command implementation * actually added back delete azure repository * added back delete Azure Registry command * changed registry tree util file name to be more general * added openInAzurePortalCommand * added back create azure registry command * move createAzureClient to azureUtils * added viewAzureProperties command back (partially) * small tweak to make build image in azure work * added untag azure image commad * added logout of docker cli command * add case sensitivity operator to be consistent * Update src/tree/registries/Azure/AzureRegistryDataProvider.ts Co-authored-by: Brandon Waterloo [MSFT] <36966225+bwateratmsft@users.noreply.github.com> * added void * removed symbol as it already exists --------- Co-authored-by: Brandon Waterloo [MSFT] <36966225+bwateratmsft@users.noreply.github.com> --- package.json | 8 +-- src/commands/registerCommands.ts | 9 ++- .../azure/tasks/scheduleRunRequest.ts | 2 +- .../registries/azure/untagAzureImage.ts | 58 +++++++++---------- .../registries/azure/viewAzureProperties.ts | 31 +++++----- src/commands/registries/logInToDockerCli.ts | 2 +- src/commands/registries/logOutOfDockerCli.ts | 48 ++++++++------- .../Azure/AzureRegistryDataProvider.ts | 41 +++++++++++-- .../UnifiedRegistryTreeDataProvider.ts | 6 +- 9 files changed, 118 insertions(+), 87 deletions(-) diff --git a/package.json b/package.json index e4aa4d60eb..809414b5b0 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ ], "editor/context": [ { - "when": "isWorkspaceTrusted && editorLangId == dockerfile && isAzureAccountInstalled", + "when": "isWorkspaceTrusted && editorLangId == dockerfile", "command": "vscode-docker.registries.azure.buildImage", "group": "docker" }, @@ -186,7 +186,7 @@ ], "explorer/context": [ { - "when": "isWorkspaceTrusted && resourceFilename =~ /dockerfile/i && isAzureAccountInstalled", + "when": "isWorkspaceTrusted && resourceFilename =~ /dockerfile/i", "command": "vscode-docker.registries.azure.buildImage", "group": "docker" }, @@ -526,7 +526,7 @@ }, { "command": "vscode-docker.registries.azure.untagImage", - "when": "view == dockerRegistries && viewItem == azure;DockerV2;Tag;", + "when": "view == dockerRegistries && viewItem =~ /azureContainerTag/i", "group": "regs_tag_2_destructive@1" }, { @@ -571,7 +571,7 @@ }, { "command": "vscode-docker.registries.azure.viewProperties", - "when": "view == dockerRegistries && viewItem =~ /azure(TaskRun|;DockerV2;Registry;)/", + "when": "view == dockerRegistries && viewItem =~ /azureContainerRegistry/i", "group": "regs_zzz_common@2" }, { diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index 15671deed9..cd0610c4d7 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -71,10 +71,13 @@ import { connectRegistry } from "./registries/connectRegistry"; import { deleteAzureRegistry } from "./registries/azure/deleteAzureRegistry"; import { deleteAzureRepository } from "./registries/azure/deleteAzureRepository"; import { buildImageInAzure } from "./registries/azure/tasks/buildImageInAzure"; +import { untagAzureImage } from "./registries/azure/untagAzureImage"; +import { viewAzureProperties } from "./registries/azure/viewAzureProperties"; import { copyRemoteFullTag } from "./registries/copyRemoteFullTag"; import { copyRemoteImageDigest } from "./registries/copyRemoteImageDigest"; import { disconnectRegistry } from "./registries/disconnectRegistry"; import { logInToDockerCli } from "./registries/logInToDockerCli"; +import { logOutOfDockerCli } from "./registries/logOutOfDockerCli"; import { pullImageFromRepository, pullRepository } from "./registries/pullImages"; import { registryHelp } from "./registries/registryHelp"; import { openDockerDownloadPage } from "./showDockerLearnMoreNotification"; @@ -178,7 +181,7 @@ export function registerCommands(): void { registerCommand('vscode-docker.registries.disconnectRegistry', disconnectRegistry); registerCommand('vscode-docker.registries.help', registryHelp); registerWorkspaceCommand('vscode-docker.registries.logInToDockerCli', logInToDockerCli); - // registerWorkspaceCommand('vscode-docker.registries.logOutOfDockerCli', logOutOfDockerCli); + registerWorkspaceCommand('vscode-docker.registries.logOutOfDockerCli', logOutOfDockerCli); registerWorkspaceCommand('vscode-docker.registries.pullImage', pullImageFromRepository); registerWorkspaceCommand('vscode-docker.registries.pullRepository', pullRepository); // registerCommand('vscode-docker.registries.reconnectRegistry', reconnectRegistry); @@ -193,8 +196,8 @@ export function registerCommands(): void { registerCommand('vscode-docker.registries.azure.runTask', runAzureTask); // registerWorkspaceCommand('vscode-docker.registries.azure.runFileAsTask', runFileAsAzureTask); registerCommand('vscode-docker.registries.azure.selectSubscriptions', () => commands.executeCommand("azure-account.selectSubscriptions")); - // registerCommand('vscode-docker.registries.azure.untagImage', untagAzureImage); - // registerCommand('vscode-docker.registries.azure.viewProperties', viewAzureProperties); + registerCommand('vscode-docker.registries.azure.untagImage', untagAzureImage); + registerCommand('vscode-docker.registries.azure.viewProperties', viewAzureProperties); // registerCommand('vscode-docker.registries.azure.viewTaskLogs', viewAzureTaskLogs); registerCommand('vscode-docker.volumes.configureExplorer', configureVolumesExplorer); diff --git a/src/commands/registries/azure/tasks/scheduleRunRequest.ts b/src/commands/registries/azure/tasks/scheduleRunRequest.ts index d0cf24e22d..265e7ac862 100644 --- a/src/commands/registries/azure/tasks/scheduleRunRequest.ts +++ b/src/commands/registries/azure/tasks/scheduleRunRequest.ts @@ -68,7 +68,7 @@ export async function scheduleRunRequest(context: IActionContext, requestType: ' } const azureRegistryClient = await createAzureContainerRegistryClient(registry.subscription); - const uploadedSourceLocation: string = await uploadSourceCode(azureRegistryClient, registry.subscription.subscriptionId, resourceGroup, rootUri, tarFilePath); + const uploadedSourceLocation: string = await uploadSourceCode(azureRegistryClient, registry.label, resourceGroup, rootUri, tarFilePath); ext.outputChannel.info(vscode.l10n.t('Uploaded source code from {0}', tarFilePath)); let runRequest: AcrDockerBuildRequest | AcrFileTaskRunRequest; diff --git a/src/commands/registries/azure/untagAzureImage.ts b/src/commands/registries/azure/untagAzureImage.ts index 7db25a4dc5..a005951259 100644 --- a/src/commands/registries/azure/untagAzureImage.ts +++ b/src/commands/registries/azure/untagAzureImage.ts @@ -1,36 +1,32 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * 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 "@microsoft/vscode-azext-utils"; -// import { l10n, ProgressLocation, window } from "vscode"; -// import { ext } from "../../../extensionVariables"; -// import { registryExpectedContextValues } from "../../../tree/registries/registryContextValues"; -// import { RemoteTagTreeItem } from "../../../tree/registries/RemoteTagTreeItem"; -// import { registryRequest } from "../../../utils/registryRequestUtils"; +import { contextValueExperience, IActionContext } from "@microsoft/vscode-azext-utils"; +import { l10n, ProgressLocation, window } from "vscode"; +import { ext } from "../../../extensionVariables"; +import { AzureRegistryDataProvider, AzureTag } from "../../../tree/registries/Azure/AzureRegistryDataProvider"; +import { getFullImageNameFromRegistryItem } from "../../../tree/registries/registryTreeUtils"; +import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; -// export async function untagAzureImage(context: IActionContext, node?: RemoteTagTreeItem): Promise { -// if (!node) { -// node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.tag, { -// ...context, -// suppressCreatePick: true, -// noItemFoundErrorMessage: l10n.t('No images are available to untag') -// }); -// } +export async function untagAzureImage(context: IActionContext, node?: UnifiedRegistryItem): Promise { + if (!node) { + node = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerTag' }); + } -// const confirmUntag: string = l10n.t('Are you sure you want to untag image "{0}"? This does not delete the manifest referenced by the tag.', node.repoNameAndTag); -// // no need to check result - cancel will throw a UserCancelledError -// await context.ui.showWarningMessage(confirmUntag, { modal: true }, { title: "Untag" }); + const fullTag = getFullImageNameFromRegistryItem(node); + const confirmUntag: string = l10n.t('Are you sure you want to untag image "{0}"? This does not delete the manifest referenced by the tag.', fullTag); + // no need to check result - cancel will throw a UserCancelledError + await context.ui.showWarningMessage(confirmUntag, { modal: true }, { title: "Untag" }); -// const untagging = l10n.t('Untagging image "{0}"...', node.repoNameAndTag); -// const repoTI = node.parent; -// await window.withProgress({ location: ProgressLocation.Notification, title: untagging }, async () => { -// await registryRequest(repoTI, 'DELETE', `v2/_acr/${repoTI.repoName}/tags/${node.tag}`); -// await repoTI.refresh(context); -// }); + const untagging = l10n.t('Untagging image "{0}"...', fullTag); + await window.withProgress({ location: ProgressLocation.Notification, title: untagging }, async () => { + const provider = node.provider as unknown as AzureRegistryDataProvider; + await provider.deleteTag(node.wrappedItem); + }); -// // don't wait -// /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ -// window.showInformationMessage(l10n.t('Successfully untagged image "{0}".', node.repoNameAndTag)); -// } + // don't wait + /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ + window.showInformationMessage(l10n.t('Successfully untagged image "{0}".', fullTag)); +} diff --git a/src/commands/registries/azure/viewAzureProperties.ts b/src/commands/registries/azure/viewAzureProperties.ts index 4de3cd4a05..0b52efbc36 100644 --- a/src/commands/registries/azure/viewAzureProperties.ts +++ b/src/commands/registries/azure/viewAzureProperties.ts @@ -1,19 +1,18 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ -// import { IActionContext, openReadOnlyJson } from "@microsoft/vscode-azext-utils"; -// import { ext } from "../../../extensionVariables"; -// import { AzureRegistryTreeItem } from "../../../tree/registries/azure/AzureRegistryTreeItem"; -// import { AzureTaskRunTreeItem } from "../../../tree/registries/azure/AzureTaskRunTreeItem"; -// import { AzureTaskTreeItem } from "../../../tree/registries/azure/AzureTaskTreeItem"; -// import { registryExpectedContextValues } from "../../../tree/registries/registryContextValues"; +import { IActionContext, contextValueExperience, openReadOnlyJson } from "@microsoft/vscode-azext-utils"; +import { ext } from "../../../extensionVariables"; +import { AzureRegistry } from "../../../tree/registries/Azure/AzureRegistryDataProvider"; +import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; -// export async function viewAzureProperties(context: IActionContext, node?: AzureRegistryTreeItem | AzureTaskTreeItem | AzureTaskRunTreeItem): Promise { -// if (!node) { -// node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.azure.registry, context); -// } +export async function viewAzureProperties(context: IActionContext, node?: UnifiedRegistryItem): Promise { + // TODO: add logic for azure task and azure tree task + if (!node) { + node = await contextValueExperience(context, ext.registriesRoot, { 'include': 'azureContainerRegistry' }); + } -// await openReadOnlyJson(node, node.properties); -// } + await openReadOnlyJson({ label: node.wrappedItem.label, fullId: node.wrappedItem.id }, node.wrappedItem.registryProperties); +} diff --git a/src/commands/registries/logInToDockerCli.ts b/src/commands/registries/logInToDockerCli.ts index 2b7fd92b20..7b6a459978 100644 --- a/src/commands/registries/logInToDockerCli.ts +++ b/src/commands/registries/logInToDockerCli.ts @@ -12,7 +12,7 @@ import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDa export async function logInToDockerCli(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesRoot, { include: 'commonroot' }); + node = await contextValueExperience(context, ext.registriesRoot, { include: 'commonregistry' }); } const creds = await node.provider?.getLoginInformation?.(node.wrappedItem); diff --git a/src/commands/registries/logOutOfDockerCli.ts b/src/commands/registries/logOutOfDockerCli.ts index ccf26ec220..f0d212a69d 100644 --- a/src/commands/registries/logOutOfDockerCli.ts +++ b/src/commands/registries/logOutOfDockerCli.ts @@ -1,29 +1,27 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * 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 '@microsoft/vscode-azext-utils'; -// import { ext } from '../../extensionVariables'; -// import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; -// import { registryExpectedContextValues } from '../../tree/registries/registryContextValues'; -// import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase'; +import { IActionContext, contextValueExperience } from '@microsoft/vscode-azext-utils'; +import { CommonRegistry } from '@microsoft/vscode-docker-registries'; +import { ext } from '../../extensionVariables'; +import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; +import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; -// export async function logOutOfDockerCli(context: IActionContext, node?: RegistryTreeItemBase): Promise { -// if (!node) { -// node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.all.registry, context); -// } +export async function logOutOfDockerCli(context: IActionContext, node?: UnifiedRegistryItem): Promise { + if (!node) { + node = await contextValueExperience(context, ext.registriesTree, { include: 'commonregistry' }); + } -// const creds = await node.getDockerCliCredentials(); + const client = await ext.runtimeManager.getClient(); + const taskCRF = new TaskCommandRunnerFactory( + { + taskName: 'Docker' + } + ); -// const client = await ext.runtimeManager.getClient(); -// const taskCRF = new TaskCommandRunnerFactory( -// { -// taskName: 'Docker' -// } -// ); - -// await taskCRF.getCommandRunner()( -// client.logout({ registry: creds.registryPath }) -// ); -// } + await taskCRF.getCommandRunner()( + client.logout({ registry: node.wrappedItem.baseUrl?.toString() || '' }), + ); +} diff --git a/src/tree/registries/Azure/AzureRegistryDataProvider.ts b/src/tree/registries/Azure/AzureRegistryDataProvider.ts index 89a185cb66..f2e5cdaf7a 100644 --- a/src/tree/registries/Azure/AzureRegistryDataProvider.ts +++ b/src/tree/registries/Azure/AzureRegistryDataProvider.ts @@ -5,7 +5,7 @@ import type { Registry as AcrRegistry } from '@azure/arm-containerregistry'; import { AzureSubscription, VSCodeAzureSubscriptionProvider } from '@microsoft/vscode-azext-azureauth'; -import { RegistryV2DataProvider, V2Registry, V2RegistryItem, V2Repository, registryV2Request } from '@microsoft/vscode-docker-registries'; +import { RegistryV2DataProvider, V2Registry, V2RegistryItem, V2Repository, V2Tag, registryV2Request } from '@microsoft/vscode-docker-registries'; import { CommonRegistryItem, isRegistryRoot } from '@microsoft/vscode-docker-registries/lib/clients/Common/models'; import * as vscode from 'vscode'; import { createAzureContainerRegistryClient, getResourceGroupFromId } from '../../../utils/azureUtils'; @@ -21,10 +21,14 @@ export interface AzureSubscriptionRegistryItem extends CommonRegistryItem { readonly type: 'azuresubscription'; } -export type AzureRegistry = V2Registry & AzureRegistryItem; +export type AzureRegistry = V2Registry & AzureRegistryItem & { + readonly registryProperties: AcrRegistry; +}; export type AzureRepository = V2Repository; +export type AzureTag = V2Tag; + export function isAzureSubscriptionRegistryItem(item: unknown): item is AzureSubscriptionRegistryItem { return !!item && typeof item === 'object' && (item as AzureSubscriptionRegistryItem).type === 'azuresubscription'; } @@ -63,7 +67,8 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements label: sub.name, type: 'azuresubscription', subscription: sub, - additionalContextValues: ['azuresubscription'] + additionalContextValues: ['azuresubscription'], + iconPath: vscode.Uri.joinPath(this.extensionContext.extensionUri, 'resources', 'azureSubscription.svg'), } as AzureSubscriptionRegistryItem; }); } else if (isAzureSubscriptionRegistryItem(element)) { @@ -107,7 +112,8 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements subscription: subscriptionItem.subscription, additionalContextValues: ['azureContainerRegistry'], // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - id: registry.id! + id: registry.id!, + registryProperties: registry }; }); } @@ -122,6 +128,16 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements return repositoriesWithAdditionalContext; } + public override async getTags(repository: AzureRepository): Promise { + const tags = await super.getTags(repository); + const tagsWithAdditionalContext = tags.map(tag => ({ + ...tag, + additionalContextValues: ['azureContainerTag'] + })); + + return tagsWithAdditionalContext; + } + public override getTreeItem(element: CommonRegistryItem): Promise { if (isAzureSubscriptionRegistryItem(element)) { return Promise.resolve({ @@ -157,6 +173,23 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements await client.registries.beginDeleteAndWait(resourceGroup, item.label); } + public async deleteTag(item: AzureTag): Promise { + const authenticationProvider = this.getAuthenticationProvider(item.parent.parent as unknown as AzureRegistryItem); + + const reponse = await registryV2Request({ + method: 'DELETE', + registryUri: item.baseUrl, + path: ['v2', '_acr', `${item.parent.label}`, 'tags', `${item.label}`], + scopes: [`repository:${item.parent.label}:delete`], + authenticationProvider: authenticationProvider, + }); + + if (!reponse.succeeded) { + throw new Error(`Failed to delete tag: ${reponse.statusText}`); + + } + } + protected override getAuthenticationProvider(item: AzureRegistryItem): ACROAuthProvider { const registryString = item.baseUrl.toString(); diff --git a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts index 9df806fd6d..4b1ea3ef52 100644 --- a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts +++ b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts @@ -117,15 +117,17 @@ export class UnifiedRegistryTreeDataProvider implements vscode.TreeDataProvider< await this.storageMemento.update(ConnectedRegistryProvidersKey, Array.from(connectedProviderIdsSet)); } - this.refresh(); + void this.refresh(); } public async disconnectRegistryProvider(item: UnifiedRegistryItem): Promise { await item.provider?.onDisconnect?.(); + const newConnectedProviderIds = this.storageMemento .get(ConnectedRegistryProvidersKey, []) .filter(cpi => cpi !== item.provider.id); await this.storageMemento.update(ConnectedRegistryProvidersKey, newConnectedProviderIds); - this.refresh(); + + void this.refresh(); } } From c967595b312d6af72b69b224731730f77aad7502 Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Fri, 11 Aug 2023 13:47:26 -0400 Subject: [PATCH 05/43] Added Back Open Docker Hub Item in Browser (#4033) * added view docker hub item in browser command * updated npm package * Added More Commands Back (#4031) * adjusted scheduleRunReuqest to work with new registries tree * delete azure repository command implementation * actually added back delete azure repository * added back delete Azure Registry command * changed registry tree util file name to be more general * added openInAzurePortalCommand * added back create azure registry command * move createAzureClient to azureUtils * added viewAzureProperties command back (partially) * small tweak to make build image in azure work * added untag azure image commad * added logout of docker cli command * add case sensitivity operator to be consistent * Update src/tree/registries/Azure/AzureRegistryDataProvider.ts Co-authored-by: Brandon Waterloo [MSFT] <36966225+bwateratmsft@users.noreply.github.com> * added void * removed symbol as it already exists --------- Co-authored-by: Brandon Waterloo [MSFT] <36966225+bwateratmsft@users.noreply.github.com> * minor tweak to avoid index out of bounds * use built in methods to determine the type of item --------- Co-authored-by: Brandon Waterloo [MSFT] <36966225+bwateratmsft@users.noreply.github.com> --- package-lock.json | 2 +- package.json | 2 +- src/commands/registerCommands.ts | 3 +- .../dockerHub/openDockerHubInBrowser.ts | 52 ++++++++++--------- 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index d9409d10aa..324f739002 100644 --- a/package-lock.json +++ b/package-lock.json @@ -814,7 +814,7 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.0.1-alpha", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", - "integrity": "sha512-b7VTChrnCksWeobO7jgCqdoobtjJpAJKKVJg+dzF3fdw6ypxAXsmG70aiqKZGdkJaJJGq5rIn1lYc8JcCGlEJQ==", + "integrity": "sha512-iUMbTBE3+s6uWg/Upy8pl8x+JXBp74JmFDS62p60JcJyhl+pPNs2rp+cxLIrl4mH9Ml6vlZeZZCeTJJdq/H+Nw==", "license": "See LICENSE in the project root for license information.", "dependencies": { "node-fetch": "^2.6.11" diff --git a/package.json b/package.json index 809414b5b0..59cbea8447 100644 --- a/package.json +++ b/package.json @@ -566,7 +566,7 @@ }, { "command": "vscode-docker.registries.dockerHub.openInBrowser", - "when": "view == dockerRegistries && viewItem =~ /dockerHub;DockerHubV2;(Tag|Repository|Registry);/", + "when": "view == dockerRegistries && viewItem =~ /(dockerHubRegistry|dockerHubRepository|dockerHubTag)/i", "group": "regs_zzz_common@1" }, { diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index cd0610c4d7..5751ca6b74 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -76,6 +76,7 @@ import { viewAzureProperties } from "./registries/azure/viewAzureProperties"; import { copyRemoteFullTag } from "./registries/copyRemoteFullTag"; import { copyRemoteImageDigest } from "./registries/copyRemoteImageDigest"; import { disconnectRegistry } from "./registries/disconnectRegistry"; +import { openDockerHubInBrowser } from "./registries/dockerHub/openDockerHubInBrowser"; import { logInToDockerCli } from "./registries/logInToDockerCli"; import { logOutOfDockerCli } from "./registries/logOutOfDockerCli"; import { pullImageFromRepository, pullRepository } from "./registries/pullImages"; @@ -186,7 +187,7 @@ export function registerCommands(): void { registerWorkspaceCommand('vscode-docker.registries.pullRepository', pullRepository); // registerCommand('vscode-docker.registries.reconnectRegistry', reconnectRegistry); - // registerCommand('vscode-docker.registries.dockerHub.openInBrowser', openDockerHubInBrowser); + registerCommand('vscode-docker.registries.dockerHub.openInBrowser', openDockerHubInBrowser); registerWorkspaceCommand('vscode-docker.registries.azure.buildImage', buildImageInAzure); registerCommand('vscode-docker.registries.azure.createRegistry', createAzureRegistry); diff --git a/src/commands/registries/dockerHub/openDockerHubInBrowser.ts b/src/commands/registries/dockerHub/openDockerHubInBrowser.ts index f2ab582bc5..1278e564c3 100644 --- a/src/commands/registries/dockerHub/openDockerHubInBrowser.ts +++ b/src/commands/registries/dockerHub/openDockerHubInBrowser.ts @@ -1,30 +1,32 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ -// import { IActionContext, contextValueExperience } from "@microsoft/vscode-azext-utils"; -// import * as vscode from "vscode"; -// import { ext } from "../../../extensionVariables"; -// import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { IActionContext, contextValueExperience } from "@microsoft/vscode-azext-utils"; +import { CommonRegistryItem, isRegistry, isRepository, isTag } from "@microsoft/vscode-docker-registries"; +import * as vscode from "vscode"; +import { dockerHubUrl } from "../../../constants"; +import { ext } from "../../../extensionVariables"; +import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; -// export async function openDockerHubInBrowser(context: IActionContext, node?: UnifiedRegistryItem): Promise { -// if (!node) { -// node = await contextValueExperience(context, ext.registriesRoot, { include: 'dockerhubregistry' }); -// } +export async function openDockerHubInBrowser(context: IActionContext, node?: UnifiedRegistryItem): Promise { + if (!node) { + node = await contextValueExperience(context, ext.registriesRoot, { include: ['dockerHubRegistry', 'dockerHubRepository', 'dockerHubTag'] }); + } -// let url = dockerHubUrl; -// if (node instanceof DockerHubNamespaceTreeItem) { -// url += `u/${node.namespace}`; -// } else if (node instanceof DockerHubRepositoryTreeItem) { -// url += `r/${node.parent.namespace}/${node.repoName}`; -// } else { -// const repoTI = node.parent; -// url += `r/${repoTI.parent.namespace}/${repoTI.repoName}/tags`; -// } + let url = dockerHubUrl; + const dockerHubItem = node.wrappedItem; -// const url = ''; -// // TODO: review this later + if (isRegistry(dockerHubItem)) { + url = `${url}u/${dockerHubItem.label}`; + } else if (isRepository(dockerHubItem)) { + url = `${url}r/${dockerHubItem.parent.label}/${dockerHubItem.label}`; + } else if (isTag(dockerHubItem)) { + url = `${url}r/${dockerHubItem.parent.parent.label}/${dockerHubItem.parent.label}/tags`; + } else { + throw new Error(`Unexpected node type ${dockerHubItem.additionalContextValues || ''}`); + } -// await vscode.env.openExternal(vscode.Uri.parse(url)); -// } + await vscode.env.openExternal(vscode.Uri.parse(url)); +} From 6df7928f7c2c8389dc81a8cb9f31ced9ae3d4ea5 Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Thu, 17 Aug 2023 15:35:18 -0400 Subject: [PATCH 06/43] Added Back `pushImage()` (#4034) * make pushImage work * added getAllRegistries command * added getLoginInformation() * account for cases where registry in undefined * added filtering to make get all registries more optima * added logic to filter github repositories --- package.json | 2 +- src/commands/images/pushImage.ts | 170 ++++++++++-------- src/commands/images/tagImage.ts | 5 +- src/commands/registerCommands.ts | 31 +--- .../registries/azure/untagAzureImage.ts | 4 +- src/commands/registries/copyRemoteFullTag.ts | 4 +- src/commands/registries/pullImages.ts | 4 +- src/tree/registries/Azure/ACROAuthProvider.ts | 40 +++-- .../UnifiedRegistryTreeDataProvider.ts | 63 ++++++- src/tree/registries/registryTreeUtils.ts | 39 ++-- 10 files changed, 224 insertions(+), 138 deletions(-) diff --git a/package.json b/package.json index 59cbea8447..c3eeb9f80d 100644 --- a/package.json +++ b/package.json @@ -506,7 +506,7 @@ }, { "command": "vscode-docker.registries.copyRemoteFullTag", - "when": "view == dockerRegistries && viewItem =~ /(dockerHubTag|registryV2Tag)/i", + "when": "view == dockerRegistries && viewItem =~ /(dockerHubTag|registryV2Tag|azureContainerTag)/i", "group": "regs_tag_1_general@2" }, { diff --git a/src/commands/images/pushImage.ts b/src/commands/images/pushImage.ts index 114748bb41..3626f9ce36 100644 --- a/src/commands/images/pushImage.ts +++ b/src/commands/images/pushImage.ts @@ -1,78 +1,92 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ - -// import { IActionContext, NoResourceFoundError, contextValueExperience } from '@microsoft/vscode-azext-utils'; -// import * as vscode from 'vscode'; -// import { ext } from '../../extensionVariables'; -// import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; -// import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; -// import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; -// import { addImageTaggingTelemetry, tagImage } from './tagImage'; - -// export async function pushImage(context: IActionContext, node: ImageTreeItem | undefined): Promise { -// if (!node) { -// await ext.imagesTree.refresh(context); -// node = await ext.imagesTree.showTreeItemPicker(ImageTreeItem.contextValue, { -// ...context, -// noItemFoundErrorMessage: vscode.l10n.t('No images are available to push'), -// }); -// } - -// let connectedRegistry: UnifiedRegistryItem | undefined; - -// if (!node.fullTag.includes('/')) { -// // The registry to push to is indeterminate--could be Docker Hub, or could need tagging. -// const prompt: boolean = vscode.workspace.getConfiguration('docker').get('promptForRegistryWhenPushingImages', true); - -// // If the prompt setting is true, we'll ask; if not we'll assume Docker Hub. -// if (prompt) { -// try { -// connectedRegistry = await contextValueExperience(context, ext.registriesRoot, { include: 'commonregistry' }); -// } catch (error) { -// if (error instanceof NoResourceFoundError) { -// // Do nothing, move on without a selected registry -// } else { -// // Rethrow -// throw error; -// } -// } -// } else { -// // Try to find a connected Docker Hub registry (primarily for login credentials) -// connectedRegistry = await contextValueExperience(context, ext.registriesRoot, { include: 'dockerHubRegistry' }); -// } -// } else { -// // The registry to push to is determinate. If there's a connected registry in the tree view, we'll try to find it, to perform login ahead of time. -// // Registry path is everything up to the last slash. -// const baseImagePath = node.fullTag.substring(0, node.fullTag.lastIndexOf('/')); - -// const progressOptions: vscode.ProgressOptions = { -// location: vscode.ProgressLocation.Notification, -// title: vscode.l10n.t('Fetching login credentials...'), -// }; - -// // connectedRegistry = await vscode.window.withProgress(progressOptions, async () => await tryGetConnectedRegistryForPath(context, baseImagePath)); TODO: review this later -// } - -// // Give the user a chance to modify the tag however they want -// const finalTag = await tagImage(context, node, connectedRegistry); - -// // if (connectedRegistry && finalTag.startsWith(connectedRegistry.baseImagePath)) { -// // // If a registry was found/chosen and is still the same as the final tag's registry, try logging in -// // await vscode.commands.executeCommand('vscode-docker.registries.logInToDockerCli', connectedRegistry); -// // } TODO: review this later - -// addImageTaggingTelemetry(context, finalTag, ''); - -// const client = await ext.runtimeManager.getClient(); -// const taskCRF = new TaskCommandRunnerFactory( -// { -// taskName: finalTag -// } -// ); - -// await taskCRF.getCommandRunner()( -// client.pushImage({ imageRef: finalTag }) -// ); -// } +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IActionContext, NoResourceFoundError, contextValueExperience } from '@microsoft/vscode-azext-utils'; +import { CommonRegistry } from '@microsoft/vscode-docker-registries'; +import * as vscode from 'vscode'; +import { ext } from '../../extensionVariables'; +import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; +import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; +import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { getBaseImagePathFromRegistryItem } from '../../tree/registries/registryTreeUtils'; +import { addImageTaggingTelemetry, tagImage } from './tagImage'; + +export async function pushImage(context: IActionContext, node: ImageTreeItem | undefined): Promise { + if (!node) { + await ext.imagesTree.refresh(context); + node = await ext.imagesTree.showTreeItemPicker(ImageTreeItem.contextValue, { + ...context, + noItemFoundErrorMessage: vscode.l10n.t('No images are available to push'), + }); + } + + let connectedRegistry: UnifiedRegistryItem | undefined; + + if (!node.fullTag.includes('/')) { + // The registry to push to is indeterminate--could be Docker Hub, or could need tagging. + const prompt: boolean = vscode.workspace.getConfiguration('docker').get('promptForRegistryWhenPushingImages', true); + + // If the prompt setting is true, we'll ask; if not we'll assume Docker Hub. + if (prompt) { + try { + connectedRegistry = await contextValueExperience(context, ext.registriesTree, { include: ['commonregistry'] }); + } catch (error) { + if (error instanceof NoResourceFoundError) { + // Do nothing, move on without a selected registry + } else { + // Rethrow + throw error; + } + } + } else { + // Try to find a connected Docker Hub registry (primarily for login credentials) + connectedRegistry = await contextValueExperience(context, ext.registriesTree, { include: ['dockerHubRegistry'] }); + } + } else { + // The registry to push to is determinate. If there's a connected registry in the tree view, we'll try to find it, to perform login ahead of time. + // Registry path is everything up to the last slash. + const baseImagePath = node.fullTag.substring(0, node.fullTag.lastIndexOf('/')); + + const progressOptions: vscode.ProgressOptions = { + location: vscode.ProgressLocation.Notification, + title: vscode.l10n.t('Fetching login credentials...'), + }; + + connectedRegistry = await vscode.window.withProgress(progressOptions, async () => await tryGetConnectedRegistryForPath(context, baseImagePath)); + } + + // Give the user a chance to modify the tag however they want + const finalTag = await tagImage(context, node, connectedRegistry); + + if (connectedRegistry && finalTag.startsWith(getBaseImagePathFromRegistryItem(connectedRegistry.wrappedItem))) { + // If a registry was found/chosen and is still the same as the final tag's registry, try logging in + await vscode.commands.executeCommand('vscode-docker.registries.logInToDockerCli', connectedRegistry); + } + + addImageTaggingTelemetry(context, finalTag, ''); + + const client = await ext.runtimeManager.getClient(); + const taskCRF = new TaskCommandRunnerFactory( + { + taskName: finalTag + } + ); + + await taskCRF.getCommandRunner()( + client.pushImage({ imageRef: finalTag }) + ); +} + +async function tryGetConnectedRegistryForPath(context: IActionContext, baseImagePath: string): Promise | undefined> { + const allRegistries = await ext.registriesTree.getConnectedRegistries(vscode.Uri.parse(baseImagePath)); + + let matchedRegistry = allRegistries.find((registry) => getBaseImagePathFromRegistryItem(registry.wrappedItem) === baseImagePath); + + if (!matchedRegistry) { + matchedRegistry = await contextValueExperience(context, ext.registriesTree, { include: ['commonregistry'] }); + } + + return matchedRegistry; +} diff --git a/src/commands/images/tagImage.ts b/src/commands/images/tagImage.ts index aff407e5bd..ebc8554e61 100644 --- a/src/commands/images/tagImage.ts +++ b/src/commands/images/tagImage.ts @@ -4,10 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext, TelemetryProperties } from '@microsoft/vscode-azext-utils'; +import { CommonRegistry, isRegistry } from '@microsoft/vscode-docker-registries'; import * as vscode from 'vscode'; import { ext } from '../../extensionVariables'; import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { getBaseImagePathFromRegistryItem } from '../../tree/registries/registryTreeUtils'; export async function tagImage(context: IActionContext, node?: ImageTreeItem, registry?: UnifiedRegistryItem): Promise { if (!node) { @@ -19,7 +21,8 @@ export async function tagImage(context: IActionContext, node?: ImageTreeItem, re } addImageTaggingTelemetry(context, node.fullTag, '.before'); - const newTaggedName: string = await getTagFromUserInput(context, node.fullTag, ''); // TODO: review this later + const baseImagePath = isRegistry(registry) ? getBaseImagePathFromRegistryItem(registry.wrappedItem as CommonRegistry) : undefined; + const newTaggedName: string = await getTagFromUserInput(context, node.fullTag, baseImagePath); addImageTaggingTelemetry(context, newTaggedName, '.after'); await ext.runWithDefaults(client => diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index 5751ca6b74..641055ba06 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -31,17 +31,19 @@ import { inspectDockerContext } from "./context/inspectDockerContext"; import { removeDockerContext } from "./context/removeDockerContext"; import { useDockerContext } from "./context/useDockerContext"; import { help } from "./help"; -// import { buildImage } from "./images/buildImage"; +import { buildImage } from "./images/buildImage"; import { configureImagesExplorer } from "./images/configureImagesExplorer"; import { copyFullTag } from "./images/copyFullTag"; import { inspectImage } from "./images/inspectImage"; import { pruneImages } from "./images/pruneImages"; import { pullImage } from "./images/pullImage"; +import { pushImage } from "./images/pushImage"; import { removeImage } from "./images/removeImage"; import { removeImageGroup } from "./images/removeImageGroup"; import { runAzureCliImage } from "./images/runAzureCliImage"; import { runImage, runImageInteractive } from "./images/runImage"; import { hideDanglingImages, setInitialDanglingContextValue, showDanglingImages } from "./images/showDanglingImages"; +import { tagImage } from "./images/tagImage"; import { configureNetworksExplorer } from "./networks/configureNetworksExplorer"; import { createNetwork } from "./networks/createNetwork"; import { inspectNetwork } from "./networks/inspectNetwork"; @@ -50,29 +52,14 @@ import { removeNetwork } from "./networks/removeNetwork"; import { pruneSystem } from "./pruneSystem"; import { registerWorkspaceCommand } from "./registerWorkspaceCommand"; import { createAzureRegistry } from "./registries/azure/createAzureRegistry"; -// import { deleteAzureRegistry } from "./registries/azure/deleteAzureRegistry"; -// import { deleteAzureRepository } from "./registries/azure/deleteAzureRepository"; -// import { deployImageToAzure } from "./registries/azure/deployImageToAzure"; -import { openInAzurePortal } from "./registries/azure/openInAzurePortal"; -// import { buildImageInAzure } from "./registries/azure/tasks/buildImageInAzure"; -import { runAzureTask } from "./registries/azure/tasks/runAzureTask"; -// import { runFileAsAzureTask } from "./registries/azure/tasks/runFileAsAzureTask"; -// import { viewAzureTaskLogs } from "./registries/azure/tasks/viewAzureTaskLogs"; -// import { untagAzureImage } from "./registries/azure/untagAzureImage"; -// import { viewAzureProperties } from "./registries/azure/viewAzureProperties"; -import { connectRegistry } from "./registries/connectRegistry"; -// import { copyRemoteFullTag } from './registries/copyRemoteFullTag'; -// import { copyRemoteImageDigest } from "./registries/copyRemoteImageDigest"; -// import { deleteRemoteImage } from "./registries/deleteRemoteImage"; -// import { logInToDockerCli } from "./registries/logInToDockerCli"; -// import { logOutOfDockerCli } from "./registries/logOutOfDockerCli"; -// import { pullImageFromRepository, pullRepository } from "./registries/pullImages"; -// import { reconnectRegistry } from "./registries/reconnectRegistry"; import { deleteAzureRegistry } from "./registries/azure/deleteAzureRegistry"; import { deleteAzureRepository } from "./registries/azure/deleteAzureRepository"; +import { openInAzurePortal } from "./registries/azure/openInAzurePortal"; import { buildImageInAzure } from "./registries/azure/tasks/buildImageInAzure"; +import { runAzureTask } from "./registries/azure/tasks/runAzureTask"; import { untagAzureImage } from "./registries/azure/untagAzureImage"; import { viewAzureProperties } from "./registries/azure/viewAzureProperties"; +import { connectRegistry } from "./registries/connectRegistry"; import { copyRemoteFullTag } from "./registries/copyRemoteFullTag"; import { copyRemoteImageDigest } from "./registries/copyRemoteImageDigest"; import { disconnectRegistry } from "./registries/disconnectRegistry"; @@ -150,7 +137,7 @@ export function registerCommands(): void { registerWorkspaceCommand('vscode-docker.containers.composeGroup.restart', composeGroupRestart); registerWorkspaceCommand('vscode-docker.containers.composeGroup.down', composeGroupDown); - // registerWorkspaceCommand('vscode-docker.images.build', buildImage); + registerWorkspaceCommand('vscode-docker.images.build', buildImage); registerCommand('vscode-docker.images.configureExplorer', configureImagesExplorer); registerCommand('vscode-docker.images.inspect', inspectImage); registerCommand('vscode-docker.images.prune', pruneImages); @@ -158,13 +145,13 @@ export function registerCommands(): void { registerCommand('vscode-docker.images.hideDangling', hideDanglingImages); setInitialDanglingContextValue(); registerWorkspaceCommand('vscode-docker.images.pull', pullImage); - // registerWorkspaceCommand('vscode-docker.images.push', pushImage); + registerWorkspaceCommand('vscode-docker.images.push', pushImage); registerCommand('vscode-docker.images.remove', removeImage); registerCommand('vscode-docker.images.group.remove', removeImageGroup); registerWorkspaceCommand('vscode-docker.images.run', runImage); registerWorkspaceCommand('vscode-docker.images.runAzureCli', runAzureCliImage); registerWorkspaceCommand('vscode-docker.images.runInteractive', runImageInteractive); - // registerCommand('vscode-docker.images.tag', tagImage); + registerCommand('vscode-docker.images.tag', tagImage); registerCommand('vscode-docker.images.copyFullTag', copyFullTag); registerCommand('vscode-docker.networks.configureExplorer', configureNetworksExplorer); diff --git a/src/commands/registries/azure/untagAzureImage.ts b/src/commands/registries/azure/untagAzureImage.ts index a005951259..f5713131b6 100644 --- a/src/commands/registries/azure/untagAzureImage.ts +++ b/src/commands/registries/azure/untagAzureImage.ts @@ -7,7 +7,7 @@ import { contextValueExperience, IActionContext } from "@microsoft/vscode-azext- import { l10n, ProgressLocation, window } from "vscode"; import { ext } from "../../../extensionVariables"; import { AzureRegistryDataProvider, AzureTag } from "../../../tree/registries/Azure/AzureRegistryDataProvider"; -import { getFullImageNameFromRegistryItem } from "../../../tree/registries/registryTreeUtils"; +import { getFullImageNameFromRegistryTagItem } from "../../../tree/registries/registryTreeUtils"; import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; export async function untagAzureImage(context: IActionContext, node?: UnifiedRegistryItem): Promise { @@ -15,7 +15,7 @@ export async function untagAzureImage(context: IActionContext, node?: UnifiedReg node = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerTag' }); } - const fullTag = getFullImageNameFromRegistryItem(node); + const fullTag = getFullImageNameFromRegistryTagItem(node.wrappedItem); const confirmUntag: string = l10n.t('Are you sure you want to untag image "{0}"? This does not delete the manifest referenced by the tag.', fullTag); // no need to check result - cancel will throw a UserCancelledError await context.ui.showWarningMessage(confirmUntag, { modal: true }, { title: "Untag" }); diff --git a/src/commands/registries/copyRemoteFullTag.ts b/src/commands/registries/copyRemoteFullTag.ts index 4cf7ba996c..1ded232f32 100644 --- a/src/commands/registries/copyRemoteFullTag.ts +++ b/src/commands/registries/copyRemoteFullTag.ts @@ -8,13 +8,13 @@ import { CommonTag } from '@microsoft/vscode-docker-registries'; import * as vscode from 'vscode'; import { ext } from '../../extensionVariables'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; -import { getFullImageNameFromRegistryItem } from '../../tree/registries/registryTreeUtils'; +import { getFullImageNameFromRegistryTagItem } from '../../tree/registries/registryTreeUtils'; export async function copyRemoteFullTag(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { node = await contextValueExperience(context, ext.registriesTree, { include: ['registryV2Tag', 'dockerHubTag'] }); } - const fullTag = getFullImageNameFromRegistryItem(node); + const fullTag = getFullImageNameFromRegistryTagItem(node.wrappedItem); void vscode.env.clipboard.writeText(fullTag); return fullTag; } diff --git a/src/commands/registries/pullImages.ts b/src/commands/registries/pullImages.ts index 738b05b1af..53a68a2a58 100644 --- a/src/commands/registries/pullImages.ts +++ b/src/commands/registries/pullImages.ts @@ -8,7 +8,7 @@ import { CommonRegistry, CommonRepository, CommonTag } from '@microsoft/vscode-d import { ext } from '../../extensionVariables'; import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; -import { getImageNameFromRegistryItem } from '../../tree/registries/registryTreeUtils'; +import { getImageNameFromRegistryTagItem } from '../../tree/registries/registryTreeUtils'; import { logInToDockerCli } from './logInToDockerCli'; export async function pullRepository(context: IActionContext, node?: UnifiedRegistryItem): Promise { @@ -24,7 +24,7 @@ export async function pullImageFromRepository(context: IActionContext, node?: Un node = await contextValueExperience(context, ext.registriesTree, { include: 'commontag' }); } - await pullImages(context, node.parent.parent, getImageNameFromRegistryItem(node), false); + await pullImages(context, node.parent.parent, getImageNameFromRegistryTagItem(node.wrappedItem), false); } async function pullImages(context: IActionContext, node: UnifiedRegistryItem, imageRequest: string, allTags: boolean): Promise { diff --git a/src/tree/registries/Azure/ACROAuthProvider.ts b/src/tree/registries/Azure/ACROAuthProvider.ts index 887811a8ac..207a80b088 100644 --- a/src/tree/registries/Azure/ACROAuthProvider.ts +++ b/src/tree/registries/Azure/ACROAuthProvider.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { AzureSubscription } from '@microsoft/vscode-azext-azureauth'; -import { httpRequest } from '@microsoft/vscode-docker-registries'; +import { LoginInformation, httpRequest } from '@microsoft/vscode-docker-registries'; import { AuthenticationProvider } from "@microsoft/vscode-docker-registries/"; import * as vscode from 'vscode'; +import { NULL_GUID } from '../../../constants'; // export interface ACROAuthOptions extends BasicOAuthOptions { // readonly subscription: AzureSubscription; @@ -18,16 +19,7 @@ export class ACROAuthProvider implements AuthenticationProvider { public constructor(private readonly registryUri: vscode.Uri, private readonly subscription: AzureSubscription) { } public async getSession(scopes: string[], options?: vscode.AuthenticationGetSessionOptions): Promise { - const accessToken = await this.getAccessToken(this.subscription); - const registryString = this.registryUri.toString(); - - let refreshToken: string; - if (!options?.forceNewSession && this.refreshTokenCache.has(registryString)) { - refreshToken = this.refreshTokenCache.get(registryString)!; - } else { - refreshToken = await this.getRefreshTokenFromAccessToken(accessToken, this.registryUri, this.subscription); - this.refreshTokenCache.set(registryString, refreshToken); - } + const refreshToken = await this.getRefreshToken(options); const oauthToken = await this.getOAuthTokenFromRefreshToken(refreshToken, this.registryUri, scopes.join(' '), this.subscription); const { sub, jti } = this.parseToken(oauthToken); @@ -44,6 +36,15 @@ export class ACROAuthProvider implements AuthenticationProvider { }; } + public async getLoginInformation(options?: vscode.AuthenticationGetSessionOptions): Promise { + const refreshToken = await this.getRefreshToken(options); + return { + username: NULL_GUID, + secret: refreshToken, + server: this.registryUri.toString(), + }; + } + private parseToken(accessToken: string): { sub: string, jti: string } { const tokenParts = accessToken.split('.'); const tokenBody = JSON.parse(Buffer.from(tokenParts[1], 'base64').toString('utf8')); @@ -105,6 +106,23 @@ export class ACROAuthProvider implements AuthenticationProvider { // Registry scopes, i.e. those passed to `getSession()`, are not valid for acquiring this // access token--instead, those only need to be passed to `getOAuthTokenFromRefreshToken()` const token = await subscription.credential.getToken([]); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return token!.token; } + + private async getRefreshToken(options?: vscode.AuthenticationGetSessionOptions): Promise { + const accessToken = await this.getAccessToken(this.subscription); + const registryString = this.registryUri.toString(); + + let refreshToken: string; + if (!options?.forceNewSession && this.refreshTokenCache.has(registryString)) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + refreshToken = this.refreshTokenCache.get(registryString)!; + } else { + refreshToken = await this.getRefreshTokenFromAccessToken(accessToken, this.registryUri, this.subscription); + this.refreshTokenCache.set(registryString, refreshToken); + } + + return refreshToken; + } } diff --git a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts index 4b1ea3ef52..8f35ab3537 100644 --- a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts +++ b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts @@ -1,5 +1,6 @@ -import { RegistryDataProvider } from '@microsoft/vscode-docker-registries'; +import { CommonRegistry, CommonRegistryRoot, RegistryDataProvider, isRegistry } from '@microsoft/vscode-docker-registries'; import * as vscode from 'vscode'; +import { isAzureSubscriptionRegistryItem } from './Azure/AzureRegistryDataProvider'; export interface UnifiedRegistryItem { provider: RegistryDataProvider; @@ -130,4 +131,64 @@ export class UnifiedRegistryTreeDataProvider implements vscode.TreeDataProvider< void this.refresh(); } + + /** + * + * @param imageBaseName The base name of the image to find registries for. e.g. 'docker.io' + * @returns A list of registries that are connected to the extension. If imageBaseName is provided, only registries that + * can be used to push to that image will be returned. + */ + public async getConnectedRegistries(imageBaseName?: vscode.Uri): Promise[]> { + let registryRoots = await this.getChildren(); + let findAzureRegistryOnly = false; + + // filter out registry roots that don't match the image base name + if (imageBaseName) { + const authority = imageBaseName.authority; + + if (authority === 'docker.io') { + registryRoots = registryRoots.filter(r => (r.wrappedItem as CommonRegistryRoot).label === 'Docker Hub'); + } + else if (authority.endsWith('azurecr.io')) { + registryRoots = registryRoots.filter(r => (r.wrappedItem as CommonRegistryRoot).label === 'Azure'); + findAzureRegistryOnly = true; + } + else if (authority === 'ghcr.io') { + registryRoots = registryRoots.filter(r => (r.wrappedItem as CommonRegistryRoot).label === 'GitHub'); + } + else { + registryRoots = registryRoots.filter( + r => (r.wrappedItem as CommonRegistryRoot).label !== 'Docker Hub' + && (r.wrappedItem as CommonRegistryRoot).label !== 'Azure' + && (r.wrappedItem as CommonRegistryRoot).label !== 'GitHub'); + } + } + + const results: UnifiedRegistryItem[] = []; + + for (const registryRoot of registryRoots) { + try { + const maybeRegistries = await this.getChildren(registryRoot); + + for (const maybeRegistry of maybeRegistries) { + // short circuit if we're only looking for Azure registries + if (!findAzureRegistryOnly && isRegistry(maybeRegistry.wrappedItem)) { + results.push(maybeRegistry as UnifiedRegistryItem); + } else if (findAzureRegistryOnly || isAzureSubscriptionRegistryItem(maybeRegistry.wrappedItem)) { + const registries = await this.getChildren(maybeRegistry); + + for (const registry of registries) { + if (isRegistry(registry.wrappedItem)) { + results.push(registry as UnifiedRegistryItem); + } + } + } + } + } catch { + // best effort + } + } + + return results; + } } diff --git a/src/tree/registries/registryTreeUtils.ts b/src/tree/registries/registryTreeUtils.ts index eccab72abf..683ac1f33e 100644 --- a/src/tree/registries/registryTreeUtils.ts +++ b/src/tree/registries/registryTreeUtils.ts @@ -4,33 +4,36 @@ *--------------------------------------------------------------------------------------------*/ import { CommonRegistry, CommonRepository, CommonTag, isRegistry, isRepository, isTag } from "@microsoft/vscode-docker-registries"; -import { UnifiedRegistryItem } from "./UnifiedRegistryTreeDataProvider"; +import { l10n } from "vscode"; -export function getImageNameFromRegistryItem(node: UnifiedRegistryItem): string { - if (!isTag(node.wrappedItem) || !isRepository(node.parent.wrappedItem)) { - throw new Error('Unable to get image name'); +export function getImageNameFromRegistryTagItem(tag: CommonTag): string { + if (!isTag(tag) || !isRepository(tag.parent)) { + throw new Error(l10n.t('Unable to get image name')); } - const repository = node.parent.wrappedItem as CommonRepository; + const repository = tag.parent as CommonRepository; - return `${repository.label}:${node.wrappedItem.label}`; + return `${repository.label}:${tag.label}`; } -export function getFullImageNameFromRegistryItem(node: UnifiedRegistryItem): string { - const imageName = getImageNameFromRegistryItem(node); - if (!isRegistry(node.parent.parent?.wrappedItem)) { - throw new Error('Unable to get full image name'); +export function getBaseImagePathFromRegistryItem(registry: CommonRegistry): string { + if (!isRegistry(registry)) { + throw new Error(l10n.t('Unable to get base image path')); } - const registry = node.parent.parent.wrappedItem as CommonRegistry; - - switch (node.wrappedItem.additionalContextValues?.[0] ?? '') { - case 'registryV2Tag': { - const authority = registry.baseUrl.authority; - return `${authority.toLowerCase()}/${imageName}`; + switch (registry.additionalContextValues?.[0] ?? '') { + case 'azureContainerRegistry': + case 'genericRegistryV2': { + return registry.baseUrl.authority.toLowerCase(); } - case 'dockerHubTag': + case 'dockerHubRegistry': default: - return `${registry.label}/${imageName}`; + return `${registry.label}`; } } + +export function getFullImageNameFromRegistryTagItem(tag: CommonTag): string { + const imageName = getImageNameFromRegistryTagItem(tag); + const baseImagePath = getBaseImagePathFromRegistryItem(tag.parent.parent); + return `${baseImagePath}/${imageName}`; +} From ea8f0bcd22f216de6fc9fa4dd78a3c827f66ca74 Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:32:39 -0400 Subject: [PATCH 07/43] Added Back Azure Deploy to ACA & Azure Deploy to AAS (#4038) * added back basic implementation of deploy ACA * saving progress * added step for selecting subscriptions * small tweaks * progress on deploy image to azure app service * improved azure subscription selection experience * actually made deploy image to AAS work * remove accidetal change to settings.json * fix small mistake * fixed more mistakes * fix more mistakes * fixed even more mistakes... --- package-lock.json | 208 +++++++++++-- package.json | 10 +- src/commands/registerCommands.ts | 6 +- .../azure/DockerAssignAcrPullRoleStep.ts | 180 ++++++------ .../registries/azure/DockerSiteCreateStep.ts | 275 +++++++++--------- .../azure/DockerWebhookCreateStep.ts | 177 +++++------ .../azure/WebSitesPortPromptStep.ts | 56 ++-- .../registries/azure/deployImageToAca.ts | 192 ++++++------ .../registries/azure/deployImageToAzure.ts | 134 +++++---- src/extensionVariables.ts | 2 + src/tree/registerTrees.ts | 4 +- .../Azure/AzureRegistryDataProvider.ts | 19 +- src/tree/registries/registryTreeUtils.ts | 10 + src/utils/azureUtils.ts | 53 ---- src/utils/registryExperience.ts | 24 ++ 15 files changed, 759 insertions(+), 591 deletions(-) create mode 100644 src/utils/registryExperience.ts diff --git a/package-lock.json b/package-lock.json index 324f739002..955306476e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,10 +13,10 @@ "@azure/arm-containerregistry": "^10.1.0", "@azure/storage-blob": "^12.14.0", "@microsoft/compose-language-service": "^0.2.0", - "@microsoft/vscode-azext-azureappservice": "^1.0.2", + "@microsoft/vscode-azext-azureappservice": "^2.2.0", "@microsoft/vscode-azext-azureauth": "^1.1.2", - "@microsoft/vscode-azext-azureutils": "^1.1.5", - "@microsoft/vscode-azext-utils": "^1.2.2", + "@microsoft/vscode-azext-azureutils": "^2.0.1", + "@microsoft/vscode-azext-utils": "^2.0.1", "@microsoft/vscode-container-client": "^0.1.0", "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", "dayjs": "^1.11.7", @@ -681,9 +681,9 @@ "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" }, "node_modules/@microsoft/vscode-azext-azureappservice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureappservice/-/vscode-azext-azureappservice-1.0.2.tgz", - "integrity": "sha512-YaKqYIkeX0kH8KgUsUOUrr7RA9ghtFnZXcS8RfSIs6/HmssN3pYdKEjlq4Yq3U5oOkX8Cq5xlujHoCiUaUsh7Q==", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureappservice/-/vscode-azext-azureappservice-2.2.5.tgz", + "integrity": "sha512-pnbunSuGd0W1XojN7P/sNcdMeVC1FttXYR9UY4tNgwgstz+zeN5ckYtwWTHdu4fTP+uEczpHKz0zGf6Mn4fkfw==", "dependencies": { "@azure/abort-controller": "^1.0.4", "@azure/arm-appinsights": "^5.0.0-beta.4", @@ -694,8 +694,9 @@ "@azure/core-client": "^1.7.2", "@azure/core-rest-pipeline": "^1.10.3", "@azure/storage-blob": "^12.3.0", - "@microsoft/vscode-azext-azureutils": "^1.1.5", - "@microsoft/vscode-azext-utils": "^1.2.2", + "@microsoft/vscode-azext-azureutils": "^2.0.2", + "@microsoft/vscode-azext-github": "^1.0.0", + "@microsoft/vscode-azext-utils": "^2.0.0", "dayjs": "^1.11.2", "fs-extra": "^10.0.0", "globby": "^11.0.2", @@ -707,7 +708,7 @@ }, "peerDependencies": { "@azure/ms-rest-azure-env": "^2.0.0", - "@microsoft/vscode-azext-azureappsettings": "^0.1.0" + "@microsoft/vscode-azext-azureappsettings": "^0.2.0" } }, "node_modules/@microsoft/vscode-azext-azureappservice/node_modules/fs-extra": { @@ -724,12 +725,12 @@ } }, "node_modules/@microsoft/vscode-azext-azureappsettings": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureappsettings/-/vscode-azext-azureappsettings-0.1.0.tgz", - "integrity": "sha512-oxq3tYgb9yt/Vxh8larmd3XTRW0FEaIfhtzEpZyb0YcqFaTEhY299J9oogiu78+dGDztgR3y8g0AhDHBpEmCiQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureappsettings/-/vscode-azext-azureappsettings-0.2.0.tgz", + "integrity": "sha512-fHv+m+dOluuYgPCQ7Mt8HoDgguWy8zHWofP3T6uxkuDF8VAJbjl9LFYHwV0frVcK4Qgxcj95QDjw5AMSUMHtqw==", "peer": true, "dependencies": { - "@microsoft/vscode-azext-utils": "^1.2.1" + "@microsoft/vscode-azext-utils": "^2.0.0" } }, "node_modules/@microsoft/vscode-azext-azureauth": { @@ -742,9 +743,9 @@ } }, "node_modules/@microsoft/vscode-azext-azureutils": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureutils/-/vscode-azext-azureutils-1.1.5.tgz", - "integrity": "sha512-iG89BMp57ydHHl3NbW+T9vn/zksDOoYxgebAZmoF6fZzrIJn2VVmzWGllBsfBa1vEx/qDrvdfT6juSY0UtLe0w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureutils/-/vscode-azext-azureutils-2.0.2.tgz", + "integrity": "sha512-r+7NqedkvfFazztO7kxT1AU/B/UfiGhTDlRtFHx+W5bhp+yJw0eOtX8VPJXXSXoOaq2dzuGBwxAQ0bDD1lNPcQ==", "dependencies": { "@azure/arm-resources": "^5.0.0", "@azure/arm-resources-profile-2020-09-01-hybrid": "^2.0.0", @@ -754,7 +755,7 @@ "@azure/core-client": "^1.6.0", "@azure/core-rest-pipeline": "^1.9.0", "@azure/logger": "^1.0.4", - "@microsoft/vscode-azext-utils": "^1.2.2", + "@microsoft/vscode-azext-utils": "^2.0.0", "semver": "^7.3.7", "uuid": "^9.0.0" }, @@ -770,10 +771,19 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@microsoft/vscode-azext-github": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-github/-/vscode-azext-github-1.0.0.tgz", + "integrity": "sha512-LYZ5fN0Yv2zfBy8uNuIL/JaXKf8aw+QfW64fuIDknt9qs7UZtFzfTvOimTB5nGd1oN4o7cpthcZg3YHOH2YpKg==", + "dependencies": { + "@microsoft/vscode-azext-utils": "^2.0.0", + "@octokit/rest": "^18.5.2" + } + }, "node_modules/@microsoft/vscode-azext-utils": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-utils/-/vscode-azext-utils-1.2.2.tgz", - "integrity": "sha512-mOTcJF8IMsz+Xn8QTUP1AC3K5tPl/3f17L2xGTTtLeV/HJ2sTh/3712NFuN58tnOwdISdazId4tHwaqUta8HEA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-utils/-/vscode-azext-utils-2.0.5.tgz", + "integrity": "sha512-DNyORyHfPuohBy7Z0rD6YRa4iNLW2LpA+lAv75E9NQ3c1ZhO27tQ2EHj4sYowAqrKZXlqvPHvgjYFj3Dqu5eOQ==", "dependencies": { "@microsoft/vscode-azureresources-api": "^2.0.4", "@vscode/extension-telemetry": "^0.6.2", @@ -814,9 +824,10 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.0.1-alpha", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", - "integrity": "sha512-iUMbTBE3+s6uWg/Upy8pl8x+JXBp74JmFDS62p60JcJyhl+pPNs2rp+cxLIrl4mH9Ml6vlZeZZCeTJJdq/H+Nw==", + "integrity": "sha512-AUa6YyRJgGzc96gJylSiI+EGm0B5s28LS7Hi8yHH4aIMz8vU/yK5O9PWrt/GxIxm2AHP4oFqQ59lWy2mk2xunA==", "license": "See LICENSE in the project root for license information.", "dependencies": { + "dayjs": "^1.11.7", "node-fetch": "^2.6.11" } }, @@ -852,6 +863,142 @@ "node": ">= 8" } }, + "node_modules/@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "dependencies": { + "@octokit/types": "^6.0.3" + } + }, + "node_modules/@octokit/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", + "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", + "dependencies": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.3", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "dependencies": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/endpoint/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "12.11.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", + "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", + "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", + "dependencies": { + "@octokit/types": "^6.40.0" + }, + "peerDependencies": { + "@octokit/core": ">=2" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "5.16.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", + "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", + "dependencies": { + "@octokit/types": "^6.39.0", + "deprecation": "^2.3.1" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "dependencies": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "dependencies": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "node_modules/@octokit/request/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@octokit/rest": { + "version": "18.12.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", + "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", + "dependencies": { + "@octokit/core": "^3.5.1", + "@octokit/plugin-paginate-rest": "^2.16.8", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^5.12.0" + } + }, + "node_modules/@octokit/types": { + "version": "6.41.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", + "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", + "dependencies": { + "@octokit/openapi-types": "^12.11.0" + } + }, "node_modules/@opentelemetry/api": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz", @@ -1773,6 +1920,11 @@ ], "optional": true }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -2384,6 +2536,11 @@ "node": ">=0.4.0" } }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, "node_modules/detect-libc": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", @@ -4611,7 +4768,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -6098,6 +6254,11 @@ "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, + "node_modules/universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -6633,8 +6794,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { "version": "8.13.0", diff --git a/package.json b/package.json index c3eeb9f80d..f3c6b6149d 100644 --- a/package.json +++ b/package.json @@ -516,12 +516,12 @@ }, { "command": "vscode-docker.registries.deployImageToAzure", - "when": "view == dockerRegistries && viewItem =~ /(DockerV2|DockerHubV2);Tag;/ && isAzureAccountInstalled", + "when": "view == dockerRegistries && viewItem =~ /commontag/i", "group": "regs_tag_1_general@4" }, { "command": "vscode-docker.registries.deployImageToAca", - "when": "view == dockerRegistries && viewItem =~ /(DockerV2|DockerHubV2|GitLabV4);Tag;/ && isAzureAccountInstalled", + "when": "view == dockerRegistries && viewItem =~ /commontag/i", "group": "regs_tag_1_general@6" }, { @@ -3030,10 +3030,10 @@ "@azure/arm-containerregistry": "^10.1.0", "@azure/storage-blob": "^12.14.0", "@microsoft/compose-language-service": "^0.2.0", - "@microsoft/vscode-azext-azureappservice": "^1.0.2", + "@microsoft/vscode-azext-azureappservice": "^2.2.0", "@microsoft/vscode-azext-azureauth": "^1.1.2", - "@microsoft/vscode-azext-azureutils": "^1.1.5", - "@microsoft/vscode-azext-utils": "^1.2.2", + "@microsoft/vscode-azext-azureutils": "^2.0.1", + "@microsoft/vscode-azext-utils": "^2.0.1", "@microsoft/vscode-container-client": "^0.1.0", "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", "dayjs": "^1.11.7", diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index 641055ba06..c7403f0be3 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -54,6 +54,8 @@ import { registerWorkspaceCommand } from "./registerWorkspaceCommand"; import { createAzureRegistry } from "./registries/azure/createAzureRegistry"; import { deleteAzureRegistry } from "./registries/azure/deleteAzureRegistry"; import { deleteAzureRepository } from "./registries/azure/deleteAzureRepository"; +import { deployImageToAca } from "./registries/azure/deployImageToAca"; +import { deployImageToAzure } from "./registries/azure/deployImageToAzure"; import { openInAzurePortal } from "./registries/azure/openInAzurePortal"; import { buildImageInAzure } from "./registries/azure/tasks/buildImageInAzure"; import { runAzureTask } from "./registries/azure/tasks/runAzureTask"; @@ -164,8 +166,8 @@ export function registerCommands(): void { registerCommand('vscode-docker.registries.copyImageDigest', copyRemoteImageDigest); registerCommand('vscode-docker.registries.copyRemoteFullTag', copyRemoteFullTag); // registerCommand('vscode-docker.registries.deleteImage', deleteRemoteImage); - // registerCommand('vscode-docker.registries.deployImageToAzure', deployImageToAzure); - // registerCommand('vscode-docker.registries.deployImageToAca', deployImageToAca); + registerCommand('vscode-docker.registries.deployImageToAzure', deployImageToAzure); + registerCommand('vscode-docker.registries.deployImageToAca', deployImageToAca); registerCommand('vscode-docker.registries.disconnectRegistry', disconnectRegistry); registerCommand('vscode-docker.registries.help', registryHelp); registerWorkspaceCommand('vscode-docker.registries.logInToDockerCli', logInToDockerCli); diff --git a/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts b/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts index 74095300da..0761162134 100644 --- a/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts +++ b/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts @@ -1,89 +1,91 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ - -// import type { IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy -// import { AzureWizardExecuteStep } from "@microsoft/vscode-azext-utils"; -// import { randomUUID } from "crypto"; -// import { l10n, Progress } from "vscode"; -// import { ext } from "../../../extensionVariables"; -// import { AzureRegistryTreeItem } from '../../../tree/registries/azure/AzureRegistryTreeItem'; -// import { RemoteTagTreeItem } from '../../../tree/registries/RemoteTagTreeItem'; -// import { getArmAuth, getArmContainerRegistry, getAzExtAppService, getAzExtAzureUtils } from "../../../utils/lazyPackages"; - -// export class DockerAssignAcrPullRoleStep extends AzureWizardExecuteStep { -// public priority: number = 141; // execute after DockerSiteCreateStep - -// public constructor(private readonly tagTreeItem: RemoteTagTreeItem) { -// super(); -// } - -// public async execute(context: IAppServiceWizardContext, progress: Progress<{ message?: string; increment?: number }>): Promise { -// const message: string = l10n.t('Granting permission for App Service to pull image from ACR...'); -// ext.outputChannel.info(message); -// progress.report({ message: message }); - -// const azExtAzureUtils = await getAzExtAzureUtils(); -// const vscAzureAppService = await getAzExtAppService(); -// const armAuth = await getArmAuth(); -// const armContainerRegistry = await getArmContainerRegistry(); -// const authClient = azExtAzureUtils.createAzureClient(context, armAuth.AuthorizationManagementClient); -// const crmClient = azExtAzureUtils.createAzureClient(context, armContainerRegistry.ContainerRegistryManagementClient); -// const appSvcClient = await vscAzureAppService.createWebSiteClient(context); - -// // If we're in `execute`, then `shouldExecute` passed and `this.tagTreeItem.parent.parent` is guaranteed to be an AzureRegistryTreeItem -// const registryTreeItem: AzureRegistryTreeItem = this.tagTreeItem.parent.parent as unknown as AzureRegistryTreeItem; - -// // 1. Get the registry resource. We will need the ID. -// const registry = await crmClient.registries.get(registryTreeItem.resourceGroup, registryTreeItem.registryName); - -// if (!(registry?.id)) { -// throw new Error( -// l10n.t('Unable to get details from Container Registry {0}', registryTreeItem.baseUrl) -// ); -// } - -// // 2. Get the role definition for the AcrPull role. We will need the definition ID. This role is built-in and should always exist. -// const acrPullRoleDefinition = (await azExtAzureUtils.uiUtils.listAllIterator(authClient.roleDefinitions.list(registry.id, { filter: `roleName eq 'AcrPull'` })))[0]; - -// if (!(acrPullRoleDefinition?.id)) { -// throw new Error( -// l10n.t('Unable to get AcrPull role definition on subscription {0}', context.subscriptionId) -// ); -// } - -// // 3. Get the info for the now-created web site. We will need the principal ID. -// const siteInfo = await appSvcClient.webApps.get(context.site.resourceGroup, context.site.name); - -// if (!(siteInfo?.identity?.principalId)) { -// throw new Error( -// l10n.t('Unable to get identity principal ID for web site {0}', context.site.name) -// ); -// } - -// // 4. On the registry, assign the AcrPull role to the principal representing the website -// await authClient.roleAssignments.create(registry.id, randomUUID(), { -// principalId: siteInfo.identity.principalId, -// roleDefinitionId: acrPullRoleDefinition.id, -// principalType: 'ServicePrincipal', -// }); - -// // 5. Set the web app to use the desired ACR image, which was not done in DockerSiteCreateStep. Get the config and then update it. -// const config = await appSvcClient.webApps.getConfiguration(context.site.resourceGroup, context.site.name); - -// if (!config) { -// throw new Error( -// l10n.t('Unable to get configuration for web site {0}', context.site.name) -// ); -// } - -// config.linuxFxVersion = `DOCKER|${this.tagTreeItem.fullTag}`; -// await appSvcClient.webApps.updateConfiguration(context.site.resourceGroup, context.site.name, config); -// } - -// public shouldExecute(context: IAppServiceWizardContext): boolean { -// return !!(context.site) && !!(this.tagTreeItem?.parent?.parent) && this.tagTreeItem.parent.parent instanceof AzureRegistryTreeItem -// && !context.customLocation; -// } -// } +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy +import { AzureWizardExecuteStep } from "@microsoft/vscode-azext-utils"; +import { CommonTag } from "@microsoft/vscode-docker-registries"; +import { randomUUID } from "crypto"; +import { Progress, l10n } from "vscode"; +import { ext } from "../../../extensionVariables"; +import { AzureRegistry, isAzureTagItem } from "../../../tree/registries/Azure/AzureRegistryDataProvider"; +import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { getFullImageNameFromRegistryTagItem, getResourceGroupFromAzureRegistryItem } from "../../../tree/registries/registryTreeUtils"; +import { getArmAuth, getArmContainerRegistry, getAzExtAppService, getAzExtAzureUtils } from "../../../utils/lazyPackages"; + +export class DockerAssignAcrPullRoleStep extends AzureWizardExecuteStep { + public priority: number = 141; // execute after DockerSiteCreateStep + + public constructor(private readonly tagTreeItem: UnifiedRegistryItem) { + super(); + } + + public async execute(context: IAppServiceWizardContext, progress: Progress<{ message?: string; increment?: number }>): Promise { + const message: string = l10n.t('Granting permission for App Service to pull image from ACR...'); + ext.outputChannel.info(message); + progress.report({ message: message }); + + const azExtAzureUtils = await getAzExtAzureUtils(); + const vscAzureAppService = await getAzExtAppService(); + const armAuth = await getArmAuth(); + const armContainerRegistry = await getArmContainerRegistry(); + const authClient = azExtAzureUtils.createAzureClient(context, armAuth.AuthorizationManagementClient); + const crmClient = azExtAzureUtils.createAzureClient(context, armContainerRegistry.ContainerRegistryManagementClient); + const appSvcClient = await vscAzureAppService.createWebSiteClient(context); + + // If we're in `execute`, then `shouldExecute` passed and `this.tagTreeItem.parent.parent` is guaranteed to be an AzureRegistryTreeItem + const registryTreeItem: UnifiedRegistryItem = this.tagTreeItem.parent.parent as unknown as UnifiedRegistryItem; + + // 1. Get the registry resource. We will need the ID. + const registry = await crmClient.registries.get(getResourceGroupFromAzureRegistryItem(registryTreeItem.wrappedItem), registryTreeItem.wrappedItem.label); + + if (!(registry?.id)) { + throw new Error( + l10n.t('Unable to get details from Container Registry {0}', registryTreeItem.wrappedItem.baseUrl) + ); + } + + // 2. Get the role definition for the AcrPull role. We will need the definition ID. This role is built-in and should always exist. + const acrPullRoleDefinition = (await azExtAzureUtils.uiUtils.listAllIterator(authClient.roleDefinitions.list(registry.id, { filter: `roleName eq 'AcrPull'` })))[0]; + + if (!(acrPullRoleDefinition?.id)) { + throw new Error( + l10n.t('Unable to get AcrPull role definition on subscription {0}', context.subscriptionId) + ); + } + + // 3. Get the info for the now-created web site. We will need the principal ID. + const siteInfo = await appSvcClient.webApps.get(context.site.resourceGroup, context.site.name); + + if (!(siteInfo?.identity?.principalId)) { + throw new Error( + l10n.t('Unable to get identity principal ID for web site {0}', context.site.name) + ); + } + + // 4. On the registry, assign the AcrPull role to the principal representing the website + await authClient.roleAssignments.create(registry.id, randomUUID(), { + principalId: siteInfo.identity.principalId, + roleDefinitionId: acrPullRoleDefinition.id, + principalType: 'ServicePrincipal', + }); + + // 5. Set the web app to use the desired ACR image, which was not done in DockerSiteCreateStep. Get the config and then update it. + const config = await appSvcClient.webApps.getConfiguration(context.site.resourceGroup, context.site.name); + + if (!config) { + throw new Error( + l10n.t('Unable to get configuration for web site {0}', context.site.name) + ); + } + + const fullTag = getFullImageNameFromRegistryTagItem(this.tagTreeItem.wrappedItem); + config.linuxFxVersion = `DOCKER|${fullTag}`; + await appSvcClient.webApps.updateConfiguration(context.site.resourceGroup, context.site.name, config); + } + + public shouldExecute(context: IAppServiceWizardContext): boolean { + return !!(context.site) && isAzureTagItem(this.tagTreeItem.wrappedItem) && !context.customLocation; + } +} diff --git a/src/commands/registries/azure/DockerSiteCreateStep.ts b/src/commands/registries/azure/DockerSiteCreateStep.ts index 6d05b1f474..38131a36f0 100644 --- a/src/commands/registries/azure/DockerSiteCreateStep.ts +++ b/src/commands/registries/azure/DockerSiteCreateStep.ts @@ -1,138 +1,137 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ - -// import type { NameValuePair, Site, SiteConfig, WebSiteManagementClient } from '@azure/arm-appservice'; // These are only dev-time imports so don't need to be lazy -// import type { CustomLocation } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy -// import type { AzExtLocation } from '@microsoft/vscode-azext-azureutils'; // These are only dev-time imports so don't need to be lazy -// import { AzureWizardExecuteStep, nonNullProp, nonNullValueAndProp } from "@microsoft/vscode-azext-utils"; -// import { Progress, l10n } from "vscode"; -// import { ext } from "../../../extensionVariables"; -// import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; -// import { getAzExtAppService, getAzExtAzureUtils } from '../../../utils/lazyPackages'; -// import { IAppServiceContainerWizardContext } from './deployImageToAzure'; - -// export class DockerSiteCreateStep extends AzureWizardExecuteStep { -// public priority: number = 140; - -// public constructor(private readonly node: UnifiedRegistryItem) { -// super(); -// } - -// public async execute(context: IAppServiceContainerWizardContext, progress: Progress<{ message?: string; increment?: number }>): Promise { -// const creatingNewApp: string = l10n.t('Creating web app "{0}"...', context.newSiteName); -// ext.outputChannel.info(creatingNewApp); -// progress.report({ message: creatingNewApp }); -// const siteConfig = await this.getNewSiteConfig(context); - -// const azExtAzureUtils = await getAzExtAzureUtils(); -// const vscAzureAppService = await getAzExtAppService(); - -// const location: AzExtLocation = await azExtAzureUtils.LocationListStep.getLocation(context); -// const locationName: string = nonNullProp(location, 'name'); - -// const client: WebSiteManagementClient = await vscAzureAppService.createWebSiteClient(context); -// const siteEnvelope: Site = { -// name: context.newSiteName, -// location: locationName, -// serverFarmId: nonNullValueAndProp(context.plan, 'id'), -// siteConfig: siteConfig -// }; - -// if (context.customLocation) { -// // deploying to Azure Arc -// siteEnvelope.kind = 'app,linux,kubernetes,container'; -// await this.addCustomLocationProperties(siteEnvelope, context.customLocation); -// } else { -// siteEnvelope.identity = { -// type: 'SystemAssigned' -// }; -// } - -// context.site = await client.webApps.beginCreateOrUpdateAndWait(nonNullValueAndProp(context.resourceGroup, 'name'), nonNullProp(context, 'newSiteName'), siteEnvelope); -// } - -// private async getNewSiteConfig(context: IAppServiceContainerWizardContext): Promise { -// const registryTI: UnifiedRegistryItem = this.node.parent.parent; - -// let username: string | undefined; -// let password: string | undefined; -// let registryUrl: string | undefined; -// const appSettings: NameValuePair[] = []; - -// // Scenarios: -// // ACR -> App Service, NOT Arc App Service. Use managed service identity. -// if (registryTI instanceof AzureRegistryTreeItem && !context.customLocation) { -// appSettings.push({ name: 'DOCKER_ENABLE_CI', value: 'true' }); - -// // Don't need an image, username, or password--just create an empty web app to assign permissions and then configure with an image -// // `DockerAssignAcrPullRoleStep` handles it after that -// return { -// acrUseManagedIdentityCreds: true, -// appSettings -// }; -// } -// // ACR -> Arc App Service. Use regular auth. Same as any V2 registry but different way of getting auth. -// else if (registryTI instanceof AzureRegistryTreeItem && context.customLocation) { -// const cred = await registryTI.tryGetAdminCredentials(context); -// if (!cred?.username || !cred?.passwords?.[0]?.value) { -// throw new Error(l10n.t('Azure App service deployment on Azure Arc only supports running images from Azure Container Registries with admin enabled')); -// } - -// username = cred.username; -// password = cred.passwords[0].value; -// registryUrl = registryTI.baseUrl; -// } -// // Docker Hub -> App Service *OR* Arc App Service -// else if (registryTI instanceof DockerHubNamespaceTreeItem) { -// username = registryTI.parent.username; -// password = await registryTI.parent.getPassword(); -// registryUrl = 'https://index.docker.io'; -// } -// // Generic registry -> App Service *OR* Arc App Service -// else if (registryTI instanceof DockerV2RegistryTreeItemBase) { -// if (registryTI instanceof GenericDockerV2RegistryTreeItem) { -// username = registryTI.cachedProvider.username; -// password = await getRegistryPassword(registryTI.cachedProvider); -// } else { -// throw new RangeError(l10n.t('Unrecognized node type "{0}"', registryTI.constructor.name)); -// } - -// registryUrl = registryTI.baseUrl; -// } else { -// throw new RangeError(l10n.t('Unrecognized node type "{0}"', registryTI.constructor.name)); -// } - -// if (username && password) { -// appSettings.push({ name: "DOCKER_REGISTRY_SERVER_USERNAME", value: username }); -// appSettings.push({ name: "DOCKER_REGISTRY_SERVER_PASSWORD", value: password }); -// } - -// if (registryUrl) { -// appSettings.push({ name: 'DOCKER_REGISTRY_SERVER_URL', value: registryUrl }); -// } - -// if (context.webSitesPort) { -// appSettings.push({ name: "WEBSITES_PORT", value: context.webSitesPort.toString() }); -// } - -// const linuxFxVersion = `DOCKER|${this.node.fullTag}`; -// TODO: review this later -// const linuxFxVersion = ''; -// return { -// linuxFxVersion, -// appSettings -// }; -// } - -// private addCustomLocationProperties(site: Site, customLocation: CustomLocation): void { -// site.extendedLocation = { name: customLocation.id, type: 'customLocation' }; -// } - -// public shouldExecute(context: IAppServiceContainerWizardContext): boolean { -// return !context.site; -// } -// } - -// TODO: review this later +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { NameValuePair, Site, SiteConfig, WebSiteManagementClient } from '@azure/arm-appservice'; // These are only dev-time imports so don't need to be lazy +import type { CustomLocation } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy +import type { AzExtLocation } from '@microsoft/vscode-azext-azureutils'; // These are only dev-time imports so don't need to be lazy +import { AzureWizardExecuteStep, nonNullProp, nonNullValueAndProp } from "@microsoft/vscode-azext-utils"; +import { CommonRegistry, CommonTag, isDockerHubRegistryItem, isGenericV2Registry } from '@microsoft/vscode-docker-registries'; +import { Progress, l10n } from "vscode"; +import { ext } from "../../../extensionVariables"; +import { AzureRegistryDataProvider, isAzureRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; +import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { getFullImageNameFromRegistryTagItem } from '../../../tree/registries/registryTreeUtils'; +import { getAzExtAppService, getAzExtAzureUtils } from '../../../utils/lazyPackages'; +import { IAppServiceContainerWizardContext } from './deployImageToAzure'; + +export class DockerSiteCreateStep extends AzureWizardExecuteStep { + public priority: number = 140; + + public constructor(private readonly tagItem: UnifiedRegistryItem) { + super(); + } + + public async execute(context: IAppServiceContainerWizardContext, progress: Progress<{ message?: string; increment?: number }>): Promise { + const creatingNewApp: string = l10n.t('Creating web app "{0}"...', context.newSiteName); + ext.outputChannel.info(creatingNewApp); + progress.report({ message: creatingNewApp }); + const siteConfig = await this.getNewSiteConfig(context); + + const azExtAzureUtils = await getAzExtAzureUtils(); + const vscAzureAppService = await getAzExtAppService(); + + const location: AzExtLocation = await azExtAzureUtils.LocationListStep.getLocation(context); + const locationName: string = nonNullProp(location, 'name'); + + const client: WebSiteManagementClient = await vscAzureAppService.createWebSiteClient(context); + const siteEnvelope: Site = { + name: context.newSiteName, + location: locationName, + serverFarmId: nonNullValueAndProp(context.plan, 'id'), + siteConfig: siteConfig + }; + + if (context.customLocation) { + // deploying to Azure Arc + siteEnvelope.kind = 'app,linux,kubernetes,container'; + this.addCustomLocationProperties(siteEnvelope, context.customLocation); + } else { + siteEnvelope.identity = { + type: 'SystemAssigned' + }; + } + + context.site = await client.webApps.beginCreateOrUpdateAndWait(nonNullValueAndProp(context.resourceGroup, 'name'), nonNullProp(context, 'newSiteName'), siteEnvelope); + } + + private async getNewSiteConfig(context: IAppServiceContainerWizardContext): Promise { + const registryTI: UnifiedRegistryItem = this.tagItem.parent.parent as unknown as UnifiedRegistryItem; + + let username: string | undefined; + let password: string | undefined; + let registryUrl: string | undefined; + const appSettings: NameValuePair[] = []; + + // Scenarios: + // ACR -> App Service, NOT Arc App Service. Use managed service identity. + if (isAzureRegistryItem(registryTI.wrappedItem) && !context.customLocation) { + appSettings.push({ name: 'DOCKER_ENABLE_CI', value: 'true' }); + + // Don't need an image, username, or password--just create an empty web app to assign permissions and then configure with an image + // `DockerAssignAcrPullRoleStep` handles it after that + return { + acrUseManagedIdentityCreds: true, + appSettings + }; + } + // ACR -> Arc App Service. Use regular auth. Same as any V2 registry but different way of getting auth. + else if (isAzureRegistryItem(registryTI.wrappedItem) && context.customLocation) { + const cred = await (registryTI.provider as unknown as AzureRegistryDataProvider).tryGetAdminCredentials(registryTI.wrappedItem); + if (!cred?.username || !cred?.passwords?.[0]?.value) { + throw new Error(l10n.t('Azure App service deployment on Azure Arc only supports running images from Azure Container Registries with admin enabled')); + } + + username = cred.username; + password = cred.passwords[0].value; + registryUrl = registryTI.wrappedItem.baseUrl.toString(); + } + // Docker Hub -> App Service *OR* Arc App Service + else if (isDockerHubRegistryItem(registryTI.wrappedItem)) { + const loginInformation = await registryTI.provider.getLoginInformation(registryTI.wrappedItem); + username = loginInformation.username; + password = loginInformation.secret; + registryUrl = 'https://index.docker.io'; + } + // Generic registry -> App Service *OR* Arc App Service + else if (isGenericV2Registry(registryTI.wrappedItem)) { + const loginInformation = await registryTI.provider.getLoginInformation(registryTI.wrappedItem); + username = loginInformation.username; + password = loginInformation.secret; + registryUrl = (registryTI.wrappedItem as CommonRegistry).baseUrl.toString(); + } + // TODO: add case for GitHub Container Registry + else { + throw new RangeError(l10n.t('Unrecognized node type "{0}"', registryTI.constructor.name)); + } + + if (username && password) { + appSettings.push({ name: "DOCKER_REGISTRY_SERVER_USERNAME", value: username }); + appSettings.push({ name: "DOCKER_REGISTRY_SERVER_PASSWORD", value: password }); + } + + if (registryUrl) { + appSettings.push({ name: 'DOCKER_REGISTRY_SERVER_URL', value: registryUrl }); + } + + if (context.webSitesPort) { + appSettings.push({ name: "WEBSITES_PORT", value: context.webSitesPort.toString() }); + } + + const linuxFxVersion = `DOCKER|${getFullImageNameFromRegistryTagItem(this.tagItem.wrappedItem)}`; + + return { + linuxFxVersion, + appSettings + }; + } + + private addCustomLocationProperties(site: Site, customLocation: CustomLocation): void { + site.extendedLocation = { name: customLocation.id, type: 'customLocation' }; + } + + public shouldExecute(context: IAppServiceContainerWizardContext): boolean { + return !context.site; + } +} diff --git a/src/commands/registries/azure/DockerWebhookCreateStep.ts b/src/commands/registries/azure/DockerWebhookCreateStep.ts index 25f56c23d1..5c48b185dd 100644 --- a/src/commands/registries/azure/DockerWebhookCreateStep.ts +++ b/src/commands/registries/azure/DockerWebhookCreateStep.ts @@ -1,96 +1,99 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ -// import type { Site } from '@azure/arm-appservice'; // These are only dev-time imports so don't need to be lazy -// import type { Webhook, WebhookCreateParameters } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy -// import type { IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy -// import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils"; -// import * as vscode from "vscode"; -// import { ext } from "../../../extensionVariables"; -// import { AzureRegistryTreeItem } from '../../../tree/registries/azure/AzureRegistryTreeItem'; -// import { AzureRepositoryTreeItem } from '../../../tree/registries/azure/AzureRepositoryTreeItem'; -// import { DockerHubRepositoryTreeItem } from '../../../tree/registries/dockerHub/DockerHubRepositoryTreeItem'; -// import { RemoteTagTreeItem } from '../../../tree/registries/RemoteTagTreeItem'; -// import { cryptoUtils } from '../../../utils/cryptoUtils'; -// import { getArmContainerRegistry, getAzExtAppService, getAzExtAzureUtils } from "../../../utils/lazyPackages"; +import type { Site } from '@azure/arm-appservice'; // These are only dev-time imports so don't need to be lazy +import type { Webhook, WebhookCreateParameters } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy +import type { IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy +import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils"; +import { CommonRepository, CommonTag, isDockerHubRepositoryItem } from '@microsoft/vscode-docker-registries'; +import * as vscode from "vscode"; +import { ext } from "../../../extensionVariables"; +import { AzureRegistry, isAzureRepositoryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; +import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { getResourceGroupFromAzureRegistryItem } from '../../../tree/registries/registryTreeUtils'; +import { cryptoUtils } from '../../../utils/cryptoUtils'; +import { getArmContainerRegistry, getAzExtAppService, getAzExtAzureUtils } from "../../../utils/lazyPackages"; -// export class DockerWebhookCreateStep extends AzureWizardExecuteStep { -// public priority: number = 142; // execute after DockerAssignAcrPullRoleStep -// private _treeItem: RemoteTagTreeItem; -// public constructor(treeItem: RemoteTagTreeItem) { -// super(); -// this._treeItem = treeItem; -// } +export class DockerWebhookCreateStep extends AzureWizardExecuteStep { + public priority: number = 142; // execute after DockerAssignAcrPullRoleStep -// public async execute(context: IAppServiceWizardContext, progress: vscode.Progress<{ -// message?: string; -// increment?: number; -// }>): Promise { -// const vscAzureAppService = await getAzExtAppService(); -// vscAzureAppService.registerAppServiceExtensionVariables(ext); -// const site: Site = nonNullProp(context, 'site'); -// const parsedSite = new vscAzureAppService.ParsedSite(site, context); -// const siteClient = await parsedSite.createClient(context); -// const appUri: string = (await siteClient.getWebAppPublishCredential()).scmUri; -// if (this._treeItem.parent instanceof AzureRepositoryTreeItem) { -// const creatingNewWebhook: string = vscode.l10n.t('Creating webhook for web app "{0}"...', context.newSiteName); -// ext.outputChannel.info(creatingNewWebhook); -// progress.report({ message: creatingNewWebhook }); -// const webhook = await this.createWebhookForApp(context, this._treeItem, context.site, appUri); -// ext.outputChannel.info(vscode.l10n.t('Created webhook "{0}" with scope "{1}", id: "{2}" and location: "{3}"', webhook.name, webhook.scope, webhook.id, webhook.location)); -// } else if (this._treeItem.parent instanceof DockerHubRepositoryTreeItem) { -// // point to dockerhub to create a webhook -// // http://cloud.docker.com/repository/docker///webHooks -// const dockerhubPrompt: string = vscode.l10n.t('Copy & Open'); -// const dockerhubUri: string = `https://cloud.docker.com/repository/docker/${this._treeItem.parent.parent.namespace}/${this._treeItem.parent.repoName}/webHooks`; + public constructor(private readonly tagItem: UnifiedRegistryItem) { + super(); + } -// // NOTE: The response to the information message is not awaited but handled independently of the wizard steps. -// // VS Code will hide such messages in the notifications pane after a period of time; awaiting them risks -// // the user never noticing them in the first place, which means the wizard would never complete, and the -// // user left with the impression that the action never completes. + public async execute(context: IAppServiceWizardContext, progress: vscode.Progress<{ + message?: string; + increment?: number; + }>): Promise { + const vscAzureAppService = await getAzExtAppService(); + vscAzureAppService.registerAppServiceExtensionVariables(ext); + const site: Site = nonNullProp(context, 'site'); + const parsedSite = new vscAzureAppService.ParsedSite(site, context); + const siteClient = await parsedSite.createClient(context); + const appUri: string = (await siteClient.getWebAppPublishCredential()).scmUri; -// /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ -// vscode.window -// .showInformationMessage(vscode.l10n.t('To set up a CI/CD webhook, open the page "{0}" and enter the URI to the created web app in your dockerhub account', dockerhubUri), dockerhubPrompt) -// .then(response => { -// if (response) { -// void vscode.env.clipboard.writeText(appUri); -// void vscode.env.openExternal(vscode.Uri.parse(dockerhubUri)); -// } -// }); -// } -// } + if (isAzureRepositoryItem(this.tagItem.parent.wrappedItem)) { + const creatingNewWebhook: string = vscode.l10n.t('Creating webhook for web app "{0}"...', context.newSiteName); + ext.outputChannel.info(creatingNewWebhook); + progress.report({ message: creatingNewWebhook }); + const webhook = await this.createWebhookForApp(context, context.site, appUri); + ext.outputChannel.info(vscode.l10n.t('Created webhook "{0}" with scope "{1}", id: "{2}" and location: "{3}"', webhook.name, webhook.scope, webhook.id, webhook.location)); + } else if (isDockerHubRepositoryItem(this.tagItem.parent.wrappedItem)) { + const registryName = this.tagItem.parent.parent.wrappedItem.label; + const repoName = (this.tagItem.parent as unknown as CommonRepository).wrappedItem.label; + // point to dockerhub to create a webhook + // http://cloud.docker.com/repository/docker///webHooks + const dockerhubPrompt: string = vscode.l10n.t('Copy & Open'); + const dockerhubUri: string = `https://cloud.docker.com/repository/docker/${registryName}/${repoName}/webHooks`; -// public shouldExecute(context: IAppServiceWizardContext): boolean { -// return !!context.site && (this._treeItem.parent instanceof AzureRepositoryTreeItem || this._treeItem.parent instanceof DockerHubRepositoryTreeItem); -// } + // NOTE: The response to the information message is not awaited but handled independently of the wizard steps. + // VS Code will hide such messages in the notifications pane after a period of time; awaiting them risks + // the user never noticing them in the first place, which means the wizard would never complete, and the + // user left with the impression that the action never completes. -// private async createWebhookForApp(context: IAppServiceWizardContext, node: RemoteTagTreeItem, site: Site, appUri: string): Promise { -// const maxLength: number = 50; -// const numRandomChars: number = 6; + /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ + vscode.window + .showInformationMessage(vscode.l10n.t('To set up a CI/CD webhook, open the page "{0}" and enter the URI to the created web app in your dockerhub account', dockerhubUri), dockerhubPrompt) + .then(response => { + if (response) { + void vscode.env.clipboard.writeText(appUri); + void vscode.env.openExternal(vscode.Uri.parse(dockerhubUri)); + } + }); + } + } -// let webhookName: string = site.name; -// // remove disallowed characters -// webhookName = webhookName.replace(/[^a-zA-Z0-9]/g, ''); -// // trim to max length -// webhookName = webhookName.substr(0, maxLength - numRandomChars); -// // add random chars for uniqueness and to ensure min length is met -// webhookName += cryptoUtils.getRandomHexString(numRandomChars); + public shouldExecute(context: IAppServiceWizardContext): boolean { + return !!context.site && (isAzureRepositoryItem(this.tagItem.parent.wrappedItem) || isDockerHubRepositoryItem(this.tagItem.parent.wrappedItem)); + } -// // variables derived from the container registry -// const registryTreeItem: AzureRegistryTreeItem = (node.parent).parent; -// const armContainerRegistry = await getArmContainerRegistry(); -// const azExtAzureUtils = await getAzExtAzureUtils(); -// const crmClient = azExtAzureUtils.createAzureClient(context, armContainerRegistry.ContainerRegistryManagementClient); -// const webhookCreateParameters: WebhookCreateParameters = { -// location: registryTreeItem.registryLocation, -// serviceUri: appUri, -// scope: `${node.parent.repoName}:${node.tag}`, -// actions: ["push"], -// status: 'enabled' -// }; -// return await crmClient.webhooks.beginCreateAndWait(registryTreeItem.resourceGroup, registryTreeItem.registryName, webhookName, webhookCreateParameters); -// } -// } + private async createWebhookForApp(context: IAppServiceWizardContext, site: Site, appUri: string): Promise { + const maxLength: number = 50; + const numRandomChars: number = 6; + + let webhookName: string = site.name; + // remove disallowed characters + webhookName = webhookName.replace(/[^a-zA-Z0-9]/g, ''); + // trim to max length + webhookName = webhookName.substr(0, maxLength - numRandomChars); + // add random chars for uniqueness and to ensure min length is met + webhookName += cryptoUtils.getRandomHexString(numRandomChars); + + // variables derived from the container registry + const registryTreeItem: UnifiedRegistryItem = this.tagItem.parent.parent as unknown as UnifiedRegistryItem; + const armContainerRegistry = await getArmContainerRegistry(); + const azExtAzureUtils = await getAzExtAzureUtils(); + const crmClient = azExtAzureUtils.createAzureClient(context, armContainerRegistry.ContainerRegistryManagementClient); + const webhookCreateParameters: WebhookCreateParameters = { + location: registryTreeItem.wrappedItem.registryProperties.location, + serviceUri: appUri, + scope: `${this.tagItem.parent.wrappedItem.label}:${this.tagItem.wrappedItem.label}`, + actions: ["push"], + status: 'enabled' + }; + const resourceGroup = getResourceGroupFromAzureRegistryItem(registryTreeItem.wrappedItem); + return await crmClient.webhooks.beginCreateAndWait(resourceGroup, registryTreeItem.wrappedItem.label, webhookName, webhookCreateParameters); + } +} diff --git a/src/commands/registries/azure/WebSitesPortPromptStep.ts b/src/commands/registries/azure/WebSitesPortPromptStep.ts index e16ca26dab..48667641c0 100644 --- a/src/commands/registries/azure/WebSitesPortPromptStep.ts +++ b/src/commands/registries/azure/WebSitesPortPromptStep.ts @@ -1,35 +1,35 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * 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 "@microsoft/vscode-azext-utils"; -// import { l10n } from 'vscode'; -// import { IAppServiceContainerWizardContext } from './deployImageToAzure'; +import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils"; +import { l10n } from 'vscode'; +import { IAppServiceContainerWizardContext } from './deployImageToAzure'; -// export class WebSitesPortPromptStep extends AzureWizardPromptStep { +export class WebSitesPortPromptStep extends AzureWizardPromptStep { -// public async prompt(context: IAppServiceContainerWizardContext): Promise { -// const prompt: string = l10n.t('What port does your app listen on?'); -// const placeHolder: string = '80'; -// const value: string = '80'; -// const portString: string = await context.ui.showInputBox({ prompt, placeHolder, value, validateInput }); -// context.webSitesPort = parseInt(portString, 10); -// } + public async prompt(context: IAppServiceContainerWizardContext): Promise { + const prompt: string = l10n.t('What port does your app listen on?'); + const placeHolder: string = '80'; + const value: string = '80'; + const portString: string = await context.ui.showInputBox({ prompt, placeHolder, value, validateInput }); + context.webSitesPort = parseInt(portString, 10); + } -// public shouldPrompt(context: IAppServiceContainerWizardContext): boolean { -// return !!context.customLocation; -// } -// } + public shouldPrompt(context: IAppServiceContainerWizardContext): boolean { + return !!context.customLocation; + } +} -// function validateInput(value: string | undefined): string | undefined { -// if (Number(value)) { -// const port: number = parseInt(value, 10); -// if (port >= 1 && port <= 65535) { -// return undefined; -// } -// } +function validateInput(value: string | undefined): string | undefined { + if (Number(value)) { + const port: number = parseInt(value, 10); + if (port >= 1 && port <= 65535) { + return undefined; + } + } -// return l10n.t('Port must be a positive integer (1 to 65535).'); -// } + return l10n.t('Port must be a positive integer (1 to 65535).'); +} diff --git a/src/commands/registries/azure/deployImageToAca.ts b/src/commands/registries/azure/deployImageToAca.ts index da4d9a495a..a2e0332a49 100644 --- a/src/commands/registries/azure/deployImageToAca.ts +++ b/src/commands/registries/azure/deployImageToAca.ts @@ -1,94 +1,98 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ - -// import { contextValueExperience, IActionContext, UserCancelledError } from '@microsoft/vscode-azext-utils'; -// import * as semver from 'semver'; -// import * as vscode from 'vscode'; -// import { ext } from '../../../extensionVariables'; -// import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; -// import { installExtension } from '../../../utils/installExtension'; -// import { addImageTaggingTelemetry } from '../../images/tagImage'; - -// const acaExtensionId = 'ms-azuretools.vscode-azurecontainerapps'; -// const minimumAcaExtensionVersion = '0.4.0'; - -// // The interface of the command options passed to the Azure Container Apps extension's deployImageToAca command -// interface DeployImageToAcaOptionsContract { -// image: string; -// registryName: string; -// username?: string; -// secret?: string; -// } - -// export async function deployImageToAca(context: IActionContext, node?: UnifiedRegistryItem): Promise { -// // Assert installation of the ACA extension -// if (!isAcaExtensionInstalled()) { -// // This will always throw a `UserCancelledError` but with the appropriate step name -// // based on user choice about installation -// await openAcaInstallPage(context); -// } - -// if (!node) { -// node = await contextValueExperience(context, ext.registriesRoot, { include: 'azurecontainerregistry' }); -// } - -// const commandOptions: Partial = { -// // image: node.fullTag, -// }; - -// addImageTaggingTelemetry(context, commandOptions.image, ''); - -// // const registry: RegistryTreeItemBase = node.parent.parent; -// // if (registry instanceof AzureRegistryTreeItem) { -// // // No additional work to do; ACA can handle this on its own -// // } else { -// // const { auth } = await registry.getDockerCliCredentials() as { auth?: { username?: string, password?: string } }; - -// // if (!auth?.username || !auth?.password) { -// // throw new Error(vscode.l10n.t('No credentials found for registry "{0}".', registry.label)); -// // } - -// // if (registry instanceof DockerHubNamespaceTreeItem) { -// // // Ensure Docker Hub images are prefixed with 'docker.io/...' -// // if (!/^docker\.io\//i.test(commandOptions.image)) { -// // commandOptions.image = 'docker.io/' + commandOptions.image; -// // } -// // } - -// // commandOptions.username = auth.username; -// // commandOptions.secret = auth.password; -// // } - -// // commandOptions.registryName = nonNullProp(parseDockerLikeImageName(commandOptions.image), 'registry'); - -// // // Don't wait -// // void vscode.commands.executeCommand('containerApps.deployImageApi', commandOptions); -// // TODO: review this later -// } - -// function isAcaExtensionInstalled(): boolean { -// const acaExtension = vscode.extensions.getExtension(acaExtensionId); - -// if (!acaExtension?.packageJSON?.version) { -// // If the ACA extension is not present, or the package JSON didn't come through, or the version is not present, then it's not installed -// return false; -// } - -// const acaVersion = semver.coerce(acaExtension.packageJSON.version); -// const minVersion = semver.coerce(minimumAcaExtensionVersion); - -// return semver.gte(acaVersion, minVersion); -// } - -// async function openAcaInstallPage(context: IActionContext): Promise { -// const message = vscode.l10n.t( -// 'Version {0} or higher of the Azure Container Apps extension is required to deploy to Azure Container Apps. Would you like to install it now?', -// minimumAcaExtensionVersion -// ); - -// await installExtension(context, acaExtensionId, message); - -// throw new UserCancelledError('installAcaExtensionAccepted'); -// } +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { contextValueExperience, IActionContext, nonNullProp, UserCancelledError } from '@microsoft/vscode-azext-utils'; +import { parseDockerLikeImageName } from '@microsoft/vscode-container-client'; +import { CommonRegistry, CommonTag, isDockerHubRegistryItem, LoginInformation } from '@microsoft/vscode-docker-registries'; +import * as semver from 'semver'; +import * as vscode from 'vscode'; +import { ext } from '../../../extensionVariables'; +import { isAzureRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; +import { getFullImageNameFromRegistryTagItem } from '../../../tree/registries/registryTreeUtils'; +import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { installExtension } from '../../../utils/installExtension'; +import { addImageTaggingTelemetry } from '../../images/tagImage'; + +const acaExtensionId = 'ms-azuretools.vscode-azurecontainerapps'; +const minimumAcaExtensionVersion = '0.4.0'; + +// The interface of the command options passed to the Azure Container Apps extension's deployImageToAca command +interface DeployImageToAcaOptionsContract { + image: string; + registryName: string; + username?: string; + secret?: string; +} + +export async function deployImageToAca(context: IActionContext, node?: UnifiedRegistryItem): Promise { + // Assert installation of the ACA extension + if (!isAcaExtensionInstalled()) { + // This will always throw a `UserCancelledError` but with the appropriate step name + // based on user choice about installation + await openAcaInstallPage(context); + } + + if (!node) { + node = await contextValueExperience(context, ext.registriesTree, { include: 'commontag' }); + } + + const commandOptions: Partial = { + image: getFullImageNameFromRegistryTagItem(node.wrappedItem), + }; + + addImageTaggingTelemetry(context, commandOptions.image, ''); + + const registry: UnifiedRegistryItem = node.parent.parent as unknown as UnifiedRegistryItem; + + if (isAzureRegistryItem(registry.wrappedItem)) { + // No additional work to do; ACA can handle this on its own + } else { + const logInInfo: LoginInformation = await registry.provider.getLoginInformation(registry.wrappedItem); + + if (!logInInfo?.username || !logInInfo?.secret) { + throw new Error(vscode.l10n.t('No credentials found for registry "{0}".', registry.wrappedItem.label)); + } + + if (isDockerHubRegistryItem(registry.wrappedItem)) { + // Ensure Docker Hub images are prefixed with 'docker.io/...' + if (!/^docker\.io\//i.test(commandOptions.image)) { + commandOptions.image = 'docker.io/' + commandOptions.image; + } + } + + commandOptions.username = logInInfo.username; + commandOptions.secret = logInInfo.secret; + } + + commandOptions.registryName = nonNullProp(parseDockerLikeImageName(commandOptions.image), 'registry'); + + // Don't wait + void vscode.commands.executeCommand('containerApps.deployImageApi', commandOptions); +} + +function isAcaExtensionInstalled(): boolean { + const acaExtension = vscode.extensions.getExtension(acaExtensionId); + + if (!acaExtension?.packageJSON?.version) { + // If the ACA extension is not present, or the package JSON didn't come through, or the version is not present, then it's not installed + return false; + } + + const acaVersion = semver.coerce(acaExtension.packageJSON.version); + const minVersion = semver.coerce(minimumAcaExtensionVersion); + + return semver.gte(acaVersion, minVersion); +} + +async function openAcaInstallPage(context: IActionContext): Promise { + const message = vscode.l10n.t( + 'Version {0} or higher of the Azure Container Apps extension is required to deploy to Azure Container Apps. Would you like to install it now?', + minimumAcaExtensionVersion + ); + + await installExtension(context, acaExtensionId, message); + + throw new UserCancelledError('installAcaExtensionAccepted'); +} diff --git a/src/commands/registries/azure/deployImageToAzure.ts b/src/commands/registries/azure/deployImageToAzure.ts index 375982c4ea..cbed25dcf4 100644 --- a/src/commands/registries/azure/deployImageToAzure.ts +++ b/src/commands/registries/azure/deployImageToAzure.ts @@ -1,80 +1,76 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ -// import type { Site } from '@azure/arm-appservice'; // These are only dev-time imports so don't need to be lazy -// import type { IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy -// import { AzureWizard, AzureWizardExecuteStep, AzureWizardPromptStep, IActionContext, nonNullProp } from "@microsoft/vscode-azext-utils"; -// import { env, l10n, Uri, window } from "vscode"; -// import { ext } from "../../../extensionVariables"; -// import { RegistryApi } from '../../../tree/registries/all/RegistryApi'; -// import { azureRegistryProviderId } from '../../../tree/registries/azure/azureRegistryProvider'; -// import { registryExpectedContextValues } from '../../../tree/registries/registryContextValues'; -// import { RemoteTagTreeItem } from '../../../tree/registries/RemoteTagTreeItem'; -// import { getAzActTreeItem, getAzExtAppService, getAzExtAzureUtils } from '../../../utils/lazyPackages'; -// import { DockerAssignAcrPullRoleStep } from './DockerAssignAcrPullRoleStep'; -// import { DockerSiteCreateStep } from './DockerSiteCreateStep'; -// import { DockerWebhookCreateStep } from './DockerWebhookCreateStep'; -// import { WebSitesPortPromptStep } from './WebSitesPortPromptStep'; +import type { Site } from '@azure/arm-appservice'; // These are only dev-time imports so don't need to be lazy +import type { IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy +import { AzureWizard, AzureWizardExecuteStep, AzureWizardPromptStep, IActionContext, contextValueExperience, createSubscriptionContext, nonNullProp } from "@microsoft/vscode-azext-utils"; +import { CommonTag } from '@microsoft/vscode-docker-registries'; +import { Uri, env, l10n, window } from "vscode"; +import { ext } from "../../../extensionVariables"; +import { AzureSubscriptionRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; +import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { getAzExtAppService, getAzExtAzureUtils } from '../../../utils/lazyPackages'; +import { registryExperience } from '../../../utils/registryExperience'; +import { DockerAssignAcrPullRoleStep } from './DockerAssignAcrPullRoleStep'; +import { DockerSiteCreateStep } from './DockerSiteCreateStep'; +import { DockerWebhookCreateStep } from './DockerWebhookCreateStep'; +import { WebSitesPortPromptStep } from './WebSitesPortPromptStep'; +export interface IAppServiceContainerWizardContext extends IAppServiceWizardContext { + webSitesPort?: number; +} -// export interface IAppServiceContainerWizardContext extends IAppServiceWizardContext { -// webSitesPort?: number; -// } +export async function deployImageToAzure(context: IActionContext, node?: UnifiedRegistryItem): Promise { + if (!node) { + node = await contextValueExperience(context, ext.registriesTree, { include: 'commontag' }); + } -// export async function deployImageToAzure(context: IActionContext, node?: RemoteTagTreeItem): Promise { -// if (!node) { -// node = await ext.registriesTree.showTreeItemPicker([registryExpectedContextValues.dockerHub.tag, registryExpectedContextValues.dockerV2.tag], context); -// } + const azExtAzureUtils = await getAzExtAzureUtils(); + const vscAzureAppService = await getAzExtAppService(); -// const azExtAzureUtils = await getAzExtAzureUtils(); -// const vscAzureAppService = await getAzExtAppService(); -// const azActTreeItem = await getAzActTreeItem(); + const promptSteps: AzureWizardPromptStep[] = []; -// const wizardContext: IActionContext & Partial = { -// ...context, -// newSiteOS: vscAzureAppService.WebsiteOS.linux, -// newSiteKind: vscAzureAppService.AppKind.app -// }; -// const promptSteps: AzureWizardPromptStep[] = []; -// // Create a temporary azure account tree item since Azure might not be connected -// const azureAccountTreeItem = new azActTreeItem.AzureAccountTreeItem(ext.registriesRoot, { id: azureRegistryProviderId, api: RegistryApi.DockerV2 }); -// const subscriptionStep = await azureAccountTreeItem.getSubscriptionPromptStep(wizardContext); -// if (subscriptionStep) { -// promptSteps.push(subscriptionStep); -// } + const subscriptionItem = await registryExperience(context, ext.azureRegistryDataProvider, { include: 'azuresubscription' }) as AzureSubscriptionRegistryItem; + const subscriptionContext = createSubscriptionContext(subscriptionItem.subscription); + const wizardContext: IActionContext & Partial = { + ...context, + ...subscriptionContext, + newSiteOS: vscAzureAppService.WebsiteOS.linux, + newSiteKind: vscAzureAppService.AppKind.app + }; -// promptSteps.push(new vscAzureAppService.SiteNameStep()); -// promptSteps.push(new azExtAzureUtils.ResourceGroupListStep()); -// vscAzureAppService.CustomLocationListStep.addStep(wizardContext, promptSteps); -// promptSteps.push(new WebSitesPortPromptStep()); -// promptSteps.push(new vscAzureAppService.AppServicePlanListStep()); + promptSteps.push(new vscAzureAppService.SiteNameStep()); + promptSteps.push(new azExtAzureUtils.ResourceGroupListStep()); + vscAzureAppService.CustomLocationListStep.addStep(wizardContext, promptSteps); + promptSteps.push(new WebSitesPortPromptStep()); + promptSteps.push(new vscAzureAppService.AppServicePlanListStep()); -// // Get site config before running the wizard so that any problems with the tag tree item are shown at the beginning of the process -// const executeSteps: AzureWizardExecuteStep[] = [ -// new DockerSiteCreateStep(node), -// new DockerAssignAcrPullRoleStep(node), -// new DockerWebhookCreateStep(node), -// ]; + // Get site config before running the wizard so that any problems with the tag tree item are shown at the beginning of the process + const executeSteps: AzureWizardExecuteStep[] = [ + new DockerSiteCreateStep(node), + new DockerAssignAcrPullRoleStep(node), + new DockerWebhookCreateStep(node), + ]; -// const title = l10n.t('Create new web app'); -// const wizard = new AzureWizard(wizardContext, { title, promptSteps, executeSteps }); -// await wizard.prompt(); -// await wizard.execute(); + const title = l10n.t('Create new web app'); + const wizard = new AzureWizard(wizardContext, { title, promptSteps, executeSteps }); + await wizard.prompt(); + await wizard.execute(); -// const site: Site = nonNullProp(wizardContext, 'site'); -// const siteUri: string = `https://${site.defaultHostName}`; -// const createdNewWebApp: string = l10n.t('Successfully created web app "{0}": {1}', site.name, siteUri); -// ext.outputChannel.info(createdNewWebApp); + const site: Site = nonNullProp(wizardContext, 'site'); + const siteUri: string = `https://${site.defaultHostName}`; + const createdNewWebApp: string = l10n.t('Successfully created web app "{0}": {1}', site.name, siteUri); + ext.outputChannel.info(createdNewWebApp); -// const openSite: string = l10n.t('Open Site'); -// // don't wait -// /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ -// window.showInformationMessage(createdNewWebApp, ...[openSite]).then((selection) => { -// if (selection === openSite) { -// /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ -// env.openExternal(Uri.parse(siteUri)); -// } -// }); -// } + const openSite: string = l10n.t('Open Site'); + // don't wait + /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ + window.showInformationMessage(createdNewWebApp, ...[openSite]).then((selection) => { + if (selection === openSite) { + /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ + env.openExternal(Uri.parse(siteUri)); + } + }); +} diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 93c3fad0db..2f42ce0a33 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -14,6 +14,7 @@ import { ContainersTreeItem } from './tree/containers/ContainersTreeItem'; import { ContextsTreeItem } from './tree/contexts/ContextsTreeItem'; import { ImagesTreeItem } from './tree/images/ImagesTreeItem'; import { NetworksTreeItem } from './tree/networks/NetworksTreeItem'; +import { AzureRegistryDataProvider } from './tree/registries/Azure/AzureRegistryDataProvider'; import { UnifiedRegistryItem, UnifiedRegistryTreeDataProvider } from './tree/registries/UnifiedRegistryTreeDataProvider'; import { VolumesTreeItem } from './tree/volumes/VolumesTreeItem'; import { AzExtLogOutputChannelWrapper } from './utils/AzExtLogOutputChannelWrapper'; @@ -50,6 +51,7 @@ export namespace ext { export let registriesTreeView: TreeView>; export let registriesRoot: UnifiedRegistryTreeDataProvider; export let genericRegistryV2DataProvider: GenericRegistryV2DataProvider; + export let azureRegistryDataProvider: AzureRegistryDataProvider; export let volumesTree: AzExtTreeDataProvider; export let volumesTreeView: TreeView; diff --git a/src/tree/registerTrees.ts b/src/tree/registerTrees.ts index e55991df7f..2280a11905 100644 --- a/src/tree/registerTrees.ts +++ b/src/tree/registerTrees.ts @@ -46,14 +46,16 @@ export function registerTrees(): void { const urtdp = new UnifiedRegistryTreeDataProvider(ext.context.globalState); const genericRegistryV2DataProvider = new GenericRegistryV2DataProvider(ext.context); + const azureRegistryDataProvider = new AzureRegistryDataProvider(ext.context); urtdp.registerProvider(new GitHubRegistryDataProvider(ext.context)); urtdp.registerProvider(new DockerHubRegistryDataProvider(ext.context)); - urtdp.registerProvider(new AzureRegistryDataProvider(ext.context)); + urtdp.registerProvider(azureRegistryDataProvider); urtdp.registerProvider(genericRegistryV2DataProvider); ext.registriesRoot = urtdp; ext.registriesTreeView = vscode.window.createTreeView('dockerRegistries', { treeDataProvider: urtdp }); ext.registriesTree = urtdp; ext.genericRegistryV2DataProvider = genericRegistryV2DataProvider; + ext.azureRegistryDataProvider = azureRegistryDataProvider; ext.volumesRoot = new VolumesTreeItem(undefined); const volumesLoadMore = 'vscode-docker.volumes.loadMore'; diff --git a/src/tree/registries/Azure/AzureRegistryDataProvider.ts b/src/tree/registries/Azure/AzureRegistryDataProvider.ts index f2e5cdaf7a..2da1ca14f1 100644 --- a/src/tree/registries/Azure/AzureRegistryDataProvider.ts +++ b/src/tree/registries/Azure/AzureRegistryDataProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { Registry as AcrRegistry } from '@azure/arm-containerregistry'; +import type { Registry as AcrRegistry, RegistryListCredentialsResult } from '@azure/arm-containerregistry'; import { AzureSubscription, VSCodeAzureSubscriptionProvider } from '@microsoft/vscode-azext-azureauth'; import { RegistryV2DataProvider, V2Registry, V2RegistryItem, V2Repository, V2Tag, registryV2Request } from '@microsoft/vscode-docker-registries'; import { CommonRegistryItem, isRegistryRoot } from '@microsoft/vscode-docker-registries/lib/clients/Common/models'; @@ -37,6 +37,14 @@ export function isAzureRegistryItem(item: unknown): item is AzureRegistry { return !!item && typeof item === 'object' && (item as AzureRegistryItem).additionalContextValues?.includes('azureContainerRegistry'); } +export function isAzureRepositoryItem(item: unknown): item is AzureRepository { + return !!item && typeof item === 'object' && (item as AzureRepository).additionalContextValues?.includes('azureContainerRepository'); +} + +export function isAzureTagItem(item: unknown): item is AzureTag { + return !!item && typeof item === 'object' && (item as AzureTag).additionalContextValues?.includes('azureContainerTag'); +} + export class AzureRegistryDataProvider extends RegistryV2DataProvider implements vscode.Disposable { public readonly id = 'vscode-docker.azureContainerRegistry'; public readonly label = vscode.l10n.t('Azure'); @@ -190,6 +198,15 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements } } + public async tryGetAdminCredentials(azureRegistry: AzureRegistry): Promise { + if (azureRegistry.registryProperties.adminUserEnabled) { + const client = await createAzureContainerRegistryClient(azureRegistry.subscription); + return await client.registries.listCredentials(getResourceGroupFromId(azureRegistry.id), azureRegistry.label); + } else { + return undefined; + } + } + protected override getAuthenticationProvider(item: AzureRegistryItem): ACROAuthProvider { const registryString = item.baseUrl.toString(); diff --git a/src/tree/registries/registryTreeUtils.ts b/src/tree/registries/registryTreeUtils.ts index 683ac1f33e..6c8fc98acc 100644 --- a/src/tree/registries/registryTreeUtils.ts +++ b/src/tree/registries/registryTreeUtils.ts @@ -5,6 +5,8 @@ import { CommonRegistry, CommonRepository, CommonTag, isRegistry, isRepository, isTag } from "@microsoft/vscode-docker-registries"; import { l10n } from "vscode"; +import { getResourceGroupFromId } from "../../utils/azureUtils"; +import { AzureRegistryItem } from "./Azure/AzureRegistryDataProvider"; export function getImageNameFromRegistryTagItem(tag: CommonTag): string { if (!isTag(tag) || !isRepository(tag.parent)) { @@ -37,3 +39,11 @@ export function getFullImageNameFromRegistryTagItem(tag: CommonTag): string { const baseImagePath = getBaseImagePathFromRegistryItem(tag.parent.parent); return `${baseImagePath}/${imageName}`; } + +export function getResourceGroupFromAzureRegistryItem(node: AzureRegistryItem): string { + if (!isRegistry(node)) { + throw new Error('Unable to get resource group'); + } + + return getResourceGroupFromId(node.id); +} diff --git a/src/utils/azureUtils.ts b/src/utils/azureUtils.ts index dbcc5ae66f..8d23588739 100644 --- a/src/utils/azureUtils.ts +++ b/src/utils/azureUtils.ts @@ -5,13 +5,7 @@ import type { ContainerRegistryManagementClient } from '@azure/arm-containerregistry'; import { AzureSubscription } from '@microsoft/vscode-azext-azureauth'; -import { ISubscriptionContext } from '@microsoft/vscode-azext-utils'; -import { Request } from 'node-fetch'; -import { URLSearchParams } from 'url'; import { l10n } from 'vscode'; -import { httpRequest, RequestOptionsLike } from './httpRequest'; - -const refreshTokens: { [key: string]: string } = {}; function parseResourceId(id: string): RegExpMatchArray { const matches: RegExpMatchArray | null = id.match(/\/subscriptions\/(.*)\/resourceGroups\/(.*)\/providers\/(.*)\/(.*)/i); @@ -25,53 +19,6 @@ export function getResourceGroupFromId(id: string): string { return parseResourceId(id)[2]; } -/* eslint-disable @typescript-eslint/naming-convention */ -export async function acquireAcrAccessToken(registryHost: string, subContext: ISubscriptionContext, scope: string): Promise { - const options: RequestOptionsLike = { - form: { - grant_type: 'refresh_token', - service: registryHost, - scope: scope, - refresh_token: undefined - }, - method: 'POST', - }; - - try { - if (refreshTokens[registryHost]) { - options.form.refresh_token = refreshTokens[registryHost]; - const responseFromCachedToken = await httpRequest<{ access_token: string }>(`https://${registryHost}/oauth2/token`, options); - return (await responseFromCachedToken.json()).access_token; - } - } catch { /* No-op, fall back to a new refresh token */ } - - options.form.refresh_token = refreshTokens[registryHost] = await acquireAcrRefreshToken(registryHost, subContext); - const response = await httpRequest<{ access_token: string }>(`https://${registryHost}/oauth2/token`, options); - return (await response.json()).access_token; -} - -export async function acquireAcrRefreshToken(registryHost: string, subContext: ISubscriptionContext): Promise { - const options: RequestOptionsLike = { - method: 'POST', - form: { - grant_type: 'access_token', - service: registryHost, - tenant: subContext.tenantId, - }, - }; - - const response = await httpRequest<{ refresh_token: string }>(`https://${registryHost}/oauth2/exchange`, options, async (request) => { - // Obnoxiously, the oauth2/exchange endpoint wants the token in the form data's access_token field, so we need to pick it off the signed auth header and move it there - await subContext.credentials.signRequest(request); - const token = (request.headers.get('authorization') as string).replace(/Bearer\s+/i, ''); - - const formData = new URLSearchParams({ ...options.form, access_token: token }); - return new Request(request.url, { method: 'POST', body: formData }); - }); - - return (await response.json()).refresh_token; -} - export async function createAzureContainerRegistryClient(subscriptionItem: AzureSubscription): Promise { return new (await import('@azure/arm-containerregistry')).ContainerRegistryManagementClient(subscriptionItem.credential, subscriptionItem.subscriptionId); } diff --git a/src/utils/registryExperience.ts b/src/utils/registryExperience.ts new file mode 100644 index 0000000000..25a8d5d5dc --- /dev/null +++ b/src/utils/registryExperience.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureWizardPromptStep, ContextValueFilter, IActionContext, QuickPickWizardContext, RecursiveQuickPickStep, runQuickPickWizard } from '@microsoft/vscode-azext-utils'; +import * as vscode from 'vscode'; + +export async function registryExperience(context: IActionContext, tdp: vscode.TreeDataProvider, contextValueFilter: ContextValueFilter): Promise { + const promptSteps: AzureWizardPromptStep[] = [ + new RecursiveQuickPickStep( + tdp, + { + contextValueFilter: contextValueFilter, + skipIfOne: true + } + ) + ]; + + return await runQuickPickWizard(context, { + hideStepCount: true, + promptSteps: promptSteps, + }); +} From 96b1a2162bff52768c86081b8ce6885569831517 Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Fri, 25 Aug 2023 09:30:06 -0400 Subject: [PATCH 08/43] Implemented Delete Remote Image for GenericV2 (#4039) * added logic for copy remote imgage digest * added back delete image * refined logic for delete image * fixed pushImage * reverted changes to registryExperience * tag logic for GitHub * be able to debug github image * added back skipIfOne flag --- package-lock.json | 2 +- package.json | 4 +- src/commands/images/pushImage.ts | 4 +- src/commands/images/tagImage.ts | 4 +- src/commands/registerCommands.ts | 3 +- .../registries/copyRemoteImageDigest.ts | 6 +- src/commands/registries/deleteRemoteImage.ts | 77 +++++++++++-------- .../Azure/AzureRegistryDataProvider.ts | 3 +- src/tree/registries/registryTreeUtils.ts | 5 +- src/utils/registryExperience.ts | 4 +- 10 files changed, 64 insertions(+), 48 deletions(-) diff --git a/package-lock.json b/package-lock.json index 955306476e..69a751f00d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -824,7 +824,7 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.0.1-alpha", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", - "integrity": "sha512-AUa6YyRJgGzc96gJylSiI+EGm0B5s28LS7Hi8yHH4aIMz8vU/yK5O9PWrt/GxIxm2AHP4oFqQ59lWy2mk2xunA==", + "integrity": "sha512-SA18suNyHYMsmrfWRN4fkgAHXHG29inYpy5vfhHv7TNs7ha7HRZp93NXpebD1DuPYxFPpmFx3rjdLyADc6DdNA==", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", diff --git a/package.json b/package.json index f3c6b6149d..f6ac326631 100644 --- a/package.json +++ b/package.json @@ -511,7 +511,7 @@ }, { "command": "vscode-docker.registries.copyImageDigest", - "when": "view == dockerRegistries && viewItem =~ /registryV2Tag/", + "when": "view == dockerRegistries && viewItem =~ /(dockerHubTag|registryV2Tag|azureContainerTag|githubRegistryTag)/", "group": "regs_tag_1_general@3" }, { @@ -531,7 +531,7 @@ }, { "command": "vscode-docker.registries.deleteImage", - "when": "view == dockerRegistries && viewItem =~ /DockerV2;Tag;/", + "when": "view == dockerRegistries && viewItem =~ /(genericRegistryV2Tag|azureContainerTag|githubRegistryTag)/i", "group": "regs_tag_2_destructive@2" }, { diff --git a/src/commands/images/pushImage.ts b/src/commands/images/pushImage.ts index 3626f9ce36..4a15304cac 100644 --- a/src/commands/images/pushImage.ts +++ b/src/commands/images/pushImage.ts @@ -59,8 +59,8 @@ export async function pushImage(context: IActionContext, node: ImageTreeItem | u // Give the user a chance to modify the tag however they want const finalTag = await tagImage(context, node, connectedRegistry); - - if (connectedRegistry && finalTag.startsWith(getBaseImagePathFromRegistryItem(connectedRegistry.wrappedItem))) { + const baseImagePath = getBaseImagePathFromRegistryItem(connectedRegistry.wrappedItem); + if (connectedRegistry && finalTag.startsWith(baseImagePath)) { // If a registry was found/chosen and is still the same as the final tag's registry, try logging in await vscode.commands.executeCommand('vscode-docker.registries.logInToDockerCli', connectedRegistry); } diff --git a/src/commands/images/tagImage.ts b/src/commands/images/tagImage.ts index ebc8554e61..f5bfd8f97b 100644 --- a/src/commands/images/tagImage.ts +++ b/src/commands/images/tagImage.ts @@ -11,7 +11,7 @@ import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; import { getBaseImagePathFromRegistryItem } from '../../tree/registries/registryTreeUtils'; -export async function tagImage(context: IActionContext, node?: ImageTreeItem, registry?: UnifiedRegistryItem): Promise { +export async function tagImage(context: IActionContext, node?: ImageTreeItem, registry?: UnifiedRegistryItem): Promise { if (!node) { await ext.imagesTree.refresh(context); node = await ext.imagesTree.showTreeItemPicker(ImageTreeItem.contextValue, { @@ -21,7 +21,7 @@ export async function tagImage(context: IActionContext, node?: ImageTreeItem, re } addImageTaggingTelemetry(context, node.fullTag, '.before'); - const baseImagePath = isRegistry(registry) ? getBaseImagePathFromRegistryItem(registry.wrappedItem as CommonRegistry) : undefined; + const baseImagePath = isRegistry(registry.wrappedItem) ? getBaseImagePathFromRegistryItem(registry.wrappedItem) : undefined; const newTaggedName: string = await getTagFromUserInput(context, node.fullTag, baseImagePath); addImageTaggingTelemetry(context, newTaggedName, '.after'); diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index c7403f0be3..2e8a1481c9 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -64,6 +64,7 @@ import { viewAzureProperties } from "./registries/azure/viewAzureProperties"; import { connectRegistry } from "./registries/connectRegistry"; import { copyRemoteFullTag } from "./registries/copyRemoteFullTag"; import { copyRemoteImageDigest } from "./registries/copyRemoteImageDigest"; +import { deleteRemoteImage } from "./registries/deleteRemoteImage"; import { disconnectRegistry } from "./registries/disconnectRegistry"; import { openDockerHubInBrowser } from "./registries/dockerHub/openDockerHubInBrowser"; import { logInToDockerCli } from "./registries/logInToDockerCli"; @@ -165,7 +166,7 @@ export function registerCommands(): void { registerCommand('vscode-docker.registries.connectRegistry', connectRegistry); registerCommand('vscode-docker.registries.copyImageDigest', copyRemoteImageDigest); registerCommand('vscode-docker.registries.copyRemoteFullTag', copyRemoteFullTag); - // registerCommand('vscode-docker.registries.deleteImage', deleteRemoteImage); + registerCommand('vscode-docker.registries.deleteImage', deleteRemoteImage); registerCommand('vscode-docker.registries.deployImageToAzure', deployImageToAzure); registerCommand('vscode-docker.registries.deployImageToAca', deployImageToAca); registerCommand('vscode-docker.registries.disconnectRegistry', disconnectRegistry); diff --git a/src/commands/registries/copyRemoteImageDigest.ts b/src/commands/registries/copyRemoteImageDigest.ts index 1e022c75cf..62f9164d6c 100644 --- a/src/commands/registries/copyRemoteImageDigest.ts +++ b/src/commands/registries/copyRemoteImageDigest.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext, contextValueExperience } from "@microsoft/vscode-azext-utils"; -import { CommonTag, RegistryV2DataProvider, V2Tag } from "@microsoft/vscode-docker-registries"; +import { CommonRegistryDataProvider, CommonTag } from "@microsoft/vscode-docker-registries"; import * as vscode from "vscode"; import { ext } from "../../extensionVariables"; import { UnifiedRegistryItem } from "../../tree/registries/UnifiedRegistryTreeDataProvider"; @@ -14,8 +14,8 @@ export async function copyRemoteImageDigest(context: IActionContext, node?: Unif node = await contextValueExperience(context, ext.registriesTree, { include: ['registryV2Tag'] }); } - const v2DataProvider = node.provider as unknown as RegistryV2DataProvider; - const digest = await v2DataProvider.getImageDigest(node.wrappedItem as V2Tag); + const v2DataProvider = node.provider as unknown as CommonRegistryDataProvider; + const digest = await v2DataProvider.getImageDigest?.(node.wrappedItem as CommonTag); /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ vscode.env.clipboard.writeText(digest); diff --git a/src/commands/registries/deleteRemoteImage.ts b/src/commands/registries/deleteRemoteImage.ts index 0495177910..6a16de34cd 100644 --- a/src/commands/registries/deleteRemoteImage.ts +++ b/src/commands/registries/deleteRemoteImage.ts @@ -1,37 +1,50 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ -// import { DialogResponses, IActionContext } from '@microsoft/vscode-azext-utils'; -// import { l10n, ProgressLocation, window } from 'vscode'; -// import { ext } from '../../extensionVariables'; -// import { DockerV2TagTreeItem } from '../../tree/registries/dockerV2/DockerV2TagTreeItem'; -// import { registryExpectedContextValues } from '../../tree/registries/registryContextValues'; +import { DialogResponses, IActionContext, UserCancelledError, parseError } from '@microsoft/vscode-azext-utils'; +import { CommonTag, GenericRegistryV2DataProvider } from '@microsoft/vscode-docker-registries'; +import { ProgressLocation, l10n, window } from 'vscode'; +import { ext } from '../../extensionVariables'; +import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { getImageNameFromRegistryTagItem } from '../../tree/registries/registryTreeUtils'; +import { registryExperience } from '../../utils/registryExperience'; -// export async function deleteRemoteImage(context: IActionContext, node?: DockerV2TagTreeItem): Promise { -// if (!node) { -// node = await ext.registriesTree.showTreeItemPicker(registryExpectedContextValues.dockerV2.tag, { -// ...context, -// suppressCreatePick: true, -// noItemFoundErrorMessage: l10n.t('No remote images are available to delete') -// }); -// } +export async function deleteRemoteImage(context: IActionContext, node?: UnifiedRegistryItem): Promise { + if (!node) { + node = await registryExperience(context, ext.registriesTree, { include: ['genericRegistryV2Tag', 'azureContainerTag', 'githubRegistryTag'] }, false); + } -// const confirmDelete = l10n.t('Are you sure you want to delete image "{0}"? This will delete all images that have the same digest.', node.repoNameAndTag); -// // no need to check result - cancel will throw a UserCancelledError -// await context.ui.showWarningMessage(confirmDelete, { modal: true }, DialogResponses.deleteResponse); + const tagName = getImageNameFromRegistryTagItem(node.wrappedItem); + const confirmDelete = l10n.t('Are you sure you want to delete image "{0}"? This will delete all images that have the same digest.', tagName); + // no need to check result - cancel will throw a UserCancelledError + await context.ui.showWarningMessage(confirmDelete, { modal: true }, DialogResponses.deleteResponse); -// const repoTI = node.parent; -// const deleting = l10n.t('Deleting image "{0}"...', node.repoNameAndTag); -// await window.withProgress({ location: ProgressLocation.Notification, title: deleting }, async () => { -// await node.deleteTreeItem(context); -// }); + const deleting = l10n.t('Deleting image "{0}"...', tagName); + await window.withProgress({ location: ProgressLocation.Notification, title: deleting }, async () => { + const provider = node.provider as unknown as GenericRegistryV2DataProvider; -// // Other tags that also matched the image may have been deleted, so refresh the whole repository -// await repoTI.refresh(context); -// const message = l10n.t('Successfully deleted image "{0}".', node.repoNameAndTag); -// // don't wait -// /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ -// window.showInformationMessage(message); -// } + try { + await provider.deleteTag(node.wrappedItem); + } catch (error) { + const errorType: string = parseError(error).errorType.toLowerCase(); + if (errorType === '405' || errorType === 'unsupported') { + // Don't wait + // eslint-disable-next-line @typescript-eslint/no-floating-promises + context.ui.showWarningMessage('Deleting remote images is not supported on this registry. It may need to be enabled.', { learnMoreLink: 'https://aka.ms/AA7jsql' }); + throw new UserCancelledError(); + } else { + throw error; + } + } + }); + + // TODO: investigate if we can do this for GitHub + + // Other tags that also matched the image may have been deleted, so refresh the whole repository + // don't wait + void ext.registriesTree.refresh(); + /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ + window.showInformationMessage(l10n.t('Successfully deleted image "{0}".', tagName)); +} diff --git a/src/tree/registries/Azure/AzureRegistryDataProvider.ts b/src/tree/registries/Azure/AzureRegistryDataProvider.ts index 2da1ca14f1..f3ac530d4d 100644 --- a/src/tree/registries/Azure/AzureRegistryDataProvider.ts +++ b/src/tree/registries/Azure/AzureRegistryDataProvider.ts @@ -181,7 +181,7 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements await client.registries.beginDeleteAndWait(resourceGroup, item.label); } - public async deleteTag(item: AzureTag): Promise { + public override async deleteTag(item: AzureTag): Promise { const authenticationProvider = this.getAuthenticationProvider(item.parent.parent as unknown as AzureRegistryItem); const reponse = await registryV2Request({ @@ -194,7 +194,6 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements if (!reponse.succeeded) { throw new Error(`Failed to delete tag: ${reponse.statusText}`); - } } diff --git a/src/tree/registries/registryTreeUtils.ts b/src/tree/registries/registryTreeUtils.ts index 6c8fc98acc..b07b6ad88a 100644 --- a/src/tree/registries/registryTreeUtils.ts +++ b/src/tree/registries/registryTreeUtils.ts @@ -25,9 +25,12 @@ export function getBaseImagePathFromRegistryItem(registry: CommonRegistry): stri switch (registry.additionalContextValues?.[0] ?? '') { case 'azureContainerRegistry': - case 'genericRegistryV2': { + case 'genericRegistryV2Registry': { return registry.baseUrl.authority.toLowerCase(); } + case 'githubRegistry': { + return `${registry.baseUrl.authority.toLowerCase()}/${registry.label}`; + } case 'dockerHubRegistry': default: return `${registry.label}`; diff --git a/src/utils/registryExperience.ts b/src/utils/registryExperience.ts index 25a8d5d5dc..e4a7eb0700 100644 --- a/src/utils/registryExperience.ts +++ b/src/utils/registryExperience.ts @@ -6,13 +6,13 @@ import { AzureWizardPromptStep, ContextValueFilter, IActionContext, QuickPickWizardContext, RecursiveQuickPickStep, runQuickPickWizard } from '@microsoft/vscode-azext-utils'; import * as vscode from 'vscode'; -export async function registryExperience(context: IActionContext, tdp: vscode.TreeDataProvider, contextValueFilter: ContextValueFilter): Promise { +export async function registryExperience(context: IActionContext, tdp: vscode.TreeDataProvider, contextValueFilter: ContextValueFilter, skipIfOne: boolean = true): Promise { const promptSteps: AzureWizardPromptStep[] = [ new RecursiveQuickPickStep( tdp, { contextValueFilter: contextValueFilter, - skipIfOne: true + skipIfOne: skipIfOne } ) ]; From 6d10af169889fb727839732c296edb29e5e8e56e Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Fri, 25 Aug 2023 14:03:56 -0400 Subject: [PATCH 09/43] Added Untag Image Command for Azure RDP (#4041) * adjusted context values * added untag Image * remove github option for delete tag * commtag -> v2tag * slighty changed context values --- package-lock.json | 2 +- package.json | 6 +++--- src/commands/registries/azure/untagAzureImage.ts | 3 ++- src/commands/registries/deleteRemoteImage.ts | 10 ++++------ src/tree/registries/Azure/AzureRegistryDataProvider.ts | 2 +- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 69a751f00d..bac0e3135e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -824,7 +824,7 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.0.1-alpha", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", - "integrity": "sha512-SA18suNyHYMsmrfWRN4fkgAHXHG29inYpy5vfhHv7TNs7ha7HRZp93NXpebD1DuPYxFPpmFx3rjdLyADc6DdNA==", + "integrity": "sha512-8lbvSpwXXQDWw/Z6kbwuOEd5HEU+P4lYoGFgJW1FOE9+MF7xHsABvW/C4hA8ydJ3GRKEHvoaQB7Gf8yQG9a+0w==", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", diff --git a/package.json b/package.json index f6ac326631..c60b4f7510 100644 --- a/package.json +++ b/package.json @@ -506,12 +506,12 @@ }, { "command": "vscode-docker.registries.copyRemoteFullTag", - "when": "view == dockerRegistries && viewItem =~ /(dockerHubTag|registryV2Tag|azureContainerTag)/i", + "when": "view == dockerRegistries && viewItem =~ /(dockerHubTag|registryV2Tag)/i", "group": "regs_tag_1_general@2" }, { "command": "vscode-docker.registries.copyImageDigest", - "when": "view == dockerRegistries && viewItem =~ /(dockerHubTag|registryV2Tag|azureContainerTag|githubRegistryTag)/", + "when": "view == dockerRegistries && viewItem =~ /(dockerHubTag|genericRegistryV2Tag|azureContainerTag|githubRegistryTag)/", "group": "regs_tag_1_general@3" }, { @@ -531,7 +531,7 @@ }, { "command": "vscode-docker.registries.deleteImage", - "when": "view == dockerRegistries && viewItem =~ /(genericRegistryV2Tag|azureContainerTag|githubRegistryTag)/i", + "when": "view == dockerRegistries && viewItem =~ /(genericRegistryV2Tag|azureContainerTag)/i", "group": "regs_tag_2_destructive@2" }, { diff --git a/src/commands/registries/azure/untagAzureImage.ts b/src/commands/registries/azure/untagAzureImage.ts index f5713131b6..fa80f814b9 100644 --- a/src/commands/registries/azure/untagAzureImage.ts +++ b/src/commands/registries/azure/untagAzureImage.ts @@ -23,10 +23,11 @@ export async function untagAzureImage(context: IActionContext, node?: UnifiedReg const untagging = l10n.t('Untagging image "{0}"...', fullTag); await window.withProgress({ location: ProgressLocation.Notification, title: untagging }, async () => { const provider = node.provider as unknown as AzureRegistryDataProvider; - await provider.deleteTag(node.wrappedItem); + await provider.untagImage(node.wrappedItem); }); // don't wait + void ext.registriesTree.refresh(); /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ window.showInformationMessage(l10n.t('Successfully untagged image "{0}".', fullTag)); } diff --git a/src/commands/registries/deleteRemoteImage.ts b/src/commands/registries/deleteRemoteImage.ts index 6a16de34cd..add9a4b318 100644 --- a/src/commands/registries/deleteRemoteImage.ts +++ b/src/commands/registries/deleteRemoteImage.ts @@ -4,16 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { DialogResponses, IActionContext, UserCancelledError, parseError } from '@microsoft/vscode-azext-utils'; -import { CommonTag, GenericRegistryV2DataProvider } from '@microsoft/vscode-docker-registries'; +import { RegistryV2DataProvider, V2Tag } from '@microsoft/vscode-docker-registries'; import { ProgressLocation, l10n, window } from 'vscode'; import { ext } from '../../extensionVariables'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; import { getImageNameFromRegistryTagItem } from '../../tree/registries/registryTreeUtils'; import { registryExperience } from '../../utils/registryExperience'; -export async function deleteRemoteImage(context: IActionContext, node?: UnifiedRegistryItem): Promise { +export async function deleteRemoteImage(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await registryExperience(context, ext.registriesTree, { include: ['genericRegistryV2Tag', 'azureContainerTag', 'githubRegistryTag'] }, false); + node = await registryExperience(context, ext.registriesTree, { include: ['genericRegistryV2Tag', 'azureContainerTag'] }, false); } const tagName = getImageNameFromRegistryTagItem(node.wrappedItem); @@ -23,7 +23,7 @@ export async function deleteRemoteImage(context: IActionContext, node?: UnifiedR const deleting = l10n.t('Deleting image "{0}"...', tagName); await window.withProgress({ location: ProgressLocation.Notification, title: deleting }, async () => { - const provider = node.provider as unknown as GenericRegistryV2DataProvider; + const provider = node.provider as unknown as RegistryV2DataProvider; try { await provider.deleteTag(node.wrappedItem); @@ -40,8 +40,6 @@ export async function deleteRemoteImage(context: IActionContext, node?: UnifiedR } }); - // TODO: investigate if we can do this for GitHub - // Other tags that also matched the image may have been deleted, so refresh the whole repository // don't wait void ext.registriesTree.refresh(); diff --git a/src/tree/registries/Azure/AzureRegistryDataProvider.ts b/src/tree/registries/Azure/AzureRegistryDataProvider.ts index f3ac530d4d..5a015c5067 100644 --- a/src/tree/registries/Azure/AzureRegistryDataProvider.ts +++ b/src/tree/registries/Azure/AzureRegistryDataProvider.ts @@ -181,7 +181,7 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements await client.registries.beginDeleteAndWait(resourceGroup, item.label); } - public override async deleteTag(item: AzureTag): Promise { + public async untagImage(item: AzureTag): Promise { const authenticationProvider = this.getAuthenticationProvider(item.parent.parent as unknown as AzureRegistryItem); const reponse = await registryV2Request({ From e03bb574e63ef8acf49a0be3616a30a52ce1c440 Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Mon, 28 Aug 2023 10:27:15 -0400 Subject: [PATCH 10/43] Improve UX for Connecting & Disconnecting Generic V2 Registries (#4043) * added remove tracked registry command * added command to add generic v2 registry * Update package.nls.json Co-authored-by: Brandon Waterloo [MSFT] <36966225+bwateratmsft@users.noreply.github.com> * Update package.nls.json Co-authored-by: Brandon Waterloo [MSFT] <36966225+bwateratmsft@users.noreply.github.com> --------- Co-authored-by: Brandon Waterloo [MSFT] <36966225+bwateratmsft@users.noreply.github.com> --- package-lock.json | 2 +- package.json | 21 ++++++++++++++++--- package.nls.json | 2 ++ src/commands/registerCommands.ts | 5 +++++ .../genericV2/addTrackedGenericV2Registry.ts | 14 +++++++++++++ .../removeTrackedGenericV2Registry.ts | 19 +++++++++++++++++ 6 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 src/commands/registries/genericV2/addTrackedGenericV2Registry.ts create mode 100644 src/commands/registries/genericV2/removeTrackedGenericV2Registry.ts diff --git a/package-lock.json b/package-lock.json index bac0e3135e..60b5a3e935 100644 --- a/package-lock.json +++ b/package-lock.json @@ -824,7 +824,7 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.0.1-alpha", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", - "integrity": "sha512-8lbvSpwXXQDWw/Z6kbwuOEd5HEU+P4lYoGFgJW1FOE9+MF7xHsABvW/C4hA8ydJ3GRKEHvoaQB7Gf8yQG9a+0w==", + "integrity": "sha512-+f2czfeua/7ehyAgz1382+R0sDlgOgGMr3ydtZGuAJbVMv646murPvB/YyLvkPSp+gbxWRihdalty7H+QktI1g==", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", diff --git a/package.json b/package.json index c60b4f7510..6a93314153 100644 --- a/package.json +++ b/package.json @@ -551,12 +551,17 @@ }, { "command": "vscode-docker.registries.disconnectRegistry", - "when": "view == dockerRegistries && viewItem =~ /commonregistryroot/i && !(viewItem =~ /genericregistryrootv2/i)", + "when": "view == dockerRegistries && viewItem =~ /commonregistryroot/i && !(viewItem =~ /genericRegistryV2Root/i)", "group": "regs_yyy_destructive@1" }, { - "command": "vscode-docker.registries.disconnectRegistry", - "when": "view == dockerRegistries && viewItem == invalidRegistryProvider", + "command": "vscode-docker.registries.genericV2.removeTrackedRegistry", + "when": "view == dockerRegistries && viewItem =~ /genericRegistryV2Registry/i", + "group": "regs_yyy_destructive@1" + }, + { + "command": "vscode-docker.registries.genericV2.addTrackedRegistry", + "when": "view == dockerRegistries && viewItem =~ /genericRegistryV2Root/i", "group": "regs_yyy_destructive@1" }, { @@ -2665,6 +2670,16 @@ "title": "%vscode-docker.commands.registries.disconnectRegistry%", "category": "%vscode-docker.commands.category.dockerRegistries%" }, + { + "command": "vscode-docker.registries.genericV2.removeTrackedRegistry", + "title": "%vscode-docker.commands.registries.genericV2.removeTrackedRegistry%", + "category": "%vscode-docker.commands.category.dockerRegistries%" + }, + { + "command": "vscode-docker.registries.genericV2.addTrackedRegistry", + "title": "%vscode-docker.commands.registries.genericV2.addTrackedRegistry%", + "category": "%vscode-docker.commands.category.dockerRegistries%" + }, { "command": "vscode-docker.registries.dockerHub.openInBrowser", "title": "%vscode-docker.commands.registries.dockerHub.openInBrowser%", diff --git a/package.nls.json b/package.nls.json index 51e38b7181..157dc49800 100644 --- a/package.nls.json +++ b/package.nls.json @@ -276,6 +276,8 @@ "vscode-docker.commands.registries.deployImageToAzure": "Deploy Image to Azure App Service...", "vscode-docker.commands.registries.deployImageToAca": "Deploy Image to Azure Container Apps...", "vscode-docker.commands.registries.disconnectRegistry": "Disconnect", + "vscode-docker.commands.registries.genericV2.removeTrackedRegistry": "Disconnect from Generic Docker Registry", + "vscode-docker.commands.registries.genericV2.addTrackedRegistry": "Connect to Generic Docker Registry...", "vscode-docker.commands.registries.dockerHub.openInBrowser": "Open in Browser", "vscode-docker.commands.registries.help": "Registries Help", "vscode-docker.commands.registries.logInToDockerCli": "Log In to Docker CLI", diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index 2e8a1481c9..335b8000d6 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -67,6 +67,8 @@ import { copyRemoteImageDigest } from "./registries/copyRemoteImageDigest"; import { deleteRemoteImage } from "./registries/deleteRemoteImage"; import { disconnectRegistry } from "./registries/disconnectRegistry"; import { openDockerHubInBrowser } from "./registries/dockerHub/openDockerHubInBrowser"; +import { addTrackedGenericV2Registry } from "./registries/genericV2/addTrackedGenericV2Registry"; +import { removeTrackedGenericV2Registry } from "./registries/genericV2/removeTrackedGenericV2Registry"; import { logInToDockerCli } from "./registries/logInToDockerCli"; import { logOutOfDockerCli } from "./registries/logOutOfDockerCli"; import { pullImageFromRepository, pullRepository } from "./registries/pullImages"; @@ -177,6 +179,9 @@ export function registerCommands(): void { registerWorkspaceCommand('vscode-docker.registries.pullRepository', pullRepository); // registerCommand('vscode-docker.registries.reconnectRegistry', reconnectRegistry); + registerCommand('vscode-docker.registries.genericV2.removeTrackedRegistry', removeTrackedGenericV2Registry); + registerCommand('vscode-docker.registries.genericV2.addTrackedRegistry', addTrackedGenericV2Registry); + registerCommand('vscode-docker.registries.dockerHub.openInBrowser', openDockerHubInBrowser); registerWorkspaceCommand('vscode-docker.registries.azure.buildImage', buildImageInAzure); diff --git a/src/commands/registries/genericV2/addTrackedGenericV2Registry.ts b/src/commands/registries/genericV2/addTrackedGenericV2Registry.ts new file mode 100644 index 0000000000..91baad9b44 --- /dev/null +++ b/src/commands/registries/genericV2/addTrackedGenericV2Registry.ts @@ -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 { IActionContext } from "@microsoft/vscode-azext-utils"; +import { ext } from "../../../extensionVariables"; +import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; + +export async function addTrackedGenericV2Registry(context: IActionContext, node?: UnifiedRegistryItem): Promise { + await ext.genericRegistryV2DataProvider.addTrackedRegistry(); + // don't wait + void ext.registriesTree.refresh(); +} diff --git a/src/commands/registries/genericV2/removeTrackedGenericV2Registry.ts b/src/commands/registries/genericV2/removeTrackedGenericV2Registry.ts new file mode 100644 index 0000000000..23e1540c82 --- /dev/null +++ b/src/commands/registries/genericV2/removeTrackedGenericV2Registry.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IActionContext, contextValueExperience } from "@microsoft/vscode-azext-utils"; +import { GenericV2Registry } from "@microsoft/vscode-docker-registries"; +import { ext } from "../../../extensionVariables"; +import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; + +export async function removeTrackedGenericV2Registry(context: IActionContext, node?: UnifiedRegistryItem): Promise { + if (!node) { + node = await contextValueExperience(context, ext.genericRegistryV2DataProvider, { include: 'genericRegistryV2Registry' }); + } + + await ext.genericRegistryV2DataProvider.removeTrackedRegistry(node.wrappedItem); + // don't wait + void ext.registriesTree.refresh(); +} From befa438bc6af84e56a8c56ba3a781a40955c33aa Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Tue, 29 Aug 2023 09:36:19 -0400 Subject: [PATCH 11/43] added logic for reconnect (#4045) --- package-lock.json | 2 +- package.json | 8 ++--- src/commands/registerCommands.ts | 3 +- src/commands/registries/reconnectRegistry.ts | 38 +++++++++++--------- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 60b5a3e935..00f67dd6d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -824,7 +824,7 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.0.1-alpha", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", - "integrity": "sha512-+f2czfeua/7ehyAgz1382+R0sDlgOgGMr3ydtZGuAJbVMv646murPvB/YyLvkPSp+gbxWRihdalty7H+QktI1g==", + "integrity": "sha512-MmLx1agpaOlrn4XEZDQ0WSyf1uWrLMJ4FO9gfew71wCqmKVLL9X3fqqHX4prLMMzxV6SKrfEnt1MYRqutZHHQA==", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", diff --git a/package.json b/package.json index 6a93314153..171d961a7f 100644 --- a/package.json +++ b/package.json @@ -581,7 +581,7 @@ }, { "command": "vscode-docker.registries.reconnectRegistry", - "when": "view == dockerRegistries && viewItem == registryConnectError", + "when": "view == dockerRegistries && viewItem =~ /registryConnectError/i", "group": "regs_zzz_common@8" }, { @@ -2671,9 +2671,9 @@ "category": "%vscode-docker.commands.category.dockerRegistries%" }, { - "command": "vscode-docker.registries.genericV2.removeTrackedRegistry", - "title": "%vscode-docker.commands.registries.genericV2.removeTrackedRegistry%", - "category": "%vscode-docker.commands.category.dockerRegistries%" + "command": "vscode-docker.registries.genericV2.removeTrackedRegistry", + "title": "%vscode-docker.commands.registries.genericV2.removeTrackedRegistry%", + "category": "%vscode-docker.commands.category.dockerRegistries%" }, { "command": "vscode-docker.registries.genericV2.addTrackedRegistry", diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index 335b8000d6..cf830bcb0b 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -72,6 +72,7 @@ import { removeTrackedGenericV2Registry } from "./registries/genericV2/removeTra import { logInToDockerCli } from "./registries/logInToDockerCli"; import { logOutOfDockerCli } from "./registries/logOutOfDockerCli"; import { pullImageFromRepository, pullRepository } from "./registries/pullImages"; +import { reconnectRegistry } from "./registries/reconnectRegistry"; import { registryHelp } from "./registries/registryHelp"; import { openDockerDownloadPage } from "./showDockerLearnMoreNotification"; import { configureVolumesExplorer } from "./volumes/configureVolumesExplorer"; @@ -177,7 +178,7 @@ export function registerCommands(): void { registerWorkspaceCommand('vscode-docker.registries.logOutOfDockerCli', logOutOfDockerCli); registerWorkspaceCommand('vscode-docker.registries.pullImage', pullImageFromRepository); registerWorkspaceCommand('vscode-docker.registries.pullRepository', pullRepository); - // registerCommand('vscode-docker.registries.reconnectRegistry', reconnectRegistry); + registerCommand('vscode-docker.registries.reconnectRegistry', reconnectRegistry); registerCommand('vscode-docker.registries.genericV2.removeTrackedRegistry', removeTrackedGenericV2Registry); registerCommand('vscode-docker.registries.genericV2.addTrackedRegistry', addTrackedGenericV2Registry); diff --git a/src/commands/registries/reconnectRegistry.ts b/src/commands/registries/reconnectRegistry.ts index 0537f79960..16abcdecf1 100644 --- a/src/commands/registries/reconnectRegistry.ts +++ b/src/commands/registries/reconnectRegistry.ts @@ -1,19 +1,25 @@ -// /*--------------------------------------------------------------------------------------------- -// * Copyright (c) Microsoft Corporation. All rights reserved. -// * Licensed under the MIT License. See LICENSE.md in the project root for license information. -// *--------------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + * 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 "@microsoft/vscode-azext-utils"; -// import { l10n } from 'vscode'; -// import { ext } from "../../extensionVariables"; -// import { RegistryConnectErrorTreeItem } from "../../tree/registries/RegistryConnectErrorTreeItem"; +import { IActionContext } from "@microsoft/vscode-azext-utils"; +import { GenericV2Registry, RegistryConnectError, isGenericV2Registry } from "@microsoft/vscode-docker-registries"; +import { l10n } from 'vscode'; +import { ext } from "../../extensionVariables"; +import { UnifiedRegistryItem } from "../../tree/registries/UnifiedRegistryTreeDataProvider"; -// export async function reconnectRegistry(context: IActionContext, node?: RegistryConnectErrorTreeItem): Promise { -// if (!node?.cachedProvider || !node?.provider) { -// // This is not expected ever, so we'll throw an error which can be bubbled up to a Report Issue if it does -// throw new Error(l10n.t('Unable to determine provider to re-enter credentials. Please disconnect and connect again.')); -// } +export async function reconnectRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { + if (!node?.provider || !node?.wrappedItem || !node?.parent) { + // This is not expected ever, so we'll throw an error which can be bubbled up to a Report Issue if it does + throw new Error(l10n.t('Unable to determine provider to re-enter credentials. Please disconnect and connect again.')); + } -// await ext.registriesRoot.disconnectRegistry(context, node.cachedProvider); -// await ext.registriesRoot.connectRegistry(context, node.provider, node.url); -// } + if (isGenericV2Registry(node.parent.wrappedItem)) { + await ext.genericRegistryV2DataProvider.removeTrackedRegistry(node.parent.wrappedItem as GenericV2Registry); + await ext.genericRegistryV2DataProvider.addTrackedRegistry(); + } else { + await ext.registriesTree.disconnectRegistryProvider(node.parent); + await ext.registriesRoot.connectRegistryProvider(node.provider); + } +} From a1872690e94f553fa6e9e263324a6e45f4f84f2c Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Wed, 30 Aug 2023 09:49:05 -0400 Subject: [PATCH 12/43] Cleanup Registries Implementation (#4046) * fixed genericV2 pull image bug * improved utils to use outside methods * enable copyImageDigest for all v2 registries * removed run task & did TODOs * did TODOs --- package-lock.json | 2 +- package.json | 54 +------------------ package.nls.json | 3 -- src/commands/registerCommands.ts | 4 -- .../registries/azure/DockerSiteCreateStep.ts | 4 +- .../azure/DockerWebhookCreateStep.ts | 6 +-- .../registries/azure/deployImageToAca.ts | 4 +- .../registries/azure/tasks/runAzureTask.ts | 21 -------- .../azure/tasks/runFileAsAzureTask.ts | 16 ------ .../azure/tasks/viewAzureTaskLogs.ts | 29 ---------- .../registries/azure/viewAzureProperties.ts | 1 - src/commands/registries/logInToDockerCli.ts | 2 +- src/commands/registries/pullImages.ts | 8 +-- .../Azure/AzureRegistryDataProvider.ts | 4 +- src/tree/registries/registryTreeUtils.ts | 33 ++++++------ 15 files changed, 32 insertions(+), 159 deletions(-) delete mode 100644 src/commands/registries/azure/tasks/runAzureTask.ts delete mode 100644 src/commands/registries/azure/tasks/runFileAsAzureTask.ts delete mode 100644 src/commands/registries/azure/tasks/viewAzureTaskLogs.ts diff --git a/package-lock.json b/package-lock.json index 00f67dd6d9..5360e71c23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -824,7 +824,7 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.0.1-alpha", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", - "integrity": "sha512-MmLx1agpaOlrn4XEZDQ0WSyf1uWrLMJ4FO9gfew71wCqmKVLL9X3fqqHX4prLMMzxV6SKrfEnt1MYRqutZHHQA==", + "integrity": "sha512-Hpb82tseHfol1XypPkV7K9EFJ5dWMtvLIeaCVkFvKkW8lgSLsn83qqXgNlj7nd/4zg4sr2Lm72NkqoIw/yQscQ==", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", diff --git a/package.json b/package.json index 171d961a7f..4959131c51 100644 --- a/package.json +++ b/package.json @@ -122,10 +122,6 @@ "command": "vscode-docker.registries.azure.buildImage", "when": "isWorkspaceTrusted" }, - { - "command": "vscode-docker.registries.azure.runFileAsTask", - "when": "isWorkspaceTrusted" - }, { "command": "vscode-docker.help.openWalkthrough", "when": "never" @@ -153,11 +149,6 @@ "command": "vscode-docker.registries.azure.buildImage", "group": "docker" }, - { - "when": "isWorkspaceTrusted && editorLangId == yaml && isAzureAccountInstalled", - "command": "vscode-docker.registries.azure.runFileAsTask", - "group": "docker" - }, { "when": "isWorkspaceTrusted && editorLangId == dockercompose", "command": "vscode-docker.compose.down", @@ -190,11 +181,6 @@ "command": "vscode-docker.registries.azure.buildImage", "group": "docker" }, - { - "when": "isWorkspaceTrusted && resourceLangId == yaml && isAzureAccountInstalled", - "command": "vscode-docker.registries.azure.runFileAsTask", - "group": "docker" - }, { "when": "isWorkspaceTrusted && resourceLangId == dockercompose", "command": "vscode-docker.compose.down", @@ -464,11 +450,6 @@ "when": "view == dockerRegistries && viewItem == azure;DockerV2;RegistryProvider;", "group": "inline" }, - { - "command": "vscode-docker.registries.azure.viewTaskLogs", - "when": "view == dockerRegistries && viewItem == azureTaskRun", - "group": "inline" - }, { "command": "vscode-docker.networks.inspect", "when": "view == dockerNetworks && viewItem =~ /network$/i", @@ -506,12 +487,12 @@ }, { "command": "vscode-docker.registries.copyRemoteFullTag", - "when": "view == dockerRegistries && viewItem =~ /(dockerHubTag|registryV2Tag)/i", + "when": "view == dockerRegistries && viewItem =~ /commontag/i", "group": "regs_tag_1_general@2" }, { "command": "vscode-docker.registries.copyImageDigest", - "when": "view == dockerRegistries && viewItem =~ /(dockerHubTag|genericRegistryV2Tag|azureContainerTag|githubRegistryTag)/", + "when": "view == dockerRegistries && viewItem =~ /registryV2Tag/i", "group": "regs_tag_1_general@3" }, { @@ -534,21 +515,6 @@ "when": "view == dockerRegistries && viewItem =~ /(genericRegistryV2Tag|azureContainerTag)/i", "group": "regs_tag_2_destructive@2" }, - { - "command": "vscode-docker.registries.azure.runTask", - "when": "view == dockerRegistries && viewItem == azureTask", - "group": "regs_task_1_general@1" - }, - { - "command": "vscode-docker.registries.copyImageDigest", - "when": "view == dockerRegistries && viewItem == azureTaskRun", - "group": "regs_taskRun_1_general@1" - }, - { - "command": "vscode-docker.registries.azure.viewTaskLogs", - "when": "view == dockerRegistries && viewItem == azureTaskRun", - "group": "regs_taskRun_1_general@2" - }, { "command": "vscode-docker.registries.disconnectRegistry", "when": "view == dockerRegistries && viewItem =~ /commonregistryroot/i && !(viewItem =~ /genericRegistryV2Root/i)", @@ -2603,16 +2569,6 @@ "title": "%vscode-docker.commands.registries.azure.openInPortal%", "category": "%vscode-docker.commands.category.azureContainerRegistry%" }, - { - "command": "vscode-docker.registries.azure.runFileAsTask", - "title": "%vscode-docker.commands.registries.azure.runFileAsTask%", - "category": "%vscode-docker.commands.category.azureContainerRegistry%" - }, - { - "command": "vscode-docker.registries.azure.runTask", - "title": "%vscode-docker.commands.registries.azure.runTask%", - "category": "%vscode-docker.commands.category.azureContainerRegistry%" - }, { "command": "vscode-docker.registries.azure.selectSubscriptions", "title": "%vscode-docker.commands.registries.azure.selectSubscriptions%", @@ -2628,12 +2584,6 @@ "title": "%vscode-docker.commands.registries.azure.viewProperties%", "category": "%vscode-docker.commands.category.azureContainerRegistry%" }, - { - "command": "vscode-docker.registries.azure.viewTaskLogs", - "title": "%vscode-docker.commands.registries.azure.viewLogs%", - "category": "%vscode-docker.commands.category.azureContainerRegistry%", - "icon": "$(output)" - }, { "command": "vscode-docker.registries.connectRegistry", "title": "%vscode-docker.commands.registries.connect%", diff --git a/package.nls.json b/package.nls.json index 157dc49800..e262a068c3 100644 --- a/package.nls.json +++ b/package.nls.json @@ -262,12 +262,9 @@ "vscode-docker.commands.registries.azure.deleteRegistry": "Delete Registry...", "vscode-docker.commands.registries.azure.deleteRepository": "Delete Repository...", "vscode-docker.commands.registries.azure.openInPortal": "Open in Portal", - "vscode-docker.commands.registries.azure.runFileAsTask": "Run as Task in Azure...", - "vscode-docker.commands.registries.azure.runTask": "Run Task", "vscode-docker.commands.registries.azure.selectSubscriptions": "Select Subscriptions...", "vscode-docker.commands.registries.azure.untagImage": "Untag Image...", "vscode-docker.commands.registries.azure.viewProperties": "View Properties", - "vscode-docker.commands.registries.azure.viewLogs": "View Logs", "vscode-docker.commands.registries.connect": "Connect Registry...", "vscode-docker.commands.registries.reconnectRegistry": "Re-enter credentials", "vscode-docker.commands.registries.copyImageDigest": "Copy Image Digest", diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index cf830bcb0b..ee589a2dae 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -58,7 +58,6 @@ import { deployImageToAca } from "./registries/azure/deployImageToAca"; import { deployImageToAzure } from "./registries/azure/deployImageToAzure"; import { openInAzurePortal } from "./registries/azure/openInAzurePortal"; import { buildImageInAzure } from "./registries/azure/tasks/buildImageInAzure"; -import { runAzureTask } from "./registries/azure/tasks/runAzureTask"; import { untagAzureImage } from "./registries/azure/untagAzureImage"; import { viewAzureProperties } from "./registries/azure/viewAzureProperties"; import { connectRegistry } from "./registries/connectRegistry"; @@ -190,12 +189,9 @@ export function registerCommands(): void { registerCommand('vscode-docker.registries.azure.deleteRegistry', deleteAzureRegistry); registerCommand('vscode-docker.registries.azure.deleteRepository', deleteAzureRepository); registerCommand('vscode-docker.registries.azure.openInPortal', openInAzurePortal); - registerCommand('vscode-docker.registries.azure.runTask', runAzureTask); - // registerWorkspaceCommand('vscode-docker.registries.azure.runFileAsTask', runFileAsAzureTask); registerCommand('vscode-docker.registries.azure.selectSubscriptions', () => commands.executeCommand("azure-account.selectSubscriptions")); registerCommand('vscode-docker.registries.azure.untagImage', untagAzureImage); registerCommand('vscode-docker.registries.azure.viewProperties', viewAzureProperties); - // registerCommand('vscode-docker.registries.azure.viewTaskLogs', viewAzureTaskLogs); registerCommand('vscode-docker.volumes.configureExplorer', configureVolumesExplorer); registerCommand('vscode-docker.volumes.inspect', inspectVolume); diff --git a/src/commands/registries/azure/DockerSiteCreateStep.ts b/src/commands/registries/azure/DockerSiteCreateStep.ts index 38131a36f0..a2a0f05236 100644 --- a/src/commands/registries/azure/DockerSiteCreateStep.ts +++ b/src/commands/registries/azure/DockerSiteCreateStep.ts @@ -7,7 +7,7 @@ import type { NameValuePair, Site, SiteConfig, WebSiteManagementClient } from '@ import type { CustomLocation } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy import type { AzExtLocation } from '@microsoft/vscode-azext-azureutils'; // These are only dev-time imports so don't need to be lazy import { AzureWizardExecuteStep, nonNullProp, nonNullValueAndProp } from "@microsoft/vscode-azext-utils"; -import { CommonRegistry, CommonTag, isDockerHubRegistryItem, isGenericV2Registry } from '@microsoft/vscode-docker-registries'; +import { CommonRegistry, CommonTag, isDockerHubRegistry, isGenericV2Registry } from '@microsoft/vscode-docker-registries'; import { Progress, l10n } from "vscode"; import { ext } from "../../../extensionVariables"; import { AzureRegistryDataProvider, isAzureRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; @@ -88,7 +88,7 @@ export class DockerSiteCreateStep extends AzureWizardExecuteStep App Service *OR* Arc App Service - else if (isDockerHubRegistryItem(registryTI.wrappedItem)) { + else if (isDockerHubRegistry(registryTI.wrappedItem)) { const loginInformation = await registryTI.provider.getLoginInformation(registryTI.wrappedItem); username = loginInformation.username; password = loginInformation.secret; diff --git a/src/commands/registries/azure/DockerWebhookCreateStep.ts b/src/commands/registries/azure/DockerWebhookCreateStep.ts index 5c48b185dd..70510788ac 100644 --- a/src/commands/registries/azure/DockerWebhookCreateStep.ts +++ b/src/commands/registries/azure/DockerWebhookCreateStep.ts @@ -7,7 +7,7 @@ import type { Site } from '@azure/arm-appservice'; // These are only dev-time im import type { Webhook, WebhookCreateParameters } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy import type { IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-utils"; -import { CommonRepository, CommonTag, isDockerHubRepositoryItem } from '@microsoft/vscode-docker-registries'; +import { CommonRepository, CommonTag, isDockerHubRepository } from '@microsoft/vscode-docker-registries'; import * as vscode from "vscode"; import { ext } from "../../../extensionVariables"; import { AzureRegistry, isAzureRepositoryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; @@ -40,7 +40,7 @@ export class DockerWebhookCreateStep extends AzureWizardExecuteStep { diff --git a/src/commands/registries/azure/deployImageToAca.ts b/src/commands/registries/azure/deployImageToAca.ts index a2e0332a49..bf4f3d675c 100644 --- a/src/commands/registries/azure/deployImageToAca.ts +++ b/src/commands/registries/azure/deployImageToAca.ts @@ -5,7 +5,7 @@ import { contextValueExperience, IActionContext, nonNullProp, UserCancelledError } from '@microsoft/vscode-azext-utils'; import { parseDockerLikeImageName } from '@microsoft/vscode-container-client'; -import { CommonRegistry, CommonTag, isDockerHubRegistryItem, LoginInformation } from '@microsoft/vscode-docker-registries'; +import { CommonRegistry, CommonTag, isDockerHubRegistry, LoginInformation } from '@microsoft/vscode-docker-registries'; import * as semver from 'semver'; import * as vscode from 'vscode'; import { ext } from '../../../extensionVariables'; @@ -55,7 +55,7 @@ export async function deployImageToAca(context: IActionContext, node?: UnifiedRe throw new Error(vscode.l10n.t('No credentials found for registry "{0}".', registry.wrappedItem.label)); } - if (isDockerHubRegistryItem(registry.wrappedItem)) { + if (isDockerHubRegistry(registry.wrappedItem)) { // Ensure Docker Hub images are prefixed with 'docker.io/...' if (!/^docker\.io\//i.test(commandOptions.image)) { commandOptions.image = 'docker.io/' + commandOptions.image; diff --git a/src/commands/registries/azure/tasks/runAzureTask.ts b/src/commands/registries/azure/tasks/runAzureTask.ts deleted file mode 100644 index 5c1d73865d..0000000000 --- a/src/commands/registries/azure/tasks/runAzureTask.ts +++ /dev/null @@ -1,21 +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 { IActionContext } from "@microsoft/vscode-azext-utils"; -import { UnifiedRegistryItem } from "../../../../tree/registries/UnifiedRegistryTreeDataProvider"; - -export async function runAzureTask(context: IActionContext, node?: UnifiedRegistryItem): Promise { - // if (!node) { - // node = await ext.registriesTree.showTreeItemPicker(AzureTaskTreeItem.contextValue, context); - // } - - // const registryTI = node.parent.parent; - // const runRequest: TaskRunRequest = { type: 'TaskRunRequest', taskId: node.id }; - // const run = await (await registryTI.getClient(context)).registries.beginScheduleRunAndWait(registryTI.resourceGroup, registryTI.registryName, runRequest); - // await node.parent.refresh(context); - // // don't wait - // /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - // window.showInformationMessage(l10n.t('Successfully scheduled run "{0}" for task "{1}".', run.runId, node.taskName)); -} diff --git a/src/commands/registries/azure/tasks/runFileAsAzureTask.ts b/src/commands/registries/azure/tasks/runFileAsAzureTask.ts deleted file mode 100644 index f6ba0f5831..0000000000 --- a/src/commands/registries/azure/tasks/runFileAsAzureTask.ts +++ /dev/null @@ -1,16 +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 vscode from 'vscode'; -// import { IActionContext, UserCancelledError } from '@microsoft/vscode-azext-utils'; -// import { scheduleRunRequest } from './scheduleRunRequest'; - -// export async function runFileAsAzureTask(context: IActionContext, uri?: vscode.Uri): Promise { -// if (!vscode.workspace.isTrusted) { -// throw new UserCancelledError('enforceTrust'); -// } - -// await scheduleRunRequest(context, "FileTaskRunRequest", uri); -// } diff --git a/src/commands/registries/azure/tasks/viewAzureTaskLogs.ts b/src/commands/registries/azure/tasks/viewAzureTaskLogs.ts deleted file mode 100644 index 1fd008f1ee..0000000000 --- a/src/commands/registries/azure/tasks/viewAzureTaskLogs.ts +++ /dev/null @@ -1,29 +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 { IActionContext, nonNullProp, openReadOnlyContent } from "@microsoft/vscode-azext-utils"; -// import { l10n } from 'vscode'; -// import { ext } from "../../../../extensionVariables"; -// import { AzureTaskRunTreeItem } from "../../../../tree/registries/azure/AzureTaskRunTreeItem"; -// import { bufferToString } from "../../../../utils/execAsync"; -// import { getStorageBlob } from "../../../../utils/lazyPackages"; - -// export async function viewAzureTaskLogs(context: IActionContext, node?: AzureTaskRunTreeItem): Promise { -// if (!node) { -// node = await ext.registriesTree.showTreeItemPicker(AzureTaskRunTreeItem.contextValue, context); -// } - -// const registryTI = node.parent.parent.parent; -// await node.runWithTemporaryDescription(context, l10n.t('Retrieving logs...'), async () => { -// const result = await (await registryTI.getClient(context)).runs.getLogSasUrl(registryTI.resourceGroup, registryTI.registryName, node.runId); - -// const storageBlob = await getStorageBlob(); -// const blobClient = new storageBlob.BlobClient(nonNullProp(result, 'logLink')); -// const contentBuffer = await blobClient.downloadToBuffer(); -// const content = bufferToString(contentBuffer); - -// await openReadOnlyContent(node, content, '.log'); -// }); -// } diff --git a/src/commands/registries/azure/viewAzureProperties.ts b/src/commands/registries/azure/viewAzureProperties.ts index 0b52efbc36..85e5f8c66a 100644 --- a/src/commands/registries/azure/viewAzureProperties.ts +++ b/src/commands/registries/azure/viewAzureProperties.ts @@ -9,7 +9,6 @@ import { AzureRegistry } from "../../../tree/registries/Azure/AzureRegistryDataP import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; export async function viewAzureProperties(context: IActionContext, node?: UnifiedRegistryItem): Promise { - // TODO: add logic for azure task and azure tree task if (!node) { node = await contextValueExperience(context, ext.registriesRoot, { 'include': 'azureContainerRegistry' }); } diff --git a/src/commands/registries/logInToDockerCli.ts b/src/commands/registries/logInToDockerCli.ts index 7b6a459978..1618ffe4ca 100644 --- a/src/commands/registries/logInToDockerCli.ts +++ b/src/commands/registries/logInToDockerCli.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext, contextValueExperience, parseError } from '@microsoft/vscode-azext-utils'; -import { CommonRegistry } from '@microsoft/vscode-docker-registries/lib/clients/Common/models'; // TODO: update this +import { CommonRegistry } from '@microsoft/vscode-docker-registries'; import * as stream from 'stream'; import * as vscode from 'vscode'; import { ext } from '../../extensionVariables'; diff --git a/src/commands/registries/pullImages.ts b/src/commands/registries/pullImages.ts index 53a68a2a58..1a9489f214 100644 --- a/src/commands/registries/pullImages.ts +++ b/src/commands/registries/pullImages.ts @@ -8,7 +8,7 @@ import { CommonRegistry, CommonRepository, CommonTag } from '@microsoft/vscode-d import { ext } from '../../extensionVariables'; import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; -import { getImageNameFromRegistryTagItem } from '../../tree/registries/registryTreeUtils'; +import { getFullImageNameFromRegistryTagItem, getFullRepositoryNameFromRepositoryItem } from '../../tree/registries/registryTreeUtils'; import { logInToDockerCli } from './logInToDockerCli'; export async function pullRepository(context: IActionContext, node?: UnifiedRegistryItem): Promise { @@ -16,7 +16,7 @@ export async function pullRepository(context: IActionContext, node?: UnifiedRegi node = await contextValueExperience(context, ext.registriesTree, { include: 'commonrepository' }); } - await pullImages(context, node.parent, node.wrappedItem.label, true); + await pullImages(context, node.parent, getFullRepositoryNameFromRepositoryItem(node.wrappedItem), true); } export async function pullImageFromRepository(context: IActionContext, node?: UnifiedRegistryItem): Promise { @@ -24,7 +24,7 @@ export async function pullImageFromRepository(context: IActionContext, node?: Un node = await contextValueExperience(context, ext.registriesTree, { include: 'commontag' }); } - await pullImages(context, node.parent.parent, getImageNameFromRegistryTagItem(node.wrappedItem), false); + await pullImages(context, node.parent.parent, getFullImageNameFromRegistryTagItem(node.wrappedItem), false); } async function pullImages(context: IActionContext, node: UnifiedRegistryItem, imageRequest: string, allTags: boolean): Promise { @@ -39,7 +39,7 @@ async function pullImages(context: IActionContext, node: UnifiedRegistryItem { if (isRegistryRoot(element)) { if (!await this.subscriptionProvider.isSignedIn()) { - // TODO: show a node for sign in await this.subscriptionProvider.signIn(); this.onDidChangeTreeDataEmitter.fire(element); // TODO: this fires too fast, need a "fire soon" analogue return []; @@ -100,9 +99,8 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements public override async getRegistries(subscriptionItem: CommonRegistryItem): Promise { subscriptionItem = subscriptionItem as AzureSubscriptionRegistryItem; - // TODO: replace this with `createAzureClient` - const acrClient = new (await import('@azure/arm-containerregistry')).ContainerRegistryManagementClient(subscriptionItem.subscription.credential, subscriptionItem.subscription.subscriptionId); + const acrClient = await createAzureContainerRegistryClient(subscriptionItem.subscription); const registries: AcrRegistry[] = []; for await (const registry of acrClient.registries.list()) { diff --git a/src/tree/registries/registryTreeUtils.ts b/src/tree/registries/registryTreeUtils.ts index b07b6ad88a..7e82315591 100644 --- a/src/tree/registries/registryTreeUtils.ts +++ b/src/tree/registries/registryTreeUtils.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CommonRegistry, CommonRepository, CommonTag, isRegistry, isRepository, isTag } from "@microsoft/vscode-docker-registries"; +import { CommonRegistry, CommonRepository, CommonTag, isGenericV2Registry, isGitHubRegistry, isRegistry, isRepository, isTag } from "@microsoft/vscode-docker-registries"; import { l10n } from "vscode"; import { getResourceGroupFromId } from "../../utils/azureUtils"; -import { AzureRegistryItem } from "./Azure/AzureRegistryDataProvider"; +import { AzureRegistryItem, isAzureRegistryItem } from "./Azure/AzureRegistryDataProvider"; export function getImageNameFromRegistryTagItem(tag: CommonTag): string { if (!isTag(tag) || !isRepository(tag.parent)) { @@ -14,26 +14,16 @@ export function getImageNameFromRegistryTagItem(tag: CommonTag): string { } const repository = tag.parent as CommonRepository; - - return `${repository.label}:${tag.label}`; + return `${repository.label.toLowerCase()}:${tag.label.toLowerCase()}`; } export function getBaseImagePathFromRegistryItem(registry: CommonRegistry): string { if (!isRegistry(registry)) { throw new Error(l10n.t('Unable to get base image path')); - } - - switch (registry.additionalContextValues?.[0] ?? '') { - case 'azureContainerRegistry': - case 'genericRegistryV2Registry': { - return registry.baseUrl.authority.toLowerCase(); - } - case 'githubRegistry': { - return `${registry.baseUrl.authority.toLowerCase()}/${registry.label}`; - } - case 'dockerHubRegistry': - default: - return `${registry.label}`; + } else if (isAzureRegistryItem(registry) || isGenericV2Registry(registry) || isGitHubRegistry(registry)) { + return registry.baseUrl.authority.toLowerCase(); + } else { + return registry.label.toLowerCase(); } } @@ -43,6 +33,15 @@ export function getFullImageNameFromRegistryTagItem(tag: CommonTag): string { return `${baseImagePath}/${imageName}`; } +export function getFullRepositoryNameFromRepositoryItem(repository: CommonRepository): string { + if (!isRepository(repository)) { + throw new Error(l10n.t('Unable to get full repository name')); + } + + const baseImagePath = getBaseImagePathFromRegistryItem(repository.parent); + return `${baseImagePath}/${repository.label.toLowerCase()}`; +} + export function getResourceGroupFromAzureRegistryItem(node: AzureRegistryItem): string { if (!isRegistry(node)) { throw new Error('Unable to get resource group'); From b49d849b1d2ca391ba2f26c843f942401f3183c2 Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Wed, 30 Aug 2023 17:12:16 -0400 Subject: [PATCH 13/43] use baseUrl --- package-lock.json | 2 +- src/commands/images/pushImage.ts | 6 +- src/commands/images/tagImage.ts | 4 +- .../azure/DockerAssignAcrPullRoleStep.ts | 2 +- src/commands/registries/logInToDockerCli.ts | 3 +- src/commands/registries/logOutOfDockerCli.ts | 7 +- src/tree/registries/registryTreeUtils.ts | 29 ++++---- src/utils/registryRequestUtils.ts | 68 ------------------- 8 files changed, 30 insertions(+), 91 deletions(-) delete mode 100644 src/utils/registryRequestUtils.ts diff --git a/package-lock.json b/package-lock.json index 5360e71c23..044eccb16b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -824,7 +824,7 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.0.1-alpha", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", - "integrity": "sha512-Hpb82tseHfol1XypPkV7K9EFJ5dWMtvLIeaCVkFvKkW8lgSLsn83qqXgNlj7nd/4zg4sr2Lm72NkqoIw/yQscQ==", + "integrity": "sha512-80fmjgaySkOTE4jRQ8FcMBZVS8r/mlB6H1uc1Ia6SIj9u1N2WmFZ/Esr2M36PZwhGiMUh9mUfUVF/4IxDhJeSQ==", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", diff --git a/src/commands/images/pushImage.ts b/src/commands/images/pushImage.ts index 4a15304cac..4eaea7dd08 100644 --- a/src/commands/images/pushImage.ts +++ b/src/commands/images/pushImage.ts @@ -10,7 +10,7 @@ import { ext } from '../../extensionVariables'; import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; -import { getBaseImagePathFromRegistryItem } from '../../tree/registries/registryTreeUtils'; +import { getBaseUrlFromItem } from '../../tree/registries/registryTreeUtils'; import { addImageTaggingTelemetry, tagImage } from './tagImage'; export async function pushImage(context: IActionContext, node: ImageTreeItem | undefined): Promise { @@ -59,7 +59,7 @@ export async function pushImage(context: IActionContext, node: ImageTreeItem | u // Give the user a chance to modify the tag however they want const finalTag = await tagImage(context, node, connectedRegistry); - const baseImagePath = getBaseImagePathFromRegistryItem(connectedRegistry.wrappedItem); + const baseImagePath = getBaseUrlFromItem(connectedRegistry.wrappedItem); if (connectedRegistry && finalTag.startsWith(baseImagePath)) { // If a registry was found/chosen and is still the same as the final tag's registry, try logging in await vscode.commands.executeCommand('vscode-docker.registries.logInToDockerCli', connectedRegistry); @@ -82,7 +82,7 @@ export async function pushImage(context: IActionContext, node: ImageTreeItem | u async function tryGetConnectedRegistryForPath(context: IActionContext, baseImagePath: string): Promise | undefined> { const allRegistries = await ext.registriesTree.getConnectedRegistries(vscode.Uri.parse(baseImagePath)); - let matchedRegistry = allRegistries.find((registry) => getBaseImagePathFromRegistryItem(registry.wrappedItem) === baseImagePath); + let matchedRegistry = allRegistries.find((registry) => getBaseUrlFromItem(registry.wrappedItem) === baseImagePath); if (!matchedRegistry) { matchedRegistry = await contextValueExperience(context, ext.registriesTree, { include: ['commonregistry'] }); diff --git a/src/commands/images/tagImage.ts b/src/commands/images/tagImage.ts index f5bfd8f97b..328a1a48ac 100644 --- a/src/commands/images/tagImage.ts +++ b/src/commands/images/tagImage.ts @@ -9,7 +9,7 @@ import * as vscode from 'vscode'; import { ext } from '../../extensionVariables'; import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; -import { getBaseImagePathFromRegistryItem } from '../../tree/registries/registryTreeUtils'; +import { getBaseUrlFromItem } from '../../tree/registries/registryTreeUtils'; export async function tagImage(context: IActionContext, node?: ImageTreeItem, registry?: UnifiedRegistryItem): Promise { if (!node) { @@ -21,7 +21,7 @@ export async function tagImage(context: IActionContext, node?: ImageTreeItem, re } addImageTaggingTelemetry(context, node.fullTag, '.before'); - const baseImagePath = isRegistry(registry.wrappedItem) ? getBaseImagePathFromRegistryItem(registry.wrappedItem) : undefined; + const baseImagePath = isRegistry(registry.wrappedItem) ? getBaseUrlFromItem(registry.wrappedItem) : undefined; const newTaggedName: string = await getTagFromUserInput(context, node.fullTag, baseImagePath); addImageTaggingTelemetry(context, newTaggedName, '.after'); diff --git a/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts b/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts index 0761162134..0b1f9ce63c 100644 --- a/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts +++ b/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts @@ -42,7 +42,7 @@ export class DockerAssignAcrPullRoleStep extends AzureWizardExecuteStep client.login({ username: username, passwordStdIn: true, - registry: node.wrappedItem.baseUrl?.toString() ?? '' + registry: server }), { stdInPipe: stream.Readable.from(secret), diff --git a/src/commands/registries/logOutOfDockerCli.ts b/src/commands/registries/logOutOfDockerCli.ts index f0d212a69d..7c93a4b89d 100644 --- a/src/commands/registries/logOutOfDockerCli.ts +++ b/src/commands/registries/logOutOfDockerCli.ts @@ -5,6 +5,7 @@ import { IActionContext, contextValueExperience } from '@microsoft/vscode-azext-utils'; import { CommonRegistry } from '@microsoft/vscode-docker-registries'; +import { l10n } from 'vscode'; import { ext } from '../../extensionVariables'; import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; @@ -13,6 +14,10 @@ export async function logOutOfDockerCli(context: IActionContext, node?: UnifiedR if (!node) { node = await contextValueExperience(context, ext.registriesTree, { include: 'commonregistry' }); } + const serverUrl = (await node.provider.getLoginInformation?.(node.wrappedItem))?.server; + if (!serverUrl) { + throw new Error(l10n.t('Unable to get server URL')); + } const client = await ext.runtimeManager.getClient(); const taskCRF = new TaskCommandRunnerFactory( @@ -22,6 +27,6 @@ export async function logOutOfDockerCli(context: IActionContext, node?: UnifiedR ); await taskCRF.getCommandRunner()( - client.logout({ registry: node.wrappedItem.baseUrl?.toString() || '' }), + client.logout({ registry: serverUrl }), ); } diff --git a/src/tree/registries/registryTreeUtils.ts b/src/tree/registries/registryTreeUtils.ts index 7e82315591..2ff00b2223 100644 --- a/src/tree/registries/registryTreeUtils.ts +++ b/src/tree/registries/registryTreeUtils.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CommonRegistry, CommonRepository, CommonTag, isGenericV2Registry, isGitHubRegistry, isRegistry, isRepository, isTag } from "@microsoft/vscode-docker-registries"; +import { CommonRegistryItem, CommonRepository, CommonTag, isRegistry, isRepository, isTag } from "@microsoft/vscode-docker-registries"; import { l10n } from "vscode"; import { getResourceGroupFromId } from "../../utils/azureUtils"; -import { AzureRegistryItem, isAzureRegistryItem } from "./Azure/AzureRegistryDataProvider"; +import { AzureRegistryItem } from "./Azure/AzureRegistryDataProvider"; export function getImageNameFromRegistryTagItem(tag: CommonTag): string { if (!isTag(tag) || !isRepository(tag.parent)) { @@ -17,29 +17,30 @@ export function getImageNameFromRegistryTagItem(tag: CommonTag): string { return `${repository.label.toLowerCase()}:${tag.label.toLowerCase()}`; } -export function getBaseImagePathFromRegistryItem(registry: CommonRegistry): string { - if (!isRegistry(registry)) { - throw new Error(l10n.t('Unable to get base image path')); - } else if (isAzureRegistryItem(registry) || isGenericV2Registry(registry) || isGitHubRegistry(registry)) { - return registry.baseUrl.authority.toLowerCase(); - } else { - return registry.label.toLowerCase(); +export function getBaseUrlFromItem(item: CommonRegistryItem): string { + if (!isTag(item) && !isRepository(item) && !isRegistry(item)) { + throw new Error(l10n.t('Unable to get base URL')); } + const authority = item.baseUrl.authority; + const path = item.baseUrl.path === '/' ? '' : item.baseUrl.path; + + return `${authority}${path}`; } export function getFullImageNameFromRegistryTagItem(tag: CommonTag): string { + if (!isTag(tag) || !isRegistry(tag.parent.parent)) { + throw new Error(l10n.t('Unable to get full image name')); + } const imageName = getImageNameFromRegistryTagItem(tag); - const baseImagePath = getBaseImagePathFromRegistryItem(tag.parent.parent); - return `${baseImagePath}/${imageName}`; + return `${getBaseUrlFromItem(tag)}/${imageName}`; } export function getFullRepositoryNameFromRepositoryItem(repository: CommonRepository): string { - if (!isRepository(repository)) { + if (!isRepository(repository) || !isRegistry(repository.parent)) { throw new Error(l10n.t('Unable to get full repository name')); } - const baseImagePath = getBaseImagePathFromRegistryItem(repository.parent); - return `${baseImagePath}/${repository.label.toLowerCase()}`; + return `${getBaseUrlFromItem(repository)}/${repository.label.toLowerCase()}`; } export function getResourceGroupFromAzureRegistryItem(node: AzureRegistryItem): string { diff --git a/src/utils/registryRequestUtils.ts b/src/utils/registryRequestUtils.ts deleted file mode 100644 index 5b001886ed..0000000000 --- a/src/utils/registryRequestUtils.ts +++ /dev/null @@ -1,68 +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 { URL } from "url"; -import { ociClientId } from "../constants"; -import { ErrorHandling, RequestLike, RequestOptionsLike, ResponseLike, httpRequest } from './httpRequest'; - -export function getNextLinkFromHeaders(response: IRegistryRequestResponse): string | undefined { - const linkHeader: string | undefined = response.headers.get('link') as string; - if (linkHeader) { - const match = linkHeader.match(/<(.*)>; rel="next"/i); - return match ? match[1] : undefined; - } else { - return undefined; - } -} - -export async function registryRequest( - node: IRegistryAuthTreeItem | IRepositoryAuthTreeItem, - method: 'GET' | 'DELETE' | 'POST', - url: string, - customOptions?: RequestOptionsLike, - errorHandling: ErrorHandling = ErrorHandling.ThrowOnError -): Promise> { - const options = { - method: method, - headers: { - // eslint-disable-next-line @typescript-eslint/naming-convention - 'X-Meta-Source-Client': ociClientId, - }, - ...customOptions, - }; - - const baseUrl = node.baseUrl || (node).parent.baseUrl; - let fullUrl: string = url; - if (!url.startsWith(baseUrl)) { - const parsed = new URL(url, baseUrl); - fullUrl = parsed.toString(); - } - - const response = await httpRequest(fullUrl, options, async (request) => { - if (node.signRequest) { - return node.signRequest(request); - } else { - return (node).parent?.signRequest(request); - } - }, errorHandling); - - return { - body: method !== 'DELETE' ? await response.json() : undefined, - ...response - }; -} - -export interface IRegistryRequestResponse extends ResponseLike { - body: T -} - -export interface IRegistryAuthTreeItem { - signRequest(request: RequestLike): Promise; - baseUrl: string; -} - -export interface IRepositoryAuthTreeItem extends Partial { - parent: IRegistryAuthTreeItem; -} From c05fae62237b6aac246ac2c0d252999ab18424fd Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Thu, 31 Aug 2023 12:43:21 -0400 Subject: [PATCH 14/43] polished up contextValues --- src/commands/images/pushImage.ts | 8 ++++++-- .../registries/azure/DockerAssignAcrPullRoleStep.ts | 8 ++++---- src/commands/registries/azure/DockerSiteCreateStep.ts | 6 +++--- src/commands/registries/azure/DockerWebhookCreateStep.ts | 6 +++--- src/commands/registries/azure/createAzureRegistry.ts | 2 +- src/commands/registries/azure/deleteAzureRegistry.ts | 2 +- src/commands/registries/azure/deleteAzureRepository.ts | 2 +- src/commands/registries/azure/deployImageToAca.ts | 6 +++--- src/commands/registries/azure/deployImageToAzure.ts | 3 +-- src/commands/registries/azure/openInAzurePortal.ts | 4 ++-- src/commands/registries/azure/tasks/scheduleRunRequest.ts | 2 +- src/commands/registries/azure/untagAzureImage.ts | 2 +- src/commands/registries/azure/viewAzureProperties.ts | 2 +- src/commands/registries/copyRemoteImageDigest.ts | 6 +++--- .../registries/dockerHub/openDockerHubInBrowser.ts | 2 +- src/extensionVariables.ts | 3 ++- src/tree/registerTrees.ts | 4 +++- src/tree/registries/Azure/AzureRegistryDataProvider.ts | 6 +++--- src/utils/azureUtils.ts | 2 -- 19 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/commands/images/pushImage.ts b/src/commands/images/pushImage.ts index 4eaea7dd08..21cf525a7a 100644 --- a/src/commands/images/pushImage.ts +++ b/src/commands/images/pushImage.ts @@ -42,7 +42,7 @@ export async function pushImage(context: IActionContext, node: ImageTreeItem | u } } else { // Try to find a connected Docker Hub registry (primarily for login credentials) - connectedRegistry = await contextValueExperience(context, ext.registriesTree, { include: ['dockerHubRegistry'] }); + connectedRegistry = await contextValueExperience(context, ext.dockerHubRegistryDataProvider, { include: ['dockerHubRegistry'] }); } } else { // The registry to push to is determinate. If there's a connected registry in the tree view, we'll try to find it, to perform login ahead of time. @@ -80,7 +80,11 @@ export async function pushImage(context: IActionContext, node: ImageTreeItem | u } async function tryGetConnectedRegistryForPath(context: IActionContext, baseImagePath: string): Promise | undefined> { - const allRegistries = await ext.registriesTree.getConnectedRegistries(vscode.Uri.parse(baseImagePath)); + let baseImagePathUri = vscode.Uri.parse(baseImagePath); + if (!baseImagePathUri.scheme || baseImagePathUri.scheme === 'file') { + baseImagePathUri = vscode.Uri.parse(`https://${baseImagePath}`); // Add a scheme so that we can parse the hostname + } + const allRegistries = await ext.registriesTree.getConnectedRegistries(baseImagePathUri); let matchedRegistry = allRegistries.find((registry) => getBaseUrlFromItem(registry.wrappedItem) === baseImagePath); diff --git a/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts b/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts index 0b1f9ce63c..338ef13ce8 100644 --- a/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts +++ b/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts @@ -9,9 +9,9 @@ import { CommonTag } from "@microsoft/vscode-docker-registries"; import { randomUUID } from "crypto"; import { Progress, l10n } from "vscode"; import { ext } from "../../../extensionVariables"; -import { AzureRegistry, isAzureTagItem } from "../../../tree/registries/Azure/AzureRegistryDataProvider"; +import { AzureRegistry, isAzureTag } from "../../../tree/registries/Azure/AzureRegistryDataProvider"; import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; -import { getFullImageNameFromRegistryTagItem, getResourceGroupFromAzureRegistryItem } from "../../../tree/registries/registryTreeUtils"; +import { getBaseUrlFromItem, getFullImageNameFromRegistryTagItem, getResourceGroupFromAzureRegistryItem } from "../../../tree/registries/registryTreeUtils"; import { getArmAuth, getArmContainerRegistry, getAzExtAppService, getAzExtAzureUtils } from "../../../utils/lazyPackages"; export class DockerAssignAcrPullRoleStep extends AzureWizardExecuteStep { @@ -42,7 +42,7 @@ export class DockerAssignAcrPullRoleStep extends AzureWizardExecuteStep App Service, NOT Arc App Service. Use managed service identity. - if (isAzureRegistryItem(registryTI.wrappedItem) && !context.customLocation) { + if (isAzureRegistry(registryTI.wrappedItem) && !context.customLocation) { appSettings.push({ name: 'DOCKER_ENABLE_CI', value: 'true' }); // Don't need an image, username, or password--just create an empty web app to assign permissions and then configure with an image @@ -77,7 +77,7 @@ export class DockerSiteCreateStep extends AzureWizardExecuteStep Arc App Service. Use regular auth. Same as any V2 registry but different way of getting auth. - else if (isAzureRegistryItem(registryTI.wrappedItem) && context.customLocation) { + else if (isAzureRegistry(registryTI.wrappedItem) && context.customLocation) { const cred = await (registryTI.provider as unknown as AzureRegistryDataProvider).tryGetAdminCredentials(registryTI.wrappedItem); if (!cred?.username || !cred?.passwords?.[0]?.value) { throw new Error(l10n.t('Azure App service deployment on Azure Arc only supports running images from Azure Container Registries with admin enabled')); diff --git a/src/commands/registries/azure/DockerWebhookCreateStep.ts b/src/commands/registries/azure/DockerWebhookCreateStep.ts index 70510788ac..52d1c78809 100644 --- a/src/commands/registries/azure/DockerWebhookCreateStep.ts +++ b/src/commands/registries/azure/DockerWebhookCreateStep.ts @@ -10,7 +10,7 @@ import { AzureWizardExecuteStep, nonNullProp } from "@microsoft/vscode-azext-uti import { CommonRepository, CommonTag, isDockerHubRepository } from '@microsoft/vscode-docker-registries'; import * as vscode from "vscode"; import { ext } from "../../../extensionVariables"; -import { AzureRegistry, isAzureRepositoryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; +import { AzureRegistry, isAzureRepository } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; import { getResourceGroupFromAzureRegistryItem } from '../../../tree/registries/registryTreeUtils'; import { cryptoUtils } from '../../../utils/cryptoUtils'; @@ -34,7 +34,7 @@ export class DockerWebhookCreateStep extends AzureWizardExecuteStep { diff --git a/src/commands/registries/azure/createAzureRegistry.ts b/src/commands/registries/azure/createAzureRegistry.ts index 30d4efd305..9c3eebc976 100644 --- a/src/commands/registries/azure/createAzureRegistry.ts +++ b/src/commands/registries/azure/createAzureRegistry.ts @@ -17,7 +17,7 @@ import { getAzExtAzureUtils } from '../../../utils/lazyPackages'; export async function createAzureRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: 'azuresubscription' }); + node = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: 'azuresubscription' }); } const subscriptionContext = createSubscriptionContext(node.wrappedItem.subscription); diff --git a/src/commands/registries/azure/deleteAzureRegistry.ts b/src/commands/registries/azure/deleteAzureRegistry.ts index e6f059b123..33da8b554e 100644 --- a/src/commands/registries/azure/deleteAzureRegistry.ts +++ b/src/commands/registries/azure/deleteAzureRegistry.ts @@ -11,7 +11,7 @@ import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTre export async function deleteAzureRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerRegistry' }); + node = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: 'azureContainerRegistry' }); } const registryName = node.wrappedItem.label; diff --git a/src/commands/registries/azure/deleteAzureRepository.ts b/src/commands/registries/azure/deleteAzureRepository.ts index 627b347a7d..2d293d623e 100644 --- a/src/commands/registries/azure/deleteAzureRepository.ts +++ b/src/commands/registries/azure/deleteAzureRepository.ts @@ -11,7 +11,7 @@ import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTre export async function deleteAzureRepository(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerRepository' }); + node = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: 'azureContainerRepository' }); } const confirmDelete = l10n.t('Are you sure you want to delete repository "{0}" and its associated images?', node.wrappedItem.label); diff --git a/src/commands/registries/azure/deployImageToAca.ts b/src/commands/registries/azure/deployImageToAca.ts index bf4f3d675c..3bc513f002 100644 --- a/src/commands/registries/azure/deployImageToAca.ts +++ b/src/commands/registries/azure/deployImageToAca.ts @@ -9,7 +9,7 @@ import { CommonRegistry, CommonTag, isDockerHubRegistry, LoginInformation } from import * as semver from 'semver'; import * as vscode from 'vscode'; import { ext } from '../../../extensionVariables'; -import { isAzureRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; +import { isAzureRegistry } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; import { getFullImageNameFromRegistryTagItem } from '../../../tree/registries/registryTreeUtils'; import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; import { installExtension } from '../../../utils/installExtension'; @@ -35,7 +35,7 @@ export async function deployImageToAca(context: IActionContext, node?: UnifiedRe } if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: 'commontag' }); + node = await contextValueExperience(context, ext.registriesTree, { include: ['registryV2Tag', 'dockerHubTag'], }); } const commandOptions: Partial = { @@ -46,7 +46,7 @@ export async function deployImageToAca(context: IActionContext, node?: UnifiedRe const registry: UnifiedRegistryItem = node.parent.parent as unknown as UnifiedRegistryItem; - if (isAzureRegistryItem(registry.wrappedItem)) { + if (isAzureRegistry(registry.wrappedItem)) { // No additional work to do; ACA can handle this on its own } else { const logInInfo: LoginInformation = await registry.provider.getLoginInformation(registry.wrappedItem); diff --git a/src/commands/registries/azure/deployImageToAzure.ts b/src/commands/registries/azure/deployImageToAzure.ts index cbed25dcf4..13660715e8 100644 --- a/src/commands/registries/azure/deployImageToAzure.ts +++ b/src/commands/registries/azure/deployImageToAzure.ts @@ -24,12 +24,11 @@ export interface IAppServiceContainerWizardContext extends IAppServiceWizardCont export async function deployImageToAzure(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: 'commontag' }); + node = await contextValueExperience(context, ext.registriesTree, { include: ['registryV2Tag', 'dockerHubTag'], }); } const azExtAzureUtils = await getAzExtAzureUtils(); const vscAzureAppService = await getAzExtAppService(); - const promptSteps: AzureWizardPromptStep[] = []; const subscriptionItem = await registryExperience(context, ext.azureRegistryDataProvider, { include: 'azuresubscription' }) as AzureSubscriptionRegistryItem; diff --git a/src/commands/registries/azure/openInAzurePortal.ts b/src/commands/registries/azure/openInAzurePortal.ts index 2dd09988a7..b9f0915d2a 100644 --- a/src/commands/registries/azure/openInAzurePortal.ts +++ b/src/commands/registries/azure/openInAzurePortal.ts @@ -6,7 +6,7 @@ import { IActionContext, UserCancelledError, contextValueExperience } from '@microsoft/vscode-azext-utils'; import * as vscode from 'vscode'; import { ext } from '../../../extensionVariables'; -import { AzureRegistry, AzureSubscriptionRegistryItem, isAzureRegistryItem, isAzureSubscriptionRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; +import { AzureRegistry, AzureSubscriptionRegistryItem, isAzureRegistry, isAzureSubscriptionRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; export async function openInAzurePortal(context: IActionContext, node?: UnifiedRegistryItem): Promise { @@ -20,7 +20,7 @@ export async function openInAzurePortal(context: IActionContext, node?: UnifiedR if (isAzureSubscriptionRegistryItem(azureRegistryItem)) { url = `${baseUrl}/subscriptions/${azureRegistryItem.subscription.subscriptionId}`; - } else if (isAzureRegistryItem(azureRegistryItem)) { + } else if (isAzureRegistry(azureRegistryItem)) { url = `${baseUrl}/${azureRegistryItem.id}`; } else { throw new UserCancelledError(); diff --git a/src/commands/registries/azure/tasks/scheduleRunRequest.ts b/src/commands/registries/azure/tasks/scheduleRunRequest.ts index 265e7ac862..f0964443bd 100644 --- a/src/commands/registries/azure/tasks/scheduleRunRequest.ts +++ b/src/commands/registries/azure/tasks/scheduleRunRequest.ts @@ -47,7 +47,7 @@ export async function scheduleRunRequest(context: IActionContext, requestType: ' throw new Error(vscode.l10n.t('Run Request Type Currently not supported.')); } - const node: UnifiedRegistryItem = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerRegistry' }); + const node: UnifiedRegistryItem = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: 'azureContainerRegistry' }); const registry: AzureRegistryItem = node.wrappedItem; const resourceGroup = getResourceGroupFromId(registry.id); diff --git a/src/commands/registries/azure/untagAzureImage.ts b/src/commands/registries/azure/untagAzureImage.ts index fa80f814b9..4aed76a28c 100644 --- a/src/commands/registries/azure/untagAzureImage.ts +++ b/src/commands/registries/azure/untagAzureImage.ts @@ -12,7 +12,7 @@ import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTre export async function untagAzureImage(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerTag' }); + node = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: 'azureContainerTag' }); } const fullTag = getFullImageNameFromRegistryTagItem(node.wrappedItem); diff --git a/src/commands/registries/azure/viewAzureProperties.ts b/src/commands/registries/azure/viewAzureProperties.ts index 85e5f8c66a..7e8f5d1a2b 100644 --- a/src/commands/registries/azure/viewAzureProperties.ts +++ b/src/commands/registries/azure/viewAzureProperties.ts @@ -10,7 +10,7 @@ import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTre export async function viewAzureProperties(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesRoot, { 'include': 'azureContainerRegistry' }); + node = await contextValueExperience(context, ext.azureRegistryDataProvider, { 'include': 'azureContainerRegistry' }); } await openReadOnlyJson({ label: node.wrappedItem.label, fullId: node.wrappedItem.id }, node.wrappedItem.registryProperties); diff --git a/src/commands/registries/copyRemoteImageDigest.ts b/src/commands/registries/copyRemoteImageDigest.ts index 62f9164d6c..1e022c75cf 100644 --- a/src/commands/registries/copyRemoteImageDigest.ts +++ b/src/commands/registries/copyRemoteImageDigest.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext, contextValueExperience } from "@microsoft/vscode-azext-utils"; -import { CommonRegistryDataProvider, CommonTag } from "@microsoft/vscode-docker-registries"; +import { CommonTag, RegistryV2DataProvider, V2Tag } from "@microsoft/vscode-docker-registries"; import * as vscode from "vscode"; import { ext } from "../../extensionVariables"; import { UnifiedRegistryItem } from "../../tree/registries/UnifiedRegistryTreeDataProvider"; @@ -14,8 +14,8 @@ export async function copyRemoteImageDigest(context: IActionContext, node?: Unif node = await contextValueExperience(context, ext.registriesTree, { include: ['registryV2Tag'] }); } - const v2DataProvider = node.provider as unknown as CommonRegistryDataProvider; - const digest = await v2DataProvider.getImageDigest?.(node.wrappedItem as CommonTag); + const v2DataProvider = node.provider as unknown as RegistryV2DataProvider; + const digest = await v2DataProvider.getImageDigest(node.wrappedItem as V2Tag); /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ vscode.env.clipboard.writeText(digest); diff --git a/src/commands/registries/dockerHub/openDockerHubInBrowser.ts b/src/commands/registries/dockerHub/openDockerHubInBrowser.ts index 1278e564c3..35bba3f218 100644 --- a/src/commands/registries/dockerHub/openDockerHubInBrowser.ts +++ b/src/commands/registries/dockerHub/openDockerHubInBrowser.ts @@ -12,7 +12,7 @@ import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTre export async function openDockerHubInBrowser(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesRoot, { include: ['dockerHubRegistry', 'dockerHubRepository', 'dockerHubTag'] }); + node = await contextValueExperience(context, ext.registriesRoot, { include: ['dockerHubRegistry'] }); // TODO: use dockerHub tree here } let url = dockerHubUrl; diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 2f42ce0a33..989c79ec9a 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { AzExtTreeDataProvider, AzExtTreeItem, IExperimentationServiceAdapter } from '@microsoft/vscode-azext-utils'; -import { GenericRegistryV2DataProvider } from '@microsoft/vscode-docker-registries'; +import { DockerHubRegistryDataProvider, GenericRegistryV2DataProvider } from '@microsoft/vscode-docker-registries'; import { ExtensionContext, StatusBarItem, TreeView } from 'vscode'; import { ContainerRuntimeManager } from './runtimes/ContainerRuntimeManager'; import { OrchestratorRuntimeManager } from './runtimes/OrchestratorRuntimeManager'; @@ -52,6 +52,7 @@ export namespace ext { export let registriesRoot: UnifiedRegistryTreeDataProvider; export let genericRegistryV2DataProvider: GenericRegistryV2DataProvider; export let azureRegistryDataProvider: AzureRegistryDataProvider; + export let dockerHubRegistryDataProvider: DockerHubRegistryDataProvider; export let volumesTree: AzExtTreeDataProvider; export let volumesTreeView: TreeView; diff --git a/src/tree/registerTrees.ts b/src/tree/registerTrees.ts index 2280a11905..9451b97b4a 100644 --- a/src/tree/registerTrees.ts +++ b/src/tree/registerTrees.ts @@ -47,8 +47,9 @@ export function registerTrees(): void { const urtdp = new UnifiedRegistryTreeDataProvider(ext.context.globalState); const genericRegistryV2DataProvider = new GenericRegistryV2DataProvider(ext.context); const azureRegistryDataProvider = new AzureRegistryDataProvider(ext.context); + const dockerHubRegistryDataProvider = new DockerHubRegistryDataProvider(ext.context); urtdp.registerProvider(new GitHubRegistryDataProvider(ext.context)); - urtdp.registerProvider(new DockerHubRegistryDataProvider(ext.context)); + urtdp.registerProvider(dockerHubRegistryDataProvider); urtdp.registerProvider(azureRegistryDataProvider); urtdp.registerProvider(genericRegistryV2DataProvider); ext.registriesRoot = urtdp; @@ -56,6 +57,7 @@ export function registerTrees(): void { ext.registriesTree = urtdp; ext.genericRegistryV2DataProvider = genericRegistryV2DataProvider; ext.azureRegistryDataProvider = azureRegistryDataProvider; + ext.dockerHubRegistryDataProvider = dockerHubRegistryDataProvider; ext.volumesRoot = new VolumesTreeItem(undefined); const volumesLoadMore = 'vscode-docker.volumes.loadMore'; diff --git a/src/tree/registries/Azure/AzureRegistryDataProvider.ts b/src/tree/registries/Azure/AzureRegistryDataProvider.ts index 3b07b5a782..9c9b159203 100644 --- a/src/tree/registries/Azure/AzureRegistryDataProvider.ts +++ b/src/tree/registries/Azure/AzureRegistryDataProvider.ts @@ -33,15 +33,15 @@ export function isAzureSubscriptionRegistryItem(item: unknown): item is AzureSub return !!item && typeof item === 'object' && (item as AzureSubscriptionRegistryItem).type === 'azuresubscription'; } -export function isAzureRegistryItem(item: unknown): item is AzureRegistry { +export function isAzureRegistry(item: unknown): item is AzureRegistry { return !!item && typeof item === 'object' && (item as AzureRegistryItem).additionalContextValues?.includes('azureContainerRegistry'); } -export function isAzureRepositoryItem(item: unknown): item is AzureRepository { +export function isAzureRepository(item: unknown): item is AzureRepository { return !!item && typeof item === 'object' && (item as AzureRepository).additionalContextValues?.includes('azureContainerRepository'); } -export function isAzureTagItem(item: unknown): item is AzureTag { +export function isAzureTag(item: unknown): item is AzureTag { return !!item && typeof item === 'object' && (item as AzureTag).additionalContextValues?.includes('azureContainerTag'); } diff --git a/src/utils/azureUtils.ts b/src/utils/azureUtils.ts index 8d23588739..0125b7c6cc 100644 --- a/src/utils/azureUtils.ts +++ b/src/utils/azureUtils.ts @@ -22,5 +22,3 @@ export function getResourceGroupFromId(id: string): string { export async function createAzureContainerRegistryClient(subscriptionItem: AzureSubscription): Promise { return new (await import('@azure/arm-containerregistry')).ContainerRegistryManagementClient(subscriptionItem.credential, subscriptionItem.subscriptionId); } - -/* eslint-enable @typescript-eslint/naming-convention */ From 840f91601ead35e991d89795f87ec4d1a3d01d12 Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Thu, 31 Aug 2023 17:06:55 -0400 Subject: [PATCH 15/43] fixed push image error for dockerhub --- src/commands/images/pushImage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/images/pushImage.ts b/src/commands/images/pushImage.ts index 21cf525a7a..2b4fdc3847 100644 --- a/src/commands/images/pushImage.ts +++ b/src/commands/images/pushImage.ts @@ -47,7 +47,7 @@ export async function pushImage(context: IActionContext, node: ImageTreeItem | u } else { // The registry to push to is determinate. If there's a connected registry in the tree view, we'll try to find it, to perform login ahead of time. // Registry path is everything up to the last slash. - const baseImagePath = node.fullTag.substring(0, node.fullTag.lastIndexOf('/')); + const baseImagePath = node.parent.label.substring(0, node.parent.label.lastIndexOf('/')); const progressOptions: vscode.ProgressOptions = { location: vscode.ProgressLocation.Notification, From 39591a25001e0ca4fa3fc5f41ea9619862fc53b6 Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Tue, 5 Sep 2023 16:15:04 -0400 Subject: [PATCH 16/43] removed azure account listener --- src/extension.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 8fb384f336..e3a70c3181 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -25,7 +25,6 @@ import { registerListeners } from './telemetry/registerListeners'; import { registerTrees } from './tree/registerTrees'; import { AlternateYamlLanguageServiceClientFeature } from './utils/AlternateYamlLanguageServiceClientFeature'; import { AzExtLogOutputChannelWrapper } from './utils/AzExtLogOutputChannelWrapper'; -import { AzureAccountExtensionListener } from './utils/AzureAccountExtensionListener'; import { logDockerEnvironment, logSystemInfo } from './utils/diagnostics'; import { DocumentSettingsClientFeature } from './utils/DocumentSettingsClientFeature'; import { migrateOldEnvironmentSettingsIfNeeded } from './utils/migrateOldEnvironmentSettingsIfNeeded'; @@ -142,7 +141,6 @@ export async function activateInternal(ctx: vscode.ExtensionContext, perfStats: export async function deactivateInternal(ctx: vscode.ExtensionContext): Promise { await callWithTelemetryAndErrorHandling('docker.deactivate', async (activateContext: IActionContext) => { activateContext.telemetry.properties.isActivationEvent = 'true'; - AzureAccountExtensionListener.dispose(); await Promise.all([ dockerfileLanguageClient.stop(), From 7a5ec73c9fd8dd10beeb206ae1846bf3f21494c3 Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Tue, 5 Sep 2023 16:17:06 -0400 Subject: [PATCH 17/43] actually removed AzureAccountExtensionListener --- src/utils/AzureAccountExtensionListener.ts | 55 ---------------------- 1 file changed, 55 deletions(-) delete mode 100644 src/utils/AzureAccountExtensionListener.ts diff --git a/src/utils/AzureAccountExtensionListener.ts b/src/utils/AzureAccountExtensionListener.ts deleted file mode 100644 index ee099096f0..0000000000 --- a/src/utils/AzureAccountExtensionListener.ts +++ /dev/null @@ -1,55 +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 { Disposable, Event, EventEmitter, extensions } from "vscode"; - -export class AzureAccountExtensionListener extends Disposable { - - private static readonly extensionName: string = 'ms-vscode.azure-account'; - private static extensionInstalledEmitter: EventEmitter; - private static extensionsChangeEventListener: Disposable; - - public static get onExtensionInstalled(): Event { - // Subscribe to extensions change event only once. - if (!AzureAccountExtensionListener.extensionsChangeEventListener) { - AzureAccountExtensionListener.extensionsChangeEventListener = this.subscribeToExtensionsChange(); - } - - // If there were any previous subscribers to this event, dispose them so the old subscriber will not receive the event. - if (AzureAccountExtensionListener.extensionInstalledEmitter) { - AzureAccountExtensionListener.extensionInstalledEmitter.dispose(); - } - AzureAccountExtensionListener.extensionInstalledEmitter = new EventEmitter(); - return this.extensionInstalledEmitter.event; - } - - private static subscribeToExtensionsChange(): Disposable { - const listener: Disposable = extensions.onDidChange(() => { - if (this.isExtensionInstalled(AzureAccountExtensionListener.extensionName)) { - // Once the extension is installed, no need to continue listening for the event. - AzureAccountExtensionListener.extensionInstalledEmitter.fire(true); - AzureAccountExtensionListener.extensionInstalledEmitter.dispose(); - listener.dispose(); - AzureAccountExtensionListener.extensionsChangeEventListener = undefined; - } - }); - return listener; - } - - private static isExtensionInstalled(extensionName: string): boolean { - return extensions.getExtension(AzureAccountExtensionListener.extensionName) !== undefined; - } - - public static dispose(): void { - if (AzureAccountExtensionListener.extensionInstalledEmitter) { - AzureAccountExtensionListener.extensionInstalledEmitter.dispose(); - AzureAccountExtensionListener.extensionInstalledEmitter = undefined; - } - if (AzureAccountExtensionListener.extensionsChangeEventListener) { - AzureAccountExtensionListener.extensionsChangeEventListener.dispose(); - AzureAccountExtensionListener.extensionsChangeEventListener = undefined; - } - } -} From 88380e4cf4ee3c6a6144f551910e3422da89c9dd Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Tue, 5 Sep 2023 16:46:04 -0400 Subject: [PATCH 18/43] made minor adjustments based in brandon's comments --- src/commands/registries/azure/createAzureRegistry.ts | 3 +-- src/commands/registries/azure/deleteAzureRepository.ts | 4 +--- src/commands/registries/azure/untagAzureImage.ts | 3 +-- src/tree/registries/Azure/ACROAuthProvider.ts | 4 ---- .../Azure/createWizard/IAzureRegistryWizardContext.ts | 4 ++-- 5 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/commands/registries/azure/createAzureRegistry.ts b/src/commands/registries/azure/createAzureRegistry.ts index 9c3eebc976..f48bdcab20 100644 --- a/src/commands/registries/azure/createAzureRegistry.ts +++ b/src/commands/registries/azure/createAzureRegistry.ts @@ -50,7 +50,6 @@ export async function createAzureRegistry(context: IActionContext, node?: Unifie const newRegistryName: string = nonNullProp(wizardContext, 'newRegistryName'); await wizard.execute(); - /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - window.showInformationMessage(`Successfully created registry "${newRegistryName}".`); + void window.showInformationMessage(`Successfully created registry "${newRegistryName}".`); void ext.registriesTree.refresh(); } diff --git a/src/commands/registries/azure/deleteAzureRepository.ts b/src/commands/registries/azure/deleteAzureRepository.ts index 2d293d623e..11773fd276 100644 --- a/src/commands/registries/azure/deleteAzureRepository.ts +++ b/src/commands/registries/azure/deleteAzureRepository.ts @@ -27,7 +27,5 @@ export async function deleteAzureRepository(context: IActionContext, node?: Unif void ext.registriesTree.refresh(); const deleteSucceeded = l10n.t('Successfully deleted repository "{0}".', node.wrappedItem.label); - // don't wait - /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - window.showInformationMessage(deleteSucceeded); + void window.showInformationMessage(deleteSucceeded); } diff --git a/src/commands/registries/azure/untagAzureImage.ts b/src/commands/registries/azure/untagAzureImage.ts index 4aed76a28c..f21048b0d4 100644 --- a/src/commands/registries/azure/untagAzureImage.ts +++ b/src/commands/registries/azure/untagAzureImage.ts @@ -28,6 +28,5 @@ export async function untagAzureImage(context: IActionContext, node?: UnifiedReg // don't wait void ext.registriesTree.refresh(); - /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - window.showInformationMessage(l10n.t('Successfully untagged image "{0}".', fullTag)); + void window.showInformationMessage(l10n.t('Successfully untagged image "{0}".', fullTag)); } diff --git a/src/tree/registries/Azure/ACROAuthProvider.ts b/src/tree/registries/Azure/ACROAuthProvider.ts index 207a80b088..a2a47888c3 100644 --- a/src/tree/registries/Azure/ACROAuthProvider.ts +++ b/src/tree/registries/Azure/ACROAuthProvider.ts @@ -9,10 +9,6 @@ import { AuthenticationProvider } from "@microsoft/vscode-docker-registries/"; import * as vscode from 'vscode'; import { NULL_GUID } from '../../../constants'; -// export interface ACROAuthOptions extends BasicOAuthOptions { -// readonly subscription: AzureSubscription; -// } - export class ACROAuthProvider implements AuthenticationProvider { private refreshTokenCache = new Map(); diff --git a/src/tree/registries/Azure/createWizard/IAzureRegistryWizardContext.ts b/src/tree/registries/Azure/createWizard/IAzureRegistryWizardContext.ts index 65b42efa4c..21e659baf7 100644 --- a/src/tree/registries/Azure/createWizard/IAzureRegistryWizardContext.ts +++ b/src/tree/registries/Azure/createWizard/IAzureRegistryWizardContext.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import type { Registry as AcrRegistry, SkuName as AcrSkuName } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy -import { AzureSubscription } from '@microsoft/vscode-azext-azureauth'; -import { IResourceGroupWizardContext } from '@microsoft/vscode-azext-azureutils'; +import type { AzureSubscription } from '@microsoft/vscode-azext-azureauth'; +import type { IResourceGroupWizardContext } from '@microsoft/vscode-azext-azureutils'; export interface IAzureRegistryWizardContext extends IResourceGroupWizardContext { newRegistryName?: string; From de06e554225fe9787f13d437ef53425ccb1e07cb Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Wed, 6 Sep 2023 11:35:11 -0400 Subject: [PATCH 19/43] use azutils for open in azure portal --- package.json | 2 +- .../registries/azure/openInAzurePortal.ts | 26 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 4959131c51..c52ea2d608 100644 --- a/package.json +++ b/package.json @@ -532,7 +532,7 @@ }, { "command": "vscode-docker.registries.azure.openInPortal", - "when": "view == dockerRegistries && viewItem =~ /(azuresubscription|azureContainerRegistry)/i", + "when": "view == dockerRegistries && viewItem =~ /(azuresubscription|azureContainerRegistry|azureContainerRepository)/i", "group": "regs_zzz_common@1" }, { diff --git a/src/commands/registries/azure/openInAzurePortal.ts b/src/commands/registries/azure/openInAzurePortal.ts index b9f0915d2a..4208ec2f31 100644 --- a/src/commands/registries/azure/openInAzurePortal.ts +++ b/src/commands/registries/azure/openInAzurePortal.ts @@ -3,28 +3,28 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, UserCancelledError, contextValueExperience } from '@microsoft/vscode-azext-utils'; -import * as vscode from 'vscode'; +import { IActionContext, contextValueExperience, createSubscriptionContext } from '@microsoft/vscode-azext-utils'; import { ext } from '../../../extensionVariables'; -import { AzureRegistry, AzureSubscriptionRegistryItem, isAzureRegistry, isAzureSubscriptionRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; +import { AzureRegistry, AzureRepository, AzureSubscriptionRegistryItem, isAzureRegistry, isAzureSubscriptionRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { getAzExtAzureUtils } from '../../../utils/lazyPackages'; -export async function openInAzurePortal(context: IActionContext, node?: UnifiedRegistryItem): Promise { +export async function openInAzurePortal(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: ['azuresubscription', 'azureContainerRegistry'] }); + node = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: ['azureContainerRegistry'] }); } const azureRegistryItem = node.wrappedItem; - const baseUrl = `${azureRegistryItem.subscription.environment.portalUrl}/#@${azureRegistryItem.subscription.tenantId}/resource`; - let url: string; - + const azExtAzureUtils = await getAzExtAzureUtils(); + let subscriptionContext = undefined; if (isAzureSubscriptionRegistryItem(azureRegistryItem)) { - url = `${baseUrl}/subscriptions/${azureRegistryItem.subscription.subscriptionId}`; + subscriptionContext = createSubscriptionContext(azureRegistryItem.subscription); + await azExtAzureUtils.openInPortal(subscriptionContext, `/subscriptions/${subscriptionContext.subscriptionId}`); } else if (isAzureRegistry(azureRegistryItem)) { - url = `${baseUrl}/${azureRegistryItem.id}`; + subscriptionContext = createSubscriptionContext(azureRegistryItem.parent.subscription); + await azExtAzureUtils.openInPortal(subscriptionContext, azureRegistryItem.id); } else { - throw new UserCancelledError(); + subscriptionContext = createSubscriptionContext(azureRegistryItem.parent.parent.subscription); + await azExtAzureUtils.openInPortal(subscriptionContext, `${azureRegistryItem.parent.id}/repository`); } - - await vscode.env.openExternal(vscode.Uri.parse(url)); } From 44d8b517550358cf08619b07a5e50a11f58c972d Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Wed, 6 Sep 2023 11:45:14 -0400 Subject: [PATCH 20/43] Fix subscription icon --- src/tree/registries/Azure/AzureRegistryDataProvider.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tree/registries/Azure/AzureRegistryDataProvider.ts b/src/tree/registries/Azure/AzureRegistryDataProvider.ts index 9c9b159203..fd5036e74c 100644 --- a/src/tree/registries/Azure/AzureRegistryDataProvider.ts +++ b/src/tree/registries/Azure/AzureRegistryDataProvider.ts @@ -5,7 +5,7 @@ import type { Registry as AcrRegistry, RegistryListCredentialsResult } from '@azure/arm-containerregistry'; import { AzureSubscription, VSCodeAzureSubscriptionProvider } from '@microsoft/vscode-azext-azureauth'; -import { RegistryV2DataProvider, V2Registry, V2RegistryItem, V2Repository, V2Tag, registryV2Request } from '@microsoft/vscode-docker-registries'; +import { RegistryV2DataProvider, V2Registry, V2RegistryItem, V2Repository, V2Tag, getContextValue, registryV2Request } from '@microsoft/vscode-docker-registries'; import { CommonRegistryItem, isRegistryRoot } from '@microsoft/vscode-docker-registries/lib/clients/Common/models'; import * as vscode from 'vscode'; import { createAzureContainerRegistryClient, getResourceGroupFromId } from '../../../utils/azureUtils'; @@ -75,7 +75,7 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements type: 'azuresubscription', subscription: sub, additionalContextValues: ['azuresubscription'], - iconPath: vscode.Uri.joinPath(this.extensionContext.extensionUri, 'resources', 'azureSubscription.svg'), + iconPath: vscode.Uri.joinPath(this.extensionContext.extensionUri, 'dist', 'node_modules', '@microsoft', 'vscode-azext-azureutils', 'resources', 'azureSubscription.svg'), } as AzureSubscriptionRegistryItem; }); } else if (isAzureSubscriptionRegistryItem(element)) { @@ -149,8 +149,8 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements return Promise.resolve({ label: element.label, collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - contextValue: 'azuresubscription', - iconPath: vscode.Uri.joinPath(this.extensionContext.extensionUri, 'resources', 'azureSubscription.svg'), + contextValue: getContextValue(element), + iconPath: element.iconPath, }); } else { return super.getTreeItem(element); From f2f92be92c9c83ba350242f5dd8048b0264a4517 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Wed, 6 Sep 2023 11:47:58 -0400 Subject: [PATCH 21/43] Small fix --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 044eccb16b..09e439a321 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@microsoft/vscode-azext-azureutils": "^2.0.1", "@microsoft/vscode-azext-utils": "^2.0.1", "@microsoft/vscode-container-client": "^0.1.0", - "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", + "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", "dayjs": "^1.11.7", "dockerfile-language-server-nodejs": "^0.10.2", "fs-extra": "^11.1.1", @@ -822,9 +822,9 @@ } }, "node_modules/@microsoft/vscode-docker-registries": { - "version": "0.0.1-alpha", - "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", - "integrity": "sha512-80fmjgaySkOTE4jRQ8FcMBZVS8r/mlB6H1uc1Ia6SIj9u1N2WmFZ/Esr2M36PZwhGiMUh9mUfUVF/4IxDhJeSQ==", + "version": "0.1.0", + "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", + "integrity": "sha512-nIM+TwNMUEwQWqS5zj5Yy1vNUc0XGF5mZm8tC1vbCUIAFma1tHl4E+8l2wL+aaGjRPV1k5iGScdSNjOJRi0ZSA==", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", diff --git a/package.json b/package.json index c52ea2d608..a7d8c71611 100644 --- a/package.json +++ b/package.json @@ -3000,7 +3000,7 @@ "@microsoft/vscode-azext-azureutils": "^2.0.1", "@microsoft/vscode-azext-utils": "^2.0.1", "@microsoft/vscode-container-client": "^0.1.0", - "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.0.1-alpha.tgz", + "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", "dayjs": "^1.11.7", "dockerfile-language-server-nodejs": "^0.10.2", "fs-extra": "^11.1.1", From 62ff37adb0c454f5c478f029d32146c5d2bc1e89 Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Wed, 6 Sep 2023 13:57:14 -0400 Subject: [PATCH 22/43] minor changes to imports --- package-lock.json | 2 +- src/commands/registries/azure/tasks/scheduleRunRequest.ts | 3 +-- src/commands/registries/copyRemoteImageDigest.ts | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 09e439a321..bbb57f6338 100644 --- a/package-lock.json +++ b/package-lock.json @@ -824,7 +824,7 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.1.0", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", - "integrity": "sha512-nIM+TwNMUEwQWqS5zj5Yy1vNUc0XGF5mZm8tC1vbCUIAFma1tHl4E+8l2wL+aaGjRPV1k5iGScdSNjOJRi0ZSA==", + "integrity": "sha512-k3/52AFawEXmgZEXgTJgli8ceii/tPReHrDBlKUY2bE4m7NhKOo0SA/Nq1B1z6f2vMXc66emqiIgOeOXNWDiqg==", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", diff --git a/src/commands/registries/azure/tasks/scheduleRunRequest.ts b/src/commands/registries/azure/tasks/scheduleRunRequest.ts index f0964443bd..a1713985f3 100644 --- a/src/commands/registries/azure/tasks/scheduleRunRequest.ts +++ b/src/commands/registries/azure/tasks/scheduleRunRequest.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import type { DockerBuildRequest as AcrDockerBuildRequest, FileTaskRunRequest as AcrFileTaskRunRequest, OS as AcrOS, Run as AcrRun, ContainerRegistryManagementClient } from "@azure/arm-containerregistry"; // These are only dev-time imports so don't need to be lazy -import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { IActionContext, IAzureQuickPickItem, contextValueExperience, nonNullProp } from '@microsoft/vscode-azext-utils'; import * as fse from 'fs-extra'; import * as os from 'os'; @@ -15,7 +14,7 @@ import * as vscode from 'vscode'; import { ext } from '../../../../extensionVariables'; import { AzureRegistryItem } from "../../../../tree/registries/Azure/AzureRegistryDataProvider"; import { UnifiedRegistryItem } from "../../../../tree/registries/UnifiedRegistryTreeDataProvider"; -import { createAzureContainerRegistryClient } from "../../../../utils/azureUtils"; +import { createAzureContainerRegistryClient, getResourceGroupFromId } from "../../../../utils/azureUtils"; import { getStorageBlob } from '../../../../utils/lazyPackages'; import { delay } from '../../../../utils/promiseUtils'; import { Item, quickPickDockerFileItem, quickPickYamlFileItem } from '../../../../utils/quickPickFile'; diff --git a/src/commands/registries/copyRemoteImageDigest.ts b/src/commands/registries/copyRemoteImageDigest.ts index 1e022c75cf..fe82e57179 100644 --- a/src/commands/registries/copyRemoteImageDigest.ts +++ b/src/commands/registries/copyRemoteImageDigest.ts @@ -4,18 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext, contextValueExperience } from "@microsoft/vscode-azext-utils"; -import { CommonTag, RegistryV2DataProvider, V2Tag } from "@microsoft/vscode-docker-registries"; +import { RegistryV2DataProvider, V2Tag } from "@microsoft/vscode-docker-registries"; import * as vscode from "vscode"; import { ext } from "../../extensionVariables"; import { UnifiedRegistryItem } from "../../tree/registries/UnifiedRegistryTreeDataProvider"; -export async function copyRemoteImageDigest(context: IActionContext, node?: UnifiedRegistryItem): Promise { +export async function copyRemoteImageDigest(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { node = await contextValueExperience(context, ext.registriesTree, { include: ['registryV2Tag'] }); } const v2DataProvider = node.provider as unknown as RegistryV2DataProvider; - const digest = await v2DataProvider.getImageDigest(node.wrappedItem as V2Tag); + const digest = await v2DataProvider.getImageDigest(node.wrappedItem); /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ vscode.env.clipboard.writeText(digest); From 6548a863afa9876c3125eed40703589ea91f9413 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Thu, 7 Sep 2023 12:55:51 -0400 Subject: [PATCH 23/43] Remove GitHub package transitive dependency --- package-lock.json | 181 +++------------------------------------------- package.json | 6 +- 2 files changed, 14 insertions(+), 173 deletions(-) diff --git a/package-lock.json b/package-lock.json index bbb57f6338..d3fd15718e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,10 +13,10 @@ "@azure/arm-containerregistry": "^10.1.0", "@azure/storage-blob": "^12.14.0", "@microsoft/compose-language-service": "^0.2.0", - "@microsoft/vscode-azext-azureappservice": "^2.2.0", + "@microsoft/vscode-azext-azureappservice": "~2.0.0", "@microsoft/vscode-azext-azureauth": "^1.1.2", - "@microsoft/vscode-azext-azureutils": "^2.0.1", - "@microsoft/vscode-azext-utils": "^2.0.1", + "@microsoft/vscode-azext-azureutils": "^2.0.0", + "@microsoft/vscode-azext-utils": "^2.0.0", "@microsoft/vscode-container-client": "^0.1.0", "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", "dayjs": "^1.11.7", @@ -681,9 +681,9 @@ "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" }, "node_modules/@microsoft/vscode-azext-azureappservice": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureappservice/-/vscode-azext-azureappservice-2.2.5.tgz", - "integrity": "sha512-pnbunSuGd0W1XojN7P/sNcdMeVC1FttXYR9UY4tNgwgstz+zeN5ckYtwWTHdu4fTP+uEczpHKz0zGf6Mn4fkfw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-azureappservice/-/vscode-azext-azureappservice-2.0.0.tgz", + "integrity": "sha512-evtnwjKZk6wBGXJQy3sNEEFdGztQAT09HeuyU4qpFwWRBIoPE1wg0TO7S7psc+N5W8sFiWBy3h15mOlU6daocA==", "dependencies": { "@azure/abort-controller": "^1.0.4", "@azure/arm-appinsights": "^5.0.0-beta.4", @@ -694,8 +694,7 @@ "@azure/core-client": "^1.7.2", "@azure/core-rest-pipeline": "^1.10.3", "@azure/storage-blob": "^12.3.0", - "@microsoft/vscode-azext-azureutils": "^2.0.2", - "@microsoft/vscode-azext-github": "^1.0.0", + "@microsoft/vscode-azext-azureutils": "^2.0.0", "@microsoft/vscode-azext-utils": "^2.0.0", "dayjs": "^1.11.2", "fs-extra": "^10.0.0", @@ -771,15 +770,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/@microsoft/vscode-azext-github": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-github/-/vscode-azext-github-1.0.0.tgz", - "integrity": "sha512-LYZ5fN0Yv2zfBy8uNuIL/JaXKf8aw+QfW64fuIDknt9qs7UZtFzfTvOimTB5nGd1oN4o7cpthcZg3YHOH2YpKg==", - "dependencies": { - "@microsoft/vscode-azext-utils": "^2.0.0", - "@octokit/rest": "^18.5.2" - } - }, "node_modules/@microsoft/vscode-azext-utils": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-utils/-/vscode-azext-utils-2.0.5.tgz", @@ -824,7 +814,7 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.1.0", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", - "integrity": "sha512-k3/52AFawEXmgZEXgTJgli8ceii/tPReHrDBlKUY2bE4m7NhKOo0SA/Nq1B1z6f2vMXc66emqiIgOeOXNWDiqg==", + "integrity": "sha512-nIM+TwNMUEwQWqS5zj5Yy1vNUc0XGF5mZm8tC1vbCUIAFma1tHl4E+8l2wL+aaGjRPV1k5iGScdSNjOJRi0ZSA==", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", @@ -863,142 +853,6 @@ "node": ">= 8" } }, - "node_modules/@octokit/auth-token": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", - "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", - "dependencies": { - "@octokit/types": "^6.0.3" - } - }, - "node_modules/@octokit/core": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", - "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", - "dependencies": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.3", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/endpoint": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", - "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", - "dependencies": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/endpoint/node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@octokit/graphql": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", - "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", - "dependencies": { - "@octokit/request": "^5.6.0", - "@octokit/types": "^6.0.3", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "12.11.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", - "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "2.21.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", - "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", - "dependencies": { - "@octokit/types": "^6.40.0" - }, - "peerDependencies": { - "@octokit/core": ">=2" - } - }, - "node_modules/@octokit/plugin-request-log": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", - "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", - "peerDependencies": { - "@octokit/core": ">=3" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "5.16.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", - "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", - "dependencies": { - "@octokit/types": "^6.39.0", - "deprecation": "^2.3.1" - }, - "peerDependencies": { - "@octokit/core": ">=3" - } - }, - "node_modules/@octokit/request": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", - "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", - "dependencies": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "dependencies": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "node_modules/@octokit/request/node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@octokit/rest": { - "version": "18.12.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", - "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", - "dependencies": { - "@octokit/core": "^3.5.1", - "@octokit/plugin-paginate-rest": "^2.16.8", - "@octokit/plugin-request-log": "^1.0.4", - "@octokit/plugin-rest-endpoint-methods": "^5.12.0" - } - }, - "node_modules/@octokit/types": { - "version": "6.41.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", - "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", - "dependencies": { - "@octokit/openapi-types": "^12.11.0" - } - }, "node_modules/@opentelemetry/api": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz", @@ -1920,11 +1774,6 @@ ], "optional": true }, - "node_modules/before-after-hook": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", - "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" - }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -2536,11 +2385,6 @@ "node": ">=0.4.0" } }, - "node_modules/deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" - }, "node_modules/detect-libc": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", @@ -4768,6 +4612,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "dependencies": { "wrappy": "1" } @@ -6254,11 +6099,6 @@ "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, - "node_modules/universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" - }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -6794,7 +6634,8 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "node_modules/ws": { "version": "8.13.0", diff --git a/package.json b/package.json index 29d423b428..f7c1f3d81b 100644 --- a/package.json +++ b/package.json @@ -2998,10 +2998,10 @@ "@azure/arm-containerregistry": "^10.1.0", "@azure/storage-blob": "^12.14.0", "@microsoft/compose-language-service": "^0.2.0", - "@microsoft/vscode-azext-azureappservice": "^2.2.0", + "@microsoft/vscode-azext-azureappservice": "~2.0.0", "@microsoft/vscode-azext-azureauth": "^1.1.2", - "@microsoft/vscode-azext-azureutils": "^2.0.1", - "@microsoft/vscode-azext-utils": "^2.0.1", + "@microsoft/vscode-azext-azureutils": "^2.0.0", + "@microsoft/vscode-azext-utils": "^2.0.0", "@microsoft/vscode-container-client": "^0.1.0", "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", "dayjs": "^1.11.7", From 1cf0e3db48d336fa15ca8364e4290082b595f473 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Thu, 7 Sep 2023 12:57:20 -0400 Subject: [PATCH 24/43] Version was wrong --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d3fd15718e..6c9624f9cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@azure/arm-containerregistry": "^10.1.0", "@azure/storage-blob": "^12.14.0", "@microsoft/compose-language-service": "^0.2.0", - "@microsoft/vscode-azext-azureappservice": "~2.0.0", + "@microsoft/vscode-azext-azureappservice": "~2.0", "@microsoft/vscode-azext-azureauth": "^1.1.2", "@microsoft/vscode-azext-azureutils": "^2.0.0", "@microsoft/vscode-azext-utils": "^2.0.0", diff --git a/package.json b/package.json index f7c1f3d81b..60693bab6e 100644 --- a/package.json +++ b/package.json @@ -2998,7 +2998,7 @@ "@azure/arm-containerregistry": "^10.1.0", "@azure/storage-blob": "^12.14.0", "@microsoft/compose-language-service": "^0.2.0", - "@microsoft/vscode-azext-azureappservice": "~2.0.0", + "@microsoft/vscode-azext-azureappservice": "~2.0", "@microsoft/vscode-azext-azureauth": "^1.1.2", "@microsoft/vscode-azext-azureutils": "^2.0.0", "@microsoft/vscode-azext-utils": "^2.0.0", From 24f7365ba099beaa1757f3995adb4ce5054ac522 Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Thu, 7 Sep 2023 13:53:41 -0400 Subject: [PATCH 25/43] allow delete image command show up on commontag --- package.json | 2 +- src/commands/registries/deleteRemoteImage.ts | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 60693bab6e..af70c2a43f 100644 --- a/package.json +++ b/package.json @@ -512,7 +512,7 @@ }, { "command": "vscode-docker.registries.deleteImage", - "when": "view == dockerRegistries && viewItem =~ /(genericRegistryV2Tag|azureContainerTag)/i", + "when": "view == dockerRegistries && viewItem =~ /commontag/i && !(viewItem =~ /(githubRegistryTag|dockerHubTag)/i)", "group": "regs_tag_2_destructive@2" }, { diff --git a/src/commands/registries/deleteRemoteImage.ts b/src/commands/registries/deleteRemoteImage.ts index add9a4b318..056282b741 100644 --- a/src/commands/registries/deleteRemoteImage.ts +++ b/src/commands/registries/deleteRemoteImage.ts @@ -4,16 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import { DialogResponses, IActionContext, UserCancelledError, parseError } from '@microsoft/vscode-azext-utils'; -import { RegistryV2DataProvider, V2Tag } from '@microsoft/vscode-docker-registries'; +import { CommonTag } from '@microsoft/vscode-docker-registries'; import { ProgressLocation, l10n, window } from 'vscode'; import { ext } from '../../extensionVariables'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; import { getImageNameFromRegistryTagItem } from '../../tree/registries/registryTreeUtils'; import { registryExperience } from '../../utils/registryExperience'; -export async function deleteRemoteImage(context: IActionContext, node?: UnifiedRegistryItem): Promise { +export async function deleteRemoteImage(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await registryExperience(context, ext.registriesTree, { include: ['genericRegistryV2Tag', 'azureContainerTag'] }, false); + node = await registryExperience(context, ext.registriesTree, { include: 'commontag', exclude: ['githubRegistryTag', 'dockerHubTag'] }, false); + } + + const provider = node.provider as unknown as CommonTag; + if (typeof provider.deleteTag !== 'function') { + throw new Error(l10n.t('Deleting remote images is not supported on this registry.')); } const tagName = getImageNameFromRegistryTagItem(node.wrappedItem); @@ -23,8 +28,6 @@ export async function deleteRemoteImage(context: IActionContext, node?: UnifiedR const deleting = l10n.t('Deleting image "{0}"...', tagName); await window.withProgress({ location: ProgressLocation.Notification, title: deleting }, async () => { - const provider = node.provider as unknown as RegistryV2DataProvider; - try { await provider.deleteTag(node.wrappedItem); } catch (error) { From 669be80183d8135e862f96c2323719ffda0d862d Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Thu, 7 Sep 2023 16:49:03 -0400 Subject: [PATCH 26/43] fix miake in deleteImage --- package-lock.json | 2 +- src/commands/registries/deleteRemoteImage.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6c9624f9cb..29967c805c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -814,7 +814,7 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.1.0", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", - "integrity": "sha512-nIM+TwNMUEwQWqS5zj5Yy1vNUc0XGF5mZm8tC1vbCUIAFma1tHl4E+8l2wL+aaGjRPV1k5iGScdSNjOJRi0ZSA==", + "integrity": "sha512-y8ZiMpHFcom/AutKtTvmLqvRrWimQnGAFkdsWWGdDCGOpg3rqrj5GlrclVS064o7k4FILqXYtfKWJr+NJ/sqvw==", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", diff --git a/src/commands/registries/deleteRemoteImage.ts b/src/commands/registries/deleteRemoteImage.ts index 056282b741..e1e1c5d870 100644 --- a/src/commands/registries/deleteRemoteImage.ts +++ b/src/commands/registries/deleteRemoteImage.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DialogResponses, IActionContext, UserCancelledError, parseError } from '@microsoft/vscode-azext-utils'; -import { CommonTag } from '@microsoft/vscode-docker-registries'; +import { CommonRegistryDataProvider, CommonTag } from '@microsoft/vscode-docker-registries'; import { ProgressLocation, l10n, window } from 'vscode'; import { ext } from '../../extensionVariables'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; @@ -16,7 +16,7 @@ export async function deleteRemoteImage(context: IActionContext, node?: UnifiedR node = await registryExperience(context, ext.registriesTree, { include: 'commontag', exclude: ['githubRegistryTag', 'dockerHubTag'] }, false); } - const provider = node.provider as unknown as CommonTag; + const provider = node.provider as unknown as CommonRegistryDataProvider; if (typeof provider.deleteTag !== 'function') { throw new Error(l10n.t('Deleting remote images is not supported on this registry.')); } From 54e73c0060b24125a258008f981626bef49f2de8 Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:35:29 -0400 Subject: [PATCH 27/43] simplified logic in pushImage (#4062) --- src/commands/images/pushImage.ts | 17 ++-- src/commands/images/tagImage.ts | 4 +- .../azure/DockerAssignAcrPullRoleStep.ts | 4 +- .../UnifiedRegistryTreeDataProvider.ts | 10 +-- src/tree/registries/registryTreeUtils.ts | 77 ++++++++++++++++--- 5 files changed, 81 insertions(+), 31 deletions(-) diff --git a/src/commands/images/pushImage.ts b/src/commands/images/pushImage.ts index 2b4fdc3847..badb092890 100644 --- a/src/commands/images/pushImage.ts +++ b/src/commands/images/pushImage.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext, NoResourceFoundError, contextValueExperience } from '@microsoft/vscode-azext-utils'; +import { parseDockerLikeImageName } from '@microsoft/vscode-container-client'; import { CommonRegistry } from '@microsoft/vscode-docker-registries'; import * as vscode from 'vscode'; import { ext } from '../../extensionVariables'; import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; -import { getBaseUrlFromItem } from '../../tree/registries/registryTreeUtils'; import { addImageTaggingTelemetry, tagImage } from './tagImage'; export async function pushImage(context: IActionContext, node: ImageTreeItem | undefined): Promise { @@ -47,19 +47,17 @@ export async function pushImage(context: IActionContext, node: ImageTreeItem | u } else { // The registry to push to is determinate. If there's a connected registry in the tree view, we'll try to find it, to perform login ahead of time. // Registry path is everything up to the last slash. - const baseImagePath = node.parent.label.substring(0, node.parent.label.lastIndexOf('/')); - const progressOptions: vscode.ProgressOptions = { location: vscode.ProgressLocation.Notification, title: vscode.l10n.t('Fetching login credentials...'), }; - connectedRegistry = await vscode.window.withProgress(progressOptions, async () => await tryGetConnectedRegistryForPath(context, baseImagePath)); + connectedRegistry = await vscode.window.withProgress(progressOptions, async () => await tryGetConnectedRegistryForPath(context, node.parent.label)); } // Give the user a chance to modify the tag however they want const finalTag = await tagImage(context, node, connectedRegistry); - const baseImagePath = getBaseUrlFromItem(connectedRegistry.wrappedItem); + const baseImagePath = connectedRegistry.wrappedItem.baseUrl.authority; if (connectedRegistry && finalTag.startsWith(baseImagePath)) { // If a registry was found/chosen and is still the same as the final tag's registry, try logging in await vscode.commands.executeCommand('vscode-docker.registries.logInToDockerCli', connectedRegistry); @@ -80,13 +78,10 @@ export async function pushImage(context: IActionContext, node: ImageTreeItem | u } async function tryGetConnectedRegistryForPath(context: IActionContext, baseImagePath: string): Promise | undefined> { - let baseImagePathUri = vscode.Uri.parse(baseImagePath); - if (!baseImagePathUri.scheme || baseImagePathUri.scheme === 'file') { - baseImagePathUri = vscode.Uri.parse(`https://${baseImagePath}`); // Add a scheme so that we can parse the hostname - } - const allRegistries = await ext.registriesTree.getConnectedRegistries(baseImagePathUri); + const baseImageNameInfo = parseDockerLikeImageName(baseImagePath); + const allRegistries = await ext.registriesTree.getConnectedRegistries(baseImageNameInfo.registry); - let matchedRegistry = allRegistries.find((registry) => getBaseUrlFromItem(registry.wrappedItem) === baseImagePath); + let matchedRegistry = allRegistries.find((registry) => registry.wrappedItem.baseUrl.authority === baseImageNameInfo.registry); if (!matchedRegistry) { matchedRegistry = await contextValueExperience(context, ext.registriesTree, { include: ['commonregistry'] }); diff --git a/src/commands/images/tagImage.ts b/src/commands/images/tagImage.ts index 328a1a48ac..ca4bb7c34b 100644 --- a/src/commands/images/tagImage.ts +++ b/src/commands/images/tagImage.ts @@ -9,7 +9,7 @@ import * as vscode from 'vscode'; import { ext } from '../../extensionVariables'; import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; -import { getBaseUrlFromItem } from '../../tree/registries/registryTreeUtils'; +import { getBaseImagePathFromRegistry } from '../../tree/registries/registryTreeUtils'; export async function tagImage(context: IActionContext, node?: ImageTreeItem, registry?: UnifiedRegistryItem): Promise { if (!node) { @@ -21,7 +21,7 @@ export async function tagImage(context: IActionContext, node?: ImageTreeItem, re } addImageTaggingTelemetry(context, node.fullTag, '.before'); - const baseImagePath = isRegistry(registry.wrappedItem) ? getBaseUrlFromItem(registry.wrappedItem) : undefined; + const baseImagePath = isRegistry(registry.wrappedItem) ? getBaseImagePathFromRegistry(registry.wrappedItem) : undefined; const newTaggedName: string = await getTagFromUserInput(context, node.fullTag, baseImagePath); addImageTaggingTelemetry(context, newTaggedName, '.after'); diff --git a/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts b/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts index 338ef13ce8..56b4ce2379 100644 --- a/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts +++ b/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts @@ -11,7 +11,7 @@ import { Progress, l10n } from "vscode"; import { ext } from "../../../extensionVariables"; import { AzureRegistry, isAzureTag } from "../../../tree/registries/Azure/AzureRegistryDataProvider"; import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; -import { getBaseUrlFromItem, getFullImageNameFromRegistryTagItem, getResourceGroupFromAzureRegistryItem } from "../../../tree/registries/registryTreeUtils"; +import { getFullImageNameFromRegistryTagItem, getResourceGroupFromAzureRegistryItem } from "../../../tree/registries/registryTreeUtils"; import { getArmAuth, getArmContainerRegistry, getAzExtAppService, getAzExtAzureUtils } from "../../../utils/lazyPackages"; export class DockerAssignAcrPullRoleStep extends AzureWizardExecuteStep { @@ -42,7 +42,7 @@ export class DockerAssignAcrPullRoleStep extends AzureWizardExecuteStep[]> { + public async getConnectedRegistries(imageBaseName?: string): Promise[]> { let registryRoots = await this.getChildren(); let findAzureRegistryOnly = false; // filter out registry roots that don't match the image base name if (imageBaseName) { - const authority = imageBaseName.authority; - - if (authority === 'docker.io') { + if (imageBaseName === 'docker.io') { registryRoots = registryRoots.filter(r => (r.wrappedItem as CommonRegistryRoot).label === 'Docker Hub'); } - else if (authority.endsWith('azurecr.io')) { + else if (imageBaseName.endsWith('azurecr.io')) { registryRoots = registryRoots.filter(r => (r.wrappedItem as CommonRegistryRoot).label === 'Azure'); findAzureRegistryOnly = true; } - else if (authority === 'ghcr.io') { + else if (imageBaseName === 'ghcr.io') { registryRoots = registryRoots.filter(r => (r.wrappedItem as CommonRegistryRoot).label === 'GitHub'); } else { diff --git a/src/tree/registries/registryTreeUtils.ts b/src/tree/registries/registryTreeUtils.ts index 2ff00b2223..77d99ee9f3 100644 --- a/src/tree/registries/registryTreeUtils.ts +++ b/src/tree/registries/registryTreeUtils.ts @@ -3,11 +3,15 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CommonRegistryItem, CommonRepository, CommonTag, isRegistry, isRepository, isTag } from "@microsoft/vscode-docker-registries"; +import { CommonRegistry, CommonRepository, CommonTag, isDockerHubRegistry, isGitHubRegistry, isRegistry, isRepository, isTag } from "@microsoft/vscode-docker-registries"; import { l10n } from "vscode"; import { getResourceGroupFromId } from "../../utils/azureUtils"; import { AzureRegistryItem } from "./Azure/AzureRegistryDataProvider"; +/** + * Returns the image name from a registry tag item + * ex: hello-world:latest + */ export function getImageNameFromRegistryTagItem(tag: CommonTag): string { if (!isTag(tag) || !isRepository(tag.parent)) { throw new Error(l10n.t('Unable to get image name')); @@ -17,32 +21,85 @@ export function getImageNameFromRegistryTagItem(tag: CommonTag): string { return `${repository.label.toLowerCase()}:${tag.label.toLowerCase()}`; } -export function getBaseUrlFromItem(item: CommonRegistryItem): string { - if (!isTag(item) && !isRepository(item) && !isRegistry(item)) { - throw new Error(l10n.t('Unable to get base URL')); +/** + * Returns the base image path from a registry + * ex: docker.io/library (Docker Hub) + * myregistry.azurecr.io (Azure) + * ghcr.io/library (GitHub) + * localhost:5000 (Local) + */ +export function getBaseImagePathFromRegistry(registry: CommonRegistry): string { + if (!isRegistry(registry)) { + throw new Error(l10n.t('Unable to get base image path')); } - const authority = item.baseUrl.authority; - const path = item.baseUrl.path === '/' ? '' : item.baseUrl.path; - return `${authority}${path}`; + const baseUrl = registry.baseUrl.authority; + + if (isDockerHubRegistry(registry) || isGitHubRegistry(registry)) { + return `${baseUrl}/${registry.label}`; + } + + return baseUrl; } +/** + * Returns the full image name from a registry tag item + * + * ex: docker.io/library/hello-world:latest (Docker Hub) + * myregistry.azurecr.io/hello-world:latest (Azure) + * ghcr.io/myregistry/hello-world:latest (GitHub) + * localhost:5000/hello-world:latest (Local) + */ export function getFullImageNameFromRegistryTagItem(tag: CommonTag): string { if (!isTag(tag) || !isRegistry(tag.parent.parent)) { throw new Error(l10n.t('Unable to get full image name')); } - const imageName = getImageNameFromRegistryTagItem(tag); - return `${getBaseUrlFromItem(tag)}/${imageName}`; + + const baseImageName = getBaseImagePathFromRegistry(tag.parent.parent); + let imageName = getImageNameFromRegistryTagItem(tag); + + // For GitHub, the image name is prefixed with the registry name so we + // need to remove it since it is already in the base image name + if (isGitHubRegistry(tag.parent.parent)) { + const regex = /\/(.*)$/; // Match "/" followed by anything until the end + const match = imageName.match(regex); + if (match) { + imageName = match[1]; + } + } + + return `${baseImageName}/${imageName}`; } +/** + * Returns the full repository name from a registry repository item + * ex: docker.io/library/hello-world (Docker Hub) + * myregistry.azurecr.io/hello-world (Azure) + * ghcr.io/myregistry/hello-world (GitHub) + * localhost:5000/hello-world (Local) + */ export function getFullRepositoryNameFromRepositoryItem(repository: CommonRepository): string { if (!isRepository(repository) || !isRegistry(repository.parent)) { throw new Error(l10n.t('Unable to get full repository name')); } - return `${getBaseUrlFromItem(repository)}/${repository.label.toLowerCase()}`; + let imageName = repository.label.toLowerCase(); + const baseImageName = getBaseImagePathFromRegistry(repository.parent); + // For GitHub, the image name is prefixed with the registry name so we + // need to remove it since it is already in the base image name + if (isGitHubRegistry(repository.parent)) { + const regex = /\/(.*)$/; // Match "/" followed by anything until the end + const match = imageName.match(regex); + if (match) { + imageName = match[1]; + } + } + return `${baseImageName}/${imageName}`; } +/** + * Returns the resource group from an Azure registry item + */ export function getResourceGroupFromAzureRegistryItem(node: AzureRegistryItem): string { if (!isRegistry(node)) { throw new Error('Unable to get resource group'); From da2d3509c94dc61f6c22503e35fc2c0261ffa71a Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Fri, 8 Sep 2023 16:33:38 -0400 Subject: [PATCH 28/43] simplify logic in deploy to Azure app service (#4063) --- package-lock.json | 2 +- .../registries/azure/DockerSiteCreateStep.ts | 24 +++++++------------ .../registries/azure/deployImageToAzure.ts | 2 +- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 29967c805c..1eab9a1fb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -814,7 +814,7 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.1.0", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", - "integrity": "sha512-y8ZiMpHFcom/AutKtTvmLqvRrWimQnGAFkdsWWGdDCGOpg3rqrj5GlrclVS064o7k4FILqXYtfKWJr+NJ/sqvw==", + "integrity": "sha512-oK8GrOtCb8q9+t+6/7sexRNewRml/nqL5y+2O1CEV7RSFDoBxgtA/Ock/VJhpUvmj+SMM1h/tIvQLMruI/5tRA==", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", diff --git a/src/commands/registries/azure/DockerSiteCreateStep.ts b/src/commands/registries/azure/DockerSiteCreateStep.ts index ffe4d4c372..c2a4bbcca9 100644 --- a/src/commands/registries/azure/DockerSiteCreateStep.ts +++ b/src/commands/registries/azure/DockerSiteCreateStep.ts @@ -7,7 +7,7 @@ import type { NameValuePair, Site, SiteConfig, WebSiteManagementClient } from '@ import type { CustomLocation } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy import type { AzExtLocation } from '@microsoft/vscode-azext-azureutils'; // These are only dev-time imports so don't need to be lazy import { AzureWizardExecuteStep, nonNullProp, nonNullValueAndProp } from "@microsoft/vscode-azext-utils"; -import { CommonRegistry, CommonTag, isDockerHubRegistry, isGenericV2Registry } from '@microsoft/vscode-docker-registries'; +import { CommonRegistry, CommonTag } from '@microsoft/vscode-docker-registries'; import { Progress, l10n } from "vscode"; import { ext } from "../../../extensionVariables"; import { AzureRegistryDataProvider, isAzureRegistry } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; @@ -87,25 +87,19 @@ export class DockerSiteCreateStep extends AzureWizardExecuteStep App Service *OR* Arc App Service - else if (isDockerHubRegistry(registryTI.wrappedItem)) { - const loginInformation = await registryTI.provider.getLoginInformation(registryTI.wrappedItem); - username = loginInformation.username; - password = loginInformation.secret; - registryUrl = 'https://index.docker.io'; - } - // Generic registry -> App Service *OR* Arc App Service - else if (isGenericV2Registry(registryTI.wrappedItem)) { + // Other registries -> App Service *OR* Arc App Service + else { + if (!registryTI.provider.getLoginInformation) { + throw new Error(l10n.t('This registry does not support deploying to Azure App Service')); + } const loginInformation = await registryTI.provider.getLoginInformation(registryTI.wrappedItem); + + registryUrl = (registryTI.wrappedItem as CommonRegistry).baseUrl.toString(); username = loginInformation.username; password = loginInformation.secret; - registryUrl = (registryTI.wrappedItem as CommonRegistry).baseUrl.toString(); - } - // TODO: add case for GitHub Container Registry - else { - throw new RangeError(l10n.t('Unrecognized node type "{0}"', registryTI.constructor.name)); } + if (username && password) { appSettings.push({ name: "DOCKER_REGISTRY_SERVER_USERNAME", value: username }); appSettings.push({ name: "DOCKER_REGISTRY_SERVER_PASSWORD", value: password }); diff --git a/src/commands/registries/azure/deployImageToAzure.ts b/src/commands/registries/azure/deployImageToAzure.ts index 13660715e8..20dd8c8531 100644 --- a/src/commands/registries/azure/deployImageToAzure.ts +++ b/src/commands/registries/azure/deployImageToAzure.ts @@ -24,7 +24,7 @@ export interface IAppServiceContainerWizardContext extends IAppServiceWizardCont export async function deployImageToAzure(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: ['registryV2Tag', 'dockerHubTag'], }); + node = await contextValueExperience(context, ext.registriesTree, { include: ['commontag'], }); } const azExtAzureUtils = await getAzExtAzureUtils(); From f84bdaa8ccbb0af4d225b70628a17b7e27b7e6b5 Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Mon, 11 Sep 2023 12:28:16 -0400 Subject: [PATCH 29/43] handle contextValueExperience edge case --- .../registries/azure/createAzureRegistry.ts | 8 ++++--- .../registries/azure/deleteAzureRegistry.ts | 3 ++- .../registries/azure/deleteAzureRepository.ts | 3 ++- .../registries/azure/deployImageToAca.ts | 2 +- .../registries/azure/openInAzurePortal.ts | 4 ++-- .../azure/tasks/scheduleRunRequest.ts | 24 +++++++++---------- .../registries/azure/untagAzureImage.ts | 3 ++- .../registries/azure/viewAzureProperties.ts | 10 ++++---- src/commands/registries/copyRemoteFullTag.ts | 2 +- .../registries/copyRemoteImageDigest.ts | 3 +-- .../dockerHub/openDockerHubInBrowser.ts | 9 +++---- .../UnifiedRegistryTreeDataProvider.ts | 4 ++++ 12 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/commands/registries/azure/createAzureRegistry.ts b/src/commands/registries/azure/createAzureRegistry.ts index f48bdcab20..f557504283 100644 --- a/src/commands/registries/azure/createAzureRegistry.ts +++ b/src/commands/registries/azure/createAzureRegistry.ts @@ -11,7 +11,7 @@ import { AzureRegistryCreateStep } from '../../../tree/registries/Azure/createWi import { AzureRegistryNameStep } from '../../../tree/registries/Azure/createWizard/AzureRegistryNameStep'; import { AzureRegistrySkuStep } from '../../../tree/registries/Azure/createWizard/AzureRegistrySkuStep'; import { IAzureRegistryWizardContext } from '../../../tree/registries/Azure/createWizard/IAzureRegistryWizardContext'; -import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { UnifiedRegistryItem, isWrappedItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; import { getAzExtAzureUtils } from '../../../utils/lazyPackages'; export async function createAzureRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { @@ -20,11 +20,13 @@ export async function createAzureRegistry(context: IActionContext, node?: Unifie node = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: 'azuresubscription' }); } - const subscriptionContext = createSubscriptionContext(node.wrappedItem.subscription); + const registryItem = isWrappedItem(node) ? node.wrappedItem : node; + + const subscriptionContext = createSubscriptionContext(registryItem.subscription); const wizardContext: IAzureRegistryWizardContext = { ...context, ...subscriptionContext, - azureSubscription: node.wrappedItem.subscription, + azureSubscription: registryItem.subscription, }; const azExtAzureUtils = await getAzExtAzureUtils(); diff --git a/src/commands/registries/azure/deleteAzureRegistry.ts b/src/commands/registries/azure/deleteAzureRegistry.ts index 33da8b554e..2ef44c8453 100644 --- a/src/commands/registries/azure/deleteAzureRegistry.ts +++ b/src/commands/registries/azure/deleteAzureRegistry.ts @@ -11,7 +11,8 @@ import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTre export async function deleteAzureRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: 'azureContainerRegistry' }); + // we can't pass in the azure tree provider because it's not a UnifiedRegistryItem and we need the provider to delete + node = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerRegistry' }); } const registryName = node.wrappedItem.label; diff --git a/src/commands/registries/azure/deleteAzureRepository.ts b/src/commands/registries/azure/deleteAzureRepository.ts index 11773fd276..335a2baa60 100644 --- a/src/commands/registries/azure/deleteAzureRepository.ts +++ b/src/commands/registries/azure/deleteAzureRepository.ts @@ -11,7 +11,8 @@ import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTre export async function deleteAzureRepository(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: 'azureContainerRepository' }); + // we can't pass in the azure tree provider because it's not a UnifiedRegistryItem and we need the provider to delete + node = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerRepository' }); } const confirmDelete = l10n.t('Are you sure you want to delete repository "{0}" and its associated images?', node.wrappedItem.label); diff --git a/src/commands/registries/azure/deployImageToAca.ts b/src/commands/registries/azure/deployImageToAca.ts index 3bc513f002..c84198e975 100644 --- a/src/commands/registries/azure/deployImageToAca.ts +++ b/src/commands/registries/azure/deployImageToAca.ts @@ -35,7 +35,7 @@ export async function deployImageToAca(context: IActionContext, node?: UnifiedRe } if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: ['registryV2Tag', 'dockerHubTag'], }); + node = await contextValueExperience(context, ext.registriesTree, { include: 'commontag' }); } const commandOptions: Partial = { diff --git a/src/commands/registries/azure/openInAzurePortal.ts b/src/commands/registries/azure/openInAzurePortal.ts index 4208ec2f31..0d787cd82d 100644 --- a/src/commands/registries/azure/openInAzurePortal.ts +++ b/src/commands/registries/azure/openInAzurePortal.ts @@ -6,7 +6,7 @@ import { IActionContext, contextValueExperience, createSubscriptionContext } from '@microsoft/vscode-azext-utils'; import { ext } from '../../../extensionVariables'; import { AzureRegistry, AzureRepository, AzureSubscriptionRegistryItem, isAzureRegistry, isAzureSubscriptionRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; -import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { UnifiedRegistryItem, isWrappedItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; import { getAzExtAzureUtils } from '../../../utils/lazyPackages'; export async function openInAzurePortal(context: IActionContext, node?: UnifiedRegistryItem): Promise { @@ -14,7 +14,7 @@ export async function openInAzurePortal(context: IActionContext, node?: UnifiedR node = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: ['azureContainerRegistry'] }); } - const azureRegistryItem = node.wrappedItem; + const azureRegistryItem = isWrappedItem(node) ? node.wrappedItem : node; const azExtAzureUtils = await getAzExtAzureUtils(); let subscriptionContext = undefined; if (isAzureSubscriptionRegistryItem(azureRegistryItem)) { diff --git a/src/commands/registries/azure/tasks/scheduleRunRequest.ts b/src/commands/registries/azure/tasks/scheduleRunRequest.ts index a1713985f3..9502b1242f 100644 --- a/src/commands/registries/azure/tasks/scheduleRunRequest.ts +++ b/src/commands/registries/azure/tasks/scheduleRunRequest.ts @@ -13,7 +13,7 @@ import * as tar from 'tar'; import * as vscode from 'vscode'; import { ext } from '../../../../extensionVariables'; import { AzureRegistryItem } from "../../../../tree/registries/Azure/AzureRegistryDataProvider"; -import { UnifiedRegistryItem } from "../../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { UnifiedRegistryItem, isWrappedItem } from "../../../../tree/registries/UnifiedRegistryTreeDataProvider"; import { createAzureContainerRegistryClient, getResourceGroupFromId } from "../../../../utils/azureUtils"; import { getStorageBlob } from '../../../../utils/lazyPackages'; import { delay } from '../../../../utils/promiseUtils'; @@ -47,8 +47,8 @@ export async function scheduleRunRequest(context: IActionContext, requestType: ' } const node: UnifiedRegistryItem = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: 'azureContainerRegistry' }); - const registry: AzureRegistryItem = node.wrappedItem; - const resourceGroup = getResourceGroupFromId(registry.id); + const registryItem: AzureRegistryItem = isWrappedItem(node) ? node.wrappedItem : node; + const resourceGroup = getResourceGroupFromId(registryItem.id); const osPick = ['Linux', 'Windows'].map(item => >{ label: item, data: item }); const osType: AcrOS = (await context.ui.showQuickPick(osPick, { placeHolder: vscode.l10n.t('Select image base OS') })).data; @@ -66,8 +66,8 @@ export async function scheduleRunRequest(context: IActionContext, requestType: ' rootUri = vscode.Uri.file(path.dirname(fileItem.absoluteFilePath)); } - const azureRegistryClient = await createAzureContainerRegistryClient(registry.subscription); - const uploadedSourceLocation: string = await uploadSourceCode(azureRegistryClient, registry.label, resourceGroup, rootUri, tarFilePath); + const azureRegistryClient = await createAzureContainerRegistryClient(registryItem.subscription); + const uploadedSourceLocation: string = await uploadSourceCode(azureRegistryClient, registryItem.label, resourceGroup, rootUri, tarFilePath); ext.outputChannel.info(vscode.l10n.t('Uploaded source code from {0}', tarFilePath)); let runRequest: AcrDockerBuildRequest | AcrFileTaskRunRequest; @@ -92,13 +92,13 @@ export async function scheduleRunRequest(context: IActionContext, requestType: ' // Schedule the run and Clean up. ext.outputChannel.info(vscode.l10n.t('Set up run request')); - const run = await azureRegistryClient.registries.beginScheduleRunAndWait(resourceGroup, registry.label, runRequest); + const run = await azureRegistryClient.registries.beginScheduleRunAndWait(resourceGroup, registryItem.label, runRequest); ext.outputChannel.info(vscode.l10n.t('Scheduled run {0}', run.runId)); - void streamLogs(context, node, run); + void streamLogs(context, registryItem, run); // function returns the AcrRun info - return async () => azureRegistryClient.runs.get(resourceGroup, registry.label, run.runId); + return async () => azureRegistryClient.runs.get(resourceGroup, registryItem.label, run.runId); } finally { if (await fse.pathExists(tarFilePath)) { await fse.unlink(tarFilePath); @@ -158,10 +158,10 @@ async function uploadSourceCode(client: ContainerRegistryManagementClient, regis const blobCheckInterval = 1000; const maxBlobChecks = 30; -async function streamLogs(context: IActionContext, node: UnifiedRegistryItem, run: AcrRun): Promise { - const azureRegistryClient = await createAzureContainerRegistryClient(node.wrappedItem.subscription); - const resourceGroup = getResourceGroupFromId(node.wrappedItem.id); - const result = await azureRegistryClient.runs.getLogSasUrl(resourceGroup, node.wrappedItem.label, run.runId); +async function streamLogs(context: IActionContext, registryItem: AzureRegistryItem, run: AcrRun): Promise { + const azureRegistryClient = await createAzureContainerRegistryClient(registryItem.subscription); + const resourceGroup = getResourceGroupFromId(registryItem.id); + const result = await azureRegistryClient.runs.getLogSasUrl(resourceGroup, registryItem.label, run.runId); const storageBlob = await getStorageBlob(); const blobClient = new storageBlob.BlobClient(nonNullProp(result, 'logLink')); diff --git a/src/commands/registries/azure/untagAzureImage.ts b/src/commands/registries/azure/untagAzureImage.ts index f21048b0d4..49867042fa 100644 --- a/src/commands/registries/azure/untagAzureImage.ts +++ b/src/commands/registries/azure/untagAzureImage.ts @@ -12,7 +12,8 @@ import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTre export async function untagAzureImage(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: 'azureContainerTag' }); + // we can't pass in the azure tree provider because it's not a UnifiedRegistryItem and we need the provider to untag + node = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerTag' }); } const fullTag = getFullImageNameFromRegistryTagItem(node.wrappedItem); diff --git a/src/commands/registries/azure/viewAzureProperties.ts b/src/commands/registries/azure/viewAzureProperties.ts index 7e8f5d1a2b..1bb05ace9a 100644 --- a/src/commands/registries/azure/viewAzureProperties.ts +++ b/src/commands/registries/azure/viewAzureProperties.ts @@ -3,15 +3,17 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, contextValueExperience, openReadOnlyJson } from "@microsoft/vscode-azext-utils"; +import { IActionContext, openReadOnlyJson } from "@microsoft/vscode-azext-utils"; import { ext } from "../../../extensionVariables"; import { AzureRegistry } from "../../../tree/registries/Azure/AzureRegistryDataProvider"; -import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { UnifiedRegistryItem, isWrappedItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { registryExperience } from "../../../utils/registryExperience"; export async function viewAzureProperties(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.azureRegistryDataProvider, { 'include': 'azureContainerRegistry' }); + node = await registryExperience(context, ext.azureRegistryDataProvider, { 'include': 'azureContainerRegistry' }, true); } - await openReadOnlyJson({ label: node.wrappedItem.label, fullId: node.wrappedItem.id }, node.wrappedItem.registryProperties); + const registryItem = isWrappedItem(node) ? node.wrappedItem : node; + await openReadOnlyJson({ label: registryItem.label, fullId: registryItem.id }, registryItem.registryProperties); } diff --git a/src/commands/registries/copyRemoteFullTag.ts b/src/commands/registries/copyRemoteFullTag.ts index 1ded232f32..1cf1c7a475 100644 --- a/src/commands/registries/copyRemoteFullTag.ts +++ b/src/commands/registries/copyRemoteFullTag.ts @@ -12,7 +12,7 @@ import { getFullImageNameFromRegistryTagItem } from '../../tree/registries/regis export async function copyRemoteFullTag(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: ['registryV2Tag', 'dockerHubTag'] }); + node = await contextValueExperience(context, ext.registriesTree, { include: 'commontag' }); } const fullTag = getFullImageNameFromRegistryTagItem(node.wrappedItem); void vscode.env.clipboard.writeText(fullTag); diff --git a/src/commands/registries/copyRemoteImageDigest.ts b/src/commands/registries/copyRemoteImageDigest.ts index fe82e57179..7daad72e76 100644 --- a/src/commands/registries/copyRemoteImageDigest.ts +++ b/src/commands/registries/copyRemoteImageDigest.ts @@ -17,6 +17,5 @@ export async function copyRemoteImageDigest(context: IActionContext, node?: Unif const v2DataProvider = node.provider as unknown as RegistryV2DataProvider; const digest = await v2DataProvider.getImageDigest(node.wrappedItem); - /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - vscode.env.clipboard.writeText(digest); + void vscode.env.clipboard.writeText(digest); } diff --git a/src/commands/registries/dockerHub/openDockerHubInBrowser.ts b/src/commands/registries/dockerHub/openDockerHubInBrowser.ts index 35bba3f218..e9353d01da 100644 --- a/src/commands/registries/dockerHub/openDockerHubInBrowser.ts +++ b/src/commands/registries/dockerHub/openDockerHubInBrowser.ts @@ -3,20 +3,21 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, contextValueExperience } from "@microsoft/vscode-azext-utils"; +import { IActionContext } from "@microsoft/vscode-azext-utils"; import { CommonRegistryItem, isRegistry, isRepository, isTag } from "@microsoft/vscode-docker-registries"; import * as vscode from "vscode"; import { dockerHubUrl } from "../../../constants"; import { ext } from "../../../extensionVariables"; -import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { UnifiedRegistryItem, isWrappedItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { registryExperience } from "../../../utils/registryExperience"; export async function openDockerHubInBrowser(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesRoot, { include: ['dockerHubRegistry'] }); // TODO: use dockerHub tree here + node = await registryExperience(context, ext.dockerHubRegistryDataProvider, { include: ['dockerHubRegistry'] }, true); } let url = dockerHubUrl; - const dockerHubItem = node.wrappedItem; + const dockerHubItem = isWrappedItem(node) ? node.wrappedItem : node; if (isRegistry(dockerHubItem)) { url = `${url}u/${dockerHubItem.label}`; diff --git a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts index 563e3e41b2..3ae7fbe25d 100644 --- a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts +++ b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts @@ -8,6 +8,10 @@ export interface UnifiedRegistryItem { parent: UnifiedRegistryItem | undefined; } +export function isWrappedItem(item: unknown): item is UnifiedRegistryItem { + return !!item && typeof item === 'object' && 'provider' in item && 'wrappedItem' in item && 'parent' in item; +} + const ConnectedRegistryProvidersKey = 'ConnectedRegistryProviders'; export class UnifiedRegistryTreeDataProvider implements vscode.TreeDataProvider> { From 62359268567140565c6553ba4d9daf011c093dea Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Mon, 11 Sep 2023 12:34:00 -0400 Subject: [PATCH 30/43] changed isWrappedItem function name to be more specific --- src/commands/registries/azure/createAzureRegistry.ts | 4 ++-- src/commands/registries/azure/openInAzurePortal.ts | 4 ++-- src/commands/registries/azure/tasks/scheduleRunRequest.ts | 4 ++-- src/commands/registries/azure/viewAzureProperties.ts | 4 ++-- src/commands/registries/dockerHub/openDockerHubInBrowser.ts | 4 ++-- src/tree/registries/UnifiedRegistryTreeDataProvider.ts | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/commands/registries/azure/createAzureRegistry.ts b/src/commands/registries/azure/createAzureRegistry.ts index f557504283..1c65bd67ea 100644 --- a/src/commands/registries/azure/createAzureRegistry.ts +++ b/src/commands/registries/azure/createAzureRegistry.ts @@ -11,7 +11,7 @@ import { AzureRegistryCreateStep } from '../../../tree/registries/Azure/createWi import { AzureRegistryNameStep } from '../../../tree/registries/Azure/createWizard/AzureRegistryNameStep'; import { AzureRegistrySkuStep } from '../../../tree/registries/Azure/createWizard/AzureRegistrySkuStep'; import { IAzureRegistryWizardContext } from '../../../tree/registries/Azure/createWizard/IAzureRegistryWizardContext'; -import { UnifiedRegistryItem, isWrappedItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { UnifiedRegistryItem, isUnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; import { getAzExtAzureUtils } from '../../../utils/lazyPackages'; export async function createAzureRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { @@ -20,7 +20,7 @@ export async function createAzureRegistry(context: IActionContext, node?: Unifie node = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: 'azuresubscription' }); } - const registryItem = isWrappedItem(node) ? node.wrappedItem : node; + const registryItem = isUnifiedRegistryItem(node) ? node.wrappedItem : node; const subscriptionContext = createSubscriptionContext(registryItem.subscription); const wizardContext: IAzureRegistryWizardContext = { diff --git a/src/commands/registries/azure/openInAzurePortal.ts b/src/commands/registries/azure/openInAzurePortal.ts index 0d787cd82d..3f66c09504 100644 --- a/src/commands/registries/azure/openInAzurePortal.ts +++ b/src/commands/registries/azure/openInAzurePortal.ts @@ -6,7 +6,7 @@ import { IActionContext, contextValueExperience, createSubscriptionContext } from '@microsoft/vscode-azext-utils'; import { ext } from '../../../extensionVariables'; import { AzureRegistry, AzureRepository, AzureSubscriptionRegistryItem, isAzureRegistry, isAzureSubscriptionRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; -import { UnifiedRegistryItem, isWrappedItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { UnifiedRegistryItem, isUnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; import { getAzExtAzureUtils } from '../../../utils/lazyPackages'; export async function openInAzurePortal(context: IActionContext, node?: UnifiedRegistryItem): Promise { @@ -14,7 +14,7 @@ export async function openInAzurePortal(context: IActionContext, node?: UnifiedR node = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: ['azureContainerRegistry'] }); } - const azureRegistryItem = isWrappedItem(node) ? node.wrappedItem : node; + const azureRegistryItem = isUnifiedRegistryItem(node) ? node.wrappedItem : node; const azExtAzureUtils = await getAzExtAzureUtils(); let subscriptionContext = undefined; if (isAzureSubscriptionRegistryItem(azureRegistryItem)) { diff --git a/src/commands/registries/azure/tasks/scheduleRunRequest.ts b/src/commands/registries/azure/tasks/scheduleRunRequest.ts index 9502b1242f..4db6aa19c2 100644 --- a/src/commands/registries/azure/tasks/scheduleRunRequest.ts +++ b/src/commands/registries/azure/tasks/scheduleRunRequest.ts @@ -13,7 +13,7 @@ import * as tar from 'tar'; import * as vscode from 'vscode'; import { ext } from '../../../../extensionVariables'; import { AzureRegistryItem } from "../../../../tree/registries/Azure/AzureRegistryDataProvider"; -import { UnifiedRegistryItem, isWrappedItem } from "../../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { UnifiedRegistryItem, isUnifiedRegistryItem } from "../../../../tree/registries/UnifiedRegistryTreeDataProvider"; import { createAzureContainerRegistryClient, getResourceGroupFromId } from "../../../../utils/azureUtils"; import { getStorageBlob } from '../../../../utils/lazyPackages'; import { delay } from '../../../../utils/promiseUtils'; @@ -47,7 +47,7 @@ export async function scheduleRunRequest(context: IActionContext, requestType: ' } const node: UnifiedRegistryItem = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: 'azureContainerRegistry' }); - const registryItem: AzureRegistryItem = isWrappedItem(node) ? node.wrappedItem : node; + const registryItem: AzureRegistryItem = isUnifiedRegistryItem(node) ? node.wrappedItem : node; const resourceGroup = getResourceGroupFromId(registryItem.id); const osPick = ['Linux', 'Windows'].map(item => >{ label: item, data: item }); diff --git a/src/commands/registries/azure/viewAzureProperties.ts b/src/commands/registries/azure/viewAzureProperties.ts index 1bb05ace9a..e70398edef 100644 --- a/src/commands/registries/azure/viewAzureProperties.ts +++ b/src/commands/registries/azure/viewAzureProperties.ts @@ -6,7 +6,7 @@ import { IActionContext, openReadOnlyJson } from "@microsoft/vscode-azext-utils"; import { ext } from "../../../extensionVariables"; import { AzureRegistry } from "../../../tree/registries/Azure/AzureRegistryDataProvider"; -import { UnifiedRegistryItem, isWrappedItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { UnifiedRegistryItem, isUnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; import { registryExperience } from "../../../utils/registryExperience"; export async function viewAzureProperties(context: IActionContext, node?: UnifiedRegistryItem): Promise { @@ -14,6 +14,6 @@ export async function viewAzureProperties(context: IActionContext, node?: Unifie node = await registryExperience(context, ext.azureRegistryDataProvider, { 'include': 'azureContainerRegistry' }, true); } - const registryItem = isWrappedItem(node) ? node.wrappedItem : node; + const registryItem = isUnifiedRegistryItem(node) ? node.wrappedItem : node; await openReadOnlyJson({ label: registryItem.label, fullId: registryItem.id }, registryItem.registryProperties); } diff --git a/src/commands/registries/dockerHub/openDockerHubInBrowser.ts b/src/commands/registries/dockerHub/openDockerHubInBrowser.ts index e9353d01da..41273d803f 100644 --- a/src/commands/registries/dockerHub/openDockerHubInBrowser.ts +++ b/src/commands/registries/dockerHub/openDockerHubInBrowser.ts @@ -8,7 +8,7 @@ import { CommonRegistryItem, isRegistry, isRepository, isTag } from "@microsoft/ import * as vscode from "vscode"; import { dockerHubUrl } from "../../../constants"; import { ext } from "../../../extensionVariables"; -import { UnifiedRegistryItem, isWrappedItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { UnifiedRegistryItem, isUnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; import { registryExperience } from "../../../utils/registryExperience"; export async function openDockerHubInBrowser(context: IActionContext, node?: UnifiedRegistryItem): Promise { @@ -17,7 +17,7 @@ export async function openDockerHubInBrowser(context: IActionContext, node?: Uni } let url = dockerHubUrl; - const dockerHubItem = isWrappedItem(node) ? node.wrappedItem : node; + const dockerHubItem = isUnifiedRegistryItem(node) ? node.wrappedItem : node; if (isRegistry(dockerHubItem)) { url = `${url}u/${dockerHubItem.label}`; diff --git a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts index 3ae7fbe25d..dc316af4e1 100644 --- a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts +++ b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts @@ -8,7 +8,7 @@ export interface UnifiedRegistryItem { parent: UnifiedRegistryItem | undefined; } -export function isWrappedItem(item: unknown): item is UnifiedRegistryItem { +export function isUnifiedRegistryItem(item: unknown): item is UnifiedRegistryItem { return !!item && typeof item === 'object' && 'provider' in item && 'wrappedItem' in item && 'parent' in item; } From 51e8fa9a43786e7ffdcf141c307034277867a29d Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Mon, 11 Sep 2023 14:04:04 -0400 Subject: [PATCH 31/43] Added fire soon function to AzureRDP (#4066) * added fire soon * clear time out --- src/tree/registries/Azure/AzureRegistryDataProvider.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/tree/registries/Azure/AzureRegistryDataProvider.ts b/src/tree/registries/Azure/AzureRegistryDataProvider.ts index fd5036e74c..118447487b 100644 --- a/src/tree/registries/Azure/AzureRegistryDataProvider.ts +++ b/src/tree/registries/Azure/AzureRegistryDataProvider.ts @@ -62,7 +62,7 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements if (isRegistryRoot(element)) { if (!await this.subscriptionProvider.isSignedIn()) { await this.subscriptionProvider.signIn(); - this.onDidChangeTreeDataEmitter.fire(element); // TODO: this fires too fast, need a "fire soon" analogue + this.fireSoon(element); return []; } @@ -215,4 +215,12 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return this.authenticationProviders.get(registryString)!; } + + private fireSoon(element?: CommonRegistryItem | undefined) { + const timeout = setTimeout(() => { + clearTimeout(timeout); + this.onDidChangeTreeDataEmitter.fire(element); + }, 5); + } + } From 4e6aa8aedbdf487f5519cb891ca90f8a579dff52 Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Thu, 14 Sep 2023 10:01:32 -0400 Subject: [PATCH 32/43] Change fireSoon to take callback function (#4071) * change firesoon to take callback function * use RDP label when determining type of registry --- src/extensionVariables.ts | 3 ++- src/tree/registerTrees.ts | 4 +++- src/tree/registries/Azure/AzureRegistryDataProvider.ts | 9 ++++----- src/tree/registries/UnifiedRegistryTreeDataProvider.ts | 7 ++++--- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 989c79ec9a..487ea372d3 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { AzExtTreeDataProvider, AzExtTreeItem, IExperimentationServiceAdapter } from '@microsoft/vscode-azext-utils'; -import { DockerHubRegistryDataProvider, GenericRegistryV2DataProvider } from '@microsoft/vscode-docker-registries'; +import { DockerHubRegistryDataProvider, GenericRegistryV2DataProvider, GitHubRegistryDataProvider } from '@microsoft/vscode-docker-registries'; import { ExtensionContext, StatusBarItem, TreeView } from 'vscode'; import { ContainerRuntimeManager } from './runtimes/ContainerRuntimeManager'; import { OrchestratorRuntimeManager } from './runtimes/OrchestratorRuntimeManager'; @@ -53,6 +53,7 @@ export namespace ext { export let genericRegistryV2DataProvider: GenericRegistryV2DataProvider; export let azureRegistryDataProvider: AzureRegistryDataProvider; export let dockerHubRegistryDataProvider: DockerHubRegistryDataProvider; + export let githubRegistryDataProvider: GitHubRegistryDataProvider; export let volumesTree: AzExtTreeDataProvider; export let volumesTreeView: TreeView; diff --git a/src/tree/registerTrees.ts b/src/tree/registerTrees.ts index 9451b97b4a..d29b58b7b3 100644 --- a/src/tree/registerTrees.ts +++ b/src/tree/registerTrees.ts @@ -48,7 +48,8 @@ export function registerTrees(): void { const genericRegistryV2DataProvider = new GenericRegistryV2DataProvider(ext.context); const azureRegistryDataProvider = new AzureRegistryDataProvider(ext.context); const dockerHubRegistryDataProvider = new DockerHubRegistryDataProvider(ext.context); - urtdp.registerProvider(new GitHubRegistryDataProvider(ext.context)); + const githubRegistryDataProvider = new GitHubRegistryDataProvider(ext.context); + urtdp.registerProvider(githubRegistryDataProvider); urtdp.registerProvider(dockerHubRegistryDataProvider); urtdp.registerProvider(azureRegistryDataProvider); urtdp.registerProvider(genericRegistryV2DataProvider); @@ -58,6 +59,7 @@ export function registerTrees(): void { ext.genericRegistryV2DataProvider = genericRegistryV2DataProvider; ext.azureRegistryDataProvider = azureRegistryDataProvider; ext.dockerHubRegistryDataProvider = dockerHubRegistryDataProvider; + ext.githubRegistryDataProvider = githubRegistryDataProvider; ext.volumesRoot = new VolumesTreeItem(undefined); const volumesLoadMore = 'vscode-docker.volumes.loadMore'; diff --git a/src/tree/registries/Azure/AzureRegistryDataProvider.ts b/src/tree/registries/Azure/AzureRegistryDataProvider.ts index 118447487b..401d285df5 100644 --- a/src/tree/registries/Azure/AzureRegistryDataProvider.ts +++ b/src/tree/registries/Azure/AzureRegistryDataProvider.ts @@ -62,7 +62,7 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements if (isRegistryRoot(element)) { if (!await this.subscriptionProvider.isSignedIn()) { await this.subscriptionProvider.signIn(); - this.fireSoon(element); + this.fireSoon(() => this.onDidChangeTreeDataEmitter.fire(element)); return []; } @@ -216,11 +216,10 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements return this.authenticationProviders.get(registryString)!; } - private fireSoon(element?: CommonRegistryItem | undefined) { + private fireSoon(callback: () => void, delay: number = 5) { const timeout = setTimeout(() => { clearTimeout(timeout); - this.onDidChangeTreeDataEmitter.fire(element); - }, 5); + callback(); + }, delay); } - } diff --git a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts index dc316af4e1..95d332496f 100644 --- a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts +++ b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts @@ -1,5 +1,6 @@ import { CommonRegistry, CommonRegistryRoot, RegistryDataProvider, isRegistry } from '@microsoft/vscode-docker-registries'; import * as vscode from 'vscode'; +import { ext } from '../../extensionVariables'; import { isAzureSubscriptionRegistryItem } from './Azure/AzureRegistryDataProvider'; export interface UnifiedRegistryItem { @@ -149,14 +150,14 @@ export class UnifiedRegistryTreeDataProvider implements vscode.TreeDataProvider< // filter out registry roots that don't match the image base name if (imageBaseName) { if (imageBaseName === 'docker.io') { - registryRoots = registryRoots.filter(r => (r.wrappedItem as CommonRegistryRoot).label === 'Docker Hub'); + registryRoots = registryRoots.filter(r => (r.wrappedItem as CommonRegistryRoot).label === ext.dockerHubRegistryDataProvider.label); } else if (imageBaseName.endsWith('azurecr.io')) { - registryRoots = registryRoots.filter(r => (r.wrappedItem as CommonRegistryRoot).label === 'Azure'); + registryRoots = registryRoots.filter(r => (r.wrappedItem as CommonRegistryRoot).label === ext.azureRegistryDataProvider.label); findAzureRegistryOnly = true; } else if (imageBaseName === 'ghcr.io') { - registryRoots = registryRoots.filter(r => (r.wrappedItem as CommonRegistryRoot).label === 'GitHub'); + registryRoots = registryRoots.filter(r => (r.wrappedItem as CommonRegistryRoot).label === ext.githubRegistryDataProvider.label); } else { registryRoots = registryRoots.filter( From d3da812b31860dc3b9606a92a7e7717d7341efe8 Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Thu, 14 Sep 2023 11:41:36 -0400 Subject: [PATCH 33/43] use new RegistryV2RequestOptions for azure requests --- package-lock.json | 2 +- src/tree/registries/Azure/AzureRegistryDataProvider.ts | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1eab9a1fb9..e733f35eb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -814,7 +814,7 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.1.0", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", - "integrity": "sha512-oK8GrOtCb8q9+t+6/7sexRNewRml/nqL5y+2O1CEV7RSFDoBxgtA/Ock/VJhpUvmj+SMM1h/tIvQLMruI/5tRA==", + "integrity": "sha512-hiIKmhq4gHDTrPzrkvj72oKSMfReexwnJzrGUL4COq7r0m8SX6EMRzRaN+DKl7ApsqCMJJXF/y7u17cWq/PWQQ==", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", diff --git a/src/tree/registries/Azure/AzureRegistryDataProvider.ts b/src/tree/registries/Azure/AzureRegistryDataProvider.ts index 401d285df5..a7a75daf88 100644 --- a/src/tree/registries/Azure/AzureRegistryDataProvider.ts +++ b/src/tree/registries/Azure/AzureRegistryDataProvider.ts @@ -159,11 +159,10 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements public async deleteRepository(item: AzureRepository): Promise { const authenticationProvider = this.getAuthenticationProvider(item.parent as unknown as AzureRegistryItem); - + const requestUrl = item.baseUrl.with({ path: `v2/_acr/${item.label}/repository` }); const reponse = await registryV2Request({ method: 'DELETE', - registryUri: item.baseUrl, - path: ['v2', '_acr', `${item.label}`, 'repository'], + requestUri: requestUrl, scopes: [`repository:${item.label}:delete`], authenticationProvider: authenticationProvider, }); @@ -181,11 +180,10 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements public async untagImage(item: AzureTag): Promise { const authenticationProvider = this.getAuthenticationProvider(item.parent.parent as unknown as AzureRegistryItem); - + const requestUrl = item.baseUrl.with({ path: `v2/_acr/${item.parent.label}/tags/${item.label}` }); const reponse = await registryV2Request({ method: 'DELETE', - registryUri: item.baseUrl, - path: ['v2', '_acr', `${item.parent.label}`, 'tags', `${item.label}`], + requestUri: requestUrl, scopes: [`repository:${item.parent.label}:delete`], authenticationProvider: authenticationProvider, }); From fbceb92457fb8e66b8e64c9e5f8eaca2b5523a75 Mon Sep 17 00:00:00 2001 From: "Alex Yang [MSFT]" <59073590+alexyaang@users.noreply.github.com> Date: Tue, 19 Sep 2023 09:47:32 -0400 Subject: [PATCH 34/43] Improve Registry Experience & Simplify Context Values (#4076) * added implementation * added comments * use RegistryQuickPickStep for everything * fix regex * fix viewproperties context value package json * remove unnecessary method * added back necessary logic * Commit current progress * Commit current progress * adjust package.json * fix azure typo * preserve existing additional context value * make regex more robust --------- Co-authored-by: Brandon Waterloo [MSFT] <36966225+bwateratmsft@users.noreply.github.com> --- package-lock.json | 11 +-- package.json | 31 +++---- src/commands/images/pushImage.ts | 16 +++- .../registries/azure/createAzureRegistry.ts | 14 +++- .../registries/azure/deleteAzureRegistry.ts | 8 +- .../registries/azure/deleteAzureRepository.ts | 8 +- .../registries/azure/deployImageToAca.ts | 6 +- .../registries/azure/deployImageToAzure.ts | 11 ++- .../registries/azure/openInAzurePortal.ts | 12 ++- .../azure/tasks/scheduleRunRequest.ts | 14 ++-- .../registries/azure/untagAzureImage.ts | 8 +- .../registries/azure/viewAzureProperties.ts | 9 ++- src/commands/registries/copyRemoteFullTag.ts | 6 +- .../registries/copyRemoteImageDigest.ts | 12 ++- src/commands/registries/deleteRemoteImage.ts | 8 +- src/commands/registries/disconnectRegistry.ts | 6 +- .../dockerHub/openDockerHubInBrowser.ts | 9 ++- .../removeTrackedGenericV2Registry.ts | 12 ++- src/commands/registries/logInToDockerCli.ts | 5 +- src/commands/registries/logOutOfDockerCli.ts | 5 +- src/commands/registries/pullImages.ts | 7 +- src/commands/registries/reconnectRegistry.ts | 4 +- .../Azure/AzureRegistryDataProvider.ts | 35 +++----- .../UnifiedRegistryTreeDataProvider.ts | 4 - src/utils/registryExperience.ts | 81 ++++++++++++++++--- 25 files changed, 218 insertions(+), 124 deletions(-) diff --git a/package-lock.json b/package-lock.json index e733f35eb4..091459e8a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@microsoft/vscode-azext-azureappservice": "~2.0", "@microsoft/vscode-azext-azureauth": "^1.1.2", "@microsoft/vscode-azext-azureutils": "^2.0.0", - "@microsoft/vscode-azext-utils": "^2.0.0", + "@microsoft/vscode-azext-utils": "file:../vscode-azuretools/utils/microsoft-vscode-azext-utils-2.1.1.tgz", "@microsoft/vscode-container-client": "^0.1.0", "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", "dayjs": "^1.11.7", @@ -771,9 +771,10 @@ } }, "node_modules/@microsoft/vscode-azext-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-utils/-/vscode-azext-utils-2.0.5.tgz", - "integrity": "sha512-DNyORyHfPuohBy7Z0rD6YRa4iNLW2LpA+lAv75E9NQ3c1ZhO27tQ2EHj4sYowAqrKZXlqvPHvgjYFj3Dqu5eOQ==", + "version": "2.1.1", + "resolved": "file:../vscode-azuretools/utils/microsoft-vscode-azext-utils-2.1.1.tgz", + "integrity": "sha512-be22iQu1IyWUjKrB6WhbmtsffZ469xRFqjRQwkBzXdwI15j7W2bRpFsyadPHxuIVjzFTcZpmB7a5R8X93UvhZQ==", + "license": "MIT", "dependencies": { "@microsoft/vscode-azureresources-api": "^2.0.4", "@vscode/extension-telemetry": "^0.6.2", @@ -814,7 +815,7 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.1.0", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", - "integrity": "sha512-hiIKmhq4gHDTrPzrkvj72oKSMfReexwnJzrGUL4COq7r0m8SX6EMRzRaN+DKl7ApsqCMJJXF/y7u17cWq/PWQQ==", + "integrity": "sha512-af963bHhdUX51Rn3rSGSRD1qO/qeUxcoRbTzvA/c8R9GJdDqtRzgyaOu57ETzHyG/fUfd4qaDCMs2FNI9HNk5g==", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", diff --git a/package.json b/package.json index af70c2a43f..64fd844a2a 100644 --- a/package.json +++ b/package.json @@ -467,7 +467,7 @@ }, { "command": "vscode-docker.registries.azure.deleteRegistry", - "when": "view == dockerRegistries && viewItem =~ /azureContainerRegistry/i", + "when": "view == dockerRegistries && viewItem =~ /azure;.*commonregistry/i", "group": "regs_reg_2_destructive@1" }, { @@ -477,7 +477,7 @@ }, { "command": "vscode-docker.registries.azure.deleteRepository", - "when": "view == dockerRegistries && viewItem =~ /azureContainerRepository/i", + "when": "view == dockerRegistries && viewItem =~ /azure;.*commonrepository/i", "group": "regs_repo_2_destructive@1" }, { @@ -492,7 +492,7 @@ }, { "command": "vscode-docker.registries.copyImageDigest", - "when": "view == dockerRegistries && viewItem =~ /registryV2Tag/i", + "when": "view == dockerRegistries && viewItem =~ /commontag/i && !(viewItem =~ /commontag;.*dockerhub/i)", "group": "regs_tag_1_general@3" }, { @@ -507,42 +507,42 @@ }, { "command": "vscode-docker.registries.azure.untagImage", - "when": "view == dockerRegistries && viewItem =~ /azureContainerTag/i", + "when": "view == dockerRegistries && viewItem =~ /azure;.*commontag/i", "group": "regs_tag_2_destructive@1" }, { "command": "vscode-docker.registries.deleteImage", - "when": "view == dockerRegistries && viewItem =~ /commontag/i && !(viewItem =~ /(githubRegistryTag|dockerHubTag)/i)", + "when": "view == dockerRegistries && viewItem =~ /commontag/i && !(viewItem =~ /commontag;.*(dockerhub|github)/i)", "group": "regs_tag_2_destructive@2" }, { "command": "vscode-docker.registries.disconnectRegistry", - "when": "view == dockerRegistries && viewItem =~ /commonregistryroot/i && !(viewItem =~ /genericRegistryV2Root/i)", + "when": "view == dockerRegistries && viewItem =~ /commonregistryroot/i && !(viewItem =~ /commonregistryroot;.*generic/i)", "group": "regs_yyy_destructive@1" }, { "command": "vscode-docker.registries.genericV2.removeTrackedRegistry", - "when": "view == dockerRegistries && viewItem =~ /genericRegistryV2Registry/i", + "when": "view == dockerRegistries && viewItem =~ /commonregistry;.*generic/i", "group": "regs_yyy_destructive@1" }, { "command": "vscode-docker.registries.genericV2.addTrackedRegistry", - "when": "view == dockerRegistries && viewItem =~ /genericRegistryV2Root/i", + "when": "view == dockerRegistries && viewItem =~ /commonregistryroot;.*generic/i", "group": "regs_yyy_destructive@1" }, { "command": "vscode-docker.registries.azure.openInPortal", - "when": "view == dockerRegistries && viewItem =~ /(azuresubscription|azureContainerRegistry|azureContainerRepository)/i", + "when": "view == dockerRegistries && viewItem =~ /azuresubscription|azure;.*(commonregistry|commonrepository)/i", "group": "regs_zzz_common@1" }, { "command": "vscode-docker.registries.dockerHub.openInBrowser", - "when": "view == dockerRegistries && viewItem =~ /(dockerHubRegistry|dockerHubRepository|dockerHubTag)/i", + "when": "view == dockerRegistries && viewItem =~ /(commonregistry|commonrepository|commontag);.*dockerhub/i", "group": "regs_zzz_common@1" }, { "command": "vscode-docker.registries.azure.viewProperties", - "when": "view == dockerRegistries && viewItem =~ /azureContainerRegistry/i", + "when": "view == dockerRegistries && viewItem =~ /azure;.*commonregistry/i", "group": "regs_zzz_common@2" }, { @@ -557,12 +557,7 @@ }, { "command": "vscode-docker.registries.refresh", - "when": "view == dockerRegistries && viewItem =~ /.*;.*;(Repository|Registry|RegistryProvider);/", - "group": "regs_zzz_common@9" - }, - { - "command": "vscode-docker.registries.refresh", - "when": "view == dockerRegistries && viewItem =~ /azure(Subscription|Tasks|Task|RunsWithoutTask)$/", + "when": "view == dockerRegistries && viewItem =~ /commonregistry|commonregistryroot|commonrepository/", "group": "regs_zzz_common@9" }, { @@ -3001,7 +2996,7 @@ "@microsoft/vscode-azext-azureappservice": "~2.0", "@microsoft/vscode-azext-azureauth": "^1.1.2", "@microsoft/vscode-azext-azureutils": "^2.0.0", - "@microsoft/vscode-azext-utils": "^2.0.0", + "@microsoft/vscode-azext-utils": "file:../vscode-azuretools/utils/microsoft-vscode-azext-utils-2.1.1.tgz", "@microsoft/vscode-container-client": "^0.1.0", "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", "dayjs": "^1.11.7", diff --git a/src/commands/images/pushImage.ts b/src/commands/images/pushImage.ts index badb092890..421e25c1e6 100644 --- a/src/commands/images/pushImage.ts +++ b/src/commands/images/pushImage.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, NoResourceFoundError, contextValueExperience } from '@microsoft/vscode-azext-utils'; +import { IActionContext, NoResourceFoundError } from '@microsoft/vscode-azext-utils'; import { parseDockerLikeImageName } from '@microsoft/vscode-container-client'; import { CommonRegistry } from '@microsoft/vscode-docker-registries'; import * as vscode from 'vscode'; @@ -11,6 +11,7 @@ import { ext } from '../../extensionVariables'; import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { registryExperience } from '../../utils/registryExperience'; import { addImageTaggingTelemetry, tagImage } from './tagImage'; export async function pushImage(context: IActionContext, node: ImageTreeItem | undefined): Promise { @@ -31,7 +32,7 @@ export async function pushImage(context: IActionContext, node: ImageTreeItem | u // If the prompt setting is true, we'll ask; if not we'll assume Docker Hub. if (prompt) { try { - connectedRegistry = await contextValueExperience(context, ext.registriesTree, { include: ['commonregistry'] }); + connectedRegistry = await registryExperience(context, { contextValueFilter: { include: [/commonregistry/i] } }); } catch (error) { if (error instanceof NoResourceFoundError) { // Do nothing, move on without a selected registry @@ -42,7 +43,14 @@ export async function pushImage(context: IActionContext, node: ImageTreeItem | u } } else { // Try to find a connected Docker Hub registry (primarily for login credentials) - connectedRegistry = await contextValueExperience(context, ext.dockerHubRegistryDataProvider, { include: ['dockerHubRegistry'] }); + connectedRegistry = await registryExperience( + context, + { + registryFilter: { include: [ext.dockerHubRegistryDataProvider.label] }, + contextValueFilter: { include: /commonregistry/i }, + skipIfOne: true + } + ); } } else { // The registry to push to is determinate. If there's a connected registry in the tree view, we'll try to find it, to perform login ahead of time. @@ -84,7 +92,7 @@ async function tryGetConnectedRegistryForPath(context: IActionContext, baseImage let matchedRegistry = allRegistries.find((registry) => registry.wrappedItem.baseUrl.authority === baseImageNameInfo.registry); if (!matchedRegistry) { - matchedRegistry = await contextValueExperience(context, ext.registriesTree, { include: ['commonregistry'] }); + matchedRegistry = await registryExperience(context, { contextValueFilter: { include: [/commonregistry/i] } }); } return matchedRegistry; diff --git a/src/commands/registries/azure/createAzureRegistry.ts b/src/commands/registries/azure/createAzureRegistry.ts index 1c65bd67ea..137be90bb3 100644 --- a/src/commands/registries/azure/createAzureRegistry.ts +++ b/src/commands/registries/azure/createAzureRegistry.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzureWizard, IActionContext, contextValueExperience, createSubscriptionContext, nonNullProp } from '@microsoft/vscode-azext-utils'; +import { AzureWizard, IActionContext, createSubscriptionContext, nonNullProp } from '@microsoft/vscode-azext-utils'; import { l10n, window } from 'vscode'; import { ext } from '../../../extensionVariables'; import { AzureSubscriptionRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; @@ -11,16 +11,22 @@ import { AzureRegistryCreateStep } from '../../../tree/registries/Azure/createWi import { AzureRegistryNameStep } from '../../../tree/registries/Azure/createWizard/AzureRegistryNameStep'; import { AzureRegistrySkuStep } from '../../../tree/registries/Azure/createWizard/AzureRegistrySkuStep'; import { IAzureRegistryWizardContext } from '../../../tree/registries/Azure/createWizard/IAzureRegistryWizardContext'; -import { UnifiedRegistryItem, isUnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; import { getAzExtAzureUtils } from '../../../utils/lazyPackages'; +import { registryExperience } from '../../../utils/registryExperience'; export async function createAzureRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: 'azuresubscription' }); + node = await registryExperience(context, + { + contextValueFilter: { include: /azuresubscription/i }, + registryFilter: { include: [ext.azureRegistryDataProvider.label] } + } + ); } - const registryItem = isUnifiedRegistryItem(node) ? node.wrappedItem : node; + const registryItem = node.wrappedItem; const subscriptionContext = createSubscriptionContext(registryItem.subscription); const wizardContext: IAzureRegistryWizardContext = { diff --git a/src/commands/registries/azure/deleteAzureRegistry.ts b/src/commands/registries/azure/deleteAzureRegistry.ts index 2ef44c8453..3125cecb78 100644 --- a/src/commands/registries/azure/deleteAzureRegistry.ts +++ b/src/commands/registries/azure/deleteAzureRegistry.ts @@ -3,16 +3,20 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DialogResponses, IActionContext, contextValueExperience } from '@microsoft/vscode-azext-utils'; +import { DialogResponses, IActionContext } from '@microsoft/vscode-azext-utils'; import { ProgressLocation, l10n, window } from 'vscode'; import { ext } from '../../../extensionVariables'; import { AzureRegistry, AzureRegistryDataProvider } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { registryExperience } from '../../../utils/registryExperience'; export async function deleteAzureRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { // we can't pass in the azure tree provider because it's not a UnifiedRegistryItem and we need the provider to delete - node = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerRegistry' }); + node = await registryExperience(context, { + contextValueFilter: { include: /commonregistry/i }, + registryFilter: { include: [ext.azureRegistryDataProvider.label] } + }); } const registryName = node.wrappedItem.label; diff --git a/src/commands/registries/azure/deleteAzureRepository.ts b/src/commands/registries/azure/deleteAzureRepository.ts index 335a2baa60..07d71f707c 100644 --- a/src/commands/registries/azure/deleteAzureRepository.ts +++ b/src/commands/registries/azure/deleteAzureRepository.ts @@ -3,16 +3,20 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DialogResponses, IActionContext, contextValueExperience } from '@microsoft/vscode-azext-utils'; +import { DialogResponses, IActionContext } from '@microsoft/vscode-azext-utils'; import { ProgressLocation, l10n, window } from 'vscode'; import { ext } from '../../../extensionVariables'; import { AzureRegistryDataProvider, AzureRepository } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { registryExperience } from '../../../utils/registryExperience'; export async function deleteAzureRepository(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { // we can't pass in the azure tree provider because it's not a UnifiedRegistryItem and we need the provider to delete - node = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerRepository' }); + node = await registryExperience(context, { + contextValueFilter: { include: /commonrepository/i }, + registryFilter: { include: [ext.azureRegistryDataProvider.label] } + }); } const confirmDelete = l10n.t('Are you sure you want to delete repository "{0}" and its associated images?', node.wrappedItem.label); diff --git a/src/commands/registries/azure/deployImageToAca.ts b/src/commands/registries/azure/deployImageToAca.ts index c84198e975..89b447eff9 100644 --- a/src/commands/registries/azure/deployImageToAca.ts +++ b/src/commands/registries/azure/deployImageToAca.ts @@ -3,16 +3,16 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { contextValueExperience, IActionContext, nonNullProp, UserCancelledError } from '@microsoft/vscode-azext-utils'; +import { IActionContext, nonNullProp, UserCancelledError } from '@microsoft/vscode-azext-utils'; import { parseDockerLikeImageName } from '@microsoft/vscode-container-client'; import { CommonRegistry, CommonTag, isDockerHubRegistry, LoginInformation } from '@microsoft/vscode-docker-registries'; import * as semver from 'semver'; import * as vscode from 'vscode'; -import { ext } from '../../../extensionVariables'; import { isAzureRegistry } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; import { getFullImageNameFromRegistryTagItem } from '../../../tree/registries/registryTreeUtils'; import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; import { installExtension } from '../../../utils/installExtension'; +import { registryExperience } from '../../../utils/registryExperience'; import { addImageTaggingTelemetry } from '../../images/tagImage'; const acaExtensionId = 'ms-azuretools.vscode-azurecontainerapps'; @@ -35,7 +35,7 @@ export async function deployImageToAca(context: IActionContext, node?: UnifiedRe } if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: 'commontag' }); + node = await registryExperience(context, { contextValueFilter: { include: /commontag/i } }); } const commandOptions: Partial = { diff --git a/src/commands/registries/azure/deployImageToAzure.ts b/src/commands/registries/azure/deployImageToAzure.ts index 20dd8c8531..7bba3abb90 100644 --- a/src/commands/registries/azure/deployImageToAzure.ts +++ b/src/commands/registries/azure/deployImageToAzure.ts @@ -5,7 +5,7 @@ import type { Site } from '@azure/arm-appservice'; // These are only dev-time imports so don't need to be lazy import type { IAppServiceWizardContext } from "@microsoft/vscode-azext-azureappservice"; // These are only dev-time imports so don't need to be lazy -import { AzureWizard, AzureWizardExecuteStep, AzureWizardPromptStep, IActionContext, contextValueExperience, createSubscriptionContext, nonNullProp } from "@microsoft/vscode-azext-utils"; +import { AzureWizard, AzureWizardExecuteStep, AzureWizardPromptStep, IActionContext, createSubscriptionContext, nonNullProp } from "@microsoft/vscode-azext-utils"; import { CommonTag } from '@microsoft/vscode-docker-registries'; import { Uri, env, l10n, window } from "vscode"; import { ext } from "../../../extensionVariables"; @@ -24,15 +24,18 @@ export interface IAppServiceContainerWizardContext extends IAppServiceWizardCont export async function deployImageToAzure(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: ['commontag'], }); + node = await registryExperience(context, { contextValueFilter: { include: 'commontag' } }); } const azExtAzureUtils = await getAzExtAzureUtils(); const vscAzureAppService = await getAzExtAppService(); const promptSteps: AzureWizardPromptStep[] = []; - const subscriptionItem = await registryExperience(context, ext.azureRegistryDataProvider, { include: 'azuresubscription' }) as AzureSubscriptionRegistryItem; - const subscriptionContext = createSubscriptionContext(subscriptionItem.subscription); + const subscriptionItem = await registryExperience(context, { + registryFilter: { include: [ext.azureRegistryDataProvider.label] }, + contextValueFilter: { include: /azuresubscription/i }, + }); + const subscriptionContext = createSubscriptionContext(subscriptionItem.wrappedItem.subscription); const wizardContext: IActionContext & Partial = { ...context, ...subscriptionContext, diff --git a/src/commands/registries/azure/openInAzurePortal.ts b/src/commands/registries/azure/openInAzurePortal.ts index 3f66c09504..f72b11b2d2 100644 --- a/src/commands/registries/azure/openInAzurePortal.ts +++ b/src/commands/registries/azure/openInAzurePortal.ts @@ -3,18 +3,22 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, contextValueExperience, createSubscriptionContext } from '@microsoft/vscode-azext-utils'; +import { IActionContext, createSubscriptionContext } from '@microsoft/vscode-azext-utils'; import { ext } from '../../../extensionVariables'; import { AzureRegistry, AzureRepository, AzureSubscriptionRegistryItem, isAzureRegistry, isAzureSubscriptionRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider'; -import { UnifiedRegistryItem, isUnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider'; import { getAzExtAzureUtils } from '../../../utils/lazyPackages'; +import { registryExperience } from '../../../utils/registryExperience'; export async function openInAzurePortal(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: ['azureContainerRegistry'] }); + node = await registryExperience(context, { + registryFilter: { include: [ext.azureRegistryDataProvider.label] }, + contextValueFilter: { include: [/commonregistry/i] }, + }); } - const azureRegistryItem = isUnifiedRegistryItem(node) ? node.wrappedItem : node; + const azureRegistryItem = node.wrappedItem; const azExtAzureUtils = await getAzExtAzureUtils(); let subscriptionContext = undefined; if (isAzureSubscriptionRegistryItem(azureRegistryItem)) { diff --git a/src/commands/registries/azure/tasks/scheduleRunRequest.ts b/src/commands/registries/azure/tasks/scheduleRunRequest.ts index 4db6aa19c2..02e680aff8 100644 --- a/src/commands/registries/azure/tasks/scheduleRunRequest.ts +++ b/src/commands/registries/azure/tasks/scheduleRunRequest.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import type { DockerBuildRequest as AcrDockerBuildRequest, FileTaskRunRequest as AcrFileTaskRunRequest, OS as AcrOS, Run as AcrRun, ContainerRegistryManagementClient } from "@azure/arm-containerregistry"; // These are only dev-time imports so don't need to be lazy -import { IActionContext, IAzureQuickPickItem, contextValueExperience, nonNullProp } from '@microsoft/vscode-azext-utils'; +import { IActionContext, IAzureQuickPickItem, nonNullProp } from '@microsoft/vscode-azext-utils'; import * as fse from 'fs-extra'; import * as os from 'os'; import * as path from 'path'; @@ -12,13 +12,14 @@ import * as readline from 'readline'; import * as tar from 'tar'; import * as vscode from 'vscode'; import { ext } from '../../../../extensionVariables'; -import { AzureRegistryItem } from "../../../../tree/registries/Azure/AzureRegistryDataProvider"; -import { UnifiedRegistryItem, isUnifiedRegistryItem } from "../../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { AzureRegistry, AzureRegistryItem } from "../../../../tree/registries/Azure/AzureRegistryDataProvider"; +import { UnifiedRegistryItem } from "../../../../tree/registries/UnifiedRegistryTreeDataProvider"; import { createAzureContainerRegistryClient, getResourceGroupFromId } from "../../../../utils/azureUtils"; import { getStorageBlob } from '../../../../utils/lazyPackages'; import { delay } from '../../../../utils/promiseUtils'; import { Item, quickPickDockerFileItem, quickPickYamlFileItem } from '../../../../utils/quickPickFile'; import { quickPickWorkspaceFolder } from '../../../../utils/quickPickWorkspaceFolder'; +import { registryExperience } from "../../../../utils/registryExperience"; import { addImageTaggingTelemetry, getTagFromUserInput } from '../../../images/tagImage'; const idPrecision = 6; @@ -46,8 +47,11 @@ export async function scheduleRunRequest(context: IActionContext, requestType: ' throw new Error(vscode.l10n.t('Run Request Type Currently not supported.')); } - const node: UnifiedRegistryItem = await contextValueExperience(context, ext.azureRegistryDataProvider, { include: 'azureContainerRegistry' }); - const registryItem: AzureRegistryItem = isUnifiedRegistryItem(node) ? node.wrappedItem : node; + const node: UnifiedRegistryItem = await registryExperience(context, { + registryFilter: { include: [ext.azureRegistryDataProvider.label] }, + contextValueFilter: { include: /commonregistry/i }, + }); + const registryItem: AzureRegistryItem = node.wrappedItem; const resourceGroup = getResourceGroupFromId(registryItem.id); const osPick = ['Linux', 'Windows'].map(item => >{ label: item, data: item }); diff --git a/src/commands/registries/azure/untagAzureImage.ts b/src/commands/registries/azure/untagAzureImage.ts index 49867042fa..285ab301bb 100644 --- a/src/commands/registries/azure/untagAzureImage.ts +++ b/src/commands/registries/azure/untagAzureImage.ts @@ -3,17 +3,21 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { contextValueExperience, IActionContext } from "@microsoft/vscode-azext-utils"; +import { IActionContext } from "@microsoft/vscode-azext-utils"; import { l10n, ProgressLocation, window } from "vscode"; import { ext } from "../../../extensionVariables"; import { AzureRegistryDataProvider, AzureTag } from "../../../tree/registries/Azure/AzureRegistryDataProvider"; import { getFullImageNameFromRegistryTagItem } from "../../../tree/registries/registryTreeUtils"; import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { registryExperience } from "../../../utils/registryExperience"; export async function untagAzureImage(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { // we can't pass in the azure tree provider because it's not a UnifiedRegistryItem and we need the provider to untag - node = await contextValueExperience(context, ext.registriesTree, { include: 'azureContainerTag' }); + node = await registryExperience(context, { + registryFilter: { include: [ext.azureRegistryDataProvider.label] }, + contextValueFilter: { include: /commontag/i }, + }); } const fullTag = getFullImageNameFromRegistryTagItem(node.wrappedItem); diff --git a/src/commands/registries/azure/viewAzureProperties.ts b/src/commands/registries/azure/viewAzureProperties.ts index e70398edef..298dfdf74e 100644 --- a/src/commands/registries/azure/viewAzureProperties.ts +++ b/src/commands/registries/azure/viewAzureProperties.ts @@ -6,14 +6,17 @@ import { IActionContext, openReadOnlyJson } from "@microsoft/vscode-azext-utils"; import { ext } from "../../../extensionVariables"; import { AzureRegistry } from "../../../tree/registries/Azure/AzureRegistryDataProvider"; -import { UnifiedRegistryItem, isUnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; import { registryExperience } from "../../../utils/registryExperience"; export async function viewAzureProperties(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await registryExperience(context, ext.azureRegistryDataProvider, { 'include': 'azureContainerRegistry' }, true); + node = await registryExperience(context, { + registryFilter: { include: [ext.azureRegistryDataProvider.label] }, + contextValueFilter: { include: /commonregistry/i }, + }); } - const registryItem = isUnifiedRegistryItem(node) ? node.wrappedItem : node; + const registryItem = node.wrappedItem; await openReadOnlyJson({ label: registryItem.label, fullId: registryItem.id }, registryItem.registryProperties); } diff --git a/src/commands/registries/copyRemoteFullTag.ts b/src/commands/registries/copyRemoteFullTag.ts index 1cf1c7a475..6cb63e1cfd 100644 --- a/src/commands/registries/copyRemoteFullTag.ts +++ b/src/commands/registries/copyRemoteFullTag.ts @@ -3,16 +3,16 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, contextValueExperience } from '@microsoft/vscode-azext-utils'; +import { IActionContext } from '@microsoft/vscode-azext-utils'; import { CommonTag } from '@microsoft/vscode-docker-registries'; import * as vscode from 'vscode'; -import { ext } from '../../extensionVariables'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; import { getFullImageNameFromRegistryTagItem } from '../../tree/registries/registryTreeUtils'; +import { registryExperience } from '../../utils/registryExperience'; export async function copyRemoteFullTag(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: 'commontag' }); + node = await registryExperience(context, { contextValueFilter: { include: /commontag/i } }); } const fullTag = getFullImageNameFromRegistryTagItem(node.wrappedItem); void vscode.env.clipboard.writeText(fullTag); diff --git a/src/commands/registries/copyRemoteImageDigest.ts b/src/commands/registries/copyRemoteImageDigest.ts index 7daad72e76..9411cca1f4 100644 --- a/src/commands/registries/copyRemoteImageDigest.ts +++ b/src/commands/registries/copyRemoteImageDigest.ts @@ -3,15 +3,19 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, contextValueExperience } from "@microsoft/vscode-azext-utils"; -import { RegistryV2DataProvider, V2Tag } from "@microsoft/vscode-docker-registries"; +import { IActionContext } from "@microsoft/vscode-azext-utils"; +import { CommonTag, RegistryV2DataProvider } from "@microsoft/vscode-docker-registries"; import * as vscode from "vscode"; import { ext } from "../../extensionVariables"; import { UnifiedRegistryItem } from "../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { registryExperience } from "../../utils/registryExperience"; -export async function copyRemoteImageDigest(context: IActionContext, node?: UnifiedRegistryItem): Promise { +export async function copyRemoteImageDigest(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: ['registryV2Tag'] }); + node = await registryExperience(context, { + registryFilter: { exclude: [ext.dockerHubRegistryDataProvider.label] }, + contextValueFilter: { include: /commontag/i, }, + }); } const v2DataProvider = node.provider as unknown as RegistryV2DataProvider; diff --git a/src/commands/registries/deleteRemoteImage.ts b/src/commands/registries/deleteRemoteImage.ts index e1e1c5d870..10b430025a 100644 --- a/src/commands/registries/deleteRemoteImage.ts +++ b/src/commands/registries/deleteRemoteImage.ts @@ -13,7 +13,10 @@ import { registryExperience } from '../../utils/registryExperience'; export async function deleteRemoteImage(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await registryExperience(context, ext.registriesTree, { include: 'commontag', exclude: ['githubRegistryTag', 'dockerHubTag'] }, false); + node = await registryExperience(context, { + registryFilter: { exclude: [ext.githubRegistryDataProvider.label, ext.dockerHubRegistryDataProvider.label] }, + contextValueFilter: { include: /commontag/i }, + }); } const provider = node.provider as unknown as CommonRegistryDataProvider; @@ -46,6 +49,5 @@ export async function deleteRemoteImage(context: IActionContext, node?: UnifiedR // Other tags that also matched the image may have been deleted, so refresh the whole repository // don't wait void ext.registriesTree.refresh(); - /* eslint-disable-next-line @typescript-eslint/no-floating-promises */ - window.showInformationMessage(l10n.t('Successfully deleted image "{0}".', tagName)); + void window.showInformationMessage(l10n.t('Successfully deleted image "{0}".', tagName)); } diff --git a/src/commands/registries/disconnectRegistry.ts b/src/commands/registries/disconnectRegistry.ts index e158c6b49d..50a81d50c5 100644 --- a/src/commands/registries/disconnectRegistry.ts +++ b/src/commands/registries/disconnectRegistry.ts @@ -3,13 +3,15 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, contextValueExperience } from "@microsoft/vscode-azext-utils"; +import { IActionContext } from "@microsoft/vscode-azext-utils"; +import { CommonRegistry } from "@microsoft/vscode-docker-registries"; import { ext } from "../../extensionVariables"; import { UnifiedRegistryItem } from "../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { registryExperience } from "../../utils/registryExperience"; export async function disconnectRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: 'commonregistryroot', exclude: 'genericRegistryV2Root' }); + node = await registryExperience(context, { registryFilter: { exclude: [ext.genericRegistryV2DataProvider.label] } }); } await ext.registriesTree.disconnectRegistryProvider(node); diff --git a/src/commands/registries/dockerHub/openDockerHubInBrowser.ts b/src/commands/registries/dockerHub/openDockerHubInBrowser.ts index 41273d803f..a3cc62af61 100644 --- a/src/commands/registries/dockerHub/openDockerHubInBrowser.ts +++ b/src/commands/registries/dockerHub/openDockerHubInBrowser.ts @@ -8,16 +8,19 @@ import { CommonRegistryItem, isRegistry, isRepository, isTag } from "@microsoft/ import * as vscode from "vscode"; import { dockerHubUrl } from "../../../constants"; import { ext } from "../../../extensionVariables"; -import { UnifiedRegistryItem, isUnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; import { registryExperience } from "../../../utils/registryExperience"; export async function openDockerHubInBrowser(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await registryExperience(context, ext.dockerHubRegistryDataProvider, { include: ['dockerHubRegistry'] }, true); + node = await registryExperience(context, { + registryFilter: { include: [ext.dockerHubRegistryDataProvider.label] }, + contextValueFilter: { include: [/commonregistry/i] }, + }); } let url = dockerHubUrl; - const dockerHubItem = isUnifiedRegistryItem(node) ? node.wrappedItem : node; + const dockerHubItem = node.wrappedItem; if (isRegistry(dockerHubItem)) { url = `${url}u/${dockerHubItem.label}`; diff --git a/src/commands/registries/genericV2/removeTrackedGenericV2Registry.ts b/src/commands/registries/genericV2/removeTrackedGenericV2Registry.ts index 23e1540c82..9c9815b3a5 100644 --- a/src/commands/registries/genericV2/removeTrackedGenericV2Registry.ts +++ b/src/commands/registries/genericV2/removeTrackedGenericV2Registry.ts @@ -3,14 +3,18 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, contextValueExperience } from "@microsoft/vscode-azext-utils"; -import { GenericV2Registry } from "@microsoft/vscode-docker-registries"; +import { IActionContext } from "@microsoft/vscode-azext-utils"; +import { V2Registry } from "@microsoft/vscode-docker-registries"; import { ext } from "../../../extensionVariables"; import { UnifiedRegistryItem } from "../../../tree/registries/UnifiedRegistryTreeDataProvider"; +import { registryExperience } from "../../../utils/registryExperience"; -export async function removeTrackedGenericV2Registry(context: IActionContext, node?: UnifiedRegistryItem): Promise { +export async function removeTrackedGenericV2Registry(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.genericRegistryV2DataProvider, { include: 'genericRegistryV2Registry' }); + node = await registryExperience(context, { + registryFilter: { include: [ext.genericRegistryV2DataProvider.label] }, + contextValueFilter: { include: /commonregistry/i }, + }); } await ext.genericRegistryV2DataProvider.removeTrackedRegistry(node.wrappedItem); diff --git a/src/commands/registries/logInToDockerCli.ts b/src/commands/registries/logInToDockerCli.ts index 594a822d85..2f30e6f8ef 100644 --- a/src/commands/registries/logInToDockerCli.ts +++ b/src/commands/registries/logInToDockerCli.ts @@ -3,16 +3,17 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, contextValueExperience, parseError } from '@microsoft/vscode-azext-utils'; +import { IActionContext, parseError } from '@microsoft/vscode-azext-utils'; import { CommonRegistry } from '@microsoft/vscode-docker-registries'; import * as stream from 'stream'; import * as vscode from 'vscode'; import { ext } from '../../extensionVariables'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { registryExperience } from '../../utils/registryExperience'; export async function logInToDockerCli(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesRoot, { include: 'commonregistry' }); + node = await registryExperience(context, { contextValueFilter: { include: /commonregistry/i } }); } const creds = await node.provider?.getLoginInformation?.(node.wrappedItem); diff --git a/src/commands/registries/logOutOfDockerCli.ts b/src/commands/registries/logOutOfDockerCli.ts index 7c93a4b89d..dd9e774798 100644 --- a/src/commands/registries/logOutOfDockerCli.ts +++ b/src/commands/registries/logOutOfDockerCli.ts @@ -3,16 +3,17 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, contextValueExperience } from '@microsoft/vscode-azext-utils'; +import { IActionContext } from '@microsoft/vscode-azext-utils'; import { CommonRegistry } from '@microsoft/vscode-docker-registries'; import { l10n } from 'vscode'; import { ext } from '../../extensionVariables'; import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; +import { registryExperience } from '../../utils/registryExperience'; export async function logOutOfDockerCli(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: 'commonregistry' }); + node = await registryExperience(context, { contextValueFilter: { include: /commonregistry/i } }); } const serverUrl = (await node.provider.getLoginInformation?.(node.wrappedItem))?.server; if (!serverUrl) { diff --git a/src/commands/registries/pullImages.ts b/src/commands/registries/pullImages.ts index 1a9489f214..7e21b5447e 100644 --- a/src/commands/registries/pullImages.ts +++ b/src/commands/registries/pullImages.ts @@ -3,17 +3,18 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext, contextValueExperience } from '@microsoft/vscode-azext-utils'; +import { IActionContext } from '@microsoft/vscode-azext-utils'; import { CommonRegistry, CommonRepository, CommonTag } from '@microsoft/vscode-docker-registries/lib/clients/Common/models'; import { ext } from '../../extensionVariables'; import { TaskCommandRunnerFactory } from '../../runtimes/runners/TaskCommandRunnerFactory'; import { UnifiedRegistryItem } from '../../tree/registries/UnifiedRegistryTreeDataProvider'; import { getFullImageNameFromRegistryTagItem, getFullRepositoryNameFromRepositoryItem } from '../../tree/registries/registryTreeUtils'; +import { registryExperience } from '../../utils/registryExperience'; import { logInToDockerCli } from './logInToDockerCli'; export async function pullRepository(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: 'commonrepository' }); + node = await registryExperience(context, { contextValueFilter: { include: /commonrepository/i } }); } await pullImages(context, node.parent, getFullRepositoryNameFromRepositoryItem(node.wrappedItem), true); @@ -21,7 +22,7 @@ export async function pullRepository(context: IActionContext, node?: UnifiedRegi export async function pullImageFromRepository(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - node = await contextValueExperience(context, ext.registriesTree, { include: 'commontag' }); + node = await registryExperience(context, { contextValueFilter: { include: /commontag/i } }); } await pullImages(context, node.parent.parent, getFullImageNameFromRegistryTagItem(node.wrappedItem), false); diff --git a/src/commands/registries/reconnectRegistry.ts b/src/commands/registries/reconnectRegistry.ts index 16abcdecf1..258d5027fb 100644 --- a/src/commands/registries/reconnectRegistry.ts +++ b/src/commands/registries/reconnectRegistry.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IActionContext } from "@microsoft/vscode-azext-utils"; -import { GenericV2Registry, RegistryConnectError, isGenericV2Registry } from "@microsoft/vscode-docker-registries"; +import { RegistryConnectError, V2Registry, isGenericV2Registry } from "@microsoft/vscode-docker-registries"; import { l10n } from 'vscode'; import { ext } from "../../extensionVariables"; import { UnifiedRegistryItem } from "../../tree/registries/UnifiedRegistryTreeDataProvider"; @@ -16,7 +16,7 @@ export async function reconnectRegistry(context: IActionContext, node?: UnifiedR } if (isGenericV2Registry(node.parent.wrappedItem)) { - await ext.genericRegistryV2DataProvider.removeTrackedRegistry(node.parent.wrappedItem as GenericV2Registry); + await ext.genericRegistryV2DataProvider.removeTrackedRegistry(node.parent.wrappedItem as V2Registry); await ext.genericRegistryV2DataProvider.addTrackedRegistry(); } else { await ext.registriesTree.disconnectRegistryProvider(node.parent); diff --git a/src/tree/registries/Azure/AzureRegistryDataProvider.ts b/src/tree/registries/Azure/AzureRegistryDataProvider.ts index a7a75daf88..494eec04c8 100644 --- a/src/tree/registries/Azure/AzureRegistryDataProvider.ts +++ b/src/tree/registries/Azure/AzureRegistryDataProvider.ts @@ -6,7 +6,7 @@ import type { Registry as AcrRegistry, RegistryListCredentialsResult } from '@azure/arm-containerregistry'; import { AzureSubscription, VSCodeAzureSubscriptionProvider } from '@microsoft/vscode-azext-azureauth'; import { RegistryV2DataProvider, V2Registry, V2RegistryItem, V2Repository, V2Tag, getContextValue, registryV2Request } from '@microsoft/vscode-docker-registries'; -import { CommonRegistryItem, isRegistryRoot } from '@microsoft/vscode-docker-registries/lib/clients/Common/models'; +import { CommonRegistryItem, isRegistry, isRegistryRoot, isRepository, isTag } from '@microsoft/vscode-docker-registries/lib/clients/Common/models'; import * as vscode from 'vscode'; import { createAzureContainerRegistryClient, getResourceGroupFromId } from '../../../utils/azureUtils'; import { ACROAuthProvider } from './ACROAuthProvider'; @@ -34,15 +34,15 @@ export function isAzureSubscriptionRegistryItem(item: unknown): item is AzureSub } export function isAzureRegistry(item: unknown): item is AzureRegistry { - return !!item && typeof item === 'object' && (item as AzureRegistryItem).additionalContextValues?.includes('azureContainerRegistry'); + return isRegistry(item) && item.additionalContextValues?.includes('azure'); } export function isAzureRepository(item: unknown): item is AzureRepository { - return !!item && typeof item === 'object' && (item as AzureRepository).additionalContextValues?.includes('azureContainerRepository'); + return isRepository(item) && item.additionalContextValues?.includes('azure'); } export function isAzureTag(item: unknown): item is AzureTag { - return !!item && typeof item === 'object' && (item as AzureTag).additionalContextValues?.includes('azureContainerTag'); + return isTag(item) && item.additionalContextValues?.includes('azure'); } export class AzureRegistryDataProvider extends RegistryV2DataProvider implements vscode.Disposable { @@ -79,13 +79,18 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements } as AzureSubscriptionRegistryItem; }); } else if (isAzureSubscriptionRegistryItem(element)) { - return await this.getRegistries(element); + const registries = await this.getRegistries(element); + registries.forEach(registry => { + registry.additionalContextValues = [...(registry.additionalContextValues || []), 'azure']; + }); + return registries; } else { const children = await super.getChildren(element); if ((element as AzureRegistryItem)?.subscription) { children.forEach(e => { e.subscription = (element as AzureRegistryItem).subscription; + e.additionalContextValues = [...(e.additionalContextValues || []), 'azure']; }); } @@ -124,26 +129,6 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements }); } - public override async getRepositories(registry: AzureRegistry): Promise { - const repositories = await super.getRepositories(registry); - const repositoriesWithAdditionalContext = repositories.map(repository => ({ - ...repository, - additionalContextValues: ['azureContainerRepository'] - })); - - return repositoriesWithAdditionalContext; - } - - public override async getTags(repository: AzureRepository): Promise { - const tags = await super.getTags(repository); - const tagsWithAdditionalContext = tags.map(tag => ({ - ...tag, - additionalContextValues: ['azureContainerTag'] - })); - - return tagsWithAdditionalContext; - } - public override getTreeItem(element: CommonRegistryItem): Promise { if (isAzureSubscriptionRegistryItem(element)) { return Promise.resolve({ diff --git a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts index 95d332496f..d4cb4066d4 100644 --- a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts +++ b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts @@ -9,10 +9,6 @@ export interface UnifiedRegistryItem { parent: UnifiedRegistryItem | undefined; } -export function isUnifiedRegistryItem(item: unknown): item is UnifiedRegistryItem { - return !!item && typeof item === 'object' && 'provider' in item && 'wrappedItem' in item && 'parent' in item; -} - const ConnectedRegistryProvidersKey = 'ConnectedRegistryProviders'; export class UnifiedRegistryTreeDataProvider implements vscode.TreeDataProvider> { diff --git a/src/utils/registryExperience.ts b/src/utils/registryExperience.ts index e4a7eb0700..8bb6092ddf 100644 --- a/src/utils/registryExperience.ts +++ b/src/utils/registryExperience.ts @@ -3,22 +3,81 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzureWizardPromptStep, ContextValueFilter, IActionContext, QuickPickWizardContext, RecursiveQuickPickStep, runQuickPickWizard } from '@microsoft/vscode-azext-utils'; -import * as vscode from 'vscode'; +import { AzureWizardPromptStep, ContextValueFilterQuickPickOptions, GenericQuickPickStep, IActionContext, PickFilter, QuickPickWizardContext, RecursiveQuickPickStep, runQuickPickWizard } from '@microsoft/vscode-azext-utils'; +import { CommonRegistryItem } from '@microsoft/vscode-docker-registries'; +import { TreeItem } from 'vscode'; +import { ext } from '../extensionVariables'; +import { UnifiedRegistryItem, UnifiedRegistryTreeDataProvider } from '../tree/registries/UnifiedRegistryTreeDataProvider'; -export async function registryExperience(context: IActionContext, tdp: vscode.TreeDataProvider, contextValueFilter: ContextValueFilter, skipIfOne: boolean = true): Promise { +export interface RegistryFilter { + /** + * This filter will include registry labels that you do want to show in the quick pick. + */ + include?: string[]; + + /** + * This filter will exclude registry labels that you don't want to show in the quick pick. If `exclude` is present, `include` will be ignored. + */ + exclude?: string[]; +} + +export interface RegistryExperienceOptions extends Partial { + // if registryFilter is undefined, all registries will be shown in the quick pick + registryFilter?: RegistryFilter; +} + +export async function registryExperience(context: IActionContext, options?: RegistryExperienceOptions): Promise> { + // get the registry provider unified item const promptSteps: AzureWizardPromptStep[] = [ - new RecursiveQuickPickStep( - tdp, - { - contextValueFilter: contextValueFilter, - skipIfOne: skipIfOne - } - ) + new RegistryQuickPickStep(ext.registriesTree, options) ]; - return await runQuickPickWizard(context, { + if (options?.contextValueFilter) { + promptSteps.push(new RecursiveQuickPickStep(ext.registriesTree, options as ContextValueFilterQuickPickOptions)); + } + + const unifiedRegistryItem = await runQuickPickWizard>(context, { hideStepCount: true, promptSteps: promptSteps, }); + + return unifiedRegistryItem; +} + +export class RegistryPickFilter implements PickFilter { + public constructor(private readonly options: RegistryExperienceOptions) { } + + public isFinalPick(treeItem: TreeItem, element: unknown): boolean { + if (this.options.contextValueFilter) { + return false; + } + + return this.matchesFilters(treeItem.label as string); + } + + public isAncestorPick(treeItem: TreeItem, element: unknown): boolean { + return this.matchesFilters(treeItem.label as string); + } + + private matchesFilters(treeItemLabel: string): boolean { + if (this.options.registryFilter?.exclude) { + return !this.options.registryFilter.exclude.includes(treeItemLabel); + } else if (this.options.registryFilter?.include) { + return this.options.registryFilter.include.includes(treeItemLabel); + } else { + return true; + } + } +} + +export class RegistryQuickPickStep extends GenericQuickPickStep { + public readonly pickFilter: PickFilter; + + public constructor( + protected readonly treeDataProvider: UnifiedRegistryTreeDataProvider, + protected readonly pickOptions: RegistryExperienceOptions, + ) { + super(treeDataProvider, pickOptions); + this.pickFilter = new RegistryPickFilter(pickOptions); + } } From 5dd3dfb9a81553ac3480c5184fdb3651b57a9c23 Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Tue, 19 Sep 2023 09:54:38 -0400 Subject: [PATCH 35/43] remove unnecessary comment --- src/commands/registries/azure/deleteAzureRegistry.ts | 1 - src/commands/registries/azure/deleteAzureRepository.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/commands/registries/azure/deleteAzureRegistry.ts b/src/commands/registries/azure/deleteAzureRegistry.ts index 3125cecb78..949256b195 100644 --- a/src/commands/registries/azure/deleteAzureRegistry.ts +++ b/src/commands/registries/azure/deleteAzureRegistry.ts @@ -12,7 +12,6 @@ import { registryExperience } from '../../../utils/registryExperience'; export async function deleteAzureRegistry(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - // we can't pass in the azure tree provider because it's not a UnifiedRegistryItem and we need the provider to delete node = await registryExperience(context, { contextValueFilter: { include: /commonregistry/i }, registryFilter: { include: [ext.azureRegistryDataProvider.label] } diff --git a/src/commands/registries/azure/deleteAzureRepository.ts b/src/commands/registries/azure/deleteAzureRepository.ts index 07d71f707c..f0fc6bbbef 100644 --- a/src/commands/registries/azure/deleteAzureRepository.ts +++ b/src/commands/registries/azure/deleteAzureRepository.ts @@ -12,7 +12,6 @@ import { registryExperience } from '../../../utils/registryExperience'; export async function deleteAzureRepository(context: IActionContext, node?: UnifiedRegistryItem): Promise { if (!node) { - // we can't pass in the azure tree provider because it's not a UnifiedRegistryItem and we need the provider to delete node = await registryExperience(context, { contextValueFilter: { include: /commonrepository/i }, registryFilter: { include: [ext.azureRegistryDataProvider.label] } From 6bb9eca3746a80c97d3bb7367ea7ec0490c2281e Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:20:27 -0400 Subject: [PATCH 36/43] uploaded & changed azext package version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 091459e8a3..016de2e6b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@microsoft/vscode-azext-azureappservice": "~2.0", "@microsoft/vscode-azext-azureauth": "^1.1.2", "@microsoft/vscode-azext-azureutils": "^2.0.0", - "@microsoft/vscode-azext-utils": "file:../vscode-azuretools/utils/microsoft-vscode-azext-utils-2.1.1.tgz", + "@microsoft/vscode-azext-utils": "^2.1.1", "@microsoft/vscode-container-client": "^0.1.0", "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", "dayjs": "^1.11.7", diff --git a/package.json b/package.json index 64fd844a2a..a19e83b6f2 100644 --- a/package.json +++ b/package.json @@ -2996,7 +2996,7 @@ "@microsoft/vscode-azext-azureappservice": "~2.0", "@microsoft/vscode-azext-azureauth": "^1.1.2", "@microsoft/vscode-azext-azureutils": "^2.0.0", - "@microsoft/vscode-azext-utils": "file:../vscode-azuretools/utils/microsoft-vscode-azext-utils-2.1.1.tgz", + "@microsoft/vscode-azext-utils": "^2.1.1", "@microsoft/vscode-container-client": "^0.1.0", "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", "dayjs": "^1.11.7", From 34e5b001c62542b19e3a8143bfa958dd2db2de2f Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Tue, 19 Sep 2023 16:52:03 -0400 Subject: [PATCH 37/43] Asynchronously fetch createdAt info (#4074) --- .../UnifiedRegistryTreeDataProvider.ts | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts index d4cb4066d4..17e74fe6cb 100644 --- a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts +++ b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts @@ -3,6 +3,15 @@ import * as vscode from 'vscode'; import { ext } from '../../extensionVariables'; import { isAzureSubscriptionRegistryItem } from './Azure/AzureRegistryDataProvider'; +interface WrappedElement { + // eslint-disable-next-line @typescript-eslint/naming-convention + _urtdp_wrapper: UnifiedRegistryItem; +} + +function canReferenceWrapper(item: unknown): item is WrappedElement { + return !!item && typeof item === 'object'; +} + export interface UnifiedRegistryItem { provider: RegistryDataProvider; wrappedItem: T; @@ -33,13 +42,23 @@ export class UnifiedRegistryTreeDataProvider implements vscode.TreeDataProvider< return []; } - return elements.map(e => { - return { + const results: UnifiedRegistryItem[] = []; + + for (const child of elements) { + const wrapper = { provider: element.provider, - wrappedItem: e, + wrappedItem: child, parent: element }; - }); + + if (canReferenceWrapper(child)) { + child._urtdp_wrapper = wrapper; + } + + results.push(wrapper); + } + + return results; } else { const unifiedRoots: UnifiedRegistryItem[] = []; @@ -74,9 +93,17 @@ export class UnifiedRegistryTreeDataProvider implements vscode.TreeDataProvider< public registerProvider(provider: RegistryDataProvider): vscode.Disposable { this.providers.set(provider.id, provider); + const disposable = provider.onDidChangeTreeData((child) => { + if (canReferenceWrapper(child) && child._urtdp_wrapper) { + this.onDidChangeTreeDataEmitter.fire(child._urtdp_wrapper); + } else { + this.onDidChangeTreeDataEmitter.fire(undefined); + } + }); return { dispose: () => { + disposable.dispose(); this.providers.delete(provider.id); } }; From fa2bb67c431c3b6d5e4855cafc72c920afbb1c51 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Tue, 19 Sep 2023 16:52:43 -0400 Subject: [PATCH 38/43] Add registry extensibility capability (#4061) --- package.json | 9 +++++++++ package.nls.json | 1 + src/DockerExtensionApi.ts | 8 +++++++- src/commands/registerCommands.ts | 6 ++++++ src/extension.ts | 4 ++++ 5 files changed, 27 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a19e83b6f2..a10f0c2f97 100644 --- a/package.json +++ b/package.json @@ -141,6 +141,10 @@ { "command": "vscode-docker.containers.group.remove", "when": "never" + }, + { + "command": "vscode-docker.activateRegistryProviders", + "when": "never" } ], "editor/context": [ @@ -2747,6 +2751,11 @@ "title": "%vscode-docker.commands.contexts.help%", "category": "%vscode-docker.commands.category.contexts%", "icon": "$(question)" + }, + { + "command": "vscode-docker.activateRegistryProviders", + "title": "%vscode-docker.commands.activateRegistryProviders%", + "category": "%vscode-docker.commands.category.docker%" } ], "views": { diff --git a/package.nls.json b/package.nls.json index e262a068c3..88dc4fe915 100644 --- a/package.nls.json +++ b/package.nls.json @@ -293,6 +293,7 @@ "vscode-docker.commands.contexts.configureExplorer": "Configure Explorer...", "vscode-docker.commands.contexts.refresh": "Refresh", "vscode-docker.commands.contexts.help": "Docker Context Help", + "vscode-docker.commands.activateRegistryProviders": "Activate Registry Providers...", "vscode-docker.commands.category.docker": "Docker", "vscode-docker.commands.category.dockerContainers": "Docker Containers", "vscode-docker.commands.category.dockerImages": "Docker Images", diff --git a/src/DockerExtensionApi.ts b/src/DockerExtensionApi.ts index d623650664..082f590a74 100644 --- a/src/DockerExtensionApi.ts +++ b/src/DockerExtensionApi.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DockerExtensionApi as DockerExtensionRegistryApi, RegistryDataProvider, RegistryItem } from '@microsoft/vscode-docker-registries'; import * as vscode from 'vscode'; +import { ext } from './extensionVariables'; -export class DockerExtensionApi implements MementoExplorerExport { +export class DockerExtensionApi implements MementoExplorerExport, DockerExtensionRegistryApi { readonly #extensionMementos: ExtensionMementos | undefined; public constructor(ctx: vscode.ExtensionContext) { @@ -18,6 +20,10 @@ export class DockerExtensionApi implements MementoExplorerExport { } } + public registerRegistryDataProvider(id: string, registryDataProvider: RegistryDataProvider): vscode.Disposable { + return ext.registriesTree.registerProvider(registryDataProvider); + } + public get memento(): ExtensionMementos | undefined { return this.#extensionMementos; } diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index ee589a2dae..e317a03568 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -207,4 +207,10 @@ export function registerCommands(): void { registerCommand('vscode-docker.openDockerDownloadPage', openDockerDownloadPage); registerCommand('vscode-docker.help', help); registerCommand('vscode-docker.help.openWalkthrough', () => commands.executeCommand('workbench.action.openWalkthrough', 'ms-azuretools.vscode-docker#dockerStart')); + + registerCommand('vscode-docker.activateRegistryProviders', (context: IActionContext) => { + // Do nothing, but registry provider extensions can use this command as an activation event + context.telemetry.suppressAll = true; + context.errorHandling.suppressDisplay = true; + }); } diff --git a/src/extension.ts b/src/extension.ts index e3a70c3181..3f814e2fdc 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -135,6 +135,10 @@ export async function activateInternal(ctx: vscode.ExtensionContext, perfStats: // Don't wait void migrateOldEnvironmentSettingsIfNeeded(); + // Call command to activate registry provider extensions + // Don't wait + void vscode.commands.executeCommand('vscode-docker.activateRegistryProviders'); + return new DockerExtensionApi(ctx); } From 7b831f42516a1a415a51c93df289daf67d421f47 Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:05:58 -0400 Subject: [PATCH 39/43] remove generic v2 root if it's the last one --- package-lock.json | 2 +- .../registries/genericV2/removeTrackedGenericV2Registry.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 016de2e6b5..5e1d9be9e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -815,7 +815,7 @@ "node_modules/@microsoft/vscode-docker-registries": { "version": "0.1.0", "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", - "integrity": "sha512-af963bHhdUX51Rn3rSGSRD1qO/qeUxcoRbTzvA/c8R9GJdDqtRzgyaOu57ETzHyG/fUfd4qaDCMs2FNI9HNk5g==", + "integrity": "sha512-MryHAYc4dfe9DoYZ6XWA3J7aP2KsZLsnVrYwsTAQR8vtUUGH98LDAmb5ZH/9f1d67FgSQhP3GUfj6eTioQjjnA==", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", diff --git a/src/commands/registries/genericV2/removeTrackedGenericV2Registry.ts b/src/commands/registries/genericV2/removeTrackedGenericV2Registry.ts index 9c9815b3a5..93dbe84464 100644 --- a/src/commands/registries/genericV2/removeTrackedGenericV2Registry.ts +++ b/src/commands/registries/genericV2/removeTrackedGenericV2Registry.ts @@ -18,6 +18,12 @@ export async function removeTrackedGenericV2Registry(context: IActionContext, no } await ext.genericRegistryV2DataProvider.removeTrackedRegistry(node.wrappedItem); + + // remove the provider if it's the last one + if ((await ext.genericRegistryV2DataProvider.getRegistries(node.parent.wrappedItem)).length === 0) { + await ext.registriesTree.disconnectRegistryProvider(node.parent); + } + // don't wait void ext.registriesTree.refresh(); } From 4dd8f3af9a16867c0d5c5b7bfbd2976f2394ac41 Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:17:44 -0400 Subject: [PATCH 40/43] get registries package from npm --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5e1d9be9e0..7c9d69062c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@microsoft/vscode-azext-azureutils": "^2.0.0", "@microsoft/vscode-azext-utils": "^2.1.1", "@microsoft/vscode-container-client": "^0.1.0", - "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", + "@microsoft/vscode-docker-registries": "^0.1.0", "dayjs": "^1.11.7", "dockerfile-language-server-nodejs": "^0.10.2", "fs-extra": "^11.1.1", diff --git a/package.json b/package.json index a10f0c2f97..0363c6513c 100644 --- a/package.json +++ b/package.json @@ -3007,7 +3007,7 @@ "@microsoft/vscode-azext-azureutils": "^2.0.0", "@microsoft/vscode-azext-utils": "^2.1.1", "@microsoft/vscode-container-client": "^0.1.0", - "@microsoft/vscode-docker-registries": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", + "@microsoft/vscode-docker-registries": "^0.1.0", "dayjs": "^1.11.7", "dockerfile-language-server-nodejs": "^0.10.2", "fs-extra": "^11.1.1", From cf174be53bd87aec3993840313abc9284273ce43 Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Thu, 21 Sep 2023 08:33:18 -0400 Subject: [PATCH 41/43] Trigger Build From 4971bb70676546fc6838ddace878e8098c8f0c7d Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Thu, 21 Sep 2023 08:38:10 -0400 Subject: [PATCH 42/43] actually get npm from npm org --- package-lock.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7c9d69062c..9e82d4d753 100644 --- a/package-lock.json +++ b/package-lock.json @@ -814,9 +814,8 @@ }, "node_modules/@microsoft/vscode-docker-registries": { "version": "0.1.0", - "resolved": "file:../vscode-docker-extensibility/packages/vscode-docker-registries/microsoft-vscode-docker-registries-0.1.0.tgz", - "integrity": "sha512-MryHAYc4dfe9DoYZ6XWA3J7aP2KsZLsnVrYwsTAQR8vtUUGH98LDAmb5ZH/9f1d67FgSQhP3GUfj6eTioQjjnA==", - "license": "See LICENSE in the project root for license information.", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-docker-registries/-/vscode-docker-registries-0.1.0.tgz", + "integrity": "sha512-d9LXqxs0JDDz0RnORfPy6ouECEA3ujxdOiXYUjysP8qaADnFfaVeOmDJylAFtwEqlz3UwmXDp+SRaOLk10BViQ==", "dependencies": { "dayjs": "^1.11.7", "node-fetch": "^2.6.11" From 2b20b9a530db0290a55e4767c15297fdac9a5082 Mon Sep 17 00:00:00 2001 From: alexyaang <59073590+alexyaang@users.noreply.github.com> Date: Thu, 21 Sep 2023 08:41:03 -0400 Subject: [PATCH 43/43] update azext util package to use npm org --- package-lock.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9e82d4d753..38a8e790ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -772,9 +772,8 @@ }, "node_modules/@microsoft/vscode-azext-utils": { "version": "2.1.1", - "resolved": "file:../vscode-azuretools/utils/microsoft-vscode-azext-utils-2.1.1.tgz", - "integrity": "sha512-be22iQu1IyWUjKrB6WhbmtsffZ469xRFqjRQwkBzXdwI15j7W2bRpFsyadPHxuIVjzFTcZpmB7a5R8X93UvhZQ==", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-utils/-/vscode-azext-utils-2.1.1.tgz", + "integrity": "sha512-MUrBET1YzS0QmpMY9ZcRjZe9/U0vmGtYI9hRTMB7NYBtutUF19+Y7W6k6G7Odlnn3MjRIrc8ZMmumvsVqlxhPg==", "dependencies": { "@microsoft/vscode-azureresources-api": "^2.0.4", "@vscode/extension-telemetry": "^0.6.2",