Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes pushImage() #4062

Merged
merged 1 commit into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions src/commands/images/pushImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
Expand Down Expand Up @@ -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);
Expand All @@ -80,13 +78,10 @@ export async function pushImage(context: IActionContext, node: ImageTreeItem | u
}

async function tryGetConnectedRegistryForPath(context: IActionContext, baseImagePath: string): Promise<UnifiedRegistryItem<CommonRegistry> | 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'] });
Expand Down
4 changes: 2 additions & 2 deletions src/commands/images/tagImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<CommonRegistry>): Promise<string> {
if (!node) {
Expand All @@ -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');

Expand Down
4 changes: 2 additions & 2 deletions src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<IAppServiceWizardContext> {
Expand Down Expand Up @@ -42,7 +42,7 @@ export class DockerAssignAcrPullRoleStep extends AzureWizardExecuteStep<IAppServ

if (!(registry?.id)) {
throw new Error(
l10n.t('Unable to get details from Container Registry {0}', getBaseUrlFromItem(registryTreeItem.wrappedItem))
l10n.t('Unable to get details from Container Registry {0}', registryTreeItem.wrappedItem.label)
);
}

Expand Down
10 changes: 4 additions & 6 deletions src/tree/registries/UnifiedRegistryTreeDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,22 +138,20 @@ export class UnifiedRegistryTreeDataProvider implements vscode.TreeDataProvider<
* @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<UnifiedRegistryItem<CommonRegistry>[]> {
public async getConnectedRegistries(imageBaseName?: string): Promise<UnifiedRegistryItem<CommonRegistry>[]> {
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 {
Expand Down
77 changes: 67 additions & 10 deletions src/tree/registries/registryTreeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand All @@ -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');
Expand Down