Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class ContainerRegistryListStep extends AzureWizardPromptStep<IContainerR
const placeHolder: string = localize('selectTag', 'Select a container registry');
const picks: IAzureQuickPickItem<SupportedRegistries | undefined>[] = [];

picks.push({ label: 'Azure Container Registries', data: acrDomain });
picks.push({ label: 'Azure Container Registry', data: acrDomain });
if (env.uiKind === UIKind.Desktop) {
// this will fails in vscode.dev due to browser CORS access policies
picks.push({ label: 'Docker Hub Registry', data: dockerHubDomain });
Expand All @@ -35,7 +35,7 @@ export class ContainerRegistryListStep extends AzureWizardPromptStep<IContainerR
}

public shouldPrompt(context: IContainerRegistryImageContext): boolean {
return !context.tag && !context.image;
return !context.image && !context.registryDomain;
}

public async getSubWizard(context: IContainerRegistryImageContext): Promise<IWizardOptions<IContainerRegistryImageContext> | undefined> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,32 @@
*--------------------------------------------------------------------------------------------*/

import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils";
import { acrDomain } from "../../../constants";
import { parseImageName } from "../../../utils/imageNameUtils";
import { localize } from "../../../utils/localize";
import { IContainerRegistryImageContext } from "./IContainerRegistryImageContext";
import { getLatestContainerAppImage } from "./getLatestContainerImage";

