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');