diff --git a/extension.bundle.ts b/extension.bundle.ts index f27dde214..5f27d2195 100644 --- a/extension.bundle.ts +++ b/extension.bundle.ts @@ -31,6 +31,7 @@ export * from './src/commands/ingress/tryGetDockerfileExposePorts'; export { activate, deactivate } from './src/extension'; export * from './src/extensionVariables'; export * from './src/utils/azureClients'; +export * from './src/utils/imageNameUtils'; export * from './src/utils/settingUtils'; export * from './src/utils/validateUtils'; diff --git a/src/commands/image/deployImageApi/deployImage.ts b/src/commands/image/deployImageApi/deployImage.ts index 33432a520..35b138483 100644 --- a/src/commands/image/deployImageApi/deployImage.ts +++ b/src/commands/image/deployImageApi/deployImage.ts @@ -3,14 +3,16 @@ * Licensed under the MIT License. See License.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzureWizard, createSubscriptionContext, type AzureWizardExecuteStep, type AzureWizardPromptStep, type IActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils"; +import { AzureWizard, createSubscriptionContext, type AzureWizardPromptStep, type IActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils"; import { type ContainerAppItem } from "../../../tree/ContainerAppItem"; import { createActivityContext } from "../../../utils/activityUtils"; import { getManagedEnvironmentFromContainerApp } from "../../../utils/getResourceUtils"; import { getVerifyProvidersStep } from "../../../utils/getVerifyProvidersStep"; +import { getImageNameWithoutTag } from "../../../utils/imageNameUtils"; import { localize } from "../../../utils/localize"; import { ContainerAppOverwriteConfirmStep } from "../../ContainerAppOverwriteConfirmStep"; import { showContainerAppNotification } from "../../createContainerApp/showContainerAppNotification"; +import { IngressPromptStep } from "../../ingress/IngressPromptStep"; import { ContainerAppUpdateStep } from "../imageSource/ContainerAppUpdateStep"; import { ImageSourceListStep } from "../imageSource/ImageSourceListStep"; import { type ContainerRegistryImageSourceContext } from "../imageSource/containerRegistry/ContainerRegistryImageSourceContext"; @@ -33,18 +35,21 @@ export async function deployImage(context: IActionContext & Partial[] = [ new ImageSourceListStep(), - new ContainerAppOverwriteConfirmStep(), ]; - const executeSteps: AzureWizardExecuteStep[] = [ - getVerifyProvidersStep(), - new ContainerAppUpdateStep() - ]; + // If more than the image tag changed, prompt for ingress again + if (getImageNameWithoutTag(wizardContext.containerApp?.template?.containers?.[0].image ?? '') !== getImageNameWithoutTag(wizardContext.image ?? '')) { + promptSteps.push(new IngressPromptStep()); + } + promptSteps.push(new ContainerAppOverwriteConfirmStep()); const wizard: AzureWizard = new AzureWizard(wizardContext, { title: localize('deploy', 'Deploy image to container app "{0}"', containerApp.name), promptSteps, - executeSteps, + executeSteps: [ + getVerifyProvidersStep(), + new ContainerAppUpdateStep(), + ], showLoadingPrompt: true }); diff --git a/src/utils/imageNameUtils.ts b/src/utils/imageNameUtils.ts index b5508652a..01db907f5 100644 --- a/src/utils/imageNameUtils.ts +++ b/src/utils/imageNameUtils.ts @@ -51,6 +51,10 @@ export function parseImageName(imageName?: string): ParsedImageName { }; } +export function getImageNameWithoutTag(imageName: string): string { + return imageName.replace(/:[^:]*$/, ''); +} + /** * @param registryName When parsed from a full image name, everything before the first slash */ diff --git a/test/imageNameUtils.test.ts b/test/imageNameUtils.test.ts new file mode 100644 index 000000000..5577449e6 --- /dev/null +++ b/test/imageNameUtils.test.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from "assert"; +import { getImageNameWithoutTag } from "../extension.bundle"; + +suite('imageNameUtils', () => { + test('getImageNameWithoutTag', () => { + const testValues: string[] = [ + '', + 'myimage:latest', + 'repository/name:1.0.0', + 'custom-registry.com/myimage:v2', + 'library/ubuntu:20.04', + 'project/service-name:dev-build', + 'docker.io/library/nginx:stable', + 'my-repo/my-service:release-candidate', + 'anotherrepo/anotherimage:test', + 'image_without_tag', + 'my-image:with:multiple:colons', + 'some-registry.io/nested/repo/image:12345', + 'edgecase-without-tag:', + 'dockerhub.com/image-name:alpha-beta', + 'registry.example.com:5000/repo/image:tagname', + 'private-repo/myimage:', + 'test-image-with-special-characters:beta@123', + 'path/to/image:no-colon-in-name', + 'simple-image:another:tag', + 'example.com:8080/repo/image:v3' + ]; + + const expectedValues: string[] = [ + '', + 'myimage', + 'repository/name', + 'custom-registry.com/myimage', + 'library/ubuntu', + 'project/service-name', + 'docker.io/library/nginx', + 'my-repo/my-service', + 'anotherrepo/anotherimage', + 'image_without_tag', + 'my-image:with:multiple', + 'some-registry.io/nested/repo/image', + 'edgecase-without-tag', + 'dockerhub.com/image-name', + 'registry.example.com:5000/repo/image', + 'private-repo/myimage', + 'test-image-with-special-characters', + 'path/to/image', + 'simple-image:another', + 'example.com:8080/repo/image' + ]; + + for (let i = 0; i < testValues.length; i++) { + assert.equal(getImageNameWithoutTag(testValues[i]), expectedValues[i]); + } + }); +});