diff --git a/src/commands/deployWorkspaceProject/DeployWorkspaceProjectConfirmStep.ts b/src/commands/deployWorkspaceProject/DeployWorkspaceProjectConfirmStep.ts index eba4d8ee0..236c25d34 100644 --- a/src/commands/deployWorkspaceProject/DeployWorkspaceProjectConfirmStep.ts +++ b/src/commands/deployWorkspaceProject/DeployWorkspaceProjectConfirmStep.ts @@ -41,8 +41,8 @@ export class DeployWorkspaceProjectConfirmStep extends OverwriteConfirmStepBase< resourcesToCreate.join(', ') ); } else { - confirmMessage = localize('overwriteConfirm', 'The latest deployment of container app "{0}" and the latest image of registry "{1}" will be overwritten.', context.containerApp?.name, context.registry?.name); - outputMessage = localize('overwriteConfirmed', 'User confirmed overwrite of container app "{0}" and the latest image of registry "{1}".', context.containerApp?.name, context.registry?.name); + confirmMessage = localize('overwriteConfirm', 'The latest deployment of container app "{0}" will be overwritten.', context.containerApp?.name); + outputMessage = localize('overwriteConfirmed', 'User confirmed overwrite of container app "{0}".', context.containerApp?.name); } if (this.hasUnsupportedFeatures(context)) { diff --git a/src/commands/deployWorkspaceProject/DeployWorkspaceProjectContext.ts b/src/commands/deployWorkspaceProject/DeployWorkspaceProjectContext.ts index f30a4eda1..2263707c1 100644 --- a/src/commands/deployWorkspaceProject/DeployWorkspaceProjectContext.ts +++ b/src/commands/deployWorkspaceProject/DeployWorkspaceProjectContext.ts @@ -8,7 +8,7 @@ import type { SetTelemetryProps } from "../../telemetry/SetTelemetryProps"; import type { DeployWorkspaceProjectTelemetryProps as TelemetryProps } from "../../telemetry/commandTelemetryProps"; import type { CreateContainerAppBaseContext } from "../createContainerApp/CreateContainerAppContext"; import type { IManagedEnvironmentContext } from "../createManagedEnvironment/IManagedEnvironmentContext"; -import { BuildImageInAzureImageSourceBaseContext } from "../image/imageSource/buildImageInAzure/BuildImageInAzureContext"; +import { BuildImageInAzureImageSourceBaseContext } from "../image/imageSource/buildImageInAzure/BuildImageInAzureImageSourceContext"; import type { CreateAcrContext } from "../image/imageSource/containerRegistry/acr/createAcr/CreateAcrContext"; // Use intersection typing instead of an interface here to bypass some minor (relatively trivial) type mismatch issues introduced by having to use the 'Partial' utility diff --git a/src/commands/deployWorkspaceProject/getDefaultValues/DefaultResourcesNameStep.ts b/src/commands/deployWorkspaceProject/getDefaultValues/DefaultResourcesNameStep.ts index 0eb0ce90f..a77185bd8 100644 --- a/src/commands/deployWorkspaceProject/getDefaultValues/DefaultResourcesNameStep.ts +++ b/src/commands/deployWorkspaceProject/getDefaultValues/DefaultResourcesNameStep.ts @@ -11,6 +11,7 @@ import { localize } from "../../../utils/localize"; import { validateUtils } from "../../../utils/validateUtils"; import { ContainerAppNameStep } from "../../createContainerApp/ContainerAppNameStep"; import { ManagedEnvironmentNameStep } from "../../createManagedEnvironment/ManagedEnvironmentNameStep"; +import { ImageNameStep } from "../../image/imageSource/buildImageInAzure/ImageNameStep"; import { RegistryNameStep } from "../../image/imageSource/containerRegistry/acr/createAcr/RegistryNameStep"; import type { DeployWorkspaceProjectContext } from "../DeployWorkspaceProjectContext"; @@ -32,7 +33,7 @@ export class DefaultResourcesNameStep extends AzureWizardPromptStep { @@ -55,7 +56,7 @@ export class DefaultResourcesNameStep extends AzureWizardPromptStep { diff --git a/src/commands/image/imageSource/buildImageInAzure/DockerFileItemStep.ts b/src/commands/image/imageSource/buildImageInAzure/DockerFileItemStep.ts index b013831b9..7eba9c819 100644 --- a/src/commands/image/imageSource/buildImageInAzure/DockerFileItemStep.ts +++ b/src/commands/image/imageSource/buildImageInAzure/DockerFileItemStep.ts @@ -6,7 +6,7 @@ import { AzureWizardPromptStep, nonNullValue } from '@microsoft/vscode-azext-utils'; import { dockerFilePick, dockerfileGlobPattern } from "../../../../constants"; import { selectWorkspaceFile } from "../../../../utils/workspaceUtils"; -import { BuildImageInAzureImageSourceContext } from './BuildImageInAzureContext'; +import { BuildImageInAzureImageSourceContext } from './BuildImageInAzureImageSourceContext'; export class DockerFileItemStep extends AzureWizardPromptStep { public async prompt(context: BuildImageInAzureImageSourceContext): Promise { diff --git a/src/commands/image/imageSource/buildImageInAzure/ImageNameStep.ts b/src/commands/image/imageSource/buildImageInAzure/ImageNameStep.ts index 00aab5465..dd1607a04 100644 --- a/src/commands/image/imageSource/buildImageInAzure/ImageNameStep.ts +++ b/src/commands/image/imageSource/buildImageInAzure/ImageNameStep.ts @@ -3,17 +3,21 @@ * Licensed under the MIT License. See License.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzureWizardPromptStep } from "@microsoft/vscode-azext-utils"; -import { URI, Utils } from "vscode-uri"; +import { AzureWizardPromptStep, nonNullProp } from "@microsoft/vscode-azext-utils"; import { localize } from "../../../../utils/localize"; import { validateUtils } from "../../../../utils/validateUtils"; -import { BuildImageInAzureImageSourceContext } from "./BuildImageInAzureContext"; +import { CreateContainerAppContext } from "../../../createContainerApp/CreateContainerAppContext"; +import type { BuildImageInAzureImageSourceContext } from "./BuildImageInAzureImageSourceContext"; const maxImageNameLength: number = 46; export class ImageNameStep extends AzureWizardPromptStep { public async prompt(context: BuildImageInAzureImageSourceContext): Promise { - const suggestedImageName = await getSuggestedName(context, context.rootFolder.name); + const suggestedImageName = ImageNameStep.getTimestampedImageName( + context.containerApp?.name || + // Step is also technically reachable from the `createContainerApp` entry point + nonNullProp((context as CreateContainerAppContext), 'newContainerAppName') + ); context.imageName = (await context.ui.showInputBox({ prompt: localize('imageNamePrompt', 'Enter a name for the image'), @@ -35,19 +39,22 @@ export class ImageNameStep extends AzureWizardPromptStep { - let suggestedImageName: string | undefined; - suggestedImageName = Utils.dirname(URI.parse(dockerFilePath)).path.split('/').pop(); - if (suggestedImageName === '') { - if (context.rootFolder) { - suggestedImageName = Utils.basename(context.rootFolder.uri).toLowerCase().replace(/\s/g, ''); - } + static getTimestampedImageName(repositoryName: string): string { + const tag: string = getTimestampTag(); + return repositoryName.slice(0, maxImageNameLength - (tag.length + 1)) + ':' + tag; } +} + +function getTimestampTag(): string { + const now = new Date(); + + const year = now.getFullYear(); + const month = (now.getMonth() + 1).toString().padStart(2, '0'); // Months start at 0, so add 1 + const day = now.getDate().toString().padStart(2, '0'); + const hours = now.getHours().toString().padStart(2, '0'); + const minutes = now.getMinutes().toString().padStart(2, '0'); + const seconds = now.getSeconds().toString().padStart(2, '0'); - const colonTag: string = ':latest'; - suggestedImageName = suggestedImageName?.slice(0, maxImageNameLength - colonTag.length); - suggestedImageName += colonTag; - return suggestedImageName; + return `${year}-${month}-${day}_${hours}${minutes}${seconds}`; } diff --git a/src/commands/image/imageSource/buildImageInAzure/OSPickStep.ts b/src/commands/image/imageSource/buildImageInAzure/OSPickStep.ts index 658486ddc..6704d2201 100644 --- a/src/commands/image/imageSource/buildImageInAzure/OSPickStep.ts +++ b/src/commands/image/imageSource/buildImageInAzure/OSPickStep.ts @@ -5,7 +5,7 @@ import { AzureWizardPromptStep, IAzureQuickPickItem } from "@microsoft/vscode-azext-utils"; import { localize } from "../../../../utils/localize"; -import { BuildImageInAzureImageSourceContext } from "./BuildImageInAzureContext"; +import { BuildImageInAzureImageSourceContext } from "./BuildImageInAzureImageSourceContext"; export enum AcrBuildSupportedOS { Windows = 'Windows', diff --git a/src/commands/image/imageSource/buildImageInAzure/RootFolderStep.ts b/src/commands/image/imageSource/buildImageInAzure/RootFolderStep.ts index 609b98238..ffb0c15de 100644 --- a/src/commands/image/imageSource/buildImageInAzure/RootFolderStep.ts +++ b/src/commands/image/imageSource/buildImageInAzure/RootFolderStep.ts @@ -6,7 +6,7 @@ import { AzureWizardPromptStep, UserCancelledError } from '@microsoft/vscode-aze import * as vscode from 'vscode'; import { isAzdWorkspaceProject } from '../../../../utils/azdUtils'; import { localize } from '../../../../utils/localize'; -import { BuildImageInAzureImageSourceContext } from './BuildImageInAzureContext'; +import { BuildImageInAzureImageSourceContext } from './BuildImageInAzureImageSourceContext'; export class RootFolderStep extends AzureWizardPromptStep { public async prompt(context: BuildImageInAzureImageSourceContext): Promise { diff --git a/src/commands/image/imageSource/buildImageInAzure/RunStep.ts b/src/commands/image/imageSource/buildImageInAzure/RunStep.ts index 40dd2fbf1..ee7ab19f4 100644 --- a/src/commands/image/imageSource/buildImageInAzure/RunStep.ts +++ b/src/commands/image/imageSource/buildImageInAzure/RunStep.ts @@ -11,7 +11,7 @@ import { activityFailContext, activityFailIcon } from "../../../../constants"; import { ExecuteActivityOutput, ExecuteActivityOutputStepBase } from "../../../../utils/activity/ExecuteActivityOutputStepBase"; import { createActivityChildContext } from "../../../../utils/activity/activityUtils"; import { localize } from "../../../../utils/localize"; -import { BuildImageInAzureImageSourceContext } from "./BuildImageInAzureContext"; +import { BuildImageInAzureImageSourceContext } from "./BuildImageInAzureImageSourceContext"; export class RunStep extends ExecuteActivityOutputStepBase { public priority: number = 440; diff --git a/src/commands/image/imageSource/buildImageInAzure/TarFileStep.ts b/src/commands/image/imageSource/buildImageInAzure/TarFileStep.ts index a3fdf6b8b..45615c9c2 100644 --- a/src/commands/image/imageSource/buildImageInAzure/TarFileStep.ts +++ b/src/commands/image/imageSource/buildImageInAzure/TarFileStep.ts @@ -6,7 +6,7 @@ import { AzureWizardExecuteStep } from "@microsoft/vscode-azext-utils"; import * as os from 'os'; import { URI, Utils } from "vscode-uri"; -import { BuildImageInAzureImageSourceContext } from "./BuildImageInAzureContext"; +import { BuildImageInAzureImageSourceContext } from "./BuildImageInAzureImageSourceContext"; const idPrecision = 6; diff --git a/src/commands/image/imageSource/buildImageInAzure/UploadSourceCodeStep.ts b/src/commands/image/imageSource/buildImageInAzure/UploadSourceCodeStep.ts index 1c6989ead..c82ade29b 100644 --- a/src/commands/image/imageSource/buildImageInAzure/UploadSourceCodeStep.ts +++ b/src/commands/image/imageSource/buildImageInAzure/UploadSourceCodeStep.ts @@ -13,7 +13,7 @@ import { ExecuteActivityOutput, ExecuteActivityOutputStepBase } from '../../../. import { createActivityChildContext } from '../../../../utils/activity/activityUtils'; import { createContainerRegistryManagementClient } from '../../../../utils/azureClients'; import { localize } from '../../../../utils/localize'; -import { BuildImageInAzureImageSourceContext } from './BuildImageInAzureContext'; +import { BuildImageInAzureImageSourceContext } from './BuildImageInAzureImageSourceContext'; const vcsIgnoreList = ['.git', '.gitignore', '.bzr', 'bzrignore', '.hg', '.hgignore', '.svn']; diff --git a/src/commands/image/imageSource/buildImageInAzure/buildImageInAzure.ts b/src/commands/image/imageSource/buildImageInAzure/buildImageInAzure.ts index 62574a745..40fc3800b 100644 --- a/src/commands/image/imageSource/buildImageInAzure/buildImageInAzure.ts +++ b/src/commands/image/imageSource/buildImageInAzure/buildImageInAzure.ts @@ -7,7 +7,7 @@ import type { Run as AcrRun } from '@azure/arm-containerregistry'; import { KnownRunStatus } from '@azure/arm-containerregistry'; import { nonNullValue } from '@microsoft/vscode-azext-utils'; import { delay } from '../../../../utils/delay'; -import { BuildImageInAzureImageSourceContext } from './BuildImageInAzureContext'; +import { BuildImageInAzureImageSourceContext } from './BuildImageInAzureImageSourceContext'; const WAIT_MS = 5000; diff --git a/src/commands/image/updateImage/UpdateImageDraftStep.ts b/src/commands/image/updateImage/UpdateImageDraftStep.ts index 337fd47cb..009598650 100644 --- a/src/commands/image/updateImage/UpdateImageDraftStep.ts +++ b/src/commands/image/updateImage/UpdateImageDraftStep.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import type { Revision } from "@azure/arm-appcontainers"; -import { nonNullProp, randomUtils } from "@microsoft/vscode-azext-utils"; +import { nonNullProp } from "@microsoft/vscode-azext-utils"; import type { Progress } from "vscode"; import { ext } from "../../../extensionVariables"; import type { ContainerAppItem, ContainerAppModel } from "../../../tree/ContainerAppItem"; @@ -29,8 +29,7 @@ export class UpdateImageDraftStep extends Revision this.revisionDraftTemplate.containers.push({ env: context.environmentVariables, image: context.image, - // We need the revision draft to always show up as having unsaved changes, we can ensure this by adding a unique ID at end of the container name - name: getContainerNameForImage(nonNullProp(context, 'image')) + `-${randomUtils.getRandomHexString(5)}`, + name: getContainerNameForImage(nonNullProp(context, 'image')), }); await this.updateRevisionDraftWithTemplate(context); diff --git a/src/utils/pickItem/pickContainerApp.ts b/src/utils/pickItem/pickContainerApp.ts index d91180f05..531b88b36 100644 --- a/src/utils/pickItem/pickContainerApp.ts +++ b/src/utils/pickItem/pickContainerApp.ts @@ -14,7 +14,7 @@ import { getPickEnvironmentSteps } from "./pickEnvironment"; export function getPickContainerAppStep(containerAppName?: string | RegExp): AzureWizardPromptStep { let containerAppFilter: RegExp | undefined; if (containerAppName) { - containerAppFilter = containerAppName instanceof RegExp ? containerAppName : new RegExp(containerAppName); + containerAppFilter = containerAppName instanceof RegExp ? containerAppName : new RegExp(`^${containerAppName}$`); } else { containerAppFilter = ContainerAppItem.contextValueRegExp; } diff --git a/src/utils/pickItem/pickEnvironment.ts b/src/utils/pickItem/pickEnvironment.ts index 9d3dda5db..4a2e11a50 100644 --- a/src/utils/pickItem/pickEnvironment.ts +++ b/src/utils/pickItem/pickEnvironment.ts @@ -16,7 +16,7 @@ export function getPickEnvironmentSteps(skipIfOne: boolean = false, environmentN let environmentFilter: RegExp | undefined; if (environmentName) { - environmentFilter = environmentName instanceof RegExp ? environmentName : new RegExp(environmentName); + environmentFilter = environmentName instanceof RegExp ? environmentName : new RegExp(`^${environmentName}$`); } else { environmentFilter = ManagedEnvironmentItem.contextValueRegExp; } diff --git a/test/deployWorkspaceProject/DefaultResourcesNameStep/DefaultResourcesNameStep.test.ts b/test/deployWorkspaceProject/DefaultResourcesNameStep/DefaultResourcesNameStep.test.ts index ebe4c92d3..052c42698 100644 --- a/test/deployWorkspaceProject/DefaultResourcesNameStep/DefaultResourcesNameStep.test.ts +++ b/test/deployWorkspaceProject/DefaultResourcesNameStep/DefaultResourcesNameStep.test.ts @@ -64,7 +64,6 @@ suite('DefaultResourcesNameStep', async () => { newResourceGroupName: 'workspace-name', newManagedEnvironmentName: 'workspace-name', newContainerAppName: 'workspace-name', - imageName: 'workspace-name:latest' } as MockDefaultResourcesNameStepContext, getMockResultContext(wizardContext) ); @@ -93,7 +92,6 @@ suite('DefaultResourcesNameStep', async () => { newResourceGroupName: 'user-name', newManagedEnvironmentName: 'user-name', newContainerAppName: 'user-name', - imageName: 'user-name:latest' } as MockDefaultResourcesNameStepContext, getMockResultContext(wizardContext) ); @@ -124,7 +122,6 @@ suite('DefaultResourcesNameStep', async () => { newResourceGroupName: undefined, newManagedEnvironmentName: undefined, newContainerAppName: 'workspace-name', - imageName: 'workspace-name:latest' } as MockDefaultResourcesNameStepContext, getMockResultContext(wizardContext) ); @@ -155,7 +152,6 @@ suite('DefaultResourcesNameStep', async () => { newResourceGroupName: undefined, newManagedEnvironmentName: undefined, newContainerAppName: 'user-name', - imageName: 'user-name:latest' } as MockDefaultResourcesNameStepContext, getMockResultContext(wizardContext) ); @@ -188,7 +184,6 @@ suite('DefaultResourcesNameStep', async () => { newResourceGroupName: undefined, newManagedEnvironmentName: undefined, newContainerAppName: undefined, - imageName: 'acr-build-1:latest' } as MockDefaultResourcesNameStepContext, getMockResultContext(wizardContext) ); @@ -216,6 +211,5 @@ function getMockResultContext(context: MockDefaultResourcesNameStepContext): Moc newResourceGroupName: context.newResourceGroupName, newManagedEnvironmentName: context.newManagedEnvironmentName, newContainerAppName: context.newContainerAppName, - imageName: context.imageName }; }