export class RegistryImageInputStep extends AzureWizardPromptStep<IContainerRegistryImageContext> {
public async prompt(context: IContainerRegistryImageContext): Promise<void> {
const prompt: string = localize('registryImagePrompt', 'Enter the container image with tag');
const placeHolder: string = localize('registryImagePlaceHolder', 'For example: `mcr.microsoft.com/azuredocs/containerapps-helloworld:latest`')
const placeHolder: string = localize('registryImagePlaceHolder', 'For example: `mcr.microsoft.com/azuredocs/containerapps-helloworld:latest`');

// Try to suggest an image name only when the user is deploying to a Container App
let value: string | undefined;
if (context.targetContainer) {
const { registryDomain, imageNameReference } = parseImageName(getLatestContainerAppImage(context.targetContainer));

// Only bother carrying over the suggestion if the old image was from a third party registry
if (registryDomain !== acrDomain) {
value = imageNameReference;
}
}

context.image = (await context.ui.showInputBox({
prompt,
placeHolder,
value
})).trim();

context.valuesToMask.push(context.image);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { QuickPickItem } from "vscode";
import { loadMoreQp, QuickPicksCache } from "../../../../constants";
import type { QuickPickItem } from "vscode";
import { currentlyDeployed, dockerHubDomain, loadMoreQp, QuickPicksCache, quickStartImageName } from "../../../../constants";
import { parseImageName } from "../../../../utils/imageNameUtils";
import { localize } from "../../../../utils/localize";
import { nonNullProp } from "../../../../utils/nonNull";
import { getLatestContainerAppImage } from "../getLatestContainerImage";
import { IContainerRegistryImageContext } from "../IContainerRegistryImageContext";
import { RegistryRepositoriesListStepBase } from "../RegistryRepositoriesListBaseStep";
import { getReposForNamespace } from "./DockerHubV2ApiCalls";
import type { DockerHubV2Repository } from "./DockerHubV2Types";

export class DockerHubContainerRepositoryListStep extends RegistryRepositoriesListStepBase {
public async getPicks(context: IContainerRegistryImageContext, cachedPicks: QuickPicksCache): Promise<QuickPickItem[]> {
Expand All @@ -19,7 +22,33 @@ export class DockerHubContainerRepositoryListStep extends RegistryRepositoriesLi
await context.ui.showWarningMessage(localize('noRepos', 'Unable to find any repositories associated to namespace "{0}"', context.dockerHubNamespace), { modal: true });
}

cachedPicks.cache.push(...response.results.map((r) => { return { label: r.name, description: r.description } }));
// Try to suggest a repository only when the user is deploying to a Container App
let suggestedRepository: string | undefined;
let srExists: boolean = false;
if (context.targetContainer) {
const { registryDomain, repositoryName, imageNameReference } = parseImageName(getLatestContainerAppImage(context.targetContainer));

// If the image is not the default quickstart image, then we can try to suggest a repository based on the latest Container App image
if (registryDomain === dockerHubDomain && imageNameReference !== quickStartImageName) {
suggestedRepository = repositoryName;
}

// Does the suggested repositoryName exist in the list of pulled repositories? If so, move it to the front of the list
const srIndex: number = response.results.findIndex((r) => !!suggestedRepository && r.name === suggestedRepository);
srExists = srIndex !== -1;
if (srExists) {
const sr: DockerHubV2Repository = response.results.splice(srIndex, 1)[0];
response.results.unshift(sr);
}
}

// Preferring 'suppressPersistence: true' over 'priority: highest' to avoid the possibility of a double parenthesis appearing in the description
const quickPicks: QuickPickItem[] = response.results.map((r) => {
return !!suggestedRepository && r.name === suggestedRepository ?
{ label: r.name, description: r.description ? `${r.description} ${currentlyDeployed}` : currentlyDeployed, suppressPersistence: true } :
{ label: r.name, description: r.description, suppressPersistence: srExists }
});
cachedPicks.cache.push(...quickPicks);

if (response.next) {
cachedPicks.next = response.next;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
*--------------------------------------------------------------------------------------------*/

import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils";
import { dockerHubDomain, quickStartImageName } from "../../../../constants";
import { parseImageName } from "../../../../utils/imageNameUtils";
import { localize } from "../../../../utils/localize";
import { IContainerRegistryImageContext } from "../IContainerRegistryImageContext";
import { getLatestContainerAppImage } from "../getLatestContainerImage";

let checkNameLength: boolean = false;
export class DockerHubNamespaceInputStep extends AzureWizardPromptStep<IContainerRegistryImageContext> {
public async prompt(context: IContainerRegistryImageContext): Promise<void> {
const prompt: string = localize('dockerHubNamespacePrompt', 'Enter a Docker Hub namespace');
context.dockerHubNamespace = (await context.ui.showInputBox({
prompt,
value: 'library',
value: this.getSuggestedNamespace(context),
validateInput: async (value: string | undefined): Promise<string | undefined> => await this.validateInput(value)
})).toLowerCase();

Expand All @@ -26,16 +28,29 @@ export class DockerHubNamespaceInputStep extends AzureWizardPromptStep<IContaine

private async validateInput(name: string | undefined): Promise<string | undefined> {
name = name ? name.trim() : '';
// to prevent showing an error when the character types the first letter
checkNameLength = checkNameLength || name.length > 1;

const { minLength, maxLength } = { minLength: 4, maxLength: 30 };
if (/\W/.test(name)) {
return localize('invalidNamespace', `A namespace name should only contain letters and/or numbers.`);
} else if ((checkNameLength && name.length < minLength) || name.length > maxLength) {
} else if (name.length < minLength || name.length > maxLength) {
return localize('invalidLength', 'The name must be between {0} and {1} characters.', minLength, maxLength);
}

return undefined;
}

private getSuggestedNamespace(context: IContainerRegistryImageContext): string {
// Try to suggest a namespace only when the user is deploying to a Container App
let suggestedNamespace: string | undefined;
if (context.targetContainer) {
const { registryDomain, namespace, imageNameReference } = parseImageName(getLatestContainerAppImage(context.targetContainer));

// If the image is not the default quickstart image, then we can try to suggest a namespace based on the latest Container App image
if (registryDomain === dockerHubDomain && imageNameReference !== quickStartImageName) {
suggestedNamespace = namespace;
}
}

return suggestedNamespace || 'library';
}
}